mirror of
https://github.com/kovidgoyal/kitty
synced 2026-06-12 19:49:32 +02:00
Start work on line based rendering
This commit is contained in:
@@ -11,7 +11,7 @@ from .fast_data_types import (
|
||||
GLFW_KEY_DOWN, GLFW_KEY_UP, ChildMonitor, destroy_global_data,
|
||||
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 .session import create_session
|
||||
from .tabs import SpecialWindow, TabManager
|
||||
@@ -142,8 +142,7 @@ class Boss:
|
||||
self.current_font_size = new_size
|
||||
w, h = cell_size.width, cell_size.height
|
||||
windows = tuple(filter(None, self.window_id_map.values()))
|
||||
cell_size.width, cell_size.height = set_font_family(
|
||||
self.opts, override_font_size=self.current_font_size)
|
||||
cell_size.width, cell_size.height = resize_fonts(self.current_font_size)
|
||||
layout_sprite_map(cell_size.width, cell_size.height, render_cell_wrapper)
|
||||
for window in windows:
|
||||
window.screen.rescale_images(w, h)
|
||||
|
||||
@@ -165,8 +165,7 @@ def parse_symbol_map(val):
|
||||
return abort()
|
||||
if b < a or max(a, b) > sys.maxunicode or min(a, b) < 1:
|
||||
return abort()
|
||||
for y in range(a, b + 1):
|
||||
symbol_map[chr(y)] = family
|
||||
symbol_map[(a, b)] = family
|
||||
return symbol_map
|
||||
|
||||
|
||||
|
||||
@@ -157,6 +157,7 @@ extern int init_Screen(PyObject *);
|
||||
extern int init_Face(PyObject *);
|
||||
extern bool init_freetype_library(PyObject*);
|
||||
extern bool init_fontconfig_library(PyObject*);
|
||||
extern bool init_fonts(PyObject*);
|
||||
extern bool init_glfw(PyObject *m);
|
||||
extern bool init_sprites(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_fontconfig_library(m)) return NULL;
|
||||
#endif
|
||||
if (!init_fonts(m)) return NULL;
|
||||
|
||||
#define OOF(n) #n, offsetof(Cell, n)
|
||||
if (PyModule_AddObject(m, "CELL", Py_BuildValue("{sI sI sI sI sI sI sI sI sI}",
|
||||
|
||||
@@ -45,6 +45,7 @@ typedef enum MouseShapes { BEAM, HAND, ARROW } MouseShape;
|
||||
#define DECORATION_MASK 3
|
||||
#define BOLD_SHIFT 4
|
||||
#define ITALIC_SHIFT 5
|
||||
#define BI_VAL(attrs) ((attrs >> 4) & 3)
|
||||
#define REVERSE_SHIFT 6
|
||||
#define STRIKE_SHIFT 7
|
||||
#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 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) \
|
||||
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
216
kitty/fonts.c
Normal 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
20
kitty/fonts.h
Normal 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*);
|
||||
@@ -3,19 +3,18 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import os
|
||||
import re
|
||||
from collections import namedtuple
|
||||
|
||||
from kitty.fast_data_types import Face, get_fontconfig_font
|
||||
|
||||
|
||||
def escape_family_name(name):
|
||||
return re.sub(r'([-:,\\])', lambda m: '\\' + m.group(1), name)
|
||||
def face_from_font(font, pt_sz, xdpi, ydpi):
|
||||
return Face(font.path, font.index, font.hinting, font.hintstyle, pt_sz, xdpi, ydpi)
|
||||
|
||||
|
||||
Font = namedtuple(
|
||||
'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,
|
||||
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(
|
||||
'Failed to find font for characters: {!r}'.format(chars)
|
||||
)
|
||||
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):
|
||||
return get_font(family, bold, italic)
|
||||
|
||||
@@ -102,7 +109,7 @@ def get_font_files(opts):
|
||||
return ans
|
||||
|
||||
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):
|
||||
b = get_font_information(
|
||||
@@ -110,8 +117,8 @@ def get_font_files(opts):
|
||||
bold=key in ('bold', 'bi'),
|
||||
italic=key in ('italic', 'bi')
|
||||
)
|
||||
if b.face != n.face:
|
||||
ans[key] = b._replace(face=Face(b.face, b.index, b.hinting, b.hintstyle))
|
||||
if b.path != n.path:
|
||||
ans[key] = b
|
||||
|
||||
do('bold'), do('italic'), do('bi')
|
||||
return ans
|
||||
@@ -119,4 +126,4 @@ def get_font_files(opts):
|
||||
|
||||
def font_for_family(family):
|
||||
ans = get_font_information(family)
|
||||
return ans._replace(face=Face(ans.face, ans.index, ans.hinting, ans.hintstyle))
|
||||
return ans
|
||||
|
||||
@@ -3,14 +3,69 @@
|
||||
# License: GPL v3 Copyright: 2016, Kovid Goyal <kovid at kovidgoyal.net>
|
||||
|
||||
import ctypes
|
||||
from collections import namedtuple
|
||||
from math import sin, pi, ceil, floor, sqrt
|
||||
|
||||
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
|
||||
if isosx:
|
||||
from .core_text import set_font_family, render_cell as rc, current_cell # noqa
|
||||
pass
|
||||
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):
|
||||
|
||||
301
kitty/freetype.c
301
kitty/freetype.c
@@ -5,14 +5,10 @@
|
||||
* Distributed under terms of the GPL3 license.
|
||||
*/
|
||||
|
||||
#include "data-types.h"
|
||||
#include "fonts.h"
|
||||
#include <math.h>
|
||||
#include <structmember.h>
|
||||
#include <ft2build.h>
|
||||
#pragma GCC diagnostic push
|
||||
#pragma GCC diagnostic ignored "-Wpedantic"
|
||||
#include <hb.h>
|
||||
#pragma GCC diagnostic pop
|
||||
#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)))
|
||||
@@ -31,10 +27,10 @@ typedef struct {
|
||||
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness;
|
||||
int hinting, hintstyle;
|
||||
bool is_scalable;
|
||||
float size_in_pts;
|
||||
FT_F26Dot6 char_width, char_height;
|
||||
FT_UInt xdpi, ydpi;
|
||||
PyObject *path;
|
||||
hb_buffer_t *harfbuzz_buffer;
|
||||
hb_font_t *harfbuzz_font;
|
||||
} Face;
|
||||
|
||||
@@ -94,6 +90,12 @@ set_font_size(Face *self, FT_F26Dot6 char_width, FT_F26Dot6 char_height, FT_UInt
|
||||
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*
|
||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
@@ -101,8 +103,8 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
char *path;
|
||||
int error, hinting, hintstyle;
|
||||
long index;
|
||||
/* unsigned int columns=80, lines=24, scrollback=0; */
|
||||
if (!PyArg_ParseTuple(args, "slii", &path, &index, &hinting, &hintstyle)) return NULL;
|
||||
unsigned int 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);
|
||||
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);
|
||||
#undef CPY
|
||||
self->is_scalable = FT_IS_SCALABLE(self->face);
|
||||
self->harfbuzz_buffer = hb_buffer_create();
|
||||
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_font_size(self, 10, 20, 96, 96)) { Py_CLEAR(self); return NULL; }
|
||||
if (!set_size_for_face((PyObject*)self, size_in_pts, xdpi, ydpi)) { Py_CLEAR(self); return NULL; }
|
||||
self->harfbuzz_font = hb_ft_font_create(self->face, NULL);
|
||||
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
|
||||
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->face) FT_Done_Face(self->face);
|
||||
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
|
||||
get_load_flags(int hinting, int hintstyle, int base) {
|
||||
int flags = base;
|
||||
@@ -172,253 +160,38 @@ load_glyph(Face *self, int glyph_index) {
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject*
|
||||
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*
|
||||
static inline unsigned int
|
||||
calc_cell_width(Face *self) {
|
||||
#define calc_cell_width_doc ""
|
||||
unsigned long ans = 0;
|
||||
unsigned int ans = 0;
|
||||
for (char_type i = 32; i < 128; i++) {
|
||||
int glyph_index = FT_Get_Char_Index(self->face, i);
|
||||
if (!load_glyph(self, glyph_index)) return NULL;
|
||||
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);
|
||||
if (load_glyph(self, glyph_index)) {
|
||||
ans = MAX(ans, (unsigned long)ceilf((float)self->face->glyph->metrics.horiAdvance / 64.f));
|
||||
}
|
||||
}
|
||||
return ans;
|
||||
}
|
||||
|
||||
typedef struct {
|
||||
unsigned char* buf;
|
||||
size_t start_x, width, stride;
|
||||
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;
|
||||
static inline int
|
||||
font_units_to_pixels(Face *self, int x) {
|
||||
return (int)ceilf(((float)x * (((float)self->size_in_pts * (float)self->ydpi) / (72.f * (float)self->units_per_EM))));
|
||||
}
|
||||
|
||||
|
||||
static inline bool
|
||||
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int num_cells, int bold, int italic, bool rescale) {
|
||||
if (!load_glyph(self, glyph_id)) return false;
|
||||
unsigned int max_width = cell_width * num_cells;
|
||||
FT_Bitmap *bitmap = &self->face->glyph->bitmap;
|
||||
ans->buf = bitmap->buffer;
|
||||
ans->start_x = 0; ans->width = bitmap->width;
|
||||
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;
|
||||
void
|
||||
cell_metrics(PyObject *s, unsigned int* cell_width, unsigned int* cell_height, unsigned int* baseline, unsigned int* underline_position, unsigned int* underline_thickness) {
|
||||
Face *self = (Face*)s;
|
||||
*cell_width = calc_cell_width(self);
|
||||
*cell_height = font_units_to_pixels(self, self->height);
|
||||
*baseline = font_units_to_pixels(self, self->ascender);
|
||||
*underline_position = MIN(*cell_height - 1, *baseline - font_units_to_pixels(self, self->underline_position));
|
||||
*underline_thickness = font_units_to_pixels(self, self->underline_thickness);
|
||||
}
|
||||
|
||||
static inline void
|
||||
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) {
|
||||
// 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.
|
||||
|
||||
// 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;
|
||||
}
|
||||
}
|
||||
bool
|
||||
face_has_codepoint(PyObject *s, char_type cp) {
|
||||
return FT_Get_Char_Index(((Face*)s)->face, cp) > 0;
|
||||
}
|
||||
|
||||
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 {{{
|
||||
|
||||
static PyMemberDef members[] = {
|
||||
@@ -437,13 +210,6 @@ static PyMemberDef members[] = {
|
||||
};
|
||||
|
||||
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 */
|
||||
};
|
||||
|
||||
@@ -482,13 +248,6 @@ init_freetype_library(PyObject *m) {
|
||||
PyErr_SetString(FreeType_Exception, "Failed to register the freetype library at exit handler");
|
||||
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;
|
||||
}
|
||||
|
||||
|
||||
27
kitty/line.c
27
kitty/line.c
@@ -165,6 +165,22 @@ text_at(Line* self, Py_ssize_t xval) {
|
||||
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*
|
||||
unicode_in_range(Line *self, index_type start, index_type limit, bool include_cc, char leading_char) {
|
||||
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; };
|
||||
ch = ' ';
|
||||
}
|
||||
buf[n++] = ch;
|
||||
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;
|
||||
}
|
||||
}
|
||||
n += cell_as_unicode(self->cells + i, include_cc, buf + n);
|
||||
previous_width = self->cells[i].attrs & WIDTH_MASK;
|
||||
}
|
||||
return PyUnicode_FromKindAndData(PyUnicode_4BYTE_KIND, buf, n);
|
||||
|
||||
@@ -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_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);
|
||||
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);
|
||||
|
||||
@@ -180,6 +180,16 @@ PYWRAP1(set_options) {
|
||||
Py_DECREF(ret); if (PyErr_Occurred()) return NULL;
|
||||
|
||||
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
|
||||
Py_RETURN_NONE;
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ typedef struct {
|
||||
double repaint_delay, input_delay;
|
||||
bool focus_follows_mouse;
|
||||
bool macos_option_as_alt;
|
||||
int adjust_line_height_px;
|
||||
float adjust_line_height_frac;
|
||||
} Options;
|
||||
|
||||
typedef struct {
|
||||
|
||||
Reference in New Issue
Block a user