Implement fallback fonts in CoreText

This commit is contained in:
Kovid Goyal 2017-11-11 08:08:03 +05:30
parent 4edfa9f4fd
commit 3592f82e4f
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
5 changed files with 53 additions and 23 deletions

View File

@ -105,16 +105,40 @@ coretext_all_fonts(PyObject UNUSED *_self) {
return ans;
}
static PyObject*
face_for_text(PyObject UNUSED *self, PyObject UNUSED *args) {
Py_RETURN_NONE; // TODO: Implement this
}
static void
free_font(void *f) {
CFRelease((CTFontRef)f);
}
static inline PyObject*
ft_face(CTFontRef font, float pt_sz, float xdpi, float ydpi) {
const char *psname = convert_cfstring(CTFontCopyPostScriptName(font), 1);
NSURL *url = (NSURL*)CTFontCopyAttribute(font, kCTFontURLAttribute);
PyObject *path = PyUnicode_FromString([[url path] UTF8String]);
[url release];
if (path == NULL) { CFRelease(font); return NULL; }
PyObject *ans = ft_face_from_path_and_psname(path, psname, (void*)font, free_font, true, 3, pt_sz, xdpi, ydpi, CTFontGetLeading(font));
Py_DECREF(path);
if (ans == NULL) { CFRelease(font); }
return ans;
}
static PyObject*
face_for_text(PyObject UNUSED *self, PyObject UNUSED *args) {
char *text;
PyObject *lp;
float pt_sz, xdpi, ydpi;
int bold, italic;
if (!PyArg_ParseTuple(args, "sO!fffpp", &text, &PyLong_Type, &lp, &pt_sz, &xdpi, &ydpi, &bold, &italic)) return NULL;
CTFontRef font = PyLong_AsVoidPtr(lp);
CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);
if (str == NULL) return PyErr_NoMemory();
CFRange range = CFRangeMake(0, CFStringGetLength(str));
CTFontRef new_font = CTFontCreateForString(font, str, range);
if (new_font == NULL) { PyErr_SetString(PyExc_ValueError, "Failed to find fallback CTFont"); CFRelease(str); return NULL; }
return ft_face(new_font, pt_sz, xdpi, ydpi);
}
static PyObject*
create_face(PyObject UNUSED *self, PyObject *args) {
PyObject *descriptor;
@ -127,15 +151,7 @@ create_face(PyObject UNUSED *self, PyObject *args) {
CTFontRef font = CTFontCreateWithFontDescriptor(desc, scaled_point_sz, NULL);
CFRelease(desc); desc = NULL;
if (!font) { PyErr_SetString(PyExc_ValueError, "Failed to create CTFont object"); return NULL; }
const char *psname = convert_cfstring(CTFontCopyPostScriptName(font), 1);
NSURL *url = (NSURL*)CTFontCopyAttribute(font, kCTFontURLAttribute);
PyObject *path = PyUnicode_FromString([[url path] UTF8String]);
[url release];
if (path == NULL) { CFRelease(font); return NULL; }
PyObject *ans = ft_face_from_path_and_psname(path, psname, (void*)font, free_font, true, 3, point_sz, xdpi, ydpi, CTFontGetLeading(font));
Py_DECREF(path);
if (ans == NULL) { CFRelease(font); }
return ans;
return ft_face(font, point_sz, xdpi, ydpi);
}
// Boilerplate {{{

View File

@ -224,6 +224,7 @@ update_cell_metrics(float pt_sz, float xdpi, float ydpi) {
if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; }
underline_position = MIN(cell_height - 1, underline_position);
sprite_tracker_set_layout(cell_width, cell_height);
global_state.cell_width = cell_width; global_state.cell_height = cell_height;
free(canvas); canvas = malloc(CELLS_IN_CANVAS * cell_width * cell_height);
if (canvas == NULL) return PyErr_NoMemory();
return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness);

View File

@ -117,7 +117,7 @@ def get_font_files(opts):
return ans
def face_from_font(font, pt_sz, xdpi, ydpi):
def face_from_font(font, pt_sz=11.0, xdpi=72.0, ydpi=72.0):
return get_face(font.resolved_family, pt_sz, xdpi, ydpi, bold=font.bold, italic=font.italic)
@ -126,7 +126,7 @@ def save_medium_face(face):
def font_for_text(text, current_font_family, pt_sz, xdpi, ydpi, bold=False, italic=False):
return face_for_text(text, pt_sz, xdpi, ydpi, bold, italic)
return face_for_text(text, save_medium_face.face.extra_data(), pt_sz, xdpi, ydpi, bold, italic)
def font_for_family(family):
@ -143,10 +143,9 @@ def test_font_matching(
def test_family_matching(name='Menlo', dpi=72.0, font_size=11.0):
all_fonts = create_font_map(coretext_all_fonts())
for bold in (False, True):
for italic in (False, True):
face = get_face(
all_fonts, name, 'Menlo', font_size, dpi, dpi, bold, italic
name, font_size, dpi, dpi, bold, italic
)
print(bold, italic, face)

View File

@ -102,4 +102,7 @@ def font_for_family(family):
def font_for_text(text, current_font_family='monospace', pt_sz=11.0, xdpi=96.0, ydpi=96.0, bold=False, italic=False):
return fc_match('monospace', bold, italic, False, pt_sz, str(text), (xdpi + ydpi) / 2.0)
ans = fc_match('monospace', bold, italic, False, pt_sz, str(text), (xdpi + ydpi) / 2.0)
if ans is not None:
ans = face_from_font(ans, pt_sz, xdpi, ydpi)
return ans

View File

@ -10,7 +10,7 @@ from math import ceil, floor, pi, sin, sqrt
from kitty.config import defaults
from kitty.constants import isosx
from kitty.fast_data_types import (
Screen, send_prerendered_sprites, set_font, set_font_size,
Screen, change_wcwidth, send_prerendered_sprites, set_font, set_font_size,
set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line
)
from kitty.fonts.box_drawing import render_box_char, render_missing_glyph
@ -49,11 +49,10 @@ FontState = namedtuple(
def get_fallback_font(text, bold, italic):
state = set_font_family.state
font = font_for_text(
return font_for_text(
text, state.family, state.pt_sz, state.xdpi, state.ydpi, bold,
italic
)
return None if font is None else create_face(font)
def set_font_family(opts=None, override_font_size=None, override_dpi=None):
@ -230,8 +229,20 @@ def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi
print('\n')
def test_fallback_font(qtext=None, bold=False, italic=False):
set_font_family(override_dpi=(96.0, 96.0))
for text in (qtext, '你好', 'He\u0347\u0305', '\U0001F929'):
if text:
f = get_fallback_font(text, bold, italic)
try:
print(text, f)
except UnicodeEncodeError:
sys.stdout.buffer.write((text + ' %s\n' % f).encode('utf-8'))
def showcase():
change_wcwidth(True)
test_render_string('He\u0347\u0305llo\u0341, w\u0302or\u0306l\u0354d!', family='Noto Sans Mono')
test_render_string('你好,世界', family='Noto Sans Mono')
test_render_string('|\U0001F929|\U0001F921|\U0001F91f|', family='Noto Sans Mono')
test_render_string('|\U0001F601|\U0001F64f|\U0001F63a|', family='Noto Sans Mono')
test_render_string('A=>>B!=C', family='Fira Code')