Advanced Topics
For more control over font matching and configuration, use the low-level API
with Config, Pattern, and 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 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
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 valueSeparate 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-matchcommand 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.confor/etc/fonts/conf.d/(system-level)
To add a custom font directory, create or edit ~/.config/fontconfig/fonts.conf:
<?xml version="1.0"?>
<!DOCTYPE fontconfig SYSTEM "fonts.dtd">
<fontconfig>
<dir>/path/to/your/fonts</dir>
</fontconfig>
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
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")