Showing posts with label QFont. Show all posts
Showing posts with label QFont. Show all posts

Monday, May 5, 2014

Being Resourceful

According to blogger, after each new post here, there's a bit of a spike in views. So at least a dozen people out there have this blog in their RSS readers. Howdy! I'll try to keep it going.

Another Unknown is Known

One of the unknowns that's been niggling at the back of my mind for weeks, is how to use the Qt resource system. I have a font that I want to carry along in the app so I can be sure it will be available. I think there will be some custom icons as well, like this one: which I think may be the icon for a button in the Find dialog that establishes a limited range of text for find/replace.

Anyway all such things shouldn't be carried along as separate files, but should be incorporated right into the program as resources. I knew Qt had a scheme for this and PyQt supported it; and I knew I would need to use it; but I didn't know how it worked and hadn't stirred myself to find out. So today I did.

As usual with these things, once you lay it out clearly it's no big deal. In a nutshell,

  • You list your resource files using XML syntax, in a file with type .rcc
  • You run a utility pyrcc5 which writes a python module.
  • You import that python module.

Then at run time, any file you named in the .rcc is available using the path prefix :/, as in :/hand-gripping.png.

The .rcc file consists of the following lines:

<!DOCTYPE RCC><RCC version="1.0">
<qresource>
   a list of files to embed, one line per file
</qresource>
</RCC>

Each file to embed is described with a line like this:

<file alias='run-time-name-of-resource'>relative-path-to-file</file>

For example, <file alias='hand-gripping.png'>hand-gripping.png</file>

And that's about it. I set up a folder Resources at the same level as my source modules. In it I put the .rcc file and the various files it named. Then from the command line, at the module level, I gave the command

pyrcc5 -o resources.py Resources/resources.rcc

And magically a 1.5MB file resources.py appeared. That file starts out with:

qt_resource_data = b"\
\x00\x04\xc8\x40\
\x00\
\x01\x00\x00\x00\x12\x01\x00\x00\x04\x00\x20\x46\x46\x54\x4d\x61\
\x13\x1d\x0b\x00\x04\x91\x38\x00\x00\x00\x1c\x47\x44\x45\x46\x79\

And ends, 20,000 lines later, with

def qInitResources():
    QtCore.qRegisterResourceData(0x01, qt_resource_struct, qt_resource_name, qt_resource_data)

qInitResources()

And that, executed upon the first import of the module, presumably tells the QApplication about these resources. It's pretty straightforward. Now I can get on with finalizing initialization of the font resources.

Sunday, March 16, 2014

Getting useful info from QFontDatabase

I'll get back to the Last(goddam)Resort font issue shortly. But in learning about it I've had to look a little closer at what Qt knows about fonts, and as a result I've got a function that might be useful to others.

The QFontDatabase object contains whatever Qt knows about fonts that are "available in the underlying window system". However it makes this information available in a rather awkward way. You can call its families() method to get a list of all font names:

from PyQt5.QtWidgets import QApplication
from PyQt5.QtGui import QFont, QFontDatabase
the_app = QApplication([])
f_db = QFontDatabase()
for family in f_db.families():
    print(family)

My macbook is rather over-endowed with fonts, and this prints a list of 238 names.

You can also ask for the recommended or default fixed and "general" fonts,

qf_fixed = f_db.systemFont(QFontDatabase.FixedFont)
print( 'Fixed font:',qf_fixed.family() )
qf_general = f_db.systemFont(QFontDatabase.GeneralFont)
print( 'General font:',qf_general.family() )

Which on my macbook prints

Fixed font: Monaco
General font: .Lucida Grande UI

Here's a surprise: what the heck is that leading dot doing in the name of the general font? There is definitely no font named ".Lucida Grande" in the list of families, nor displayed by the Fontbook app, nor (using find /System ".Lucida*') in the System folder. However, the font db will create a QFont for it:

>>> lgf = f_db.font('.Lucida Grande UI','',12)
>>> lgf.family()
'.Lucida Grande UI'
>>> lgf.pointSize()
13

So it's real in some sense, and your code can use it. Moving on...

You can ask the font db things about any single family name, for example isFixedPitch(family). But font family names aren't consistent from system to system. And often what you want is a font that meets certain criteria, such as: it's "Times" by some name, or it has a bold or italic variant, or it scales to 36pt. So I put together this little function that will return a (possibly empty) list of fonts that meet given criteria:

def font_filter( name = None, style = None, fixed = False, sizes = [], language = None ) :
    db = QFontDatabase()
    selection = db.families()
    if name :
        selection = [family for family in selection if name in family ]
    if style :
        selection = [family for family in selection if style in db.styles(family) ]
    if fixed :
        selection = [family for family in selection if db.isFixedPitch(family) ]
    if language :
        selection = [family for family in selection if language in db.writingSystems(family) ]
    if sizes :
        size_set = set(sizes)
        selection = [family for family in selection
                     if size_set.issubset( set( db.smoothSizes(family, '' ) ) ) ]
    return selection

For example,

>>> print( font_filter( name = 'Helvetica' ) )
['Helvetica', 'Helvetica CY', 'Helvetica Neue']
>>> print( font_filter( fixed = True ) )
['Anonymous Pro', 'Courier', 'Courier New', 'Inconsolata', 'PCMyungjo', 'PT Mono']
>>> print( font_filter(fixed = True, style='Bold', sizes=[12, 18]) )
['Courier', 'Courier New', 'PT Mono']
>>> font_filter(fixed=True, language=QFontDatabase.Hebrew)
['Courier New']

Next time: back to the issue of the Last(goddam)Resort font problem.