Implement fallback fonts in CoreText
This commit is contained in:
parent
4edfa9f4fd
commit
3592f82e4f
@ -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 {{{
|
||||
|
||||
@ -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);
|
||||
|
||||
@ -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)
|
||||
|
||||
@ -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
|
||||
|
||||
@ -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')
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user