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:
parent
4532194b01
commit
9aa1d74f83
@ -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)))()
|
||||||
|
|||||||
@ -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
|
||||||
|
|||||||
@ -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)
|
||||||
|
try:
|
||||||
bitmap_char = render_char(text, bold, italic, width)
|
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:
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user