Difficult-Rocket/libs/pyglet/font/fontconfig.py
2022-04-30 13:56:57 +08:00

397 lines
11 KiB
Python

# ----------------------------------------------------------------------------
# pyglet
# Copyright (c) 2006-2008 Alex Holkner
# Copyright (c) 2008-2022 pyglet contributors
# All rights reserved.
#
# Redistribution and use in source and binary forms, with or without
# modification, are permitted provided that the following conditions
# are met:
#
# * Redistributions of source code must retain the above copyright
# notice, this list of conditions and the following disclaimer.
# * Redistributions in binary form must reproduce the above copyright
# notice, this list of conditions and the following disclaimer in
# the documentation and/or other materials provided with the
# distribution.
# * Neither the name of pyglet nor the names of its
# contributors may be used to endorse or promote products
# derived from this software without specific prior written
# permission.
#
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
# COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
# LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
# CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
# ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
# POSSIBILITY OF SUCH DAMAGE.
# ----------------------------------------------------------------------------
"""
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
@staticmethod
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
@property
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
@staticmethod
def _bold_to_weight(bold):
return FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR
@staticmethod
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)
@property
def name(self):
return self._get_string(FC_FAMILY)
@property
def size(self):
return self._get_double(FC_SIZE)
@property
def bold(self):
return self._get_integer(FC_WEIGHT) == FC_WEIGHT_BOLD
@property
def italic(self):
return self._get_integer(FC_SLANT) == FC_SLANT_ITALIC
@property
def face(self):
return self._get_face(FC_FT_FACE)
@property
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