mirror of
https://github.com/kovidgoyal/kitty
synced 2026-07-09 13:45:26 +02:00
Implement symbol maps
A config option to use special fonts for specified unicode characters. Useful for things like Powerline without needing patched fonts.
This commit is contained in:
@@ -80,6 +80,38 @@ def parse_key(val, keymap):
|
||||
keymap[(mods, key)] = action
|
||||
|
||||
|
||||
def parse_symbol_map(val):
|
||||
parts = val.split(' ')
|
||||
symbol_map = {}
|
||||
|
||||
def abort():
|
||||
safe_print('Symbol map: {} is invalid, ignoring'.format(val), file=sys.stderr)
|
||||
return {}
|
||||
|
||||
if len(parts) < 2:
|
||||
return abort()
|
||||
family = ' '.join(parts[1:])
|
||||
|
||||
def to_chr(x):
|
||||
if not x.startswith('U+'):
|
||||
raise ValueError()
|
||||
x = int(x[2:], 16)
|
||||
return x
|
||||
|
||||
for x in parts[0].split(','):
|
||||
a, b = x.partition('-')[::2]
|
||||
b = b or a
|
||||
try:
|
||||
a, b = map(to_chr, (a, b))
|
||||
except Exception:
|
||||
return abort()
|
||||
if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1:
|
||||
return abort()
|
||||
for y in range(a, b+1):
|
||||
symbol_map[chr(y)] = family
|
||||
return symbol_map
|
||||
|
||||
|
||||
def to_open_url_modifiers(val):
|
||||
return parse_mods(val.split('+'))
|
||||
|
||||
@@ -125,7 +157,7 @@ for a in ('active', 'inactive'):
|
||||
|
||||
|
||||
def parse_config(lines):
|
||||
ans = {'keymap': {}}
|
||||
ans = {'keymap': {}, 'symbol_map': {}}
|
||||
for line in lines:
|
||||
line = line.strip()
|
||||
if not line or line.startswith('#'):
|
||||
@@ -136,6 +168,9 @@ def parse_config(lines):
|
||||
if key == 'map':
|
||||
parse_key(val, ans['keymap'])
|
||||
continue
|
||||
if key == 'symbol_map':
|
||||
ans['symbol_map'].update(parse_symbol_map(val))
|
||||
continue
|
||||
tm = type_map.get(key)
|
||||
if tm is not None:
|
||||
val = tm(val)
|
||||
|
||||
@@ -7,9 +7,18 @@ from kitty.fast_data_types import CTFace as Face
|
||||
from kitty.utils import get_logical_dpi, wcwidth, ceil_int
|
||||
|
||||
main_font = {}
|
||||
symbol_map = {}
|
||||
cell_width = cell_height = baseline = CellTexture = WideCellTexture = underline_thickness = underline_position = None
|
||||
|
||||
|
||||
def install_symbol_map(val, font_size, dpi):
|
||||
global symbol_map
|
||||
symbol_map = {}
|
||||
family_map = {f: Face(f, False, False, False, font_size, dpi) for f in set(val.values())}
|
||||
for ch, family in val.items():
|
||||
symbol_map[ch] = family_map[family]
|
||||
|
||||
|
||||
def set_font_family(opts, ignore_dpi_failure=False):
|
||||
global cell_width, cell_height, baseline, CellTexture, WideCellTexture, underline_thickness, underline_position
|
||||
try:
|
||||
@@ -32,6 +41,7 @@ def set_font_family(opts, ignore_dpi_failure=False):
|
||||
for bold in (False, True):
|
||||
for italic in (False, True):
|
||||
main_font[(bold, italic)] = Face(get_family(bold, italic), bold, italic, True, opts.font_size, dpi)
|
||||
install_symbol_map(opts.symbol_map, opts.font_size, dpi)
|
||||
mf = main_font[(False, False)]
|
||||
cell_width, cell_height = mf.cell_size()
|
||||
CellTexture = ctypes.c_ubyte * (cell_width * cell_height)
|
||||
@@ -57,8 +67,9 @@ def split(buf, cell_width, cell_height):
|
||||
|
||||
|
||||
def render_cell(text=' ', bold=False, italic=False):
|
||||
width = wcwidth(text[0])
|
||||
face = main_font[(bold, italic)]
|
||||
ch = text[0]
|
||||
width = wcwidth(ch)
|
||||
face = symbol_map.get(ch) or main_font[(bold, italic)]
|
||||
if width == 2:
|
||||
buf, width = WideCellTexture(), cell_width * 2
|
||||
else:
|
||||
|
||||
@@ -162,3 +162,8 @@ def get_font_files(opts):
|
||||
|
||||
do('bold'), do('italic'), do('bi')
|
||||
return ans
|
||||
|
||||
|
||||
def font_for_family(family):
|
||||
ans = get_font_information(family)
|
||||
return ans._replace(face=Face(ans.face, ans.index))
|
||||
|
||||
@@ -7,17 +7,21 @@ import sys
|
||||
import unicodedata
|
||||
from collections import namedtuple
|
||||
from functools import lru_cache
|
||||
from itertools import chain
|
||||
from threading import Lock
|
||||
|
||||
from kitty.fast_data_types import FT_PIXEL_MODE_GRAY, Face
|
||||
from kitty.fonts.box_drawing import render_missing_glyph
|
||||
from kitty.utils import ceil_int, get_logical_dpi, safe_print, wcwidth
|
||||
|
||||
from .fontconfig import find_font_for_character, get_font_files, FontNotFound
|
||||
from .fontconfig import (
|
||||
FontNotFound, find_font_for_character, font_for_family, get_font_files
|
||||
)
|
||||
|
||||
current_font_family = current_font_family_name = cff_size = cell_width = cell_height = baseline = None
|
||||
CharTexture = underline_position = underline_thickness = None
|
||||
alt_face_cache = {}
|
||||
symbol_map = {}
|
||||
|
||||
|
||||
def set_char_size(face, width=0, height=0, hres=0, vres=0):
|
||||
@@ -55,6 +59,14 @@ def font_for_char(char, bold=False, italic=False, allow_bitmaped_fonts=False):
|
||||
)
|
||||
|
||||
|
||||
def install_symbol_map(val):
|
||||
global symbol_map
|
||||
symbol_map = {}
|
||||
family_map = {f: font_for_family(f) for f in set(val.values())}
|
||||
for ch, family in val.items():
|
||||
symbol_map[ch] = family_map[family]
|
||||
|
||||
|
||||
def font_units_to_pixels(x, units_per_em, size_in_pts, dpi):
|
||||
return ceil_int(x * ((size_in_pts * dpi) / (72 * units_per_em)))
|
||||
|
||||
@@ -73,7 +85,8 @@ def set_font_family(opts):
|
||||
'hres': int(dpi[0]),
|
||||
'vres': int(dpi[1])
|
||||
}
|
||||
for fobj in current_font_family.values():
|
||||
install_symbol_map(opts.symbol_map)
|
||||
for fobj in chain(current_font_family.values(), symbol_map.values()):
|
||||
set_char_size(fobj.face, **cff_size)
|
||||
face = current_font_family['regular'].face
|
||||
cell_width = calc_cell_width(current_font_family['regular'], face)
|
||||
@@ -114,51 +127,60 @@ def render_to_bitmap(font, face, text):
|
||||
return bitmap
|
||||
|
||||
|
||||
def render_using_face(font, face, text, width, italic, bold):
|
||||
bitmap = render_to_bitmap(font, face, text)
|
||||
if width == 1 and bitmap.width > cell_width:
|
||||
extra = bitmap.width - cell_width
|
||||
if italic and extra < cell_width // 2:
|
||||
bitmap = face.trim_to_width(bitmap, cell_width)
|
||||
elif extra > max(2, 0.3 * cell_width) and face.is_scalable:
|
||||
# rescale the font size so that the glyph is visible in a single
|
||||
# cell and hope somebody updates libc's wcwidth
|
||||
sz = cff_size.copy()
|
||||
sz['width'] = int(sz['width'] * cell_width / bitmap.width)
|
||||
# Preserve aspect ratio
|
||||
sz['height'] = int(sz['height'] * cell_width / bitmap.width)
|
||||
try:
|
||||
set_char_size(face, **sz)
|
||||
bitmap = render_to_bitmap(font, face, text)
|
||||
finally:
|
||||
set_char_size(face, **cff_size)
|
||||
m = face.glyph_metrics()
|
||||
return CharBitmap(
|
||||
bitmap.buffer,
|
||||
ceil_int(abs(m.horiBearingX) / 64),
|
||||
ceil_int(abs(m.horiBearingY) / 64),
|
||||
ceil_int(m.horiAdvance / 64), bitmap.rows, bitmap.width
|
||||
)
|
||||
|
||||
|
||||
def render_char(text, bold=False, italic=False, width=1):
|
||||
key = 'regular'
|
||||
if bold:
|
||||
key = 'bi' if italic else 'bold'
|
||||
elif italic:
|
||||
key = 'italic'
|
||||
ch = text[0]
|
||||
with freetype_lock:
|
||||
font = current_font_family.get(key) or current_font_family['regular']
|
||||
face = font.face
|
||||
if not face.get_char_index(text[0]):
|
||||
try:
|
||||
font = font_for_char(text[0], bold, italic)
|
||||
except FontNotFound:
|
||||
font = font_for_char(
|
||||
text[0], bold, italic, allow_bitmaped_fonts=True
|
||||
)
|
||||
face = alt_face_cache.get(font)
|
||||
if face is None:
|
||||
face = alt_face_cache[font] = Face(font.face, font.index)
|
||||
if face.is_scalable:
|
||||
set_char_size(face, **cff_size)
|
||||
bitmap = render_to_bitmap(font, face, text)
|
||||
if width == 1 and bitmap.width > cell_width:
|
||||
extra = bitmap.width - cell_width
|
||||
if italic and extra < cell_width // 2:
|
||||
bitmap = face.trim_to_width(bitmap, cell_width)
|
||||
elif extra > max(2, 0.3 * cell_width) and face.is_scalable:
|
||||
# rescale the font size so that the glyph is visible in a single
|
||||
# cell and hope somebody updates libc's wcwidth
|
||||
sz = cff_size.copy()
|
||||
sz['width'] = int(sz['width'] * cell_width / bitmap.width)
|
||||
# Preserve aspect ratio
|
||||
sz['height'] = int(sz['height'] * cell_width / bitmap.width)
|
||||
font = symbol_map.get(ch)
|
||||
if font is None or not font.face.get_char_index(ch):
|
||||
font = current_font_family.get(key) or current_font_family['regular']
|
||||
face = font.face
|
||||
if not face.get_char_index(ch):
|
||||
try:
|
||||
set_char_size(face, **sz)
|
||||
bitmap = render_to_bitmap(font, face, text)
|
||||
finally:
|
||||
set_char_size(face, **cff_size)
|
||||
m = face.glyph_metrics()
|
||||
return CharBitmap(
|
||||
bitmap.buffer,
|
||||
ceil_int(abs(m.horiBearingX) / 64),
|
||||
ceil_int(abs(m.horiBearingY) / 64),
|
||||
ceil_int(m.horiAdvance / 64), bitmap.rows, bitmap.width
|
||||
)
|
||||
font = font_for_char(ch, bold, italic)
|
||||
except FontNotFound:
|
||||
font = font_for_char(
|
||||
ch, bold, italic, allow_bitmaped_fonts=True
|
||||
)
|
||||
face = alt_face_cache.get(font)
|
||||
if face is None:
|
||||
face = alt_face_cache[font] = Face(font.face, font.index)
|
||||
if face.is_scalable:
|
||||
set_char_size(face, **cff_size)
|
||||
else:
|
||||
face = font.face
|
||||
return render_using_face(font, face, text, width, italic, bold)
|
||||
|
||||
|
||||
def place_char_in_cell(bitmap_char):
|
||||
|
||||
@@ -207,3 +207,18 @@ map ctrl+shift+q close_tab
|
||||
map ctrl+shift+l next_layout
|
||||
map ctrl+shift+. move_tab_forward
|
||||
map ctrl+shift+, move_tab_backward
|
||||
|
||||
|
||||
# Symbol mapping (special font for specified unicode code points). Map the
|
||||
# specified unicode codepoints to a particular font. Useful if you need special
|
||||
# rendering for some symbols, such as for Powerline. Avoids the need for
|
||||
# patched fonts. Each unicode code point is specified in the form U+<code point
|
||||
# in hexadecimal>. You can specify multiple code points, separated by commas
|
||||
# and ranges separated by hyphens. symbol_map itself can be specified multiple times.
|
||||
# Syntax is:
|
||||
#
|
||||
# symbol_map codepoints Font Family Name
|
||||
#
|
||||
# For example:
|
||||
#
|
||||
# symbol_map U+E0A0-U+E0A2,U+E0B0-U+E0B3 PowerlineSymbols
|
||||
|
||||
Reference in New Issue
Block a user