diff --git a/CHANGELOG.rst b/CHANGELOG.rst index f7df33a1c..4b86f9630 100644 --- a/CHANGELOG.rst +++ b/CHANGELOG.rst @@ -6,6 +6,9 @@ kitty is a feature full, cross-platform, *fast*, GPU based terminal emulator. version 0.5.0 [future] --------------------------- +- Make it easy to select fonts by allowing listing of monospace fonts using: + kitty list-fonts + - macOS: Enable subpixel rendering of text for improved appearance - Linux: Support rendering of non-normalizable unicode combining characters by using harfbuzz diff --git a/__main__.py b/__main__.py index 9d9d57083..41b6bfa7a 100644 --- a/__main__.py +++ b/__main__.py @@ -7,6 +7,9 @@ import sys if len(sys.argv) > 1 and sys.argv[1] == 'icat': from kitty.icat import main main(sys.argv[1:]) +elif len(sys.argv) > 1 and sys.argv[1] == 'list-fonts': + from kitty.fonts.list import main + main(sys.argv[1:]) else: from kitty.main import main main() diff --git a/kitty/fonts/core_text.py b/kitty/fonts/core_text.py index 98f441489..11f69c1c4 100644 --- a/kitty/fonts/core_text.py +++ b/kitty/fonts/core_text.py @@ -34,6 +34,15 @@ def all_fonts_map(): return ans +def list_fonts(): + for fd in coretext_all_fonts(): + f = fd['family'] + if f: + fn = (f + ' ' + (fd['style'] or '')).strip() + is_mono = bool(fd['monospace']) + yield {'family': f, 'full_name': fn, 'is_monospace': is_mono} + + def find_best_match(family, bold, italic): q = re.sub(r'\s+', ' ', family.lower()) font_map = all_fonts_map() diff --git a/kitty/fonts/fontconfig.py b/kitty/fonts/fontconfig.py index 472187990..7d00b85bc 100644 --- a/kitty/fonts/fontconfig.py +++ b/kitty/fonts/fontconfig.py @@ -19,6 +19,8 @@ attr_map = {(False, False): 'font_family', def create_font_map(all_fonts): ans = {'family_map': {}, 'ps_map': {}, 'full_map': {}} for x in all_fonts: + if 'path' not in x: + continue f = (x.get('family') or '').lower() full = (x.get('full_name') or '').lower() ps = (x.get('postscript_name') or '').lower() @@ -33,14 +35,23 @@ def all_fonts_map(monospaced=True): return create_font_map(fc_list(monospaced)) +def list_fonts(): + for fd in fc_list(False): + f = fd.get('family') + if f: + fn = fd.get('full_name') or (f + ' ' + fd.get('style', '')).strip() + is_mono = fd.get('spacing') == 'MONO' + yield {'family': f, 'full_name': fn, 'is_monospace': is_mono} + + def find_best_match(family, bold=False, italic=False, monospaced=True): q = re.sub(r'\s+', ' ', family.lower()) font_map = all_fonts_map(monospaced) def score(candidate): - bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate['weight']) - italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate['slant']) - monospace_match = 0 if candidate['spacing'] == 'MONO' else 1 + bold_score = abs((FC_WEIGHT_BOLD if bold else FC_WEIGHT_REGULAR) - candidate.get('weight', 0)) + italic_score = abs((FC_SLANT_ITALIC if italic else FC_SLANT_ROMAN) - candidate.get('slant', 0)) + monospace_match = 0 if candidate.get('spacing') == 'MONO' else 1 return bold_score + italic_score, monospace_match # First look for an exact match @@ -56,8 +67,8 @@ def find_best_match(family, bold=False, italic=False, monospaced=True): def face_from_font(font, pt_sz=11.0, xdpi=96.0, ydpi=96.0): - font = fc_font(pt_sz, (xdpi + ydpi) / 2.0, font['path'], font['index']) - return Face(font['path'], font['index'], font['hinting'], font['hint_style'], pt_sz, xdpi, ydpi) + font = fc_font(pt_sz, (xdpi + ydpi) / 2.0, font['path'], font.get('index', 0)) + return Face(font['path'], font.get('index', 0), font.get('hinting', False), font.get('hint_style', 0), pt_sz, xdpi, ydpi) def resolve_family(f, main_family, bold, italic): @@ -86,8 +97,8 @@ def get_font_files(opts): def font_for_family(family): - ans = find_best_match(family) - return ans, ans['weight'] >= FC_WEIGHT_BOLD, ans['slant'] != FC_SLANT_ROMAN + ans = find_best_match(family, monospaced=False) + return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN def font_for_text(text, current_font_family='monospace', pt_sz=11.0, xdpi=96.0, ydpi=96.0, bold=False, italic=False): diff --git a/kitty/fonts/list.py b/kitty/fonts/list.py new file mode 100644 index 000000000..a39a1923b --- /dev/null +++ b/kitty/fonts/list.py @@ -0,0 +1,35 @@ +#!/usr/bin/env python +# vim:fileencoding=utf-8 +# License: GPL v3 Copyright: 2017, Kovid Goyal + +import sys +from kitty.constants import isosx + +if isosx: + from .core_text import list_fonts +else: + from .fontconfig import list_fonts + + +def create_family_groups(monospaced=True): + g = {} + for f in list_fonts(): + if not monospaced or f['is_monospace']: + g.setdefault(f['family'], []).append(f) + return g + + +def main(argv): + isatty = sys.stdout.isatty() + groups = create_family_groups() + for k in sorted(groups, key=lambda x: x.lower()): + if isatty: + print('\033[1;32m' + k + '\033[m') + else: + print(k) + for f in sorted(groups[k], key=lambda x: x['full_name'].lower()): + p = f['full_name'] + if isatty: + p = '\033[3m' + p + '\033[m' + print(' ', p) + print() diff --git a/kitty/kitty.conf b/kitty/kitty.conf index 016009089..c50d0ab97 100644 --- a/kitty/kitty.conf +++ b/kitty/kitty.conf @@ -5,14 +5,12 @@ # by the OSes font system. Setting them manually is useful for font families # that have many weight variants like Book, Medium, Thick, etc. For example: # font_family Operator Mono Book -# bold_font Operator Mono Thick -# bold_italic_font Operator Mono Medium -# or -# font_family SF Mono Medium -# bold_font SF Mono Semibold -# bold_italic_font SF Mono Semibold -# Note that you should use the full family name but do not add Bold or Italic qualifiers -# to the name. +# bold_font Operator Mono Medium +# italic_font Operator Mono Book Italic +# bold_italic_font Operator Mono Medium Italic +# +# You can get a list of full family names available on your computer by running +# kitty list-fonts font_family monospace italic_font auto bold_font auto