diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 47a5315dd..783ad9339 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -13,7 +13,7 @@ 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; + PyObject *characters; FcBool hinting, scalable, outline; FcChar8 *path = NULL; FcPattern *pat = NULL, *match = NULL; @@ -21,7 +21,8 @@ get_fontconfig_font(PyObject UNUSED *self, PyObject *args) { FcCharSet *charset = NULL; PyObject *ans = NULL; - if (!PyArg_ParseTuple(args, "spppdId", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &character, &dpi)) return NULL; + if (!PyArg_ParseTuple(args, "spppdO!d", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &PyUnicode_Type, &characters, &dpi)) return NULL; + if (PyUnicode_READY(characters) != 0) return NULL; pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); @@ -35,10 +36,16 @@ get_fontconfig_font(PyObject UNUSED *self, PyObject *args) { 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) { + if (PyUnicode_GET_LENGTH(characters) > 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; } + int kind = PyUnicode_KIND(characters); void *data = PyUnicode_DATA(characters); + for (int i = 0; i < PyUnicode_GET_LENGTH(characters); i++) { + if (!FcCharSetAddChar(charset, PyUnicode_READ(kind, data, i))) { + PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset"); + goto end; + } + } AP(FcPatternAddCharSet, FC_CHARSET, charset, "charset"); } #undef AP diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index c27f17c6d..ecbf23e2b 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -27,10 +27,11 @@ def to_bool(x): return x.lower() == 'true' -def font_not_found(err, char): +def font_not_found(err, chars): msg = 'Failed to find font' - if char is not None: - msg = 'Failed to find font for character U+{:X}, error from fontconfig: {}'. format(ord(char[0]), err) + if chars: + chars = ', '.join('U+{:X}'.format(ord(c)) for c in chars) + msg = 'Failed to find font for characters U+{:X}, error from fontconfig: {}'. format(chars, err) return FontNotFound(msg) @@ -40,16 +41,16 @@ def get_font( italic=False, allow_bitmaped_fonts=False, size_in_pts=None, - character=None, + characters='', dpi=None ): try: 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 + characters or '', dpi or 0 ) except KeyError as err: - raise font_not_found(err, character) + raise font_not_found(err, characters) return Font( path, hinting, hintstyle, bold, italic, scalable, outline, weight, @@ -57,9 +58,9 @@ def get_font( ) -def find_font_for_character( +def find_font_for_characters( family, - char, + chars, bold=False, italic=False, allow_bitmaped_fonts=False, @@ -70,14 +71,14 @@ def find_font_for_character( family, bold, italic, - character=char, + characters=chars, allow_bitmaped_fonts=allow_bitmaped_fonts, size_in_pts=size_in_pts, dpi=dpi ) if not ans.face or not os.path.exists(ans.face): raise FontNotFound( - 'Failed to find font for character U+{:X}'.format(ord(char[0])) + 'Failed to find font for characters: {!r}'.format(chars) ) return ans diff --git a/kitty/fonts/freetype.py b/kitty/fonts/freetype.py index ef9977032..9bb759d66 100644 --- a/kitty/fonts/freetype.py +++ b/kitty/fonts/freetype.py @@ -13,7 +13,7 @@ from kitty.fonts.box_drawing import render_missing_glyph from kitty.utils import ceil_int, get_logical_dpi, safe_print, wcwidth, adjust_line_height from .fontconfig import ( - FontNotFound, find_font_for_character, font_for_family, get_font_files + FontNotFound, find_font_for_characters, font_for_family, get_font_files ) current_font_family = current_font_family_name = cff_size = cell_width = cell_height = baseline = None @@ -41,9 +41,9 @@ def calc_cell_width(font, face): @lru_cache(maxsize=2**10) -def font_for_char(char, bold=False, italic=False, allow_bitmaped_fonts=False): +def font_for_text(char, bold=False, italic=False, allow_bitmaped_fonts=False): if allow_bitmaped_fonts: - return find_font_for_character( + return find_font_for_characters( current_font_family_name, char, bold, @@ -52,7 +52,7 @@ def font_for_char(char, bold=False, italic=False, allow_bitmaped_fonts=False): size_in_pts=cff_size['width'] / 64, dpi=(cff_size['hres'] + cff_size['vres']) / 2 ) - return find_font_for_character( + return find_font_for_characters( current_font_family_name, char, bold, italic ) @@ -104,7 +104,7 @@ def set_font_family(opts, override_font_size=None): face.underline_thickness, face.units_per_EM, size_in_pts, dpi[1] ) CharTexture = ctypes.c_ubyte * (cell_width * cell_height) - font_for_char.cache_clear() + font_for_text.cache_clear() alt_face_cache.clear() return cell_width, cell_height @@ -152,22 +152,29 @@ def render_using_face(font, face, text, width, italic, bold): ) -def face_for_char(ch, bold=False, italic=False): +def font_has_text(font, text): + for c in text: + if not font.face.get_char_index(c): + return False + return True + + +def face_for_text(text, bold=False, italic=False): key = 'regular' if bold: key = 'bi' if italic else 'bold' elif italic: key = 'italic' - font = symbol_map.get(ch) - if font is None or not font.face.get_char_index(ch): + font = symbol_map.get(text[0]) + if font is None or not font_has_text(font, text): font = current_font_family.get(key) or current_font_family['regular'] face = font.face - if not face.get_char_index(ch): + if not font_has_text(font, text): try: - font = font_for_char(ch, bold, italic) + font = font_for_text(text, bold, italic) except FontNotFound: - font = font_for_char( - ch, bold, italic, allow_bitmaped_fonts=True + font = font_for_text( + text, bold, italic, allow_bitmaped_fonts=True ) face = alt_face_cache.get(font) if face is None: @@ -180,12 +187,12 @@ def face_for_char(ch, bold=False, italic=False): def render_char(text, bold=False, italic=False, width=1): - font, face = face_for_char(text[0], bold, italic) + font, face = face_for_text(text[0], bold, italic) return render_using_face(font, face, text, width, italic, bold) def render_complex_char(text, bold=False, italic=False, width=1): - font, face = face_for_char(text[0], bold, italic) + font, face = face_for_text(text, bold, italic) import pprint pprint.pprint(face.shape(text, font.hinting, font.hintstyle))