Start work on line based rendering

This commit is contained in:
Kovid Goyal 2017-10-30 21:14:57 +05:30
parent 8431eef970
commit 3643a78b18
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
13 changed files with 376 additions and 297 deletions

View File

@ -11,7 +11,7 @@ from .fast_data_types import (
GLFW_KEY_DOWN, GLFW_KEY_UP, ChildMonitor, destroy_global_data, GLFW_KEY_DOWN, GLFW_KEY_UP, ChildMonitor, destroy_global_data,
destroy_sprite_map, glfw_post_empty_event, layout_sprite_map destroy_sprite_map, glfw_post_empty_event, layout_sprite_map
) )
from .fonts.render import render_cell_wrapper, set_font_family from .fonts.render import render_cell_wrapper, set_font_family, resize_fonts
from .keys import get_key_map, get_sent_data, get_shortcut from .keys import get_key_map, get_sent_data, get_shortcut
from .session import create_session from .session import create_session
from .tabs import SpecialWindow, TabManager from .tabs import SpecialWindow, TabManager
@ -142,8 +142,7 @@ class Boss:
self.current_font_size = new_size self.current_font_size = new_size
w, h = cell_size.width, cell_size.height w, h = cell_size.width, cell_size.height
windows = tuple(filter(None, self.window_id_map.values())) windows = tuple(filter(None, self.window_id_map.values()))
cell_size.width, cell_size.height = set_font_family( cell_size.width, cell_size.height = resize_fonts(self.current_font_size)
self.opts, override_font_size=self.current_font_size)
layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper) layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper)
for window in windows: for window in windows:
window.screen.rescale_images(w, h) window.screen.rescale_images(w, h)

View File

@ -165,8 +165,7 @@ def parse_symbol_map(val):
return abort() return abort()
if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1: if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1:
return abort() return abort()
for y in range(a, b + 1): symbol_map[(a, b)] = family
symbol_map[chr(y)] = family
return symbol_map return symbol_map

View File

@ -157,6 +157,7 @@ extern int init_Screen(PyObject *);
extern int init_Face(PyObject *); extern int init_Face(PyObject *);
extern bool init_freetype_library(PyObject*); extern bool init_freetype_library(PyObject*);
extern bool init_fontconfig_library(PyObject*); extern bool init_fontconfig_library(PyObject*);
extern bool init_fonts(PyObject*);
extern bool init_glfw(PyObject *m); extern bool init_glfw(PyObject *m);
extern bool init_sprites(PyObject *module); extern bool init_sprites(PyObject *module);
extern bool init_state(PyObject *module); extern bool init_state(PyObject *module);
@ -201,6 +202,7 @@ PyInit_fast_data_types(void) {
if (!init_freetype_library(m)) return NULL; if (!init_freetype_library(m)) return NULL;
if (!init_fontconfig_library(m)) return NULL; if (!init_fontconfig_library(m)) return NULL;
#endif #endif
if (!init_fonts(m)) return NULL;
#define OOF(n) #n, offsetof(Cell, n) #define OOF(n) #n, offsetof(Cell, n)
if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}", if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}",

View File

@ -45,6 +45,7 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
#define DECORATION_MASK 3 #define DECORATION_MASK 3
#define BOLD_SHIFT 4 #define BOLD_SHIFT 4
#define ITALIC_SHIFT 5 #define ITALIC_SHIFT 5
#define BI_VAL(attrs) ((attrs >> 4) & 3)
#define REVERSE_SHIFT 6 #define REVERSE_SHIFT 6
#define STRIKE_SHIFT 7 #define STRIKE_SHIFT 7
#define COL_MASK 0xFFFFFFFF #define COL_MASK 0xFFFFFFFF
@ -73,6 +74,7 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
#define COPY_SELF_CELL(s, d) COPY_CELL(self, s, self, d) #define COPY_SELF_CELL(s, d) COPY_CELL(self, s, self, d)
#define METHOD(name, arg_type) {#name, (PyCFunction)name, arg_type, name##_doc}, #define METHOD(name, arg_type) {#name, (PyCFunction)name, arg_type, name##_doc},
#define METHODB(name, arg_type) {#name, (PyCFunction)name, arg_type, ""}
#define BOOL_GETSET(type, x) \ #define BOOL_GETSET(type, x) \
static PyObject* x##_get(type *self, void UNUSED *closure) { PyObject *ans = self->x ? Py_True : Py_False; Py_INCREF(ans); return ans; } \ static PyObject* x##_get(type *self, void UNUSED *closure) { PyObject *ans = self->x ? Py_True : Py_False; Py_INCREF(ans); return ans; } \

216
kitty/fonts.c Normal file
View File

@ -0,0 +1,216 @@
/*
* fonts.c
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#include "fonts.h"
#include "state.h"
typedef uint16_t glyph_index;
typedef struct {
sprite_index x, y, z;
} SpriteIndex;
typedef struct {
PyObject *face;
hb_font_t *hb_font;
// Map glyph ids to sprite map co-ords
SpriteIndex *sprite_map;
bool bold, italic;
} Font;
static Font medium_font = {0}, bold_font = {0}, italic_font = {0}, bi_font = {0}, box_font = {0}, missing_font = {0}, blank_font = {0};
static Font fallback_fonts[256] = {{0}};
static PyObject *get_fallback_font = NULL;
static inline bool
alloc_font(Font *f, PyObject *face, bool bold, bool italic) {
f->sprite_map = calloc(1 << (sizeof(glyph_index) * 8), sizeof(SpriteIndex));
if (f->sprite_map == NULL) return false;
f->face = face; Py_INCREF(face);
f->hb_font = harfbuzz_font_for_face(face);
f->bold = bold; f->italic = italic;
return true;
}
static inline void
clear_font(Font *f) {
Py_CLEAR(f->face);
free(f->sprite_map); f->sprite_map = NULL;
f->hb_font = NULL;
f->bold = false; f->italic = false;
}
static unsigned int cell_width = 0, cell_height = 0, baseline = 0, underline_position = 0, underline_thickness = 0;
static inline PyObject*
update_cell_metrics(float pt_sz, float xdpi, float ydpi) {
#define CALL(f) { if ((f)->face && !set_size_for_face((f)->face, pt_sz, xdpi, ydpi)) return NULL; }
CALL(&medium_font); CALL(&bold_font); CALL(&italic_font); CALL(&bi_font);
for (size_t i = 0; fallback_fonts[i].face != NULL; i++) {
CALL(fallback_fonts + i);
}
#undef CALL
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);
if (OPT(adjust_line_height_frac) != 0.f) cell_height *= OPT(adjust_line_height_frac);
if (cell_height < 4) { PyErr_SetString(PyExc_ValueError, "line height too small after adjustment"); return NULL; }
if (cell_height > 1000) { PyErr_SetString(PyExc_ValueError, "line height too large after adjustment"); return NULL; }
underline_position = MIN(cell_height - 1, underline_position);
return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness);
}
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);
}
static inline bool
has_cell_text(Font *self, Cell *cell) {
if (!face_has_codepoint(self->face, cell->ch)) return false;
if (cell->cc) {
if (!face_has_codepoint(self->face, cell->cc & CC_MASK)) return false;
char_type cc = cell->cc >> 16;
if (cc && !face_has_codepoint(self->face, cc)) return false;
}
return true;
}
static inline Font*
fallback_font(Cell *cell) {
bool bold = (cell->attrs >> BOLD_SHIFT) & 1;
bool italic = (cell->attrs >> ITALIC_SHIFT) & 1;
size_t i;
for (i = 0; fallback_fonts[i].face != NULL; i++) {
if (fallback_fonts[i].bold == bold && fallback_fonts[i].italic == italic && has_cell_text(fallback_fonts + i, cell)) {
return fallback_fonts + i;
}
}
if (get_fallback_font == NULL || i == (sizeof(fallback_fonts)/sizeof(fallback_fonts[0])-1)) return &missing_font;
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 (face == NULL) { PyErr_Print(); return &missing_font; }
if (face == Py_None) { Py_DECREF(face); return &missing_font; }
if (!alloc_font(fallback_fonts + i, face, bold, italic)) { fatal("Out of memory"); }
return fallback_fonts + i;
}
typedef struct {
char_type left, right;
size_t font_idx;
} SymbolMap;
static SymbolMap* symbol_maps = NULL;
static Font *symbol_map_fonts = NULL;
static size_t symbol_maps_count = 0, symbol_map_fonts_count = 0;
static inline Font*
in_symbol_maps(char_type ch) {
for (size_t i = 0; i < symbol_maps_count; i++) {
if (symbol_maps[i].left <= ch && ch <= symbol_maps[i].right) return symbol_map_fonts + symbol_maps[i].font_idx;
}
return NULL;
}
Font*
font_for_cell(Cell *cell) {
Font *ans;
START_ALLOW_CASE_RANGE
switch(cell->ch) {
case 0:
return &blank_font;
case 0x2500 ... 0x2570:
case 0x2574 ... 0x2577:
case 0xe0b0:
case 0xe0b2:
return &box_font;
default:
ans = in_symbol_maps(cell->ch);
if (ans != NULL) return ans;
switch(BI_VAL(cell->attrs)) {
case 0:
ans = &medium_font;
break;
case 1:
ans = bold_font.face ? &bold_font : &medium_font;
break;
case 2:
ans = italic_font.face ? &italic_font : &medium_font;
break;
case 4:
ans = bi_font.face ? &bi_font : &medium_font;
break;
}
if (has_cell_text(ans, cell)) return ans;
return fallback_font(cell);
}
END_ALLOW_CASE_RANGE
}
static PyObject*
set_font(PyObject UNUSED *m, PyObject *args) {
PyObject *sm, *smf, *medium, *bold = NULL, *italic = NULL, *bi = NULL;
float xdpi, ydpi, pt_sz;
if (!PyArg_ParseTuple(args, "OO!O!fffO|OOO", &get_fallback_font, &PyTuple_Type, &sm, &PyTuple_Type, &smf, &pt_sz, &xdpi, &ydpi, &medium, &bold, &italic, &bi)) return NULL;
if (!alloc_font(&medium_font, medium, false, false)) return PyErr_NoMemory();
if (bold && !alloc_font(&bold_font, bold, false, false)) return PyErr_NoMemory();
if (italic && !alloc_font(&italic_font, italic, false, false)) return PyErr_NoMemory();
if (bi && !alloc_font(&bi_font, bi, false, false)) return PyErr_NoMemory();
symbol_maps_count = PyTuple_GET_SIZE(sm);
if (symbol_maps_count > 0) {
symbol_maps = malloc(symbol_maps_count * sizeof(SymbolMap));
symbol_map_fonts_count = PyTuple_GET_SIZE(smf);
symbol_map_fonts = calloc(symbol_map_fonts_count, sizeof(Font));
if (symbol_maps == NULL || symbol_map_fonts == NULL) return PyErr_NoMemory();
for (size_t i = 0; i < symbol_map_fonts_count; i++) {
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();
}
for (size_t i = 0; i < symbol_maps_count; i++) {
unsigned int left, right, font_idx;
if (!PyArg_ParseTuple(PyTuple_GET_ITEM(sm, i), "III", &left, &right, &font_idx)) return NULL;
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);
}
static void
finalize(void) {
Py_CLEAR(get_fallback_font);
clear_font(&medium_font); clear_font(&bold_font); clear_font(&italic_font); clear_font(&bi_font);
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++) clear_font(symbol_map_fonts + i);
free(symbol_maps); free(symbol_map_fonts);
}
static PyMethodDef module_methods[] = {
METHODB(set_font_size, METH_VARARGS),
METHODB(set_font, METH_VARARGS),
{NULL, NULL, 0, NULL} /* Sentinel */
};
bool
init_fonts(PyObject *module) {
if (Py_AtExit(finalize) != 0) {
PyErr_SetString(PyExc_RuntimeError, "Failed to register the fonts module at exit handler");
return false;
}
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
return true;
}

20
kitty/fonts.h Normal file
View File

@ -0,0 +1,20 @@
/*
* Copyright (C) 2017 Kovid Goyal <kovid at kovidgoyal.net>
*
* Distributed under terms of the GPL3 license.
*/
#pragma once
#include "lineops.h"
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#include <hb.h>
#pragma GCC diagnostic pop
bool face_has_codepoint(PyObject *, char_type);
hb_font_t* harfbuzz_font_for_face(PyObject*);
bool set_size_for_face(PyObject*, float, float, float);
void cell_metrics(PyObject*, unsigned int*, unsigned int*, unsigned int*, unsigned int*, unsigned int*);

View File

@ -3,19 +3,18 @@
# 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 os
import re
from collections import namedtuple from collections import namedtuple
from kitty.fast_data_types import Face, get_fontconfig_font from kitty.fast_data_types import Face, get_fontconfig_font
def escape_family_name(name): def face_from_font(font, pt_sz, xdpi, ydpi):
return re.sub(r'([-:,\\])', lambda m: '\\' + m.group(1), name) return Face(font.path, font.index, font.hinting, font.hintstyle, pt_sz, xdpi, ydpi)
Font = namedtuple( Font = namedtuple(
'Font', 'Font',
'face hinting hintstyle bold italic scalable outline weight slant index' 'path hinting hintstyle bold italic scalable outline weight slant index'
) )
@ -76,13 +75,21 @@ def find_font_for_characters(
size_in_pts=size_in_pts, size_in_pts=size_in_pts,
dpi=dpi dpi=dpi
) )
if not ans.face or not os.path.exists(ans.face): if not ans.path or not os.path.exists(ans.path):
raise FontNotFound( raise FontNotFound(
'Failed to find font for characters: {!r}'.format(chars) 'Failed to find font for characters: {!r}'.format(chars)
) )
return ans return ans
def font_for_text(text, current_font_family, pt_sz, xdpi, ydpi, bold=False, italic=False):
dpi = (xdpi + ydpi) / 2
try:
return find_font_for_characters(current_font_family, text, bold=bold, italic=italic, size_in_pts=pt_sz, dpi=dpi)
except FontNotFound:
return find_font_for_characters(current_font_family, text, bold=bold, italic=italic, size_in_pts=pt_sz, dpi=dpi, allow_bitmaped_fonts=True)
def get_font_information(family, bold=False, italic=False): def get_font_information(family, bold=False, italic=False):
return get_font(family, bold, italic) return get_font(family, bold, italic)
@ -102,7 +109,7 @@ def get_font_files(opts):
return ans return ans
n = get_font_information(get_family()) n = get_font_information(get_family())
ans['regular'] = n._replace(face=Face(n.face, n.index, n.hinting, n.hintstyle)) ans['medium'] = n
def do(key): def do(key):
b = get_font_information( b = get_font_information(
@ -110,8 +117,8 @@ def get_font_files(opts):
bold=key in ('bold', 'bi'), bold=key in ('bold', 'bi'),
italic=key in ('italic', 'bi') italic=key in ('italic', 'bi')
) )
if b.face != n.face: if b.path != n.path:
ans[key] = b._replace(face=Face(b.face, b.index, b.hinting, b.hintstyle)) ans[key] = b
do('bold'), do('italic'), do('bi') do('bold'), do('italic'), do('bi')
return ans return ans
@ -119,4 +126,4 @@ def get_font_files(opts):
def font_for_family(family): def font_for_family(family):
ans = get_font_information(family) ans = get_font_information(family)
return ans._replace(face=Face(ans.face, ans.index, ans.hinting, ans.hintstyle)) return ans

View File

@ -3,14 +3,69 @@
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net> # License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
import ctypes import ctypes
from collections import namedtuple
from math import sin, pi, ceil, floor, sqrt from math import sin, pi, ceil, floor, sqrt
from kitty.constants import isosx from kitty.constants import isosx
from kitty.utils import get_logical_dpi
from kitty.fast_data_types import set_font, set_font_size
from .box_drawing import render_box_char, is_renderable_box_char from .box_drawing import render_box_char, is_renderable_box_char
if isosx: if isosx:
from .core_text import set_font_family, render_cell as rc, current_cell # noqa pass
else: else:
from .freetype import set_font_family, render_cell as rc, current_cell # noqa from .fontconfig import get_font_files, font_for_text, face_from_font, font_for_family
def create_face(font):
s = set_font_family.state
return face_from_font(font, s.pt_sz, s.xdpi, s.ydpi)
def create_symbol_map(opts):
val = opts.symbol_map
family_map = {}
faces = []
for family in val.values():
if family not in family_map:
o = create_face(font_for_family(family))
family_map[family] = len(faces)
faces.append(o)
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')
def get_fallback_font(text, bold, italic):
state = set_font_family.state
return create_face(font_for_text(text, state.family, state.pt_sz, state.xdpi, state.ydpi, bold, italic))
def set_font_family(opts, override_font_size=None):
if hasattr(set_font_family, 'state'):
raise ValueError('Cannot set font family more than once, use resize_fonts() to change size')
sz = override_font_size or opts.font_size
xdpi, ydpi = get_logical_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'])]
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)
cell_width, cell_height, baseline, underline_position, underline_thickness = set_font(get_fallback_font, sm, sfaces, sz, xdpi, ydpi, *faces)
set_font_family.state = FontState(opts.font_family, sz, xdpi, ydpi, cell_width, cell_height, baseline, underline_position, underline_thickness)
return cell_width, cell_height
def resize_fonts(new_sz, xdpi=None, ydpi=None):
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)
set_font_family.state = FontState(
s.family, new_sz, xdpi, ydpi, cell_width, cell_height, baseline, underline_position, underline_thickness)
def add_line(buf, cell_width, position, thickness, cell_height): def add_line(buf, cell_width, position, thickness, cell_height):

View File

@ -5,14 +5,10 @@
* Distributed under terms of the GPL3 license. * Distributed under terms of the GPL3 license.
*/ */
#include "data-types.h" #include "fonts.h"
#include <math.h> #include <math.h>
#include <structmember.h> #include <structmember.h>
#include <ft2build.h> #include <ft2build.h>
#pragma GCC diagnostic push
#pragma GCC diagnostic ignored "-Wpedantic"
#include <hb.h>
#pragma GCC diagnostic pop
#include <hb-ft.h> #include <hb-ft.h>
#if HB_VERSION_MAJOR > 1 || (HB_VERSION_MAJOR == 1 && (HB_VERSION_MINOR > 0 || (HB_VERSION_MINOR == 0 && HB_VERSION_MICRO >= 5))) #if HB_VERSION_MAJOR > 1 || (HB_VERSION_MAJOR == 1 && (HB_VERSION_MINOR > 0 || (HB_VERSION_MINOR == 0 && HB_VERSION_MICRO >= 5)))
@ -31,10 +27,10 @@ typedef struct {
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness; int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness;
int hinting, hintstyle; int hinting, hintstyle;
bool is_scalable; bool is_scalable;
float size_in_pts;
FT_F26Dot6 char_width, char_height; FT_F26Dot6 char_width, char_height;
FT_UInt xdpi, ydpi; FT_UInt xdpi, ydpi;
PyObject *path; PyObject *path;
hb_buffer_t *harfbuzz_buffer;
hb_font_t *harfbuzz_font; hb_font_t *harfbuzz_font;
} Face; } Face;
@ -94,6 +90,12 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt
return !error; return !error;
} }
bool
set_size_for_face(PyObject *self, float pt_sz, float xdpi, float ydpi) {
FT_UInt w = (FT_UInt)(ceilf(pt_sz * 64));
((Face*)self)->size_in_pts = pt_sz;
return set_font_size((Face*)self, w, w, (FT_UInt)xdpi, (FT_UInt) ydpi);
}
static PyObject* static PyObject*
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) { new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
@ -101,8 +103,8 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
char *path; char *path;
int error, hinting, hintstyle; int error, hinting, hintstyle;
long index; long index;
/* unsigned int columns=80, lines=24, scrollback=0; */ unsigned int size_in_pts, xdpi, ydpi;
if (!PyArg_ParseTuple(args, "slii", &path, &index, &hinting, &hintstyle)) return NULL; if (!PyArg_ParseTuple(args, "sliifff", &path, &index, &hinting, &hintstyle, &size_in_pts, &xdpi, &ydpi)) return NULL;
self = (Face *)type->tp_alloc(type, 0); self = (Face *)type->tp_alloc(type, 0);
if (self != NULL) { if (self != NULL) {
@ -114,10 +116,8 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
CPY(units_per_EM); CPY(ascender); CPY(descender); CPY(height); CPY(max_advance_width); CPY(max_advance_height); CPY(underline_position); CPY(underline_thickness); 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 #undef CPY
self->is_scalable = FT_IS_SCALABLE(self->face); self->is_scalable = FT_IS_SCALABLE(self->face);
self->harfbuzz_buffer = hb_buffer_create();
self->hinting = hinting; self->hintstyle = hintstyle; self->hinting = hinting; self->hintstyle = hintstyle;
if (self->harfbuzz_buffer == NULL || !hb_buffer_allocation_successful(self->harfbuzz_buffer) || !hb_buffer_pre_allocate(self->harfbuzz_buffer, 20)) { Py_CLEAR(self); return PyErr_NoMemory(); } if (!set_size_for_face((PyObject*)self, size_in_pts, xdpi, ydpi)) { Py_CLEAR(self); return NULL; }
if (!set_font_size(self, 10, 20, 96, 96)) { Py_CLEAR(self); return NULL; }
self->harfbuzz_font = hb_ft_font_create(self->face, NULL); self->harfbuzz_font = hb_ft_font_create(self->face, NULL);
if (self->harfbuzz_font == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); } if (self->harfbuzz_font == NULL) { Py_CLEAR(self); return PyErr_NoMemory(); }
} }
@ -126,7 +126,6 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
static void static void
dealloc(Face* self) { dealloc(Face* self) {
if (self->harfbuzz_buffer) hb_buffer_destroy(self->harfbuzz_buffer);
if (self->harfbuzz_font) hb_font_destroy(self->harfbuzz_font); if (self->harfbuzz_font) hb_font_destroy(self->harfbuzz_font);
if (self->face) FT_Done_Face(self->face); if (self->face) FT_Done_Face(self->face);
Py_CLEAR(self->path); Py_CLEAR(self->path);
@ -143,17 +142,6 @@ repr(Face *self) {
} }
static PyObject*
set_char_size(Face *self, PyObject *args) {
#define set_char_size_doc "set_char_size(width, height, xdpi, ydpi) -> set the character size. width, height is in 1/64th of a pt. dpi is in pixels per inch"
long char_width, char_height;
unsigned int xdpi, ydpi;
if (!PyArg_ParseTuple(args, "llII", &char_width, &char_height, &xdpi, &ydpi)) return NULL;
if (!set_font_size(self, char_width, char_height, xdpi, ydpi)) return NULL;
Py_RETURN_NONE;
}
static inline int static inline int
get_load_flags(int hinting, int hintstyle, int base) { get_load_flags(int hinting, int hintstyle, int base) {
int flags = base; int flags = base;
@ -172,253 +160,38 @@ load_glyph(Face *self, int glyph_index) {
return true; return true;
} }
static PyObject* static inline unsigned int
get_char_index(Face *self, PyObject *args) {
#define get_char_index_doc ""
int code;
unsigned int ans;
if (!PyArg_ParseTuple(args, "C", &code)) return NULL;
ans = FT_Get_Char_Index(self->face, code);
return Py_BuildValue("I", ans);
}
static PyObject*
calc_cell_width(Face *self) { calc_cell_width(Face *self) {
#define calc_cell_width_doc "" unsigned int ans = 0;
unsigned long ans = 0;
for (char_type i = 32; i < 128; i++) { for (char_type i = 32; i < 128; i++) {
int glyph_index = FT_Get_Char_Index(self->face, i); int glyph_index = FT_Get_Char_Index(self->face, i);
if (!load_glyph(self, glyph_index)) return NULL; if (load_glyph(self, glyph_index)) {
ans = MAX(ans, (unsigned long)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f)); ans = MAX(ans, (unsigned long)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f));
} }
return PyLong_FromUnsignedLong(ans);
}
static PyStructSequence_Field shape_fields[] = {
{"glyph_id", NULL},
{"cluster", NULL},
{"mask", NULL},
{"x_offset", NULL},
{"y_offset", NULL},
{"x_advance", NULL},
{"y_advance", NULL},
{NULL}
};
static PyStructSequence_Desc shape_fields_desc = {"Shape", NULL, shape_fields, 7};
static PyTypeObject ShapeFieldsType = {{{0}}};
static inline PyObject*
shape_to_py(unsigned int i, hb_glyph_info_t *info, hb_glyph_position_t *pos) {
PyObject *ans = PyStructSequence_New(&ShapeFieldsType);
if (ans == NULL) return NULL;
#define SI(num, src, attr, conv, func, div) PyStructSequence_SET_ITEM(ans, num, func(((conv)src[i].attr) / div)); if (PyStructSequence_GET_ITEM(ans, num) == NULL) { Py_CLEAR(ans); return PyErr_NoMemory(); }
#define INFO(num, attr) SI(num, info, attr, unsigned long, PyLong_FromUnsignedLong, 1)
#define POS(num, attr) SI(num + 3, pos, attr, double, PyFloat_FromDouble, 64.0)
INFO(0, codepoint); INFO(1, cluster); INFO(2, mask);
POS(0, x_offset); POS(1, y_offset); POS(2, x_advance); POS(3, y_advance);
#undef INFO
#undef POS
#undef SI
return ans;
}
typedef struct {
unsigned int length;
hb_glyph_info_t *info;
hb_glyph_position_t *positions;
} ShapeData;
static inline void
_shape(Face *self, const char *string, int len, ShapeData *ans) {
hb_buffer_clear_contents(self->harfbuzz_buffer);
#ifdef HARBUZZ_HAS_LOAD_FLAGS
hb_ft_font_set_load_flags(self->harfbuzz_font, get_load_flags(self->hinting, self->hintstyle, FT_LOAD_DEFAULT));
#endif
hb_buffer_add_utf8(self->harfbuzz_buffer, string, len, 0, len);
hb_buffer_guess_segment_properties(self->harfbuzz_buffer);
hb_shape(self->harfbuzz_font, self->harfbuzz_buffer, NULL, 0);
unsigned int info_length, positions_length;
ans->info = hb_buffer_get_glyph_infos(self->harfbuzz_buffer, &info_length);
ans->positions = hb_buffer_get_glyph_positions(self->harfbuzz_buffer, &positions_length);
ans->length = MIN(info_length, positions_length);
}
static PyObject*
shape(Face *self, PyObject *args) {
#define shape_doc "shape(text)"
const char *string;
int len;
if (!PyArg_ParseTuple(args, "s#", &string, &len)) return NULL;
ShapeData sd;
_shape(self, string, len, &sd);
PyObject *ans = PyTuple_New(sd.length);
if (ans == NULL) return NULL;
for (unsigned int i = 0; i < sd.length; i++) {
PyObject *s = shape_to_py(i, sd.info, sd.positions);
if (s == NULL) { Py_CLEAR(ans); return NULL; }
PyTuple_SET_ITEM(ans, i, s);
} }
return ans; return ans;
} }
typedef struct { static inline int
unsigned char* buf; font_units_to_pixels(Face *self, int x) {
size_t start_x, width, stride; return (int)ceilf(((float)x * (((float)self->size_in_pts * (float)self->ydpi) / (72.f * (float)self->units_per_EM))));
size_t rows;
} ProcessedBitmap;
static inline void
trim_borders(ProcessedBitmap *ans, size_t extra) {
bool column_has_text = false;
// Trim empty columns from the right side of the bitmap
for (ssize_t x = ans->width - 1; !column_has_text && x > -1 && extra > 0; x--) {
for (size_t y = 0; y < ans->rows && !column_has_text; y++) {
if (ans->buf[x + y * ans->stride] > 200) column_has_text = true;
}
if (!column_has_text) { ans->width--; extra--; }
}
ans->start_x = extra;
ans->width -= extra;
} }
void
static inline bool cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) {
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int num_cells, int bold, int italic, bool rescale) { Face *self = (Face*)s;
if (!load_glyph(self, glyph_id)) return false; *cell_width = calc_cell_width(self);
unsigned int max_width = cell_width * num_cells; *cell_height = font_units_to_pixels(self, self->height);
FT_Bitmap *bitmap = &self->face->glyph->bitmap; *baseline = font_units_to_pixels(self, self->ascender);
ans->buf = bitmap->buffer; *underline_position = MIN(*cell_height - 1, *baseline - font_units_to_pixels(self, self->underline_position));
ans->start_x = 0; ans->width = bitmap->width; *underline_thickness = font_units_to_pixels(self, self->underline_thickness);
ans->stride = bitmap->pitch < 0 ? -bitmap->pitch : bitmap->pitch;
ans->rows = bitmap->rows;
if (ans->width > max_width) {
size_t extra = bitmap->width - max_width;
if (italic && extra < cell_width / 2) {
trim_borders(ans, extra);
} else if (rescale && self->is_scalable && extra > MAX(2, cell_width / 3)) {
FT_F26Dot6 char_width = self->char_width, char_height = self->char_height;
float ar = (float)max_width / (float)bitmap->width;
if (set_font_size(self, (FT_F26Dot6)((float)self->char_width * ar), (FT_F26Dot6)((float)self->char_height * ar), self->xdpi, self->ydpi)) {
if (!render_bitmap(self, glyph_id, ans, cell_width, num_cells, bold, italic, false)) return false;
if (!set_font_size(self, char_width, char_height, self->xdpi, self->ydpi)) return false;
}
}
}
return true;
} }
static inline void bool
place_bitmap_in_cell(unsigned char *cell, ProcessedBitmap *bm, size_t cell_width, size_t cell_height, float x_offset, float y_offset, FT_Glyph_Metrics *metrics, size_t baseline) { face_has_codepoint(PyObject *s, char_type cp) {
// We want the glyph to be positioned inside the cell based on the bearingX return FT_Get_Char_Index(((Face*)s)->face, cp) > 0;
// and bearingY values, making sure that it does not overflow the cell.
// Calculate column bounds
ssize_t xoff = (ssize_t)(x_offset + (float)metrics->horiBearingX / 64.f);
size_t src_start_column = bm->start_x, dest_start_column = 0, extra;
if (xoff < 0) src_start_column += -xoff;
else dest_start_column = xoff;
// Move the dest start column back if the width overflows because of it
if (dest_start_column > 0 && dest_start_column + bm->width > cell_width) {
extra = dest_start_column + bm->width - cell_width;
dest_start_column = extra > dest_start_column ? 0 : dest_start_column - extra;
}
// Calculate row bounds
ssize_t yoff = (ssize_t)(y_offset + (float)metrics->horiBearingY / 64.f);
size_t src_start_row, dest_start_row;
if (yoff > 0 && (size_t)yoff > baseline) {
src_start_row = 0;
dest_start_row = 0;
} else {
src_start_row = 0;
dest_start_row = baseline - yoff;
}
/* printf("src_start_row: %zu src_start_column: %zu dest_start_row: %zu dest_start_column: %zu\n", src_start_row, src_start_column, dest_start_row, dest_start_column); */
for (size_t sr = src_start_row, dr = dest_start_row; sr < bm->rows && dr < cell_height; sr++, dr++) {
for(size_t sc = src_start_column, dc = dest_start_column; sc < bm->width && dc < cell_width; sc++, dc++) {
uint16_t val = cell[dr * cell_width + dc];
val = (val + bm->buf[sr * bm->stride + sc]) % 256;
cell[dr * cell_width + dc] = val;
}
}
} }
static PyObject*
draw_single_glyph(Face *self, PyObject *args) {
#define draw_single_glyph_doc "draw_complex_glyph(codepoint, cell_width, cell_height, cell_buffer, num_cells, bold, italic, baseline)"
int bold, italic;
unsigned int cell_width, cell_height, num_cells, baseline, codepoint;
PyObject *addr;
if (!PyArg_ParseTuple(args, "IIIO!IppI", &codepoint, &cell_width, &cell_height, &PyLong_Type, &addr, &num_cells, &bold, &italic, &baseline)) return NULL;
unsigned char *cell = PyLong_AsVoidPtr(addr);
int glyph_id = FT_Get_Char_Index(self->face, codepoint);
ProcessedBitmap bm;
if (!render_bitmap(self, glyph_id, &bm, cell_width, num_cells, bold, italic, true)) return NULL;
place_bitmap_in_cell(cell, &bm, cell_width * num_cells, cell_height, 0, 0, &self->face->glyph->metrics, baseline);
Py_RETURN_NONE;
}
static PyObject*
draw_complex_glyph(Face *self, PyObject *args) {
#define draw_complex_glyph_doc "draw_complex_glyph(text, cell_width, cell_height, cell_buffer, num_cells, bold, italic, baseline)"
const char *text;
int text_len, bold, italic;
unsigned int cell_width, cell_height, num_cells, baseline;
PyObject *addr;
float x = 0.f, y = 0.f;
if (!PyArg_ParseTuple(args, "s#IIO!IppI", &text, &text_len, &cell_width, &cell_height, &PyLong_Type, &addr, &num_cells, &bold, &italic, &baseline)) return NULL;
unsigned char *cell = PyLong_AsVoidPtr(addr);
ShapeData sd;
_shape(self, text, text_len, &sd);
ProcessedBitmap bm;
for (unsigned i = 0; i < sd.length; i++) {
if (sd.info[i].codepoint == 0) continue;
if (!render_bitmap(self, sd.info[i].codepoint, &bm, cell_width, num_cells, bold, italic, true)) return NULL;
x += (float)sd.positions[i].x_offset / 64.0f;
y = (float)sd.positions[i].y_offset / 64.0f;
place_bitmap_in_cell(cell, &bm, cell_width * num_cells, cell_height, x, y, &self->face->glyph->metrics, baseline);
x += (float)sd.positions[i].x_advance / 64.0f;
}
Py_RETURN_NONE;
}
static PyObject*
split_cells(Face UNUSED *self, PyObject *args) {
#define split_cells_doc "split_cells(cell_width, cell_height, src, *cells)"
unsigned int cell_width, cell_height;
unsigned char *cells[10], *src;
size_t num_cells = PyTuple_GET_SIZE(args) - 3;
if (num_cells > sizeof(cells)/sizeof(cells[0])) { PyErr_SetString(PyExc_ValueError, "Too many cells being split"); return NULL; }
cell_width = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(args, 0));
cell_height = PyLong_AsUnsignedLong(PyTuple_GET_ITEM(args, 1));
src = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, 2));
for (size_t i = 3; i < num_cells + 3; i++) cells[i - 3] = PyLong_AsVoidPtr(PyTuple_GET_ITEM(args, i));
size_t stride = num_cells * cell_width;
for (size_t y = 0; y < cell_height; y++) {
for (size_t i = 0; i < num_cells; i++) {
unsigned char *dest = cells[i] + y * cell_width;
for (size_t x = 0; x < cell_width; x++) {
dest[x] = src[y * stride + i * cell_width + x];
}
}
}
Py_RETURN_NONE;
}
// Boilerplate {{{ // Boilerplate {{{
static PyMemberDef members[] = { static PyMemberDef members[] = {
@ -437,13 +210,6 @@ static PyMemberDef members[] = {
}; };
static PyMethodDef methods[] = { static PyMethodDef methods[] = {
METHOD(set_char_size, METH_VARARGS)
METHOD(shape, METH_VARARGS)
METHOD(draw_complex_glyph, METH_VARARGS)
METHOD(draw_single_glyph, METH_VARARGS)
METHOD(split_cells, METH_VARARGS)
METHOD(get_char_index, METH_VARARGS)
METHOD(calc_cell_width, METH_NOARGS)
{NULL} /* Sentinel */ {NULL} /* Sentinel */
}; };
@ -482,13 +248,6 @@ init_freetype_library(PyObject *m) {
PyErr_SetString(FreeType_Exception, "Failed to register the freetype library at exit handler"); PyErr_SetString(FreeType_Exception, "Failed to register the freetype library at exit handler");
return false; return false;
} }
if (PyStructSequence_InitType2(&ShapeFieldsType, &shape_fields_desc) != 0) return false;
PyModule_AddObject(m, "ShapeFields", (PyObject*)&ShapeFieldsType);
PyModule_AddIntMacro(m, FT_LOAD_RENDER);
PyModule_AddIntMacro(m, FT_LOAD_TARGET_NORMAL);
PyModule_AddIntMacro(m, FT_LOAD_TARGET_LIGHT);
PyModule_AddIntMacro(m, FT_LOAD_NO_HINTING);
PyModule_AddIntMacro(m, FT_PIXEL_MODE_GRAY);
return true; return true;
} }

View File

@ -165,6 +165,22 @@ text_at(Line* self, Py_ssize_t xval) {
return line_text_at(self->cells[xval].ch, self->cells[xval].cc); return line_text_at(self->cells[xval].ch, self->cells[xval].cc);
} }
size_t
cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf) {
size_t n = 1;
buf[0] = cell->ch;
if (include_cc) {
char_type cc = cell->cc;
Py_UCS4 cc1 = cc & CC_MASK, cc2;
if (cc1) {
buf[1] = cc1; n++;
cc2 = cc >> 16;
if (cc2) { buf[2] = cc2; n++; }
}
}
return n;
}
PyObject* PyObject*
unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char) { unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char) {
size_t n = 0; size_t n = 0;
@ -177,16 +193,7 @@ unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc
if (previous_width == 2) { previous_width = 0; continue; }; if (previous_width == 2) { previous_width = 0; continue; };
ch = ' '; ch = ' ';
} }
buf[n++] = ch; n += cell_as_unicode(self->cells + i, include_cc, buf + n);
if (include_cc) {
char_type cc = self->cells[i].cc;
Py_UCS4 cc1 = cc & CC_MASK, cc2;
if (cc1) {
buf[n++] = cc1;
cc2 = cc >> 16;
if (cc2) buf[n++] = cc2;
}
}
previous_width = self->cells[i].attrs & WIDTH_MASK; previous_width = self->cells[i].attrs & WIDTH_MASK;
} }
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n); return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);

View File

@ -67,6 +67,7 @@ index_type line_url_start_at(Line *self, index_type x);
index_type line_url_end_at(Line *self, index_type x); index_type line_url_end_at(Line *self, index_type x);
index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen); index_type line_as_ansi(Line *self, Py_UCS4 *buf, index_type buflen);
unsigned int line_length(Line *self); unsigned int line_length(Line *self);
size_t cell_as_unicode(Cell *cell, bool include_cc, Py_UCS4 *buf);
PyObject* unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char); 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); void linebuf_init_line(LineBuf *, index_type);

View File

@ -180,6 +180,16 @@ PYWRAP1(set_options) {
Py_DECREF(ret); if (PyErr_Occurred()) return NULL; Py_DECREF(ret); if (PyErr_Occurred()) return NULL;
Py_DECREF(chars); Py_DECREF(chars);
PyObject *al = PyObject_GetAttrString(args, "adjust_line_height");
if (PyFloat_Check(al)) {
OPT(adjust_line_height_frac) = (float)PyFloat_AsDouble(al);
OPT(adjust_line_height_px) = 0;
} else {
OPT(adjust_line_height_frac) = 0;
OPT(adjust_line_height_px) = (int)PyLong_AsLong(al);
}
Py_DECREF(al);
#undef S #undef S
Py_RETURN_NONE; Py_RETURN_NONE;
} }

View File

@ -20,6 +20,8 @@ typedef struct {
double repaint_delay, input_delay; double repaint_delay, input_delay;
bool focus_follows_mouse; bool focus_follows_mouse;
bool macos_option_as_alt; bool macos_option_as_alt;
int adjust_line_height_px;
float adjust_line_height_frac;
} Options; } Options;
typedef struct { typedef struct {