From 02ef3c6dc8d245ee33223f79cd092307d9bd2c43 Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 8 Feb 2017 21:52:10 +0530 Subject: [PATCH] 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 --- .travis.yml | 4 +- kitty/data-types.c | 2 + kitty/data-types.h | 2 + kitty/fontconfig.c | 84 +++++++++++++++++++++++++++++++++++++++ kitty/fonts/fontconfig.py | 39 ++++++++++++++---- setup.py | 6 +-- 6 files changed, 125 insertions(+), 12 deletions(-) create mode 100644 kitty/fontconfig.c diff --git a/.travis.yml b/.travis.yml index c36cfc076..04c63069c 100644 --- a/.travis.yml +++ b/.travis.yml @@ -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 diff --git a/kitty/data-types.c b/kitty/data-types.c index ed88ddee3..56b49b6da 100644 --- a/kitty/data-types.c +++ b/kitty/data-types.c @@ -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); diff --git a/kitty/data-types.h b/kitty/data-types.h index 236638710..479a1eb68 100644 --- a/kitty/data-types.h +++ b/kitty/data-types.h @@ -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); diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c new file mode 100644 index 000000000..6bc2221bf --- /dev/null +++ b/kitty/fontconfig.c @@ -0,0 +1,84 @@ +/* + * fontconfig.c + * Copyright (C) 2017 Kovid Goyal + * + * Distributed under terms of the GPL3 license. + */ + +#include "data-types.h" +#include + +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; +} diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index f915c5df2..642c0e6a3 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -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, diff --git a/setup.py b/setup.py index 7bf1dc276..a19a30dd1 100755 --- a/setup.py +++ b/setup.py @@ -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: