Implement rendering of glyphs from font files in Freetype

This commit is contained in:
Kovid Goyal 2017-11-03 13:11:44 +05:30
parent 891942494f
commit 4726bcd210
No known key found for this signature in database
GPG Key ID: 06BC317B515ACE7C
4 changed files with 211 additions and 17 deletions

View File

@ -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};

View File

@ -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;

View File

@ -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);

View File

@ -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[] = {