385 lines
13 KiB
C
385 lines
13 KiB
C
/*
|
|
* 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 SpritePosition SpritePosition;
|
|
|
|
struct SpritePosition {
|
|
SpritePosition *next;
|
|
bool filled, rendered, is_second;
|
|
sprite_index x, y, z;
|
|
glyph_index glyph;
|
|
uint64_t extra_glyphs;
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
size_t max_array_len, max_texture_size, max_y;
|
|
unsigned int x, y, z, xnum, ynum;
|
|
} GPUSpriteTracker;
|
|
|
|
|
|
static GPUSpriteTracker sprite_tracker = {
|
|
.max_array_len = 1000,
|
|
.max_texture_size = 1000,
|
|
.max_y = 100,
|
|
};
|
|
|
|
|
|
typedef struct {
|
|
PyObject *face;
|
|
hb_font_t *hb_font;
|
|
// Map glyphs to sprite map co-ords
|
|
SpritePosition sprite_map[1024];
|
|
bool bold, italic;
|
|
} Font;
|
|
|
|
|
|
static inline void
|
|
sprite_map_set_error(int error) {
|
|
switch(error) {
|
|
case 1:
|
|
PyErr_NoMemory(); break;
|
|
case 2:
|
|
PyErr_SetString(PyExc_RuntimeError, "Out of texture space for sprites"); break;
|
|
default:
|
|
PyErr_SetString(PyExc_RuntimeError, "Unknown error occurred while allocating sprites"); break;
|
|
}
|
|
}
|
|
|
|
void
|
|
sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len) {
|
|
sprite_tracker.max_texture_size = max_texture_size;
|
|
sprite_tracker.max_array_len = max_array_len;
|
|
}
|
|
|
|
static inline void
|
|
do_increment(int *error) {
|
|
sprite_tracker.x++;
|
|
if (sprite_tracker.x >= sprite_tracker.xnum) {
|
|
sprite_tracker.x = 0; sprite_tracker.y++;
|
|
sprite_tracker.ynum = MIN(MAX(sprite_tracker.ynum, sprite_tracker.y + 1), sprite_tracker.max_y);
|
|
if (sprite_tracker.y >= sprite_tracker.max_y) {
|
|
sprite_tracker.y = 0; sprite_tracker.z++;
|
|
if (sprite_tracker.z >= MIN(UINT16_MAX, sprite_tracker.max_array_len)) *error = 2;
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
SpritePosition*
|
|
sprite_position_for(Font *font, glyph_index glyph, uint64_t extra_glyphs, bool is_second, int *error) {
|
|
glyph_index idx = glyph & 0x3ff;
|
|
SpritePosition *s = font->sprite_map + idx;
|
|
// Optimize for the common case of glyph under 1024 already in the cache
|
|
if (LIKELY(s->glyph == glyph && s->filled && s->extra_glyphs == extra_glyphs && s->is_second == is_second)) return s; // Cache hit
|
|
while(true) {
|
|
if (s->filled) {
|
|
if (s->glyph == glyph && s->extra_glyphs == extra_glyphs && s->is_second == is_second) return s; // Cache hit
|
|
} else {
|
|
break;
|
|
}
|
|
if (!s->next) {
|
|
s->next = calloc(1, sizeof(SpritePosition));
|
|
if (s->next == NULL) { *error = 1; return NULL; }
|
|
}
|
|
s = s->next;
|
|
}
|
|
s->glyph = glyph;
|
|
s->extra_glyphs = extra_glyphs;
|
|
s->is_second = is_second;
|
|
s->filled = true;
|
|
s->rendered = false;
|
|
s->x = sprite_tracker.x; s->y = sprite_tracker.y; s->z = sprite_tracker.z;
|
|
do_increment(error);
|
|
return s;
|
|
}
|
|
|
|
void
|
|
sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z) {
|
|
*x = sprite_tracker.xnum; *y = sprite_tracker.ynum; *z = sprite_tracker.z;
|
|
}
|
|
|
|
int
|
|
sprite_tracker_increment(sprite_index *x, sprite_index *y, sprite_index *z) {
|
|
int error = 0;
|
|
*x = sprite_tracker.x; *y = sprite_tracker.y; *z = sprite_tracker.z;
|
|
do_increment(&error);
|
|
return error;
|
|
}
|
|
|
|
void
|
|
sprite_map_free(Font *font) {
|
|
SpritePosition *s, *t;
|
|
for (size_t i = 0; i < sizeof(font->sprite_map)/sizeof(font->sprite_map[0]); i++) {
|
|
s = font->sprite_map + i;
|
|
s = s->next;
|
|
while (s) {
|
|
t = s;
|
|
s = s->next;
|
|
free(t);
|
|
}
|
|
}
|
|
}
|
|
|
|
void
|
|
clear_sprite_map(Font *font) {
|
|
#define CLEAR(s) s->filled = false; s->rendered = false; s->glyph = 0; s->extra_glyphs = 0; s->x = 0; s->y = 0; s->z = 0; s->is_second = false;
|
|
SpritePosition *s;
|
|
for (size_t i = 0; i < sizeof(font->sprite_map)/sizeof(font->sprite_map[0]); i++) {
|
|
s = font->sprite_map + i;
|
|
CLEAR(s);
|
|
while ((s = s->next)) {
|
|
CLEAR(s);
|
|
}
|
|
}
|
|
#undef CLEAR
|
|
}
|
|
|
|
void
|
|
sprite_tracker_set_layout(unsigned int cell_width, unsigned int cell_height) {
|
|
sprite_tracker.xnum = MIN(MAX(1, sprite_tracker.max_texture_size / cell_width), UINT16_MAX);
|
|
sprite_tracker.max_y = MIN(MAX(1, sprite_tracker.max_texture_size / cell_height), UINT16_MAX);
|
|
sprite_tracker.ynum = 1;
|
|
sprite_tracker.x = 0; sprite_tracker.y = 0; sprite_tracker.z = 0;
|
|
}
|
|
|
|
|
|
static inline bool
|
|
alloc_font(Font *f, PyObject *face, bool bold, bool italic) {
|
|
f->face = face; Py_INCREF(face);
|
|
f->hb_font = harfbuzz_font_for_face(face);
|
|
if (f->hb_font == NULL) return false;
|
|
f->bold = bold; f->italic = italic;
|
|
return true;
|
|
}
|
|
|
|
static inline void
|
|
free_font(Font *f) {
|
|
f->hb_font = NULL;
|
|
Py_CLEAR(f->face);
|
|
sprite_map_free(f);
|
|
f->bold = false; f->italic = false;
|
|
}
|
|
|
|
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;
|
|
|
|
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 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) { if(!set_size_for_face((f)->face, pt_sz, xdpi, ydpi)) return NULL; clear_sprite_map(f); } }
|
|
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);
|
|
}
|
|
for (size_t i = 0; i < symbol_map_fonts_count; i++) {
|
|
CALL(symbol_map_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);
|
|
sprite_tracker_set_layout(cell_width, cell_height);
|
|
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;
|
|
}
|
|
|
|
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 hb_buffer_t *harfbuzz_buffer = NULL;
|
|
|
|
static void
|
|
finalize(void) {
|
|
Py_CLEAR(get_fallback_font);
|
|
free_font(&medium_font); free_font(&bold_font); free_font(&italic_font); free_font(&bi_font);
|
|
for (size_t i = 0; fallback_fonts[i].face != NULL; i++) free_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);
|
|
if (harfbuzz_buffer) hb_buffer_destroy(harfbuzz_buffer);
|
|
}
|
|
|
|
static PyObject*
|
|
sprite_map_set_limits(PyObject UNUSED *self, PyObject *args) {
|
|
unsigned int w, h;
|
|
if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL;
|
|
sprite_tracker_set_limits(w, h);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject*
|
|
sprite_map_set_layout(PyObject UNUSED *self, PyObject *args) {
|
|
unsigned int w, h;
|
|
if(!PyArg_ParseTuple(args, "II", &w, &h)) return NULL;
|
|
sprite_tracker_set_layout(w, h);
|
|
Py_RETURN_NONE;
|
|
}
|
|
|
|
static PyObject*
|
|
test_sprite_position_for(PyObject UNUSED *self, PyObject *args) {
|
|
glyph_index glyph;
|
|
uint64_t extra_glyphs = 0;
|
|
if (!PyArg_ParseTuple(args, "H|I", &glyph, &extra_glyphs)) return NULL;
|
|
int error;
|
|
SpritePosition *pos = sprite_position_for(&box_font, glyph, extra_glyphs, false, &error);
|
|
if (pos == NULL) { sprite_map_set_error(error); return NULL; }
|
|
return Py_BuildValue("III", pos->x, pos->y, pos->z);
|
|
}
|
|
|
|
static PyMethodDef module_methods[] = {
|
|
METHODB(set_font_size, METH_VARARGS),
|
|
METHODB(set_font, METH_VARARGS),
|
|
METHODB(sprite_map_set_limits, METH_VARARGS),
|
|
METHODB(sprite_map_set_layout, METH_VARARGS),
|
|
METHODB(test_sprite_position_for, 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;
|
|
}
|
|
harfbuzz_buffer = hb_buffer_create();
|
|
if (harfbuzz_buffer == NULL || !hb_buffer_allocation_successful(harfbuzz_buffer) || !hb_buffer_pre_allocate(harfbuzz_buffer, 2000)) { PyErr_NoMemory(); return false; }
|
|
if (PyModule_AddFunctions(module, module_methods) != 0) return false;
|
|
return true;
|
|
}
|