mirror of
				https://github.com/kovidgoyal/calibre.git
				synced 2025-11-01 19:17:02 -04:00 
			
		
		
		
	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
This commit is contained in:
		
							parent
							
								
									f43a4467d3
								
							
						
					
					
						commit
						e3fabe843a
					
				| @ -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)) | ||||
|  | ||||
							
								
								
									
										13
									
								
								setup.py
									
									
									
									
									
								
							
							
						
						
									
										13
									
								
								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', | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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']) | ||||
|  | ||||
| @ -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']) | ||||
|  | ||||
| @ -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']) | ||||
|  | ||||
| @ -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) | ||||
|  | ||||
| @ -1,7 +1,8 @@ | ||||
| <ui version="4.0" > | ||||
| <?xml version="1.0" encoding="UTF-8"?> | ||||
| <ui version="4.0"> | ||||
|  <class>Form</class> | ||||
|  <widget class="QWidget" name="Form" > | ||||
|   <property name="geometry" > | ||||
|  <widget class="QWidget" name="Form"> | ||||
|   <property name="geometry"> | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
| @ -9,133 +10,146 @@ | ||||
|     <height>381</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|   <property name="windowTitle" > | ||||
|   <property name="windowTitle"> | ||||
|    <string>Form</string> | ||||
|   </property> | ||||
|   <layout class="QGridLayout" name="gridLayout" > | ||||
|    <item row="0" column="0" colspan="2" > | ||||
|     <widget class="QLabel" name="label" > | ||||
|      <property name="text" > | ||||
|   <layout class="QVBoxLayout" name="verticalLayout"> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="label"> | ||||
|      <property name="text"> | ||||
|       <string>Match HTML &tags with tag name:</string> | ||||
|      </property> | ||||
|      <property name="buddy" > | ||||
|      <property name="buddy"> | ||||
|       <cstring>tag</cstring> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="1" column="0" > | ||||
|     <widget class="QComboBox" name="tag" > | ||||
|      <property name="editable" > | ||||
|    <item> | ||||
|     <widget class="QComboBox" name="tag"> | ||||
|      <property name="editable"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>*</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>a</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>br</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>div</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>h1</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>h2</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>h3</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>h4</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>h5</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>h6</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>hr</string> | ||||
|       </property> | ||||
|      </item> | ||||
|      <item> | ||||
|       <property name="text" > | ||||
|       <property name="text"> | ||||
|        <string>span</string> | ||||
|       </property> | ||||
|      </item> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="2" column="0" colspan="2" > | ||||
|     <widget class="QLabel" name="label_2" > | ||||
|      <property name="text" > | ||||
|    <item> | ||||
|     <widget class="QLabel" name="label_2"> | ||||
|      <property name="text"> | ||||
|       <string>Having the &attribute:</string> | ||||
|      </property> | ||||
|      <property name="buddy" > | ||||
|      <property name="buddy"> | ||||
|       <cstring>attribute</cstring> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="3" column="0" colspan="2" > | ||||
|     <widget class="QLineEdit" name="attribute" /> | ||||
|    <item> | ||||
|     <widget class="QLineEdit" name="attribute"/> | ||||
|    </item> | ||||
|    <item row="4" column="0" > | ||||
|     <widget class="QLabel" name="label_3" > | ||||
|      <property name="text" > | ||||
|    <item> | ||||
|     <widget class="QLabel" name="label_3"> | ||||
|      <property name="text"> | ||||
|       <string>With &value:</string> | ||||
|      </property> | ||||
|      <property name="buddy" > | ||||
|      <property name="buddy"> | ||||
|       <cstring>value</cstring> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="5" column="0" > | ||||
|     <widget class="QLineEdit" name="value" /> | ||||
|    </item> | ||||
|    <item row="5" column="1" > | ||||
|     <widget class="QLabel" name="label_4" > | ||||
|      <property name="text" > | ||||
|    <item> | ||||
|     <widget class="QLabel" name="label_4"> | ||||
|      <property name="text"> | ||||
|       <string>(A regular expression)</string> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item row="6" column="0" colspan="2" > | ||||
|     <widget class="QLabel" name="label_5" > | ||||
|      <property name="text" > | ||||
|       <string><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>.</string> | ||||
|    <item> | ||||
|     <widget class="QLineEdit" name="value"/> | ||||
|    </item> | ||||
|    <item> | ||||
|     <widget class="QLabel" name="label_5"> | ||||
|      <property name="text"> | ||||
|       <string><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>.</string> | ||||
|      </property> | ||||
|      <property name="wordWrap" > | ||||
|      <property name="wordWrap"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|      <property name="openExternalLinks" > | ||||
|      <property name="openExternalLinks"> | ||||
|       <bool>true</bool> | ||||
|      </property> | ||||
|     </widget> | ||||
|    </item> | ||||
|    <item> | ||||
|     <spacer name="verticalSpacer"> | ||||
|      <property name="orientation"> | ||||
|       <enum>Qt::Vertical</enum> | ||||
|      </property> | ||||
|      <property name="sizeHint" stdset="0"> | ||||
|       <size> | ||||
|        <width>20</width> | ||||
|        <height>40</height> | ||||
|       </size> | ||||
|      </property> | ||||
|     </spacer> | ||||
|    </item> | ||||
|   </layout> | ||||
|  </widget> | ||||
|  <resources/> | ||||
|  | ||||
| @ -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: | ||||
|  | ||||
| @ -6,7 +6,7 @@ | ||||
|    <rect> | ||||
|     <x>0</x> | ||||
|     <y>0</y> | ||||
|     <width>738</width> | ||||
|     <width>767</width> | ||||
|     <height>575</height> | ||||
|    </rect> | ||||
|   </property> | ||||
|  | ||||
| @ -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(('<b>Error:</b>%s<br><b>Traceback:</b><br>' | ||||
|                 '%s<b>Log:</b><br>%s')%(unicode(err), unicode(tb), log)) | ||||
|             d.exec_() | ||||
|             d = QErrorMessage() | ||||
|             d.showMessage(('<b>Error:</b>%s<br><b>Traceback:</b><br>' | ||||
|                 '%s<b>Log:</b><br>%s')%(unicode(err), | ||||
|                     unicode(tb).replace('\n', '<br>'), | ||||
|                     log.replace('\n', '<br>'))) | ||||
| 
 | ||||
|  | ||||
| @ -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' | ||||
|  | ||||
| @ -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() | ||||
|  | ||||
| @ -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', | ||||
|  | ||||
| @ -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 <kovid@kovidgoyal.net> | ||||
| 
 | ||||
| A ctypes based wrapper around the `fontconfig <http://fontconfig.org>`_ 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-<pointsize>:<property1=value1>:<property2=value2>... | ||||
|        | ||||
|     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()) | ||||
							
								
								
									
										150
									
								
								src/calibre/utils/fonts/__init__.py
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										150
									
								
								src/calibre/utils/fonts/__init__.py
									
									
									
									
									
										Normal file
									
								
							| @ -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 <kovid@kovidgoyal.net>' | ||||
| __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-<pointsize>:<property1=value1>:<property2=value2>... | ||||
| 
 | ||||
|         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() | ||||
							
								
								
									
										331
									
								
								src/calibre/utils/fonts/fontconfig.c
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										331
									
								
								src/calibre/utils/fonts/fontconfig.c
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,331 @@ | ||||
| /*
 | ||||
| :mod:`fontconfig` -- Pythonic interface to fontconfig | ||||
| ===================================================== | ||||
| 
 | ||||
| .. module:: fontconfig | ||||
|     :platform: All | ||||
|     :synopsis: Pythonic interface to the fontconfig library | ||||
| 
 | ||||
| .. moduleauthor:: Kovid Goyal <kovid@kovidgoyal.net> Copyright 2009 | ||||
| 
 | ||||
| */ | ||||
| 
 | ||||
| #define PY_SSIZE_T_CLEAN | ||||
| #include <Python.h> | ||||
| #include <stdio.h> | ||||
| #include <string.h> | ||||
| #include <fontconfig.h> | ||||
| 
 | ||||
| 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; | ||||
| } | ||||
| 
 | ||||
		Loading…
	
	
			
			x
			
			
		
	
		Reference in New Issue
	
	Block a user