Linux: Don't crash when fontconfig is unable to find a font for a character.

Instead render it as a missing glyph
This commit is contained in:
Kovid Goyal 2017-02-08 09:32:15 +05:30
parent 4532194b01
commit 9aa1d74f83
3 changed files with 68 additions and 15 deletions

View File

@ -26,7 +26,7 @@ def draw_hline(buf, width, x1, x2, y, level):
def draw_vline(buf, width, y1, y2, x, level): def draw_vline(buf, width, y1, y2, x, level):
' Draw a horizontal line between [y1, y2) centered at x with the thickness given by level ' ' Draw a vertical line between [y1, y2) centered at x with the thickness given by level '
sz = thickness(level=level, horizontal=True) sz = thickness(level=level, horizontal=True)
start = x - sz // 2 start = x - sz // 2
for x in range(start, start + sz): for x in range(start, start + sz):
@ -345,6 +345,15 @@ def render_box_char(ch, buf, width, height):
return buf return buf
def render_missing_glyph(buf, width, height):
hgap = thickness(level=0, horizontal=True) + 1
vgap = thickness(level=0, horizontal=False) + 1
draw_hline(buf, width, hgap, width - hgap + 1, vgap, 0)
draw_hline(buf, width, hgap, width - hgap + 1, height - vgap, 0)
draw_vline(buf, width, vgap, height - vgap + 1, hgap, 0)
draw_vline(buf, width, vgap, height - vgap + 1, width - hgap, 0)
def join_rows(width, height, rows): def join_rows(width, height, rows):
import ctypes import ctypes
ans = (ctypes.c_ubyte * (width * height * len(rows)))() ans = (ctypes.c_ubyte * (width * height * len(rows)))()

View File

@ -2,10 +2,12 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import os
import re import re
import subprocess import subprocess
from collections import namedtuple from collections import namedtuple
from functools import lru_cache from functools import lru_cache
from kitty.fast_data_types import Face from kitty.fast_data_types import Face
@ -16,9 +18,15 @@ def escape_family_name(name):
Font = namedtuple('Font', 'face hinting hintstyle bold italic') Font = namedtuple('Font', 'face hinting hintstyle bold italic')
class FontNotFound(ValueError):
pass
def get_font(query, bold, italic): def get_font(query, bold, italic):
query += ':scalable=true:outline=true' query += ':scalable=true:outline=true'
raw = subprocess.check_output(['fc-match', query, '-f', '%{file}\x1e%{hinting}\x1e%{hintstyle}']).decode('utf-8') raw = subprocess.check_output(
['fc-match', query, '-f', '%{file}\x1e%{hinting}\x1e%{hintstyle}'
]).decode('utf-8')
parts = raw.split('\x1e') parts = raw.split('\x1e')
hintstyle, hinting = 1, 'True' hintstyle, hinting = 1, 'True'
if len(parts) == 3: if len(parts) == 3:
@ -32,12 +40,21 @@ def get_font(query, bold, italic):
@lru_cache(maxsize=4096) @lru_cache(maxsize=4096)
def find_font_for_character(family, char, bold=False, italic=False): def find_font_for_character(family, char, bold=False, italic=False):
q = escape_family_name(family) + ':charset={}'.format(hex(ord(char[0]))[2:]) q = escape_family_name(family) + ':charset={}'.format(
hex(ord(char[0]))[2:])
if bold: if bold:
q += ':weight=200' q += ':weight=200'
if italic: if italic:
q += ':slant=100' q += ':slant=100'
return get_font(q, bold, italic) try:
ans = get_font(q, bold, italic)
except subprocess.CalledProcessError as err:
raise FontNotFound(
'Failed to find font for character U+{:X}, error from fontconfig: {}'.
format(ord(char[0]), err))
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])))
return ans
@lru_cache(maxsize=64) @lru_cache(maxsize=64)
@ -52,7 +69,11 @@ def get_font_information(q, bold=False, italic=False):
def get_font_files(opts): def get_font_files(opts):
ans = {} ans = {}
attr_map = {'bold': 'bold_font', 'italic': 'italic_font', 'bi': 'bold_italic_font'} attr_map = {
'bold': 'bold_font',
'italic': 'italic_font',
'bi': 'bold_italic_font'
}
def get_family(key=None): def get_family(key=None):
ans = getattr(opts, attr_map.get(key, 'font_family')) ans = getattr(opts, attr_map.get(key, 'font_family'))
@ -61,11 +82,17 @@ def get_font_files(opts):
return ans return ans
n = get_font_information(get_family()) n = get_font_information(get_family())
ans['regular'] = Font(Face(n.face), n.hinting, n.hintstyle, n.bold, n.italic) ans['regular'] = Font(
Face(n.face), n.hinting, n.hintstyle, n.bold, n.italic)
def do(key): def do(key):
b = get_font_information(get_family(key), bold=key in ('bold', 'bi'), italic=key in ('italic', 'bi')) b = get_font_information(
get_family(key),
bold=key in ('bold', 'bi'),
italic=key in ('italic', 'bi'))
if b.face != n.face: if b.face != n.face:
ans[key] = Font(Face(b.face), b.hinting, b.hintstyle, b.bold, b.italic) ans[key] = Font(
Face(b.face), b.hinting, b.hintstyle, b.bold, b.italic)
do('bold'), do('italic'), do('bi') do('bold'), do('italic'), do('bi')
return ans return ans

View File

@ -2,18 +2,18 @@
# vim:fileencoding=utf-8 # vim:fileencoding=utf-8
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import unicodedata
import ctypes import ctypes
import sys
import unicodedata
from collections import namedtuple from collections import namedtuple
from functools import lru_cache from functools import lru_cache
from threading import Lock from threading import Lock
from kitty.fast_data_types import Face, FT_PIXEL_MODE_GRAY from kitty.fast_data_types import FT_PIXEL_MODE_GRAY, Face
from kitty.utils import ceil_int from kitty.fonts.box_drawing import render_missing_glyph
from .fontconfig import find_font_for_character, get_font_files from kitty.utils import ceil_int, get_logical_dpi, safe_print, wcwidth
from kitty.utils import get_logical_dpi, wcwidth
from .fontconfig import find_font_for_character, get_font_files, FontNotFound
current_font_family = current_font_family_name = cff_size = cell_width = cell_height = baseline = None current_font_family = current_font_family_name = cff_size = cell_width = cell_height = baseline = None
CharTexture = underline_position = underline_thickness = None CharTexture = underline_position = underline_thickness = None
@ -168,12 +168,29 @@ def current_cell():
return CharTexture, cell_width, cell_height, baseline, underline_thickness, underline_position return CharTexture, cell_width, cell_height, baseline, underline_thickness, underline_position
@lru_cache(maxsize=8)
def missing_glyph(width):
w = cell_width * width
ans = bytearray(w * cell_height)
render_missing_glyph(ans, w, cell_height)
first, second = CharBitmap(ans, 0, 0, 0, cell_height, w), None
if width == 2:
first, second = split_char_bitmap(first)
second = create_cell_buffer(second, 0, 0, second.rows, 0, 0, second.columns)
first = create_cell_buffer(first, 0, 0, first.rows, 0, 0, first.columns)
return first, second
def render_cell(text=' ', bold=False, italic=False): def render_cell(text=' ', bold=False, italic=False):
# TODO: Handle non-normalizable combining chars. Probably need to use # TODO: Handle non-normalizable combining chars. Probably need to use
# harfbuzz for that # harfbuzz for that
text = unicodedata.normalize('NFC', text)[0] text = unicodedata.normalize('NFC', text)[0]
width = wcwidth(text) width = wcwidth(text)
bitmap_char = render_char(text, bold, italic, width) try:
bitmap_char = render_char(text, bold, italic, width)
except FontNotFound as err:
safe_print('ERROR:', err, file=sys.stderr)
return missing_glyph(width)
second = None second = None
if width == 2: if width == 2:
if bitmap_char.columns > cell_width: if bitmap_char.columns > cell_width: