Linux: Use libfontconfig directly instead of calling fc-match

There are apparently some linux systems out there with versions of
fontconfig that do not understanf the :charset query when passed to
fc-match. See #46
This commit is contained in:
Kovid Goyal 2017-02-08 21:52:10 +05:30
parent 882a4f2ab3
commit 02ef3c6dc8
6 changed files with 125 additions and 12 deletions

View File

@ -12,7 +12,7 @@ matrix:
addons:
apt:
packages:
- libfreetype6-dev
- libfontconfig1-dev
- libglew-dev
- libxi-dev
- libxrandr-dev
@ -30,7 +30,7 @@ matrix:
addons:
apt:
packages:
- libfreetype6-dev
- libfontconfig1-dev
- libglew-dev
- libxi-dev
- libxrandr-dev

View File

@ -40,6 +40,7 @@ static PyMethodDef module_methods[] = {
{"read_bytes_dump", (PyCFunction)read_bytes_dump, METH_VARARGS, ""},
{"wcwidth", (PyCFunction)wcwidth_wrap, METH_O, ""},
{"change_wcwidth", (PyCFunction)change_wcwidth_wrap, METH_O, ""},
{"get_fontconfig_font", (PyCFunction)get_fontconfig_font, METH_VARARGS, ""},
GLFW_FUNC_WRAPPERS
{NULL, NULL, 0, NULL} /* Sentinel */
};
@ -77,6 +78,7 @@ PyInit_fast_data_types(void) {
#else
if (!init_Face(m)) return NULL;
if (!init_freetype_library(m)) return NULL;
if (!init_fontconfig_library(m)) return NULL;
#endif
PyModule_AddIntConstant(m, "BOLD", BOLD_SHIFT);
PyModule_AddIntConstant(m, "ITALIC", ITALIC_SHIFT);

View File

@ -415,3 +415,5 @@ DECLARE_CH_SCREEN_HANDLER(linefeed)
DECLARE_CH_SCREEN_HANDLER(carriage_return)
bool init_freetype_library(PyObject*);
bool init_fontconfig_library(PyObject*);
PyObject *get_fontconfig_font(PyObject *self, PyObject *args);

84
kitty/fontconfig.c Normal file
View File

@ -0,0 +1,84 @@
/*
* fontconfig.c
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include <fontconfig/fontconfig.h>
PyObject*
get_fontconfig_font(PyObject UNUSED *self, PyObject *args) {
char *family;
int bold, italic, allow_bitmapped_fonts, index = 0, hint_style=0, weight=0, slant=0;
double size_in_pts, dpi;
unsigned int character;
FcBool hinting, scalable, outline;
FcChar8 *path = NULL;
FcPattern *pat = NULL, *match = NULL;
FcResult result;
FcCharSet *charset = NULL;
PyObject *ans = NULL;
if (!PyArg_ParseTuple(args, "spppdId", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &character, &dpi)) return NULL;
pat = FcPatternCreate();
if (pat == NULL) return PyErr_NoMemory();
#define AP(func, which, in, desc) if (!func(pat, which, in)) { PyErr_Format(PyExc_RuntimeError, "Failed to add %s to fontconfig patter", desc, NULL); goto end; }
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family");
if (!allow_bitmapped_fonts) {
AP(FcPatternAddBool, FC_OUTLINE, true, "outline");
AP(FcPatternAddBool, FC_SCALABLE, true, "scalable");
}
if (size_in_pts > 0) { AP(FcPatternAddDouble, FC_SIZE, size_in_pts, "size"); }
if (dpi > 0) { AP(FcPatternAddDouble, FC_DPI, dpi, "dpi"); }
if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); }
if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); }
if (character > 0) {
charset = FcCharSetCreate();
if (charset == NULL) { PyErr_NoMemory(); goto end; }
if (!FcCharSetAddChar(charset, character)) { PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset"); goto end; }
AP(FcPatternAddCharSet, FC_CHARSET, charset, "charset");
}
#undef AP
FcConfigSubstitute(NULL, pat, FcMatchPattern);
FcDefaultSubstitute(pat);
match = FcFontMatch(NULL, pat, &result);
if (match == NULL) { PyErr_SetString(PyExc_KeyError, "FcFontMatch() failed"); goto end; }
#define GI(func, which, out, desc) \
if (func(match, which, 0, & out) != FcResultMatch) { \
PyErr_Format(PyExc_RuntimeError, "Failed to get %s from match object", desc, NULL); goto end; \
}
GI(FcPatternGetString, FC_FILE, path, "file path");
GI(FcPatternGetInteger, FC_INDEX, index, "face index");
GI(FcPatternGetInteger, FC_WEIGHT, weight, "weight");
GI(FcPatternGetInteger, FC_SLANT, slant, "slant");
GI(FcPatternGetInteger, FC_HINT_STYLE, hint_style, "hint style");
GI(FcPatternGetBool, FC_HINTING, hinting, "hinting");
GI(FcPatternGetBool, FC_SCALABLE, scalable, "scalable");
GI(FcPatternGetBool, FC_OUTLINE, outline, "outline");
#undef GI
#define BP(x) (x ? Py_True : Py_False)
ans = Py_BuildValue("siiOOOii", path, index, hint_style, BP(hinting), BP(scalable), BP(outline), weight, slant);
#undef BP
end:
if (pat != NULL) FcPatternDestroy(pat);
if (match != NULL) FcPatternDestroy(match);
if (charset != NULL) FcCharSetDestroy(charset);
if (PyErr_Occurred()) return NULL;
return ans;
}
bool
init_fontconfig_library(PyObject UNUSED *m) {
if (!FcInit()) {
PyErr_SetString(PyExc_RuntimeError, "Failed to initialize the fontconfig library");
return false;
}
return true;
}

View File

@ -7,7 +7,7 @@ import re
import subprocess
from collections import namedtuple
from kitty.fast_data_types import Face
from kitty.fast_data_types import Face, get_fontconfig_font
def escape_family_name(name):
@ -15,7 +15,8 @@ def escape_family_name(name):
Font = namedtuple(
'Font', 'face hinting hintstyle bold italic scalable outline weight slant index'
'Font',
'face hinting hintstyle bold italic scalable outline weight slant index'
)
@ -27,10 +28,10 @@ def to_bool(x):
return x.lower() == 'true'
def get_font(
family,
bold,
italic,
def get_font_subprocess(
family='monospace',
bold=False,
italic=False,
allow_bitmaped_fonts=False,
size_in_pts=None,
character=None,
@ -63,10 +64,34 @@ def get_font(
path,
to_bool(hinting),
int(hintstyle), bold, italic,
to_bool(scalable), to_bool(outline), int(weight), int(slant), int(index)
to_bool(scalable),
to_bool(outline), int(weight), int(slant), int(index)
)
def get_font_lib(
family='monospace',
bold=False,
italic=False,
allow_bitmaped_fonts=False,
size_in_pts=None,
character=None,
dpi=None
):
path, index, hintstyle, hinting, scalable, outline, weight, slant = get_fontconfig_font(
family, bold, italic, allow_bitmaped_fonts, size_in_pts or 0,
0 if character is None else ord(character[0]), dpi or 0
)
return Font(
path, hinting, hintstyle, bold, italic, scalable, outline, weight,
slant, index
)
get_font = get_font_lib
def find_font_for_character(
family,
char,

View File

@ -106,8 +106,8 @@ def init_env(debug=False, asan=False):
if isosx:
font_libs = ['-framework', 'CoreText', '-framework', 'CoreGraphics']
else:
cflags.extend(pkg_config('freetype2', '--cflags-only-I'))
font_libs = pkg_config('freetype2', '--libs')
cflags.extend(pkg_config('fontconfig', '--cflags-only-I'))
font_libs = pkg_config('fontconfig', '--libs')
cflags.extend(pkg_config('glfw3', '--cflags-only-I'))
ldflags.append('-shared')
pylib = get_python_flags(cflags)
@ -190,7 +190,7 @@ def option_parser():
def find_c_files():
ans, headers = [], []
d = os.path.join(base, 'kitty')
exclude = {'freetype.c'} if isosx else {'core_text.m'}
exclude = {'freetype.c', 'fontconfig.c'} if isosx else {'core_text.m'}
for x in os.listdir(d):
ext = os.path.splitext(x)[1]
if ext in ('.c', '.m') and os.path.basename(x) not in exclude: