Advanced Topics
===============
For more control over font matching and configuration, use the low-level API
with :py:class:`Config`, :py:class:`Pattern`, and :py:class:`ObjectSet`.
Font Matching
-------------
Find the best matching font for a given pattern::
import fontconfig
# Get current fontconfig configuration
config = fontconfig.Config.get_current()
# Create a pattern for the desired font
pattern = fontconfig.Pattern.create()
pattern.add("family", "Arial")
pattern.add("weight", 200) # Bold weight
pattern.add("slant", 0) # Roman (not italic)
# Apply default substitutions
pattern.default_substitute()
config.substitute(pattern)
# Find the best match
matched = config.font_match(pattern)
if matched:
print(f"Matched font: {matched.get('file')}")
print(f"Family: {matched.get('family')}")
print(f"Style: {matched.get('style')}")
Font Sorting
------------
Get a sorted list of fonts matching a pattern::
import fontconfig
config = fontconfig.Config.get_current()
pattern = fontconfig.Pattern.parse(":family=Arial")
pattern.default_substitute()
# Get sorted list of matching fonts
font_set = config.font_sort(pattern, trim=True)
if font_set:
for i in range(len(font_set)):
font = font_set[i]
print(f"{i+1}. {font.get('family')} - {font.get('file')}")
List Fonts with Specific Properties
------------------------------------
Use :py:class:`ObjectSet` to specify which properties to retrieve::
import fontconfig
config = fontconfig.Config.get_current()
# Create a pattern
pattern = fontconfig.Pattern.parse(":lang=en")
# Specify which properties to retrieve
object_set = fontconfig.ObjectSet.create()
object_set.add("family")
object_set.add("style")
object_set.add("file")
object_set.add("slant")
object_set.add("weight")
# List all matching fonts
fonts = config.font_list(pattern, object_set)
for i in range(len(fonts)):
font = fonts[i]
print(f"Family: {font.get('family')}")
print(f"Style: {font.get('style')}")
print(f"Weight: {font.get('weight')}")
print("---")
Working with Patterns
---------------------
:py:class:`Pattern` objects can be created, modified, and parsed.
Understanding Pattern Syntax
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Patterns use a colon-separated format to specify font properties. The general
syntax is::
:property1=value1:property2=value2:property3=value3
**Key rules**:
- Start with a colon ``:``
- Separate properties with colons
- Use ``=`` between property name and value
- Separate multiple values for the same property with commas
- No spaces (spaces are treated as part of values)
Pattern Examples
~~~~~~~~~~~~~~~~
Common pattern formats::
import fontconfig
# Simple pattern
font = fontconfig.match(":family=Arial")
# Multiple properties
font = fontconfig.match(":family=Arial:weight=200:slant=100")
# Multiple values (fallback list for family)
font = fontconfig.match(":family=Helvetica,Arial,sans-serif")
# Boolean values
font = fontconfig.match(":family=Arial:antialias=True")
# Numeric values
font = fontconfig.match(":family=Arial:size=12.0:pixelsize=16")
# String values with spaces (avoid if possible)
font = fontconfig.match(":family=Noto Sans") # Works but be careful
Property Value Types
~~~~~~~~~~~~~~~~~~~~
Different properties accept different value types:
**String properties** (family, style, file, etc.)::
":family=Arial"
":style=Bold"
**Integer properties** (weight, slant, spacing, etc.)::
":weight=200" # Bold
":slant=100" # Italic
**Double properties** (size, pixelsize, aspect, etc.)::
":size=12.0"
":pixelsize=16.5"
**Boolean properties** (antialias, hinting, outline, etc.)::
":antialias=True"
":hinting=False"
Pattern Strings vs Properties Dict
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
You can specify font properties in two ways:
**1. Pattern strings** (fontconfig native format)::
font = fontconfig.match(":family=Arial:weight=200")
**Advantages**:
- Compact and readable for simple queries
- Native fontconfig syntax
- Can copy/paste from ``fc-match`` command line
**Disadvantages**:
- Easy to make syntax errors
- Harder to build dynamically
- Requires string formatting for variables
**2. Properties dictionary** (Python dict)::
font = fontconfig.match(properties={
"family": "Arial",
"weight": 200
})
**Advantages**:
- Type-safe and IDE-friendly
- Easy to build programmatically
- Clear and Pythonic
- Less error-prone
**Disadvantages**:
- Slightly more verbose
**Best Practice**: Use properties dict for programmatic queries, pattern strings
for simple hardcoded queries.
Example: Building patterns dynamically::
import fontconfig
# Dynamic pattern building
def find_font(family, bold=False, italic=False):
properties = {"family": family}
if bold:
properties["weight"] = 200
if italic:
properties["slant"] = 100
return fontconfig.match(properties=properties)
# Easy to use
regular = find_font("Arial")
bold = find_font("Arial", bold=True)
bold_italic = find_font("Arial", bold=True, italic=True)
Common Pattern Mistakes
~~~~~~~~~~~~~~~~~~~~~~~~
Avoid these common errors::
# ❌ WRONG: Missing initial colon
fontconfig.match("family=Arial")
# ✅ CORRECT
fontconfig.match(":family=Arial")
# ❌ WRONG: Using comma between different properties
fontconfig.match(":family=Arial,weight=200")
# ✅ CORRECT: Comma only for multiple values of SAME property
fontconfig.match(":family=Arial,Helvetica:weight=200")
# ❌ WRONG: Spaces around separators
fontconfig.match(": family = Arial : weight = 200")
# ✅ CORRECT: No spaces
fontconfig.match(":family=Arial:weight=200")
# ❌ WRONG: Invalid property name (typo)
fontconfig.match(":famly=Arial") # Will be ignored silently
# ✅ CORRECT: Use dict to catch typos
fontconfig.match(properties={"family": "Arial"})
Creating and Modifying Patterns
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Pattern objects support creation, modification, and parsing::
import fontconfig
# Create from scratch
pattern = fontconfig.Pattern.create()
pattern.add("family", "Arial")
pattern.add("size", 12.0)
pattern.add("antialias", True)
# Parse from string
pattern = fontconfig.Pattern.parse(":family=Arial:size=12:antialias=True")
# Get properties
family = pattern.get("family")
size = pattern.get("size")
# Remove properties
pattern.remove("size")
# Convert back to string
pattern_str = pattern.unparse()
print(pattern_str)
# Format pattern with custom template
formatted = pattern.format("%{family} %{style}")
print(formatted)
Configuration Management
------------------------
Access fontconfig configuration directories and files::
import fontconfig
config = fontconfig.Config.get_current()
# Get configuration directories
print("Config directories:", config.get_config_dirs())
print("Font directories:", config.get_font_dirs())
print("Config files:", config.get_config_files())
print("Cache directories:", config.get_cache_dirs())
# Get available fonts
system_fonts = config.get_fonts("system")
app_fonts = config.get_fonts("application")
print(f"System fonts: {len(system_fonts)}")
print(f"Application fonts: {len(app_fonts)}")
Configuring fontconfig
----------------------
If fontconfig does not return a font you expect, it may not be in any of the
configured font search paths. fontconfig is configured via XML files, typically
located in:
- ``~/.config/fontconfig/fonts.conf`` (user-level, Linux/macOS)
- ``/etc/fonts/local.conf`` or ``/etc/fonts/conf.d/`` (system-level)
To add a custom font directory, create or edit ``~/.config/fontconfig/fonts.conf``::
/path/to/your/fonts
After editing the configuration, rebuild the font cache::
fc-cache -f
You can verify the new directory is scanned by checking ``get_font_dirs()``::
import fontconfig
config = fontconfig.Config.get_current()
print(config.get_font_dirs())
For the full reference on fontconfig configuration — including aliases, rules,
substitutions, and per-application settings — see the
`fontconfig user documentation `_.
Working with Character Sets
----------------------------
:py:class:`CharSet` represents the set of Unicode characters supported by a font.
This is useful for checking whether a font can display specific text or for
filtering fonts by character coverage.
What is CharSet?
~~~~~~~~~~~~~~~~
A CharSet is a boolean array indicating which Unicode codepoints are present in
a font. When you need to:
- Check if a font supports specific characters
- Find fonts that can render a given string
- Filter fonts by Unicode range (e.g., only fonts with Cyrillic)
You'll work with CharSet objects.
Creating CharSets
~~~~~~~~~~~~~~~~~
Create CharSets from strings or codepoint lists::
import fontconfig
# Create from string
charset = fontconfig.CharSet.from_string("Hello, World!")
print(f"Character count: {len(charset)}")
# Create from Unicode codepoints
charset = fontconfig.CharSet.from_codepoints([0x41, 0x42, 0x43]) # A, B, C
# Create empty and add characters
charset = fontconfig.CharSet.create()
charset.add('A')
charset.add(0x42) # Add by codepoint
charset.add('C')
print(f"Contains: {len(charset)} characters")
Checking Character Membership
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Check if specific characters are in a CharSet::
import fontconfig
charset = fontconfig.CharSet.from_string("Hello")
# Check with 'in' operator
if 'H' in charset:
print("Has H")
if 'Z' not in charset:
print("Does not have Z")
# Check by codepoint
if 0x48 in charset: # 0x48 = 'H'
print("Has H (by codepoint)")
# Iterate over all codepoints
print("Characters in charset:")
for codepoint in charset:
char = chr(codepoint)
print(f" U+{codepoint:04X} = '{char}'")
Finding Fonts for Specific Text
~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~~
Find fonts that can render a given string by checking character support::
import fontconfig
def find_fonts_for_text(text, max_results=10):
"""Find fonts that support all characters in the text."""
# Create charset from text
required_chars = fontconfig.CharSet.from_string(text)
# Get all system fonts
all_fonts = fontconfig.list(select=("family", "file", "charset"))
# Filter fonts that support all required characters
compatible_fonts = []
for font in all_fonts:
if 'charset' not in font:
continue
font_charset = font['charset']
# Check if font supports all characters
all_supported = all(char in font_charset for char in text)
if all_supported:
compatible_fonts.append(font)
if len(compatible_fonts) >= max_results:
break
return compatible_fonts
# Example: Find fonts for Chinese text
chinese_text = "你好世界"
fonts = find_fonts_for_text(chinese_text)
print(f"Fonts that support '{chinese_text}':")
for font in fonts:
print(f" {font['family']}")
# Example: Find fonts for mathematical symbols
math_text = "∑∫∂∇"
fonts = find_fonts_for_text(math_text, max_results=5)
print(f"\nFonts that support math symbols:")
for font in fonts:
print(f" {font['family']}")
**Performance Note**: Checking character support for every font in the system
can be slow. For better performance, filter by language first::
# More efficient: Filter by language first
japanese_fonts = fontconfig.list(
properties={"lang": ["ja"]},
select=("family", "file", "charset")
)
# Then check character support within filtered results
text = "こんにちは"
for font in japanese_fonts:
if 'charset' in font:
charset = font['charset']
if all(char in charset for char in text):
print(f"Compatible: {font['family']}")
Getting Font CharSets
~~~~~~~~~~~~~~~~~~~~~~
Access the charset property from font patterns::
import fontconfig
# Get charset from matched font
font = fontconfig.match(":family=Arial")
if 'charset' in font:
charset = font['charset']
print(f"Font supports {len(charset)} characters")
# Check specific character support
if 'é' in charset:
print("Supports accented characters")
if '中' in charset:
print("Supports Chinese characters")
# List fonts with their character counts
fonts = fontconfig.list(select=("family", "charset"))
for font in fonts[:20]: # First 20
if 'charset' in font:
count = len(font['charset'])
print(f"{font['family']}: {count} characters")