Spaces:
Runtime error
Runtime error
""" | |
Wrapper around the Linux FontConfig library. Used to find available fonts. | |
""" | |
from collections import OrderedDict | |
from ctypes import * | |
import pyglet.lib | |
from pyglet.util import asbytes, asstr | |
from pyglet.font.base import FontException | |
# fontconfig library definitions | |
(FcResultMatch, | |
FcResultNoMatch, | |
FcResultTypeMismatch, | |
FcResultNoId, | |
FcResultOutOfMemory) = range(5) | |
FcResult = c_int | |
FC_FAMILY = asbytes('family') | |
FC_SIZE = asbytes('size') | |
FC_SLANT = asbytes('slant') | |
FC_WEIGHT = asbytes('weight') | |
FC_FT_FACE = asbytes('ftface') | |
FC_FILE = asbytes('file') | |
FC_WEIGHT_REGULAR = 80 | |
FC_WEIGHT_BOLD = 200 | |
FC_SLANT_ROMAN = 0 | |
FC_SLANT_ITALIC = 100 | |
(FcTypeVoid, | |
FcTypeInteger, | |
FcTypeDouble, | |
FcTypeString, | |
FcTypeBool, | |
FcTypeMatrix, | |
FcTypeCharSet, | |
FcTypeFTFace, | |
FcTypeLangSet) = range(9) | |
FcType = c_int | |
(FcMatchPattern, | |
FcMatchFont) = range(2) | |
FcMatchKind = c_int | |
class _FcValueUnion(Union): | |
_fields_ = [ | |
('s', c_char_p), | |
('i', c_int), | |
('b', c_int), | |
('d', c_double), | |
('m', c_void_p), | |
('c', c_void_p), | |
('f', c_void_p), | |
('p', c_void_p), | |
('l', c_void_p), | |
] | |
class FcValue(Structure): | |
_fields_ = [ | |
('type', FcType), | |
('u', _FcValueUnion) | |
] | |
# End of library definitions | |
class FontConfig: | |
def __init__(self): | |
self._fontconfig = self._load_fontconfig_library() | |
self._search_cache = OrderedDict() | |
self._cache_size = 20 | |
def dispose(self): | |
while len(self._search_cache) > 0: | |
self._search_cache.popitem().dispose() | |
self._fontconfig.FcFini() | |
self._fontconfig = None | |
def create_search_pattern(self): | |
return FontConfigSearchPattern(self._fontconfig) | |
def find_font(self, name, size=12, bold=False, italic=False): | |
result = self._get_from_search_cache(name, size, bold, italic) | |
if result: | |
return result | |
search_pattern = self.create_search_pattern() | |
search_pattern.name = name | |
search_pattern.size = size | |
search_pattern.bold = bold | |
search_pattern.italic = italic | |
result = search_pattern.match() | |
self._add_to_search_cache(search_pattern, result) | |
search_pattern.dispose() | |
return result | |
def have_font(self, name): | |
result = self.find_font(name) | |
if result: | |
# Check the name matches, fontconfig can return a default | |
if name and result.name and result.name.lower() != name.lower(): | |
return False | |
return True | |
else: | |
return False | |
def char_index(self, ft_face, character): | |
return self._fontconfig.FcFreeTypeCharIndex(ft_face, ord(character)) | |
def _add_to_search_cache(self, search_pattern, result_pattern): | |
self._search_cache[(search_pattern.name, | |
search_pattern.size, | |
search_pattern.bold, | |
search_pattern.italic)] = result_pattern | |
if len(self._search_cache) > self._cache_size: | |
self._search_cache.popitem(last=False)[1].dispose() | |
def _get_from_search_cache(self, name, size, bold, italic): | |
result = self._search_cache.get((name, size, bold, italic), None) | |
if result and result.is_valid: | |
return result | |
else: | |
return None | |
def _load_fontconfig_library(): | |
fontconfig = pyglet.lib.load_library('fontconfig') | |
fontconfig.FcInit() | |
fontconfig.FcPatternBuild.restype = c_void_p | |
fontconfig.FcPatternCreate.restype = c_void_p | |
fontconfig.FcFontMatch.restype = c_void_p | |
fontconfig.FcFreeTypeCharIndex.restype = c_uint | |
fontconfig.FcPatternAddDouble.argtypes = [c_void_p, c_char_p, c_double] | |
fontconfig.FcPatternAddInteger.argtypes = [c_void_p, c_char_p, c_int] | |
fontconfig.FcPatternAddString.argtypes = [c_void_p, c_char_p, c_char_p] | |
fontconfig.FcConfigSubstitute.argtypes = [c_void_p, c_void_p, c_int] | |
fontconfig.FcDefaultSubstitute.argtypes = [c_void_p] | |
fontconfig.FcFontMatch.argtypes = [c_void_p, c_void_p, c_void_p] | |
fontconfig.FcPatternDestroy.argtypes = [c_void_p] | |
fontconfig.FcPatternGetFTFace.argtypes = [c_void_p, c_char_p, c_int, c_void_p] | |
fontconfig.FcPatternGet.argtypes = [c_void_p, c_char_p, c_int, c_void_p] | |
return fontconfig | |
class FontConfigPattern: | |
def __init__(self, fontconfig, pattern=None): | |
self._fontconfig = fontconfig | |
self._pattern = pattern | |
def is_valid(self): | |
return self._fontconfig and self._pattern | |
def _create(self): | |
assert not self._pattern | |
assert self._fontconfig | |
self._pattern = self._fontconfig.FcPatternCreate() | |
def _destroy(self): | |
assert self._pattern | |
assert self._fontconfig | |
self._fontconfig.FcPatternDestroy(self._pattern) | |
self._pattern = None | |
def _bold_to_weight(bold): | |
return FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR | |
def _italic_to_slant(italic): | |
return FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN | |
def _set_string(self, name, value): | |
assert self._pattern | |
assert name | |
assert self._fontconfig | |
if not value: | |
return | |
value = value.encode('utf8') | |
self._fontconfig.FcPatternAddString(self._pattern, name, asbytes(value)) | |
def _set_double(self, name, value): | |
assert self._pattern | |
assert name | |
assert self._fontconfig | |
if not value: | |
return | |
self._fontconfig.FcPatternAddDouble(self._pattern, name, c_double(value)) | |
def _set_integer(self, name, value): | |
assert self._pattern | |
assert name | |
assert self._fontconfig | |
if not value: | |
return | |
self._fontconfig.FcPatternAddInteger(self._pattern, name, c_int(value)) | |
def _get_value(self, name): | |
assert self._pattern | |
assert name | |
assert self._fontconfig | |
value = FcValue() | |
result = self._fontconfig.FcPatternGet(self._pattern, name, 0, byref(value)) | |
if _handle_fcresult(result): | |
return value | |
else: | |
return None | |
def _get_string(self, name): | |
value = self._get_value(name) | |
if value and value.type == FcTypeString: | |
return asstr(value.u.s) | |
else: | |
return None | |
def _get_face(self, name): | |
value = self._get_value(name) | |
if value and value.type == FcTypeFTFace: | |
return value.u.f | |
else: | |
return None | |
def _get_integer(self, name): | |
value = self._get_value(name) | |
if value and value.type == FcTypeInteger: | |
return value.u.i | |
else: | |
return None | |
def _get_double(self, name): | |
value = self._get_value(name) | |
if value and value.type == FcTypeDouble: | |
return value.u.d | |
else: | |
return None | |
class FontConfigSearchPattern(FontConfigPattern): | |
def __init__(self, fontconfig): | |
super(FontConfigSearchPattern, self).__init__(fontconfig) | |
self.name = None | |
self.bold = False | |
self.italic = False | |
self.size = None | |
def match(self): | |
self._prepare_search_pattern() | |
result_pattern = self._get_match() | |
if result_pattern: | |
return FontConfigSearchResult(self._fontconfig, result_pattern) | |
else: | |
return None | |
def _prepare_search_pattern(self): | |
self._create() | |
self._set_string(FC_FAMILY, self.name) | |
self._set_double(FC_SIZE, self.size) | |
self._set_integer(FC_WEIGHT, self._bold_to_weight(self.bold)) | |
self._set_integer(FC_SLANT, self._italic_to_slant(self.italic)) | |
self._substitute_defaults() | |
def _substitute_defaults(self): | |
assert self._pattern | |
assert self._fontconfig | |
self._fontconfig.FcConfigSubstitute(None, self._pattern, FcMatchPattern) | |
self._fontconfig.FcDefaultSubstitute(self._pattern) | |
def _get_match(self): | |
assert self._pattern | |
assert self._fontconfig | |
match_result = FcResult() | |
match_pattern = self._fontconfig.FcFontMatch(0, self._pattern, byref(match_result)) | |
if _handle_fcresult(match_result.value): | |
return match_pattern | |
else: | |
return None | |
def dispose(self): | |
self._destroy() | |
class FontConfigSearchResult(FontConfigPattern): | |
def __init__(self, fontconfig, result_pattern): | |
super(FontConfigSearchResult, self).__init__(fontconfig, result_pattern) | |
def name(self): | |
return self._get_string(FC_FAMILY) | |
def size(self): | |
return self._get_double(FC_SIZE) | |
def bold(self): | |
return self._get_integer(FC_WEIGHT) == FC_WEIGHT_BOLD | |
def italic(self): | |
return self._get_integer(FC_SLANT) == FC_SLANT_ITALIC | |
def face(self): | |
return self._get_face(FC_FT_FACE) | |
def file(self): | |
return self._get_string(FC_FILE) | |
def dispose(self): | |
self._destroy() | |
def _handle_fcresult(result): | |
if result == FcResultMatch: | |
return True | |
elif result in (FcResultNoMatch, FcResultTypeMismatch, FcResultNoId): | |
return False | |
elif result == FcResultOutOfMemory: | |
raise FontException('FontConfig ran out of memory.') | |
_fontconfig_instance = None | |
def get_fontconfig(): | |
global _fontconfig_instance | |
if not _fontconfig_instance: | |
_fontconfig_instance = FontConfig() | |
return _fontconfig_instance | |