Implement rendering of glyphs from font files in Freetype
This commit is contained in:
parent
891942494f
commit
4726bcd210
@ -290,6 +290,12 @@ set_size_for_face(PyObject *self, float pt_sz, float xdpi, float ydpi) {
|
||||
}
|
||||
|
||||
|
||||
bool
|
||||
render_glyphs_in_cell(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) {
|
||||
// TODO: Implement this
|
||||
return true;
|
||||
}
|
||||
|
||||
static PyObject *
|
||||
repr(Face *self) {
|
||||
char buf[400] = {0};
|
||||
|
||||
@ -191,7 +191,7 @@ 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 uint8_t *canvas = NULL;
|
||||
static inline void
|
||||
clear_canvas(void) { memset(canvas, 0, cell_width * cell_height); }
|
||||
clear_canvas(void) { memset(canvas, 0, 4 * cell_width * cell_height); }
|
||||
|
||||
static void
|
||||
python_send_to_gpu(unsigned int x, unsigned int y, unsigned int z, uint8_t* buf) {
|
||||
@ -222,7 +222,7 @@ update_cell_metrics(float pt_sz, float xdpi, float ydpi) {
|
||||
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);
|
||||
free(canvas); canvas = malloc(cell_width * cell_height);
|
||||
free(canvas); canvas = malloc(4 * cell_width * cell_height);
|
||||
if (canvas == NULL) return PyErr_NoMemory();
|
||||
return Py_BuildValue("IIIII", cell_width, cell_height, baseline, underline_position, underline_thickness);
|
||||
}
|
||||
@ -357,7 +357,7 @@ load_hb_buffer(Cell *first_cell, index_type num_cells) {
|
||||
index_type num;
|
||||
hb_buffer_clear_contents(harfbuzz_buffer);
|
||||
while (num_cells) {
|
||||
for (num = 0; num_cells-- && num < sizeof(shape_buffer)/sizeof(shape_buffer[0]) - 20; first_cell++) {
|
||||
for (num = 0; num_cells && num < sizeof(shape_buffer)/sizeof(shape_buffer[0]) - 20; first_cell++, num_cells--) {
|
||||
shape_buffer[num++] = first_cell->ch;
|
||||
if (first_cell->cc) {
|
||||
shape_buffer[num++] = first_cell->cc & CC_MASK;
|
||||
@ -370,10 +370,87 @@ load_hb_buffer(Cell *first_cell, index_type num_cells) {
|
||||
hb_buffer_guess_segment_properties(harfbuzz_buffer);
|
||||
}
|
||||
|
||||
static inline void
|
||||
split_cell(uint8_t *src, uint8_t *d1, uint8_t *d2) {
|
||||
for (size_t r = 0; r < cell_height; r++, d1 += cell_width, d2 += cell_width, src += 2 * cell_width) {
|
||||
memcpy(d1, src, cell_width); memcpy(d2, src + cell_width, cell_width);
|
||||
}
|
||||
}
|
||||
|
||||
static inline unsigned int
|
||||
shape_cell(Cell *cell, Cell *second_cell, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int length, Font *font) {
|
||||
uint32_t cluster = info[0].cluster;
|
||||
unsigned int num_glyphs = 1;
|
||||
while (num_glyphs < length && info[num_glyphs].cluster == cluster) num_glyphs++;
|
||||
uint64_t extra_glyphs;
|
||||
#define G(n) ((uint64_t)(info[n].codepoint & 0xffff))
|
||||
glyph_index glyph = G(0);
|
||||
switch(num_glyphs) {
|
||||
case 1:
|
||||
extra_glyphs = 0;
|
||||
break;
|
||||
case 2:
|
||||
extra_glyphs = G(1);
|
||||
break;
|
||||
case 3:
|
||||
extra_glyphs = G(1) | (G(2) << 16);
|
||||
break;
|
||||
case 4:
|
||||
extra_glyphs = G(1) | (G(2) << 16) | (G(3) << 32);
|
||||
break;
|
||||
default: // we only support a maximum of four extra glyphs per cell
|
||||
extra_glyphs = G(1) | (G(2) << 16) | (G(3) << 32) | (G(4) << 48);
|
||||
break;
|
||||
}
|
||||
#undef G
|
||||
int error = 0;
|
||||
SpritePosition *sp = sprite_position_for(font, glyph, extra_glyphs, false, &error), *sp2 = NULL;
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return num_glyphs; }
|
||||
if (second_cell) {
|
||||
sp2 = sprite_position_for(font, glyph, extra_glyphs, true, &error);
|
||||
if (error != 0) { sprite_map_set_error(error); PyErr_Print(); return num_glyphs; }
|
||||
}
|
||||
if (sp->rendered && (!sp2 || sp2->rendered)) goto end;
|
||||
clear_canvas();
|
||||
if (!render_glyphs_in_cell(font->face, font->bold, font->italic, info, positions, num_glyphs, canvas, cell_width, cell_height, second_cell == NULL ? 1 : 2, baseline)) {
|
||||
PyErr_Print(); return num_glyphs;
|
||||
}
|
||||
if (second_cell) {
|
||||
uint8_t *d1 = canvas + (2 * cell_width * cell_height);
|
||||
uint8_t *d2 = canvas + (3 * cell_width * cell_height);
|
||||
split_cell(canvas, d1, d2);
|
||||
current_send_sprite_to_gpu(sp->x, sp->y, sp->z, d1);
|
||||
current_send_sprite_to_gpu(sp2->x, sp2->y, sp2->z, d2);
|
||||
sp2->rendered = true;
|
||||
} else {
|
||||
current_send_sprite_to_gpu(sp->x, sp->y, sp->z, canvas);
|
||||
}
|
||||
sp->rendered = true;
|
||||
end:
|
||||
cell->sprite_x = sp->x; cell->sprite_y = sp->y; cell->sprite_z = sp->z;
|
||||
if (second_cell) { second_cell->sprite_x = sp2->x; second_cell->sprite_y = sp2->y; second_cell->sprite_z = sp2->z; }
|
||||
return num_glyphs;
|
||||
}
|
||||
|
||||
|
||||
static inline void
|
||||
shape_run(Cell *first_cell, index_type num_cells, Font *font) {
|
||||
// See https://www.mail-archive.com/harfbuzz@lists.freedesktop.org/msg04698.html
|
||||
// for a discussion of glyph clustering in harfbuzz
|
||||
load_hb_buffer(first_cell, num_cells);
|
||||
hb_shape(font->hb_font, harfbuzz_buffer, NULL, 0);
|
||||
unsigned int info_length, positions_length, length;
|
||||
hb_glyph_info_t *info = hb_buffer_get_glyph_infos(harfbuzz_buffer, &info_length);
|
||||
hb_glyph_position_t *positions = hb_buffer_get_glyph_positions(harfbuzz_buffer, &positions_length);
|
||||
length = MIN(info_length, positions_length);
|
||||
unsigned int run_pos = 0;
|
||||
Cell *cell, *second_cell;
|
||||
while(length > run_pos && num_cells) {
|
||||
cell = first_cell++; num_cells--;
|
||||
if ((cell->attrs & WIDTH_MASK) == 2 && num_cells) { second_cell = first_cell++; num_cells--; }
|
||||
else second_cell = NULL;
|
||||
run_pos += shape_cell(cell, second_cell, info + run_pos, positions + run_pos, length - run_pos, font);
|
||||
}
|
||||
}
|
||||
|
||||
static inline void
|
||||
@ -425,9 +502,9 @@ set_font(PyObject UNUSED *m, PyObject *args) {
|
||||
Py_INCREF(get_fallback_font); Py_INCREF(box_drawing_function);
|
||||
clear_font(&medium_font); clear_font(&bold_font); clear_font(&italic_font); clear_font(&bi_font); clear_sprite_map(&box_font);
|
||||
if (!alloc_font(&medium_font, medium, false, false)) return PyErr_NoMemory();
|
||||
if (bold && !alloc_font(&bold_font, bold, 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();
|
||||
if (bold && !alloc_font(&bold_font, bold, true, false)) return PyErr_NoMemory();
|
||||
if (italic && !alloc_font(&italic_font, italic, false, true)) return PyErr_NoMemory();
|
||||
if (bi && !alloc_font(&bi_font, bi, true, true)) return PyErr_NoMemory();
|
||||
for (size_t i = 0; fallback_fonts[i].face != NULL; i++) clear_font(fallback_fonts + i);
|
||||
for (size_t i = 0; symbol_map_fonts_count; i++) free_font(symbol_map_fonts + i);
|
||||
free(symbol_maps); free(symbol_map_fonts); symbol_maps = NULL; symbol_map_fonts = NULL;
|
||||
|
||||
@ -19,3 +19,4 @@ 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*);
|
||||
void sprite_tracker_current_layout(unsigned int *x, unsigned int *y, unsigned int *z);
|
||||
bool render_glyphs_in_cell(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline);
|
||||
|
||||
132
kitty/freetype.c
132
kitty/freetype.c
@ -97,6 +97,17 @@ set_size_for_face(PyObject *self, float pt_sz, float xdpi, float ydpi) {
|
||||
return set_font_size((Face*)self, w, w, (FT_UInt)xdpi, (FT_UInt) ydpi);
|
||||
}
|
||||
|
||||
static inline int
|
||||
get_load_flags(int hinting, int hintstyle, int base) {
|
||||
int flags = base;
|
||||
if (hinting) {
|
||||
if (hintstyle >= 3) flags |= FT_LOAD_TARGET_NORMAL;
|
||||
else if (0 < hintstyle && hintstyle < 3) flags |= FT_LOAD_TARGET_LIGHT;
|
||||
} else flags |= FT_LOAD_NO_HINTING;
|
||||
return flags;
|
||||
}
|
||||
|
||||
|
||||
static PyObject*
|
||||
new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
Face *self;
|
||||
@ -120,6 +131,9 @@ new(PyTypeObject *type, PyObject *args, PyObject UNUSED *kwds) {
|
||||
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(); }
|
||||
#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
|
||||
}
|
||||
return (PyObject*)self;
|
||||
}
|
||||
@ -142,21 +156,11 @@ repr(Face *self) {
|
||||
}
|
||||
|
||||
|
||||
static inline int
|
||||
get_load_flags(int hinting, int hintstyle, int base) {
|
||||
int flags = base;
|
||||
if (hinting) {
|
||||
if (hintstyle >= 3) flags |= FT_LOAD_TARGET_NORMAL;
|
||||
else if (0 < hintstyle && hintstyle < 3) flags |= FT_LOAD_TARGET_LIGHT;
|
||||
} else flags |= FT_LOAD_NO_HINTING;
|
||||
return flags;
|
||||
}
|
||||
|
||||
static inline bool
|
||||
load_glyph(Face *self, int glyph_index, int load_type) {
|
||||
int flags = get_load_flags(self->hinting, self->hintstyle, load_type);
|
||||
int error = FT_Load_Glyph(self->face, glyph_index, flags);
|
||||
if (error) { set_freetype_error("Failed to load glyph, with error:", error); Py_CLEAR(self); return false; }
|
||||
if (error) { set_freetype_error("Failed to load glyph, with error:", error); return false; }
|
||||
return true;
|
||||
}
|
||||
|
||||
@ -195,6 +199,112 @@ face_has_codepoint(PyObject *s, char_type cp) {
|
||||
hb_font_t*
|
||||
harfbuzz_font_for_face(PyObject *self) { return ((Face*)self)->harfbuzz_font; }
|
||||
|
||||
|
||||
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 bool
|
||||
render_bitmap(Face *self, int glyph_id, ProcessedBitmap *ans, unsigned int cell_width, unsigned int num_cells, bool bold, bool italic, bool rescale) {
|
||||
if (!load_glyph(self, glyph_id, FT_LOAD_RENDER)) 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;
|
||||
} else return false;
|
||||
}
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
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
|
||||
render_glyphs_in_cell(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, uint8_t *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline) {
|
||||
Face *self = (Face*)f;
|
||||
float x = 0.f, y = 0.f;
|
||||
ProcessedBitmap bm;
|
||||
for (unsigned int i = 0; i < num_glyphs; i++) {
|
||||
if (info[i].codepoint == 0) continue;
|
||||
if (!render_bitmap(self, info[i].codepoint, &bm, cell_width, num_cells, bold, italic, true)) return false;
|
||||
x += (float)positions[i].x_offset / 64.0f;
|
||||
y = (float)positions[i].y_offset / 64.0f;
|
||||
place_bitmap_in_cell(canvas, &bm, cell_width * num_cells, cell_height, x, y, &self->face->glyph->metrics, baseline);
|
||||
x += (float)positions[i].x_advance / 64.0f;
|
||||
}
|
||||
return true;
|
||||
}
|
||||
|
||||
// Boilerplate {{{
|
||||
|
||||
static PyMemberDef members[] = {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user