From 0427f30c2adae469d0cebc0b00aa7b2abd4d2a5d Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 25 Jan 2017 20:30:39 +0530 Subject: [PATCH] Improve rendering of italic fonts in linux Now rather than down-size characters from italic fonts that do not fit into the cell width, trim the left and right margins to make them fit, instead. --- kitty/fonts/freetype.py | 32 +++++++++++++++---------------- kitty/freetype.c | 42 +++++++++++++++++++++++++++++++++++++++++ 2 files changed, 58 insertions(+), 16 deletions(-) diff --git a/kitty/fonts/freetype.py b/kitty/fonts/freetype.py index be0340b9a..51e1e8c39 100644 --- a/kitty/fonts/freetype.py +++ b/kitty/fonts/freetype.py @@ -100,27 +100,27 @@ def render_char(text, bold=False, italic=False, width=1): face = alt_face_cache[font] = Face(font.face) set_char_size(face, **cff_size) bitmap = render_to_bitmap(font, face, text) - if width == 1 and bitmap.width > cell_width * 1.1: - # rescale the font size so that the glyph is visible in a single - # cell and hope somebody updates libc's wcwidth - sz = cff_size.copy() - sz['width'] = int(sz['width'] * cell_width / bitmap.width) - # Preserve aspect ratio - sz['height'] = int(sz['height'] * cell_width / bitmap.width) - try: - set_char_size(face, **sz) - bitmap = render_to_bitmap(font, face, text) - finally: - set_char_size(face, **cff_size) + if width == 1 and bitmap.width > cell_width: + extra = bitmap.width - cell_width + if italic and extra < cell_width // 2: + bitmap = face.trim_to_width(bitmap, cell_width) + elif extra > max(2, 0.1 * cell_width): + # rescale the font size so that the glyph is visible in a single + # cell and hope somebody updates libc's wcwidth + sz = cff_size.copy() + sz['width'] = int(sz['width'] * cell_width / bitmap.width) + # Preserve aspect ratio + sz['height'] = int(sz['height'] * cell_width / bitmap.width) + try: + set_char_size(face, **sz) + bitmap = render_to_bitmap(font, face, text) + finally: + set_char_size(face, **cff_size) m = face.glyph_metrics() return CharBitmap(bitmap.buffer, ceil_int(abs(m.horiBearingX) / 64), ceil_int(abs(m.horiBearingY) / 64), ceil_int(m.horiAdvance / 64), bitmap.rows, bitmap.width) -def is_wide_char(bitmap_char): - return min(bitmap_char.advance, bitmap_char.columns) > cell_width * 1.1 - - def place_char_in_cell(bitmap_char): # We want the glyph to be positioned inside the cell based on the bearingX # and bearingY values, making sure that it does not overflow the cell. diff --git a/kitty/freetype.c b/kitty/freetype.c index 95d7a2002..157ae768c 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -184,6 +184,46 @@ bitmap(Face *self) { return ans; } +static PyObject* +trim_to_width(Face UNUSED *self, PyObject *args) { +#define trim_to_width_doc "Trim edges from the supplied bitmap to make it fit in the specified cell-width" + PyObject *bitmap, *t; + unsigned long cell_width, rows, width, rtrim = 0, extra, ltrim; + unsigned char *src, *dest; + bool column_has_text = false; + if (!PyArg_ParseTuple(args, "O!k", &BitmapType, &bitmap, &cell_width)) return NULL; + rows = PyLong_AsUnsignedLong(PyStructSequence_GET_ITEM(bitmap, 0)); + width = PyLong_AsUnsignedLong(PyStructSequence_GET_ITEM(bitmap, 1)); + extra = width - cell_width; + if (extra >= cell_width) { PyErr_SetString(PyExc_ValueError, "Too large for trimming"); return NULL; } + PyObject *ans = PyStructSequence_New(&BitmapType); + if (ans == NULL) return PyErr_NoMemory(); + src = (unsigned char*)PyByteArray_AS_STRING(PyStructSequence_GET_ITEM(bitmap, 3)); + PyObject *abuf = PyByteArray_FromStringAndSize(NULL, cell_width * rows); + if (abuf == NULL) { Py_CLEAR(ans); return PyErr_NoMemory(); } + dest = (unsigned char*)PyByteArray_AS_STRING(abuf); + PyStructSequence_SET_ITEM(ans, 1, PyLong_FromUnsignedLong(cell_width)); + PyStructSequence_SET_ITEM(ans, 2, PyLong_FromUnsignedLong(cell_width)); + PyStructSequence_SET_ITEM(ans, 3, abuf); +#define COPY(which) t = PyStructSequence_GET_ITEM(bitmap, which); Py_INCREF(t); PyStructSequence_SET_ITEM(ans, which, t); + COPY(0); COPY(4); COPY(5); COPY(6); +#undef COPY + + for (long x = width - 1; !column_has_text && x > -1 && rtrim < extra; x--) { + for (unsigned long y = 0; y < rows * width; y += width) { + if (src[x + y] > 200) { column_has_text = true; break; } + } + if (!column_has_text) rtrim++; + } + rtrim = MIN(extra, rtrim); + ltrim = extra - rtrim; + for (unsigned long y = 0; y < rows; y++) { + memcpy(dest + y*cell_width, src + ltrim + y*width, cell_width); + } + + return ans; +} + // Boilerplate {{{ static PyMemberDef members[] = { @@ -205,6 +245,7 @@ static PyMethodDef methods[] = { METHOD(get_char_index, METH_VARARGS) METHOD(glyph_metrics, METH_NOARGS) METHOD(bitmap, METH_NOARGS) + METHOD(trim_to_width, METH_VARARGS) {NULL} /* Sentinel */ }; @@ -234,6 +275,7 @@ init_freetype_library(PyObject *m) { if (PyStructSequence_InitType2(&GlpyhMetricsType, &gm_desc) != 0) return false; if (PyStructSequence_InitType2(&BitmapType, &bm_desc) != 0) return false; PyModule_AddObject(m, "GlyphMetrics", (PyObject*)&GlpyhMetricsType); + PyModule_AddObject(m, "Bitmap", (PyObject*)&BitmapType); PyModule_AddIntMacro(m, FT_LOAD_RENDER); PyModule_AddIntMacro(m, FT_LOAD_TARGET_NORMAL); PyModule_AddIntMacro(m, FT_LOAD_TARGET_LIGHT);