diff --git a/docs/changelog.rst b/docs/changelog.rst index debe6ab80..2a8d93308 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -22,6 +22,9 @@ To update |kitty|, :doc:`follow the instructions `. - Fix incorrect rendering of some symbols when followed by a space while using the PowerLine font which does not have a space glyph (:iss:`1225`) +- Linux: Allow using fonts with spacing=90 in addition to fonts with + spacing=100 (:iss:`1968`) + 0.14.4 [2019-08-31] --------------------- diff --git a/docs/faq.rst b/docs/faq.rst index 0c21f39b3..d00460e65 100644 --- a/docs/faq.rst +++ b/docs/faq.rst @@ -152,7 +152,7 @@ only monospace fonts, since every cell in the grid has to be the same size. If your font is not listed in ``kitty list-fonts`` it means that it is not monospace. On Linux you can list all monospace fonts with:: - fc-list : family spacing | grep spacing=100 + fc-list : family spacing | grep -e spacing=100 -e spacing=90 Note that the spacing property is calculated by fontconfig based on actual glyph widths in the font. If for some reason fontconfig concludes your favorite diff --git a/kitty/fontconfig.c b/kitty/fontconfig.c index 42fc25dab..7d3ee9da5 100644 --- a/kitty/fontconfig.c +++ b/kitty/fontconfig.c @@ -83,19 +83,19 @@ font_set(FcFontSet *fs) { static PyObject* fc_list(PyObject UNUSED *self, PyObject *args) { - int allow_bitmapped_fonts = 0, only_monospaced_fonts = 1; + int allow_bitmapped_fonts = 0, spacing = -1; PyObject *ans = NULL; FcObjectSet *os = NULL; FcPattern *pat = NULL; FcFontSet *fs = NULL; - if (!PyArg_ParseTuple(args, "|pp", &only_monospaced_fonts, &allow_bitmapped_fonts)) return NULL; + if (!PyArg_ParseTuple(args, "|ip", &spacing, &allow_bitmapped_fonts)) return NULL; pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); if (!allow_bitmapped_fonts) { AP(FcPatternAddBool, FC_OUTLINE, true, "outline"); AP(FcPatternAddBool, FC_SCALABLE, true, "scalable"); } - if (only_monospaced_fonts) AP(FcPatternAddInteger, FC_SPACING, FC_MONO, "spacing"); + if (spacing > -1) AP(FcPatternAddInteger, FC_SPACING, spacing, "spacing"); os = FcObjectSetBuild(FC_FILE, FC_POSTSCRIPT_NAME, FC_FAMILY, FC_STYLE, FC_FULLNAME, FC_WEIGHT, FC_WIDTH, FC_SLANT, FC_HINT_STYLE, FC_INDEX, FC_HINTING, FC_SCALABLE, FC_OUTLINE, FC_COLOR, FC_SPACING, NULL); if (!os) { PyErr_SetString(PyExc_ValueError, "Failed to create fontconfig object set"); goto end; } fs = FcFontList(NULL, pat, os); @@ -147,21 +147,21 @@ end: static PyObject* fc_match(PyObject UNUSED *self, PyObject *args) { char *family = NULL; - int bold = 0, italic = 0, allow_bitmapped_fonts = 0, monospaced = 0; + int bold = 0, italic = 0, allow_bitmapped_fonts = 0, spacing = FC_MONO; double size_in_pts = 0, dpi = 0; FcPattern *pat = NULL; PyObject *ans = NULL; - if (!PyArg_ParseTuple(args, "|zppppdd", &family, &bold, &italic, &monospaced, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL; + if (!PyArg_ParseTuple(args, "|zppppdd", &family, &bold, &italic, &spacing, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL; pat = FcPatternCreate(); if (pat == NULL) return PyErr_NoMemory(); if (family && strlen(family) > 0) AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)family, "family"); - if (monospaced) { + if (spacing >= FC_DUAL) { // pass the family,monospace as the family parameter to fc-match, // which will fallback to using monospace if the family does not match. AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family"); - AP(FcPatternAddInteger, FC_SPACING, FC_MONO, "spacing"); + AP(FcPatternAddInteger, FC_SPACING, spacing, "spacing"); } if (!allow_bitmapped_fonts) { AP(FcPatternAddBool, FC_OUTLINE, true, "outline"); @@ -243,5 +243,9 @@ init_fontconfig_library(PyObject *module) { PyModule_AddIntMacro(module, FC_WEIGHT_BOLD); PyModule_AddIntMacro(module, FC_SLANT_ITALIC); PyModule_AddIntMacro(module, FC_SLANT_ROMAN); + PyModule_AddIntMacro(module, FC_PROPORTIONAL); + PyModule_AddIntMacro(module, FC_DUAL); + PyModule_AddIntMacro(module, FC_MONO); + PyModule_AddIntMacro(module, FC_CHARCELL); return true; } diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index 5b90c690b..c49fb990b 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -7,7 +7,7 @@ from functools import lru_cache from kitty.fast_data_types import ( FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, FC_WEIGHT_REGULAR, - fc_list, fc_match as fc_match_impl, + fc_list, fc_match as fc_match_impl, FC_DUAL, FC_MONO ) attr_map = {(False, False): 'font_family', @@ -32,15 +32,19 @@ def create_font_map(all_fonts): @lru_cache() def all_fonts_map(monospaced=True): - return create_font_map(fc_list(monospaced)) + if monospaced: + ans = fc_list(FC_DUAL) + fc_list(FC_MONO) + else: + ans = fc_list() + return create_font_map(ans) def list_fonts(): - for fd in fc_list(False): + for fd in fc_list(): f = fd.get('family') if f: fn = fd.get('full_name') or (f + ' ' + fd.get('style', '')).strip() - is_mono = fd.get('spacing') == 'MONO' + is_mono = fd.get('spacing') in ('MONO', 'DUAL') yield {'family': f, 'full_name': fn, 'is_monospace': is_mono} @@ -49,8 +53,8 @@ def family_name_to_key(family): @lru_cache() -def fc_match(family, bold, italic): - return fc_match_impl(family, bold, italic) +def fc_match(family, bold, italic, spacing=FC_MONO): + return fc_match_impl(family, bold, italic, spacing) def find_best_match(family, bold=False, italic=False, monospaced=True): @@ -71,20 +75,21 @@ def find_best_match(family, bold=False, italic=False, monospaced=True): return candidates[0] # Use fc-match to see if we can find a monospaced font that matches family - possibility = fc_match(family, False, False) - for key, map_key in (('postscript_name', 'ps_map'), ('full_name', 'full_map'), ('family', 'family_map')): - val = possibility.get(key) - if val: - candidates = font_map[map_key].get(family_name_to_key(val)) - if candidates: - if len(candidates) == 1: - # happens if the family name is an alias, so we search with - # the actual family name to see if we can find all the - # fonts in the family. - family_name_candidates = font_map['family_map'].get(family_name_to_key(candidates[0]['family'])) - if family_name_candidates and len(family_name_candidates) > 1: - candidates = family_name_candidates - return sorted(candidates, key=score)[0] + for spacing in (FC_MONO, FC_DUAL): + possibility = fc_match(family, False, False, spacing) + for key, map_key in (('postscript_name', 'ps_map'), ('full_name', 'full_map'), ('family', 'family_map')): + val = possibility.get(key) + if val: + candidates = font_map[map_key].get(family_name_to_key(val)) + if candidates: + if len(candidates) == 1: + # happens if the family name is an alias, so we search with + # the actual family name to see if we can find all the + # fonts in the family. + family_name_candidates = font_map['family_map'].get(family_name_to_key(candidates[0]['family'])) + if family_name_candidates and len(family_name_candidates) > 1: + candidates = family_name_candidates + return sorted(candidates, key=score)[0] # Use fc-match with a generic family family = 'monospace' if monospaced else 'sans-serif'