Join GitHub today
GitHub is home to over 50 million developers working together to host and review code, manage projects, and build software together.Sign up
GitHub is where the world builds software
Millions of developers and companies build, ship, and maintain their software on GitHub — the largest and most advanced development platform in the world.
Python_fontconfig is a Python 3 binding for [Fontconfig](https://www.freedesktop.org/wiki/Software/fontconfig/), the standard Linux font-matching API. Fontconfig lets applications use and store font-matching *patterns* instead of font-file pathnames. This allows applications to cope with different system setups that have fonts installed in different places, as well as setups where the exact original font is not available, so it has to fall back to some other suitable alternative. Fontconfig also lets applications automatically discover that new fonts have been installed (or old ones removed) at any time, instead of only once at startup time. Do I Need python_fontconfig? ============================ You may not actually need to use this module. If you are making use of any of my Python wrappers for other major parts of the Linux typography stack, namely * Qahirah ([GitLab](https://gitlab.com/ldo/qahirah), [GitHub](https://github.com/ldo/qahirah)) -- my binding for the [Cairo](https://www.cairographics.org/) graphics library * python_freetype ([GitLab](https://gitlab.com/ldo/python_freetype), [GitHub](https://github.com/ldo/python_freetype)) -- my binding for [FreeType](https://www.freetype.org/) then these already include their own mini-wrappers for Fontconfig, that are sufficient for finding fonts using default system configuration settings. But if you are going beyond simple defaults, and want to customize various aspects of the font-matching behaviour of Fontconfig from within your own Python scripts, then you will need this module. Installation ============ To install python_fontconfig on your system, type python3 setup.py install This will install the Python module named `fontconfig`. Basic Usage =========== All functionality comes from the one module: import fontconfig Or you may want to abbreviate its name for convenience: import fontconfig as fc (The subsequent examples will assume this abbreviation.) You will typically also want more direct access to constants and other definitions within the `FC` class: from fontconfig import \ FC Fontconfig configurations are kept in `Config` objects. Fontconfig supplies a default *current* configuration, initially accessible via conf = fc.Config.get_current() This is already preloaded with all the system configuration settings. Or you can create a new, blank configuration with conf = fc.Config.create() Either way, you can add your own extra places to tell Fontconfig where to look for things: conf.app_font_add_file(«file») will add individual extra font files to the set to be searched, and conf.app_font_add_dir(«dir») will add directories in which to look for fonts. If you create your own `Config` object, you can make it the current one with conf.set_current() Patterns ======== Fontconfig font specifiers take the form of *patterns*, represented in Python by `Pattern` objects. Patterns specify values for one or more named *properties*, such as the font family, style, weight, character-set coverage, language support, and even things like the file name and the name of the foundry that created the font--basically everything that might be useful for trying to ensure that you get the same font the next time, or, failing that, the best available substitute. All the recognized property names are listed in the `PROP` enumeration in the `fontconfig` module. Patterns also have a string form, and can be easily converted between this form and Pattern objects. The string form looks like this: [«family»][:«prop»=«value»[:«prop»=«value»...]] where «family» is the family name, and each «prop» is a property name and «value» the corresponding property value. All parts are optional: even the empty string is a valid pattern! Whatever pattern you specify, Fontconfig will always find a match, as long as it knows of at least one font installed on your system. For example, on my Debian system, the final fallback, if no other font can be found to match a pattern, always seems to be DejaVu Sans. Testing Patterns ================ Fontconfig comes with a handy set of command-line tools that can be used to exercise your system configuration. `fc-match` will let you specify a pattern string, and will print out info (selected property values) for matching fonts, in the form of a pattern string. For example, on my system, the shell command fc-match palatino file family to see what font I would get if I asked for “Palatino”, prints out URW Palladio L:file=/usr/share/fonts/type1/gsfonts/p052003l.pfb Or, given any font file, `fc-query` will print out the full set of pattern information that could be used to specify that font. Basic Font Matching =================== Given a pattern string, you can convert it to a `Pattern` object thus: pat = fc.Pattern.name_parse(«pattern_string») To use this to find a font from the default configuration: conf = fc.Config.get_current() also perform the following steps: conf.substitute(pat, FC.MatchPattern) pat.default_substitute() which will fill in defaults for the `Pattern` from the specified configuration, and from Fontconfig’s own hard-coded fallback settings, respectively. Now, to get back a `Pattern` object for the best available matching font, simply do found, status = conf.font_match(pat) to get the result in `found`. (You can ignore the `status` value, which will usually be `FC.ResultMatch`.) To retrieve information from the pattern, you can use its `get` method. For example, to get the full font file pathname so you can actually load the font: filename = found.get(fc.PROP.FILE, 0) Or, you can print out the full contents of the `Pattern` as a string by print(found.name_unparse()) Font Coverage And Fallback ========================== There is a common need to find fonts that cover certain given ranges of code points. This includes finding suitable “fallback” fonts to cover missing code points not present within a given font. For example, the set of basic Arabic code points can be constructed as follows: coverage_wanted = set(range(0x0600, 0x06FF + 1)) Restricting this to currently-defined code points--or at least, those that Python knows about in its `unicodedata` module: coverage_wanted -= set \ ( c for c in coverage_wanted if unicodedata.name(chr(c), None) == None ) (Note that this is a standard Python set of integer character codes. Fontconfig defines its own `FcCharSet` type, but this is automatically converted to a Python set wherever it is returned from a call, and a Python set is passed wherever the Fontconfig call expects an object of this type.) Now we can construct a `Pattern` to find fonts giving this coverage: pat = fc.Pattern.create \ ( vals = ( (fc.PROP.CHARSET, coverage_wanted), ) ) conf.substitute(pat, FC.MatchPattern) pat.default_substitute() (Note the convenience option of being able to directly pass a tuple of («property», «value») pairs to the `Pattern.create` call, to be added to the new `Pattern` object.) Now we can find a list of fonts in descending order of suitability as follows: found, total_coverage, status = conf.font_sort(pat, trim = True, want_coverage = True) where the `trim` arg says not to bother including fonts that only cover code points already handled by prior fonts, and `want_coverage` says that the `total_coverage` variable should return a set of all the code points covered by the fonts in `found`; this can be used, for example, to see if there are any code points still missing from the available fonts. The following loop prints out some basic information about the fonts in `found`, including which code points in `coverage_wanted` are missing from the font’s character set: for i, f in enumerate(found) : missing = coverage_wanted - f.get(fc.PROP.CHARSET, 0) sys.stdout.write \ ( "found[%d]: %s (%s), missing(%d) = %s\n" % ( i, f.get(fc.PROP.FAMILY, 0), f.get(fc.PROP.STYLE, 0), len(missing), repr(missing), ) ) #end for For some example scripts, see my python_fontconfig_examples repository ([GitLab](https://gitlab.com/ldo/python_fontconfig_examples), [GitHub](https://github.com/ldo/python_fontconfig_examples)). Lawrence D'Oliveiro <firstname.lastname@example.org> 2020 May 20