Refactor font code

DRYer with global state stored in fewer places. And only round-tripping
through python for font selection, not face creation.
This commit is contained in:
Kovid Goyal 2017-11-11 18:14:22 +05:30
parent 98e93cb4bd
commit e5d411b80d
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
12 changed files with 211 additions and 220 deletions

View File

@ -5,6 +5,7 @@
* Distributed under terms of the GPL3 license.
*/
#include "state.h"
#include "fonts.h"
#include <structmember.h>
#include <stdint.h>
@ -111,13 +112,13 @@ free_font(void *f) {
}
static inline PyObject*
ft_face(CTFontRef font, float pt_sz, float xdpi, float ydpi) {
ft_face(CTFontRef font) {
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));
PyObject *ans = ft_face_from_path_and_psname(path, psname, (void*)font, free_font, true, 3, CTFontGetLeading(font));
Py_DECREF(path);
if (ans == NULL) { CFRelease(font); }
return ans;
@ -141,43 +142,43 @@ find_substitute_face(CFStringRef str, CTFontRef old_font) {
return NULL;
}
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;
PyObject*
create_fallback_face(PyObject *base_face, Cell* cell, bool UNUSED bold, bool UNUSED italic) {
PyObject *lp = PyObject_CallMethod(base_face, "extra_data", NULL);
if (lp == NULL) return NULL;
CTFontRef font = PyLong_AsVoidPtr(lp);
Py_CLEAR(lp);
static char text[128];
cell_as_utf8(cell, true, text, ' ');
CFStringRef str = CFStringCreateWithCString(NULL, text, kCFStringEncodingUTF8);
if (str == NULL) return PyErr_NoMemory();
CTFontRef new_font = find_substitute_face(str, font);
CFRelease(str);
if (new_font == NULL) return NULL;
return ft_face(new_font, pt_sz, xdpi, ydpi);
return ft_face(new_font);
}
static PyObject*
create_face(PyObject UNUSED *self, PyObject *args) {
PyObject *descriptor;
float point_sz, xdpi, ydpi;
if(!PyArg_ParseTuple(args, "Offf", &descriptor, &point_sz, &xdpi, &ydpi)) return NULL;
PyObject*
face_from_descriptor(PyObject *descriptor) {
CTFontDescriptorRef desc = font_descriptor_from_python(descriptor);
if (!desc) return NULL;
float scaled_point_sz = ((xdpi + ydpi) / 144.0) * point_sz;
float scaled_point_sz = ((global_state.logical_dpi_x + global_state.logical_dpi_y) / 144.0) * global_state.font_sz_in_pts;
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; }
return ft_face(font, point_sz, xdpi, ydpi);
return ft_face(font);
}
PyObject*
specialize_font_descriptor(PyObject *base_descriptor) {
Py_INCREF(base_descriptor);
return base_descriptor;
}
// Boilerplate {{{
static PyMethodDef module_methods[] = {
METHODB(coretext_all_fonts, METH_NOARGS),
METHODB(face_for_text, METH_VARARGS),
METHODB(create_face, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};

View File

@ -5,7 +5,9 @@
* Distributed under terms of the GPL3 license.
*/
#include "data-types.h"
#include "state.h"
#include "lineops.h"
#include "fonts.h"
#include <fontconfig/fontconfig.h>
#ifndef FC_COLOR
#define FC_COLOR "color"
@ -117,16 +119,16 @@ end:
return ans;
}
static Py_UCS4 char_buf[1024];
static inline void
add_charset(PyObject *characters, FcPattern *pat) {
add_charset(FcPattern *pat, size_t num) {
FcCharSet *charset = NULL;
if (PyUnicode_READY(characters) != 0) goto end;
if (PyUnicode_GET_LENGTH(characters) > 0) {
if (num) {
charset = FcCharSetCreate();
if (charset == NULL) { PyErr_NoMemory(); goto end; }
int kind = PyUnicode_KIND(characters); void *data = PyUnicode_DATA(characters);
for (int i = 0; i < PyUnicode_GET_LENGTH(characters); i++) {
if (!FcCharSetAddChar(charset, PyUnicode_READ(kind, data, i))) {
for (size_t i = 0; i < num; i++) {
if (!FcCharSetAddChar(charset, char_buf[i])) {
PyErr_SetString(PyExc_RuntimeError, "Failed to add character to fontconfig charset");
goto end;
}
@ -142,11 +144,10 @@ fc_match(PyObject UNUSED *self, PyObject *args) {
char *family = NULL;
int bold = 0, italic = 0, allow_bitmapped_fonts = 0;
double size_in_pts = 0, dpi = 0;
PyObject *characters = NULL;
FcPattern *pat = NULL;
PyObject *ans = NULL;
if (!PyArg_ParseTuple(args, "|zpppdO!d", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &PyUnicode_Type, &characters, &dpi)) return NULL;
if (!PyArg_ParseTuple(args, "|zpppdd", &family, &bold, &italic, &allow_bitmapped_fonts, &size_in_pts, &dpi)) return NULL;
pat = FcPatternCreate();
if (pat == NULL) return PyErr_NoMemory();
@ -159,7 +160,6 @@ fc_match(PyObject UNUSED *self, PyObject *args) {
if (dpi > 0) { AP(FcPatternAddDouble, FC_DPI, dpi, "dpi"); }
if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); }
if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); }
if (characters) add_charset(characters, pat);
ans = _fc_match(pat);
end:
@ -167,22 +167,36 @@ end:
return ans;
}
static PyObject*
fc_font(PyObject UNUSED *self, PyObject *args) {
double size_in_pts, dpi;
int index;
char *path;
PyObject *ans = NULL, *chars = NULL;
if (!PyArg_ParseTuple(args, "ddsi|O!", &size_in_pts, &dpi, &path, &index, &PyUnicode_Type, &chars)) return NULL;
PyObject*
specialize_font_descriptor(PyObject *base_descriptor) {
PyObject *p = PyDict_GetItemString(base_descriptor, "path"), *ans = NULL;
PyObject *idx = PyDict_GetItemString(base_descriptor, "index");
if (p == NULL) { PyErr_SetString(PyExc_ValueError, "Base descriptor has no path"); return NULL; }
if (idx == NULL) { PyErr_SetString(PyExc_ValueError, "Base descriptor has no index"); return NULL; }
FcPattern *pat = FcPatternCreate();
if (pat == NULL) return PyErr_NoMemory();
if (size_in_pts > 0) { AP(FcPatternAddDouble, FC_SIZE, size_in_pts, "size"); }
if (dpi > 0) { AP(FcPatternAddDouble, FC_DPI, dpi, "dpi"); }
AP(FcPatternAddString, FC_FILE, (const FcChar8*)path, "path");
AP(FcPatternAddInteger, FC_INDEX, index, "index");
if (chars) add_charset(chars, pat);
AP(FcPatternAddString, FC_FILE, (const FcChar8*)PyUnicode_AsUTF8(p), "path");
AP(FcPatternAddInteger, FC_INDEX, PyLong_AsLong(idx), "index");
AP(FcPatternAddDouble, FC_SIZE, global_state.font_sz_in_pts, "size");
AP(FcPatternAddDouble, FC_DPI, (global_state.logical_dpi_x + global_state.logical_dpi_y) / 2.0, "dpi");
ans = _fc_match(pat);
end:
if (pat != NULL) FcPatternDestroy(pat);
return ans;
}
PyObject*
create_fallback_face(PyObject UNUSED *base_face, Cell* cell, bool bold, bool italic) {
PyObject *ans = NULL;
FcPattern *pat = FcPatternCreate();
if (pat == NULL) return PyErr_NoMemory();
AP(FcPatternAddString, FC_FAMILY, (const FcChar8*)"monospace", "family");
if (bold) { AP(FcPatternAddInteger, FC_WEIGHT, FC_WEIGHT_BOLD, "weight"); }
if (italic) { AP(FcPatternAddInteger, FC_SLANT, FC_SLANT_ITALIC, "slant"); }
size_t num = cell_as_unicode(cell, true, char_buf, ' ');
add_charset(pat, num);
PyObject *d = _fc_match(pat);
if (d) { ans = face_from_descriptor(d); Py_CLEAR(d); }
end:
if (pat != NULL) FcPatternDestroy(pat);
return ans;
@ -192,7 +206,6 @@ end:
static PyMethodDef module_methods[] = {
METHODB(fc_list, METH_VARARGS),
METHODB(fc_match, METH_VARARGS),
METHODB(fc_font, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};

View File

@ -149,12 +149,24 @@ sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height) {
sprite_tracker.x = 0; sprite_tracker.y = 0; sprite_tracker.z = 0;
}
static inline PyObject*
desc_to_face(PyObject *desc) {
PyObject *d = specialize_font_descriptor(desc);
if (d == NULL) return NULL;
PyObject *ans = face_from_descriptor(d);
Py_DECREF(d);
return ans;
}
static inline bool
alloc_font(Font *f, PyObject *face, bool bold, bool italic) {
f->face = face; Py_INCREF(face);
alloc_font(Font *f, PyObject *descriptor, bool bold, bool italic, bool is_face) {
PyObject *face;
if (is_face) { face = descriptor; Py_INCREF(face); }
else { face = desc_to_face(descriptor); if (face == NULL) return false; }
f->face = face;
f->hb_font = harfbuzz_font_for_face(face);
if (f->hb_font == NULL) return false;
if (f->hb_font == NULL) { PyErr_NoMemory(); return false; }
f->bold = bold; f->italic = italic;
return true;
}
@ -178,7 +190,6 @@ clear_font(Font *f) {
static Font medium_font = {0}, bold_font = {0}, italic_font = {0}, bi_font = {0}, box_font = {0};
static Font fallback_fonts[256] = {{0}};
static PyObject *get_fallback_font = NULL;
typedef enum { FONT, BLANK_FONT, BOX_FONT, MISSING_FONT } FontType;
typedef struct {
@ -206,16 +217,9 @@ python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t* buf)
static inline PyObject*
update_cell_metrics(float pt_sz, float xdpi, float ydpi) {
#define CALL(f) { if ((f)->face) { if(!set_size_for_face((f)->face, pt_sz, xdpi, ydpi)) return NULL; (f)->hb_font = harfbuzz_font_for_face((f)->face); } clear_sprite_map((f)); }
CALL(&medium_font); CALL(&bold_font); CALL(&italic_font); CALL(&bi_font); CALL(&box_font);
for (size_t i = 0; fallback_fonts[i].face != NULL; i++) {
CALL(fallback_fonts + i);
}
for (size_t i = 0; i < symbol_map_fonts_count; i++) {
CALL(symbol_map_fonts + i);
}
#undef CALL
update_cell_metrics() {
#define CALL(f, desired_height) { if ((f)->face) { if(!set_size_for_face((f)->face, desired_height)) return NULL; (f)->hb_font = harfbuzz_font_for_face((f)->face); } clear_sprite_map((f)); }
CALL(&medium_font, 0); CALL(&bold_font, 0); CALL(&italic_font, 0); CALL(&bi_font, 0); CALL(&box_font, 0);
cell_metrics(medium_font.face, &cell_width, &cell_height, &baseline, &underline_position, &underline_thickness);
if (!cell_width) { PyErr_SetString(PyExc_ValueError, "Failed to calculate cell width for the specified font."); return NULL; }
if (OPT(adjust_line_height_px) != 0) cell_height += OPT(adjust_line_height_px);
@ -227,14 +231,20 @@ update_cell_metrics(float pt_sz, float xdpi, float ydpi) {
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();
for (size_t i = 0; fallback_fonts[i].face != NULL; i++) {
CALL(fallback_fonts + i, cell_height);
}
for (size_t i = 0; i < symbol_map_fonts_count; i++) {
CALL(symbol_map_fonts + i, cell_height);
}
return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness);
#undef CALL
}
static PyObject*
set_font_size(PyObject UNUSED *m, PyObject *args) {
float pt_sz, xdpi, ydpi;
if (!PyArg_ParseTuple(args, "fff", &pt_sz, &xdpi, &ydpi)) return NULL;
return update_cell_metrics(pt_sz, xdpi, ydpi);
if (!PyArg_ParseTuple(args, "f", &global_state.font_sz_in_pts)) return NULL;
return update_cell_metrics();
}
static inline bool
@ -260,13 +270,16 @@ fallback_font(Cell *cell) {
return fallback_fonts + i;
}
}
if (get_fallback_font == NULL || i == (sizeof(fallback_fonts)/sizeof(fallback_fonts[0])-1)) return NULL;
Py_UCS4 buf[10];
size_t n = cell_as_unicode(cell, true, buf, ' ');
PyObject *face = PyObject_CallFunction(get_fallback_font, "NOO", PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n), bold ? Py_True : Py_False, italic ? Py_True : Py_False);
if (i == (sizeof(fallback_fonts)/sizeof(fallback_fonts[0])-1)) { return NULL; }
Font* base_font;
if (bold) base_font = italic ? &bi_font : &bold_font;
else base_font = italic ? &italic_font : &medium_font;
if (!base_font->face) base_font = &medium_font;
PyObject *face = create_fallback_face(base_font->face, cell, bold, italic);
if (face == NULL) { PyErr_Print(); return NULL; }
if (face == Py_None) { Py_DECREF(face); return NULL; }
if (!alloc_font(fallback_fonts + i, face, bold, italic)) { Py_DECREF(face); fatal("Out of memory"); }
if (!alloc_font(fallback_fonts + i, face, bold, italic, true)) { Py_DECREF(face); fatal("Out of memory"); }
set_size_for_face(face, cell_height);
Py_DECREF(face);
return fallback_fonts + i;
}
@ -526,15 +539,14 @@ render_line(Line *line) {
static PyObject*
set_font(PyObject UNUSED *m, PyObject *args) {
PyObject *sm, *smf, *medium, *bold = NULL, *italic = NULL, *bi = NULL;
float xdpi, ydpi, pt_sz;
Py_CLEAR(get_fallback_font); Py_CLEAR(box_drawing_function);
if (!PyArg_ParseTuple(args, "OOO!O!fffO|OOO", &get_fallback_font, &box_drawing_function, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &pt_sz, &xdpi, &ydpi, &medium, &bold, &italic, &bi)) return NULL;
Py_INCREF(get_fallback_font); Py_INCREF(box_drawing_function);
Py_CLEAR(box_drawing_function);
if (!PyArg_ParseTuple(args, "OO!O!fO|OOO", &box_drawing_function, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &global_state.font_sz_in_pts, &medium, &bold, &italic, &bi)) return NULL;
Py_INCREF(box_drawing_function);
clear_font(&medium_font); clear_font(&bold_font); clear_font(&italic_font); clear_font(&bi_font); clear_sprite_map(&box_font);
if (!alloc_font(&medium_font, medium, false, false)) return PyErr_NoMemory();
if (bold && !alloc_font(&bold_font, bold, true, false)) return PyErr_NoMemory();
if (italic && !alloc_font(&italic_font, italic, false, true)) return PyErr_NoMemory();
if (bi && !alloc_font(&bi_font, bi, true, true)) return PyErr_NoMemory();
if (!alloc_font(&medium_font, medium, false, false, false)) return NULL;
if (bold && !alloc_font(&bold_font, bold, true, false, false)) return NULL;
if (italic && !alloc_font(&italic_font, italic, false, true, false)) return NULL;
if (bi && !alloc_font(&bi_font, bi, true, true, false)) return NULL;
for (size_t i = 0; fallback_fonts[i].face != NULL; i++) clear_font(fallback_fonts + i);
for (size_t i = 0; symbol_map_fonts_count; i++) free_font(symbol_map_fonts + i);
free(symbol_maps); free(symbol_map_fonts); symbol_maps = NULL; symbol_map_fonts = NULL;
@ -551,7 +563,7 @@ set_font(PyObject UNUSED *m, PyObject *args) {
PyObject *face;
int bold, italic;
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(smf, i), "Opp", &face, &bold, &italic)) return NULL;
if (!alloc_font(symbol_map_fonts + i, face, bold != 0, italic != 0)) return PyErr_NoMemory();
if (!alloc_font(symbol_map_fonts + i, face, bold != 0, italic != 0, false)) return NULL;
}
for (size_t i = 0; i < symbol_maps_count; i++) {
unsigned int left, right, font_idx;
@ -559,14 +571,13 @@ set_font(PyObject UNUSED *m, PyObject *args) {
symbol_maps[i].left = left; symbol_maps[i].right = right; symbol_maps[i].font_idx = font_idx;
}
}
return update_cell_metrics(pt_sz, xdpi, ydpi);
return update_cell_metrics();
}
static void
finalize(void) {
Py_CLEAR(python_send_to_gpu_impl);
free(canvas);
Py_CLEAR(get_fallback_font);
Py_CLEAR(box_drawing_function);
free_font(&medium_font); free_font(&bold_font); free_font(&italic_font); free_font(&bi_font); free_font(&box_font);
for (size_t i = 0; fallback_fonts[i].face != NULL; i++) free_font(fallback_fonts + i);
@ -685,6 +696,25 @@ error:
#undef SET
}
static PyObject*
get_fallback_font(PyObject UNUSED *self, PyObject *args) {
PyObject *text;
int bold, italic;
if (!PyArg_ParseTuple(args, "Upp", &text, &bold, &italic)) return NULL;
static Py_UCS4 char_buf[16];
if (!PyUnicode_AsUCS4(text, char_buf, sizeof(char_buf)/sizeof(char_buf[0]), 1)) return NULL;
Cell cell = {0};
cell.ch = char_buf[0];
if (PyUnicode_GetLength(text) > 1) cell.cc |= char_buf[1] & CC_MASK;
if (PyUnicode_GetLength(text) > 2) cell.cc |= (char_buf[2] & CC_MASK) << 16;
if (bold) cell.attrs |= 1 << BOLD_SHIFT;
if (italic) cell.attrs |= 1 << ITALIC_SHIFT;
Font *ans = fallback_font(&cell);
if (ans == NULL) { PyErr_SetString(PyExc_ValueError, "Too many fallback fonts"); return NULL; }
return ans->face;
}
static PyMethodDef module_methods[] = {
METHODB(set_font_size, METH_VARARGS),
METHODB(set_font, METH_VARARGS),
@ -696,6 +726,7 @@ static PyMethodDef module_methods[] = {
METHODB(set_send_sprite_to_gpu, METH_O),
METHODB(current_fonts, METH_NOARGS),
METHODB(test_render_line, METH_VARARGS),
METHODB(get_fallback_font, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};

View File

@ -16,7 +16,7 @@
bool face_has_codepoint(PyObject *, char_type);
hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, float, float, float);
bool set_size_for_face(PyObject*, unsigned int);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);
void sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z);
bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline);
@ -24,5 +24,8 @@ void render_line(Line *line);
void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len);
void sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height);
typedef void (*free_extra_data_func)(void*);
PyObject* ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi, float);
PyObject* ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi, float);
PyObject* ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float);
PyObject* ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float);
PyObject* specialize_font_descriptor(PyObject *base_descriptor);
PyObject* create_fallback_face(PyObject *base_face, Cell* cell, bool bold, bool italic);
PyObject* face_from_descriptor(PyObject*);

View File

@ -4,9 +4,8 @@
import re
import sys
from collections import namedtuple
from kitty.fast_data_types import coretext_all_fonts, create_face, face_for_text
from kitty.fast_data_types import coretext_all_fonts
from kitty.utils import safe_print
attr_map = {(False, False): 'font_family',
@ -43,7 +42,7 @@ def list_fonts():
yield {'family': f, 'full_name': fn, 'is_monospace': is_mono}
def find_best_match(family, bold, italic):
def find_best_match(family, bold=False, italic=False):
q = re.sub(r'\s+', ' ', family.lower())
font_map = all_fonts_map()
@ -77,7 +76,7 @@ def find_best_match(family, bold, italic):
}
def resolve_family(f, main_family, bold, italic):
def resolve_family(f, main_family, bold=False, italic=False):
if (bold or italic) and f == 'auto':
f = main_family
if f.lower() == 'monospace':
@ -85,67 +84,20 @@ def resolve_family(f, main_family, bold, italic):
return f
FaceDescription = namedtuple(
'FaceDescription', 'resolved_family family bold italic'
)
def face_description(family, main_family, bold=False, italic=False):
return FaceDescription(
resolve_family(family, main_family, bold, italic), family, bold, italic
)
def get_face(family, font_size, xdpi, ydpi, bold=False, italic=False):
descriptor = find_best_match(family, bold, italic)
return create_face(descriptor, font_size, xdpi, ydpi)
def get_font_files(opts):
ans = {}
for (bold, italic), attr in attr_map.items():
face = face_description(
getattr(opts, attr), opts.font_family, bold, italic
)
face = find_best_match(resolve_family(getattr(opts, attr), opts.font_family, bold, italic), bold, italic)
key = {(False, False): 'medium',
(True, False): 'bold',
(False, True): 'italic',
(True, True): 'bi'}[(bold, italic)]
ans[key] = face
if key == 'medium':
save_medium_face.family = face.resolved_family
get_font_files.medium_family = face['family']
return ans
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)
def save_medium_face(face):
save_medium_face.face = face
def font_for_text(text, current_font_family, pt_sz, xdpi, ydpi, bold=False, italic=False):
return face_for_text(text, save_medium_face.face.extra_data(), pt_sz, xdpi, ydpi, bold, italic)
def font_for_family(family):
ans = face_description(family, save_medium_face.family)
return ans, ans.bold, ans.italic
def test_font_matching(
name='Menlo', bold=False, italic=False, dpi=72.0, font_size=11.0
):
all_fonts = create_font_map(coretext_all_fonts())
face = get_face(all_fonts, name, 'Menlo', font_size, dpi, dpi, bold, italic)
return face
def test_family_matching(name='Menlo', dpi=72.0, font_size=11.0):
for bold in (False, True):
for italic in (False, True):
face = get_face(
name, font_size, dpi, dpi, bold, italic
)
print(bold, italic, face)
ans = find_best_match(resolve_family(family, get_font_files.medium_family))
return ans, ans['bold'], ans['italic']

View File

@ -6,8 +6,8 @@ import re
from functools import lru_cache
from kitty.fast_data_types import (
FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, FC_WEIGHT_REGULAR, Face,
fc_list, fc_match, fc_font
FC_SLANT_ITALIC, FC_SLANT_ROMAN, FC_WEIGHT_BOLD, FC_WEIGHT_REGULAR,
fc_list, fc_match,
)
attr_map = {(False, False): 'font_family',
@ -66,21 +66,12 @@ def find_best_match(family, bold=False, italic=False, monospaced=True):
return fc_match(family, bold, italic)
def face_from_font(font, pt_sz=11.0, xdpi=96.0, ydpi=96.0):
font = fc_font(pt_sz, (xdpi + ydpi) / 2.0, font['path'], font.get('index', 0))
return Face(font['path'], font.get('index', 0), font.get('hinting', False), font.get('hint_style', 0), pt_sz, xdpi, ydpi)
def resolve_family(f, main_family, bold, italic):
if (bold or italic) and f == 'auto':
f = main_family
return f
def save_medium_face(face):
pass
def get_font_files(opts):
ans = {}
for (bold, italic), attr in attr_map.items():
@ -91,18 +82,9 @@ def get_font_files(opts):
(False, True): 'italic',
(True, True): 'bi'}[(bold, italic)]
ans[key] = font
if key == 'medium':
save_medium_face.medium_font = font
return ans
def font_for_family(family):
ans = find_best_match(family, monospaced=False)
return ans, ans.get('weight', 0) >= FC_WEIGHT_BOLD, ans.get('slant', FC_SLANT_ROMAN) != FC_SLANT_ROMAN
def font_for_text(text, current_font_family='monospace', pt_sz=11.0, xdpi=96.0, ydpi=96.0, bold=False, italic=False):
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,21 +10,16 @@ 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, change_wcwidth, send_prerendered_sprites, set_font, set_font_size,
set_send_sprite_to_gpu, sprite_map_set_limits, test_render_line
Screen, change_wcwidth, get_fallback_font, send_prerendered_sprites,
set_font, set_font_size, set_logical_dpi, set_send_sprite_to_gpu,
sprite_map_set_limits, test_render_line
)
from kitty.fonts.box_drawing import render_box_char, render_missing_glyph
from kitty.utils import get_logical_dpi
if isosx:
from .core_text import get_font_files, font_for_text, face_from_font, font_for_family, save_medium_face
from .core_text import get_font_files, font_for_family
else:
from .fontconfig import get_font_files, font_for_text, face_from_font, font_for_family, save_medium_face
def create_face(font):
s = set_font_family.state
return face_from_font(font, s.pt_sz, s.xdpi, s.ydpi)
from .fontconfig import get_font_files, font_for_family
def create_symbol_map(opts):
@ -34,58 +29,42 @@ def create_symbol_map(opts):
for family in val.values():
if family not in family_map:
font, bold, italic = font_for_family(family)
o = create_face(font)
family_map[family] = len(faces)
faces.append((o, bold, italic))
faces.append((font, bold, italic))
sm = tuple((a, b, family_map[f]) for (a, b), f in val.items())
return sm, tuple(faces)
FontState = namedtuple(
'FontState',
'family pt_sz xdpi ydpi cell_width cell_height baseline underline_position underline_thickness'
'family pt_sz cell_width cell_height baseline underline_position underline_thickness'
)
def get_fallback_font(text, bold, italic):
state = set_font_family.state
return font_for_text(
text, state.family, state.pt_sz, state.xdpi, state.ydpi, bold,
italic
)
def set_font_family(opts=None, override_font_size=None, override_dpi=None):
def set_font_family(opts=None, override_font_size=None):
opts = opts or defaults
sz = override_font_size or opts.font_size
xdpi, ydpi = get_logical_dpi(override_dpi)
set_font_family.state = FontState('', sz, xdpi, ydpi, 0, 0, 0, 0, 0)
font_map = get_font_files(opts)
faces = [create_face(font_map['medium'])]
save_medium_face(faces[0])
faces = [font_map['medium']]
for k in 'bold italic bi'.split():
if k in font_map:
faces.append(create_face(font_map[k]))
sm, sfaces = create_symbol_map(opts)
faces.append(font_map[k])
sm, sfonts = create_symbol_map(opts)
cell_width, cell_height, baseline, underline_position, underline_thickness = set_font(
get_fallback_font, render_box_drawing, sm, sfaces, sz, xdpi, ydpi, *faces
render_box_drawing, sm, sfonts, sz, *faces
)
set_font_family.state = FontState(
opts.font_family, sz, xdpi, ydpi, cell_width, cell_height, baseline,
opts.font_family, sz, cell_width, cell_height, baseline,
underline_position, underline_thickness
)
return cell_width, cell_height
def resize_fonts(new_sz, xdpi=None, ydpi=None):
def resize_fonts(new_sz):
s = set_font_family.state
xdpi = xdpi or s.xdpi
ydpi = ydpi or s.ydpi
cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size(
new_sz, xdpi, ydpi
)
cell_width, cell_height, baseline, underline_position, underline_thickness = set_font_size(new_sz)
set_font_family.state = FontState(
s.family, new_sz, xdpi, ydpi, cell_width, cell_height, baseline,
s.family, new_sz, cell_width, cell_height, baseline,
underline_position, underline_thickness
)
return cell_width, cell_height
@ -175,6 +154,7 @@ def render_box_drawing(codepoint):
def setup_for_testing(family='monospace', size=11.0, dpi=96.0):
from kitty.utils import get_logical_dpi
opts = defaults._replace(font_family=family)
sprites = {}
@ -183,7 +163,9 @@ def setup_for_testing(family='monospace', size=11.0, dpi=96.0):
sprite_map_set_limits(100000, 100)
set_send_sprite_to_gpu(send_to_gpu)
cell_width, cell_height = set_font_family(opts, override_dpi=(dpi, dpi), override_font_size=size)
set_logical_dpi(dpi, dpi)
get_logical_dpi((dpi, dpi))
cell_width, cell_height = set_font_family(opts, override_font_size=size)
prerender()
return sprites, cell_width, cell_height
@ -230,8 +212,9 @@ def test_render_string(text='Hello, world!', family='monospace', size=144.0, dpi
def test_fallback_font(qtext=None, bold=False, italic=False):
set_font_family(override_dpi=(96.0, 96.0))
trials = (qtext,) if qtext else ('你好', 'He\u0347\u0305', '\U0001F929')
set_logical_dpi(96.0, 96.0)
set_font_family()
trials = (qtext,) if qtext else ('', 'He\u0347\u0305', '\U0001F929')
for text in trials:
f = get_fallback_font(text, bold, italic)
try:

View File

@ -116,12 +116,12 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt
}
bool
set_size_for_face(PyObject *s, float pt_sz, float xdpi, float ydpi) {
set_size_for_face(PyObject *s, unsigned int UNUSED desired_height) {
Face *self = (Face*)s;
FT_UInt w = (FT_UInt)(ceilf(pt_sz * 64));
if (self->char_width == w && self->char_height == w && self->xdpi == (FT_UInt)xdpi && self->ydpi == (FT_UInt)ydpi) return true;
((Face*)self)->size_in_pts = pt_sz;
return set_font_size(self, w, w, (FT_UInt)xdpi, (FT_UInt) ydpi);
FT_UInt w = (FT_UInt)(ceilf(global_state.font_sz_in_pts * 64));
if (self->char_width == w && self->char_height == w && self->xdpi == (FT_UInt)global_state.logical_dpi_x && self->ydpi == (FT_UInt)global_state.logical_dpi_x) return true;
((Face*)self)->size_in_pts = global_state.font_sz_in_pts;
return set_font_size(self, w, w, self->xdpi, self->ydpi);
}
static inline int
@ -136,13 +136,13 @@ get_load_flags(int hinting, int hintstyle, int base) {
static inline bool
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi) {
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle) {
#define CPY(n) self->n = self->face->n;
CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness);
#undef CPY
self->is_scalable = FT_IS_SCALABLE(self->face);
self->hinting = hinting; self->hintstyle = hintstyle;
if (!set_size_for_face((PyObject*)self, size_in_pts, xdpi, ydpi)) return false;
if (!set_size_for_face((PyObject*)self, 0)) return false;
self->harfbuzz_font = hb_ft_font_create(self->face, NULL);
if (self->harfbuzz_font == NULL) { PyErr_NoMemory(); return false; }
hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT));
@ -154,12 +154,12 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, float size_
}
PyObject*
ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi, float apple_leading) {
ft_face_from_data(const uint8_t* data, size_t sz, void *extra_data, free_extra_data_func fed, PyObject *path, int hinting, int hintstyle, float apple_leading) {
Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0);
if (ans == NULL) return NULL;
int error = FT_New_Memory_Face(library, data, sz, 0, &ans->face);
if(error) { set_freetype_error("Failed to load memory face, with error:", error); Py_CLEAR(ans); return NULL; }
if (!init_ft_face(ans, path, hinting, hintstyle, size_in_pts, xdpi, ydpi)) { Py_CLEAR(ans); return NULL; }
if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; }
ans->extra_data = extra_data;
ans->free_extra_data = fed;
ans->apple_leading = apple_leading;
@ -186,35 +186,40 @@ load_from_path_and_psname(const char *path, const char* psname, Face *ans) {
}
PyObject*
ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float size_in_pts, float xdpi, float ydpi, float apple_leading) {
ft_face_from_path_and_psname(PyObject* path, const char* psname, void *extra_data, free_extra_data_func fed, int hinting, int hintstyle, float apple_leading) {
if (PyUnicode_READY(path) != 0) return NULL;
Face *ans = (Face*)Face_Type.tp_alloc(&Face_Type, 0);
if (!ans) return NULL;
if (!load_from_path_and_psname(PyUnicode_AsUTF8(path), psname, ans)) { Py_CLEAR(ans); return NULL; }
if (!init_ft_face(ans, path, hinting, hintstyle, size_in_pts, xdpi, ydpi)) { Py_CLEAR(ans); return NULL; }
if (!init_ft_face(ans, path, hinting, hintstyle)) { Py_CLEAR(ans); return NULL; }
ans->extra_data = extra_data;
ans->free_extra_data = fed;
ans->apple_leading = apple_leading;
return (PyObject*)ans;
}
static PyObject*
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
Face *self;
#ifndef __APPLE__
PyObject*
face_from_descriptor(PyObject *descriptor) {
#define D(key, conv) { PyObject *t = PyDict_GetItemString(descriptor, #key); if (t == NULL) return NULL; key = conv(t); t = NULL; }
char *path;
int error, hinting, hintstyle;
long index;
float size_in_pts, xdpi, ydpi;
if (!PyArg_ParseTuple(args, "sliifff", &path, &index, &hinting, &hintstyle, &size_in_pts, &xdpi, &ydpi)) return NULL;
self = (Face *)type->tp_alloc(type, 0);
bool hinting;
long hint_style;
D(path, PyUnicode_AsUTF8);
D(index, PyLong_AsLong);
D(hinting, PyObject_IsTrue);
D(hint_style, PyLong_AsLong);
#undef D
Face *self = (Face *)Face_Type.tp_alloc(&Face_Type, 0);
if (self != NULL) {
error = FT_New_Face(library, path, index, &(self->face));
int error = FT_New_Face(library, path, index, &(self->face));
if(error) { set_freetype_error("Failed to load face, with error:", error); Py_CLEAR(self); return NULL; }
if (!init_ft_face(self, PyTuple_GET_ITEM(args, 0), hinting, hintstyle, size_in_pts, xdpi, ydpi)) { Py_CLEAR(self); return NULL; }
if (!init_ft_face(self, PyDict_GetItemString(descriptor, "path"), hinting, hint_style)) { Py_CLEAR(self); return NULL; }
}
return (PyObject*)self;
}
#endif
static void
dealloc(Face* self) {
@ -439,7 +444,6 @@ PyTypeObject Face_Type = {
.tp_doc = "FreeType Font face",
.tp_methods = methods,
.tp_members = members,
.tp_new = new,
.tp_repr = (reprfunc)repr,
};

View File

@ -181,6 +181,23 @@ cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type zero_char)
return n;
}
size_t
cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type zero_char) {
size_t n = encode_utf8(cell->ch ? cell->ch : zero_char, buf);
if (include_cc) {
char_type cc = cell->cc;
Py_UCS4 cc1 = cc & CC_MASK, cc2;
if (cc1) {
n += encode_utf8(cc1, buf + n);
cc2 = cc >> 16;
if (cc2) { n += encode_utf8(cc2, buf + n); }
}
}
buf[n] = 0;
return n;
}
PyObject*
unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char) {
size_t n = 0;

View File

@ -55,6 +55,7 @@ index_type line_url_end_at(Line *self, index_type x);
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen);
unsigned int line_length(Line *self);
size_t cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf, char_type);
size_t cell_as_utf8(Cell *cell, bool include_cc, char *buf, char_type);
PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char);
void linebuf_init_line(LineBuf *, index_type);

View File

@ -72,6 +72,7 @@ typedef struct {
bool application_focused;
double cursor_blink_zero_time, last_mouse_activity_at;
double logical_dpi_x, logical_dpi_y;
float font_sz_in_pts;
double mouse_x, mouse_y;
bool mouse_button_pressed[20];
int viewport_width, viewport_height;

View File

@ -6,11 +6,12 @@ from collections import OrderedDict
from kitty.constants import isosx
from kitty.fast_data_types import (
set_send_sprite_to_gpu, sprite_map_set_layout, sprite_map_set_limits,
test_render_line, test_sprite_position_for, wcwidth
set_logical_dpi, set_send_sprite_to_gpu, sprite_map_set_layout,
sprite_map_set_limits, test_render_line, test_sprite_position_for, wcwidth
)
from kitty.fonts.box_drawing import box_chars
from kitty.fonts.render import render_string, set_font_family, prerender
from kitty.fonts.render import prerender, render_string, set_font_family
from kitty.utils import get_logical_dpi
from . import BaseTest
@ -25,7 +26,9 @@ class Rendering(BaseTest):
self.sprites[(x, y, z)] = data
set_send_sprite_to_gpu(send_to_gpu)
self.cell_width, self.cell_height = set_font_family(override_dpi=(96.0, 96.0))
set_logical_dpi(96.0, 96.0)
get_logical_dpi((96.0, 96.0))
self.cell_width, self.cell_height = set_font_family()
prerender()
self.assertEqual([k[0] for k in self.sprites], [0, 1, 2, 3, 4])