More work on multicell font rendering

This commit is contained in:
Kovid Goyal
2024-12-06 08:42:50 +05:30
parent 8993386399
commit b96da25380
4 changed files with 206 additions and 70 deletions

View File

@@ -34,6 +34,10 @@ typedef struct {
unsigned int x, y, z, xnum, ynum; unsigned int x, y, z, xnum, ynum;
} GPUSpriteTracker; } GPUSpriteTracker;
typedef struct RunFont {
unsigned scale, subscale_n, subscale_d, vertical_align, multicell_y;
ssize_t font_idx;
} RunFont;
static hb_buffer_t *harfbuzz_buffer = NULL; static hb_buffer_t *harfbuzz_buffer = NULL;
static hb_feature_t hb_features[3] = {{0}}; static hb_feature_t hb_features[3] = {{0}};
@@ -75,6 +79,18 @@ static void free_const(const void* x) { free((void*)x); }
#define KEY_DTOR_FN free_const #define KEY_DTOR_FN free_const
#include "kitty-verstable.h" #include "kitty-verstable.h"
typedef struct ScaledFontData {
FontCellMetrics fcm;
double font_sz_in_pts;
} ScaledFontData;
#define NAME scaled_font_map_t
#define KEY_TY float
#define VAL_TY ScaledFontData
#define HASH_FN vt_hash_float
#define CMPR_FN vt_cmpr_float
#include "kitty-verstable.h"
typedef struct { typedef struct {
FONTS_DATA_HEAD FONTS_DATA_HEAD
id_type id; id_type id;
@@ -84,6 +100,7 @@ typedef struct {
Canvas canvas; Canvas canvas;
GPUSpriteTracker sprite_tracker; GPUSpriteTracker sprite_tracker;
fallback_font_map_t fallback_font_map; fallback_font_map_t fallback_font_map;
scaled_font_map_t scaled_font_map;
} FontGroup; } FontGroup;
static FontGroup* font_groups = NULL; static FontGroup* font_groups = NULL;
@@ -161,6 +178,7 @@ del_font_group(FontGroup *fg) {
free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0}; free(fg->canvas.buf); fg->canvas.buf = NULL; fg->canvas = (Canvas){0};
fg->sprite_map = free_sprite_map(fg->sprite_map); fg->sprite_map = free_sprite_map(fg->sprite_map);
vt_cleanup(&fg->fallback_font_map); vt_cleanup(&fg->fallback_font_map);
vt_cleanup(&fg->scaled_font_map);
for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i); for (size_t i = 0; i < fg->fonts_count; i++) del_font(fg->fonts + i);
free(fg->fonts); fg->fonts = NULL; fg->fonts_count = 0; free(fg->fonts); fg->fonts = NULL; fg->fonts_count = 0;
} }
@@ -419,8 +437,8 @@ adjust_ypos(unsigned int pos, unsigned int cell_height, int adjustment) {
} }
static void static void
calc_cell_metrics(FontGroup *fg) { calc_cell_metrics(FontGroup *fg, PyObject *face) {
fg->fcm = cell_metrics(fg->fonts[fg->medium_font_idx].face); fg->fcm = cell_metrics(face);
if (!fg->fcm.cell_width) fatal("Failed to calculate cell width for the specified font"); if (!fg->fcm.cell_width) fatal("Failed to calculate cell width for the specified font");
unsigned int before_cell_height = fg->fcm.cell_height; unsigned int before_cell_height = fg->fcm.cell_height;
unsigned int cw = fg->fcm.cell_width, ch = fg->fcm.cell_height; unsigned int cw = fg->fcm.cell_width, ch = fg->fcm.cell_height;
@@ -463,8 +481,6 @@ calc_cell_metrics(FontGroup *fg) {
fg->fcm.baseline += MIN(fg->fcm.cell_height - 1, (unsigned)line_height_adjustment / 2); fg->fcm.baseline += MIN(fg->fcm.cell_height - 1, (unsigned)line_height_adjustment / 2);
fg->fcm.underline_position += MIN(fg->fcm.cell_height - 1, (unsigned)line_height_adjustment / 2); fg->fcm.underline_position += MIN(fg->fcm.cell_height - 1, (unsigned)line_height_adjustment / 2);
} }
sprite_tracker_set_layout(&fg->sprite_tracker, fg->fcm.cell_width, fg->fcm.cell_height);
ensure_canvas_can_fit(fg, 8, 1);
} }
static bool static bool
@@ -718,15 +734,58 @@ ensure_glyph_render_scratch_space(size_t sz) {
#undef a #undef a
} }
static void static float
scaled_cell_dimensions(RunFont rf, unsigned *width, unsigned *height) { effective_scale(RunFont rf) {
*width *= rf.scale; float ans = MAX(1u, rf.scale);
*height *= rf.scale;
if (rf.subscale_n && rf.subscale_d && rf.subscale_n < rf.subscale_d) { if (rf.subscale_n && rf.subscale_d && rf.subscale_n < rf.subscale_d) {
double frac = ((double)rf.subscale_n) / rf.subscale_d; ans *= ((float)rf.subscale_n) / rf.subscale_d;
*width = (unsigned)ceil(frac * *width);
*height = (unsigned)ceil(frac * *height);
} }
return ans;
}
static float
scaled_cell_dimensions(RunFont rf, unsigned *width, unsigned *height) {
float frac = effective_scale(rf);
*width = (unsigned)ceil(frac * *width);
*height = (unsigned)ceil(frac * *height);
return frac;
}
static float
apply_scale_to_font_group(FontGroup *fg, RunFont *rf) {
unsigned int scaled_cell_width = fg->fcm.cell_width, scaled_cell_height = fg->fcm.cell_height;
float scale = rf ? scaled_cell_dimensions(*rf, &scaled_cell_width, &scaled_cell_height) : 1.f;
scaled_font_map_t_itr i = vt_get(&fg->scaled_font_map, scale);
ScaledFontData sfd;
if (vt_is_end(i)) {
Font *medium_font = &fg->fonts[fg->medium_font_idx];
FontGroup copy = {.fcm=fg->fcm, .logical_dpi_x=fg->logical_dpi_x, .logical_dpi_y=fg->logical_dpi_y};
copy.fcm.cell_width = scaled_cell_width; copy.fcm.cell_height = scaled_cell_height;
copy.font_sz_in_pts = scale * fg->font_sz_in_pts;
if (!face_apply_scaling(medium_font->face, (FONTS_DATA_HANDLE)&copy)) {
if (PyErr_Occurred()) PyErr_Print();
fatal("Could not apply scale of %f to font group at size: %f", scale, copy.font_sz_in_pts);
}
calc_cell_metrics(&copy, medium_font->face);
if (copy.fcm.cell_width > scaled_cell_width || copy.fcm.cell_height > scaled_cell_height) {
float wfrac = (float)copy.fcm.cell_width / scaled_cell_width, hfrac = (float)copy.fcm.cell_height / scaled_cell_height;
float frac = MIN(wfrac, hfrac);
copy.font_sz_in_pts *= frac;
while(true) {
if (!face_apply_scaling(medium_font->face, (FONTS_DATA_HANDLE)&copy)) {
if (PyErr_Occurred()) PyErr_Print();
fatal("Could not apply scale of %f to font group at size: %f", scale, copy.font_sz_in_pts);
}
calc_cell_metrics(&copy, medium_font->face);
if (copy.fcm.cell_width <= scaled_cell_width && copy.fcm.cell_height <= scaled_cell_height) break;
if (copy.font_sz_in_pts < 1) fatal("Could not apply scale of %f to font group as font size (%f) is less than minimum threshold", scale, copy.font_sz_in_pts);
copy.font_sz_in_pts -= 0.1;
}
}
} else sfd = i.data->val;
fg->font_sz_in_pts = sfd.font_sz_in_pts;
fg->fcm = sfd.fcm;
return scale;
} }
static pixel* static pixel*
@@ -738,6 +797,28 @@ extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells)
return ans; return ans;
} }
static pixel*
extract_scaled_cell_from_canvas(
Canvas *canvas, unsigned i, unsigned num_cells, Region src, Region dest,
unsigned unscaled_cell_width, unsigned unscaled_cell_height, unsigned scaled_cell_width, unsigned scaled_cell_height
) {
unsigned unscaled_cell_area = unscaled_cell_width * unscaled_cell_height;
pixel *ans = canvas->buf + (canvas->size_in_bytes / sizeof(canvas->buf[0]) - unscaled_cell_area);
memset(ans, 0, sizeof(ans[0]) * unscaled_cell_area);
unsigned xoff = i * unscaled_cell_width;
unsigned src_row_size = scaled_cell_width * num_cells;
if (xoff >= src_row_size) return ans; // nothing to copy
unsigned width = MIN(src_row_size - xoff, unscaled_cell_width);
/*printf("\n");*/
for (unsigned src_y=src.top, dest_y=dest.top; src_y < src.bottom && src_y < scaled_cell_height && dest_y < dest.bottom && dest_y < unscaled_cell_height; src_y++, dest_y++) {
pixel *srcp = canvas->buf + src_row_size * src_y;
pixel *destp = ans + unscaled_cell_width * dest_y;
/*for (unsigned x = xoff; x < xoff + width; x++) { printf("%d ", srcp[x] != 0); } printf("\n");*/
memcpy(destp, srcp + xoff, width * sizeof(destp[0]));
}
return ans;
}
static void static void
calculate_regions_for_line(RunFont rf, unsigned cell_height, Region *src, Region *dest) { calculate_regions_for_line(RunFont rf, unsigned cell_height, Region *src, Region *dest) {
unsigned src_height = src->bottom; unsigned src_height = src->bottom;
@@ -794,14 +875,15 @@ render_box_cell(FontGroup *fg, RunFont rf, CPUCell *cpu_cell, GPUCell *gpu_cell,
uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0)); uint8_t *alpha_mask = PyLong_AsVoidPtr(PyTuple_GET_ITEM(ret, 0));
ensure_canvas_can_fit(fg, 2, rf.scale); ensure_canvas_can_fit(fg, 2, rf.scale);
Region src = { .right = width, .bottom = height }, dest = src; Region src = { .right = width, .bottom = height }, dest = src;
unsigned dest_stride = rf.scale * fg->fcm.cell_width, src_stride = width; render_alpha_mask(alpha_mask, fg->canvas.buf, &src, &dest, width, width, 0xffffff);
calculate_regions_for_line(rf, fg->fcm.cell_height, &src, &dest); /*for (unsigned y = 0; y < height; y++) { for (unsigned x = 0; x < width; x++) { printf("%d ", (fg->canvas.buf + width * y)[0] != 0); } printf("\n"); }*/
render_alpha_mask(alpha_mask, fg->canvas.buf, &src, &dest, src_stride, dest_stride, 0xffffff);
if (rf.scale == 1) { if (rf.scale == 1) {
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[0]->x, sp[0]->y, sp[0]->z, fg->canvas.buf); current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[0]->x, sp[0]->y, sp[0]->z, fg->canvas.buf);
} else { } else {
calculate_regions_for_line(rf, fg->fcm.cell_height, &src, &dest);
/*printf("width: %u height: %u src.top: %u src.bottom: %u\n", width, height, src.top, src.bottom);*/
for (unsigned i = 0; i < rf.scale; i++) { for (unsigned i = 0; i < rf.scale; i++) {
pixel *b = extract_cell_from_canvas(fg, i, rf.scale); pixel *b = extract_scaled_cell_from_canvas(&fg->canvas, i, 1, src, dest, fg->fcm.cell_width, fg->fcm.cell_height, width, height);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, b); current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, b);
} }
} }
@@ -835,7 +917,11 @@ set_cell_sprite(GPUCell *cell, const SpritePosition *sp) {
} }
static void static void
render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, hb_glyph_info_t *info, hb_glyph_position_t *positions, RunFont rf, glyph_index *glyphs, unsigned glyph_count, bool center_glyph, const TextCache *tc) { render_group(
FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells,
hb_glyph_info_t *info, hb_glyph_position_t *positions, RunFont rf, glyph_index *glyphs, unsigned glyph_count,
bool center_glyph, const TextCache *tc, float scale, FontCellMetrics unscaled_metrics
) {
#define sp global_glyph_render_scratch.sprite_positions #define sp global_glyph_render_scratch.sprite_positions
int error = 0; int error = 0;
bool all_rendered = true; bool all_rendered = true;
@@ -862,14 +948,26 @@ render_group(FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPU
render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas.buf, fg->fcm.cell_width, fg->fcm.cell_height, num_cells, fg->fcm.baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph); render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas.buf, fg->fcm.cell_width, fg->fcm.cell_height, num_cells, fg->fcm.baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph);
if (PyErr_Occurred()) PyErr_Print(); if (PyErr_Occurred()) PyErr_Print();
for (unsigned i = 0; i < num_cells; i++) { if (scale == 1.f) {
if (!sp[i]->rendered) { for (unsigned i = 0; i < num_cells; i++) {
sp[i]->rendered = true; if (!sp[i]->rendered) {
sp[i]->colored = was_colored; sp[i]->rendered = true;
pixel *buf = num_cells == 1 ? fg->canvas.buf : extract_cell_from_canvas(fg, i, num_cells); sp[i]->colored = was_colored;
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, buf); pixel *buf = num_cells == 1 ? fg->canvas.buf : extract_cell_from_canvas(fg, i, num_cells);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, buf);
}
set_cell_sprite(gpu_cells + i, sp[i]);
}
} else {
Region src={.bottom=fg->fcm.cell_height, .right=fg->fcm.cell_width * num_cells}, dest={0};
calculate_regions_for_line(rf, unscaled_metrics.cell_height, &src, &dest);
for (unsigned i = 0; i < num_cells; i++) {
if (!sp[i]->rendered) {
pixel *b = extract_scaled_cell_from_canvas(&fg->canvas, i, num_cells, src, dest, unscaled_metrics.cell_width, unscaled_metrics.cell_height, fg->fcm.cell_width, fg->fcm.cell_height);
current_send_sprite_to_gpu((FONTS_DATA_HANDLE)fg, sp[i]->x, sp[i]->y, sp[i]->z, b);
}
set_cell_sprite(gpu_cells + i, sp[i]);
} }
set_cell_sprite(gpu_cells + i, sp[i]);
} }
#undef sp #undef sp
} }
@@ -1269,8 +1367,9 @@ group_normal(Font *font, hb_font_t *hbf, const TextCache *tc) {
static void static void
shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, RunFont rf, bool disable_ligature, const TextCache *tc) { shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, Font *font, RunFont rf, FontGroup *fg, bool disable_ligature, const TextCache *tc) {
face_apply_scaling(font->face, rf); float scale = apply_scale_to_font_group(fg, &rf);
if (scale != 1.f) if (!face_apply_scaling(font->face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
hb_font_t *hbf = harfbuzz_font_for_face(font->face); hb_font_t *hbf = harfbuzz_font_for_face(font->face);
if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font, tc); if (font->spacer_strategy == SPACER_STRATEGY_UNKNOWN) detect_spacer_strategy(hbf, font, tc);
shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature, tc); shape(first_cpu_cell, first_gpu_cell, num_cells, hbf, font, disable_ligature, tc);
@@ -1282,6 +1381,10 @@ shape_run(CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells
hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, dbuf, sizeof(dbuf), NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS); hb_buffer_serialize_glyphs(harfbuzz_buffer, 0, group_state.num_glyphs, dbuf, sizeof(dbuf), NULL, harfbuzz_font_for_face(font->face), HB_BUFFER_SERIALIZE_FORMAT_TEXT, HB_BUFFER_SERIALIZE_FLAG_DEFAULT | HB_BUFFER_SERIALIZE_FLAG_GLYPH_EXTENTS);
printf("\n%s\n", dbuf); printf("\n%s\n", dbuf);
#endif #endif
if (scale != 1.f) {
apply_scale_to_font_group(fg, NULL);
if (!face_apply_scaling(font->face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
}
} }
static void static void
@@ -1321,6 +1424,9 @@ split_run_at_offset(index_type cursor_offset, index_type *left, index_type *righ
static void static void
render_groups(FontGroup *fg, RunFont rf, bool center_glyph, const TextCache *tc) { render_groups(FontGroup *fg, RunFont rf, bool center_glyph, const TextCache *tc) {
unsigned idx = 0; unsigned idx = 0;
FontCellMetrics unscaled_metrics = fg->fcm;
float scale = apply_scale_to_font_group(fg, &rf);
if (scale != 1.f) if (!face_apply_scaling(fg->fonts[rf.font_idx].face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
while (idx <= G(group_idx)) { while (idx <= G(group_idx)) {
Group *group = G(groups) + idx; Group *group = G(groups) + idx;
if (!group->num_cells) break; if (!group->num_cells) break;
@@ -1329,10 +1435,15 @@ render_groups(FontGroup *fg, RunFont rf, bool center_glyph, const TextCache *tc)
if (group->num_glyphs) { if (group->num_glyphs) {
ensure_glyph_render_scratch_space(MAX(group->num_glyphs, group->num_cells)); ensure_glyph_render_scratch_space(MAX(group->num_glyphs, group->num_cells));
for (unsigned i = 0; i < group->num_glyphs; i++) global_glyph_render_scratch.glyphs[i] = G(info)[group->first_glyph_idx + i].codepoint; for (unsigned i = 0; i < group->num_glyphs; i++) global_glyph_render_scratch.glyphs[i] = G(info)[group->first_glyph_idx + i].codepoint;
render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, rf, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph, tc); render_group(fg, group->num_cells, group->num_glyphs, G(first_cpu_cell) + group->first_cell_idx, G(first_gpu_cell) + group->first_cell_idx, G(info) + group->first_glyph_idx, G(positions) + group->first_glyph_idx, rf, global_glyph_render_scratch.glyphs, group->num_glyphs, center_glyph, tc, scale, unscaled_metrics);
} }
idx++; idx++;
} }
if (scale != 1.f) {
apply_scale_to_font_group(fg, NULL);
if (!face_apply_scaling(fg->fonts[rf.font_idx].face, (FONTS_DATA_HANDLE)fg) && PyErr_Occurred()) PyErr_Print();
}
} }
static PyObject* static PyObject*
@@ -1353,6 +1464,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
PyObject *face = NULL; PyObject *face = NULL;
Font *font; Font *font;
if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; } if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; }
FontGroup *fg = font_groups;
if (path) { if (path) {
face = face_from_path(path, index, (FONTS_DATA_HANDLE)font_groups); face = face_from_path(path, index, (FONTS_DATA_HANDLE)font_groups);
if (face == NULL) return NULL; if (face == NULL) return NULL;
@@ -1360,11 +1472,10 @@ test_shape(PyObject UNUSED *self, PyObject *args) {
font->face = face; font->face = face;
if (!init_hash_tables(font)) return NULL; if (!init_hash_tables(font)) return NULL;
} else { } else {
FontGroup *fg = font_groups;
font = fg->fonts + fg->medium_font_idx; font = fg->fonts + fg->medium_font_idx;
} }
RunFont rf = {0}; RunFont rf = {0};
shape_run(line->cpu_cells, line->gpu_cells, num, font, rf, false, line->text_cache); shape_run(line->cpu_cells, line->gpu_cells, num, font, rf, fg, false, line->text_cache);
PyObject *ans = PyList_New(0); PyObject *ans = PyList_New(0);
unsigned int idx = 0; unsigned int idx = 0;
@@ -1388,20 +1499,20 @@ static void
render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, RunFont rf, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy, const TextCache *tc) { render_run(FontGroup *fg, CPUCell *first_cpu_cell, GPUCell *first_gpu_cell, index_type num_cells, RunFont rf, bool pua_space_ligature, bool center_glyph, int cursor_offset, DisableLigature disable_ligature_strategy, const TextCache *tc) {
switch(rf.font_idx) { switch(rf.font_idx) {
default: default:
shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[rf.font_idx], rf, disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS, tc); shape_run(first_cpu_cell, first_gpu_cell, num_cells, &fg->fonts[rf.font_idx], rf, fg, disable_ligature_strategy == DISABLE_LIGATURES_ALWAYS, tc);
if (pua_space_ligature) collapse_pua_space_ligature(num_cells); if (pua_space_ligature) collapse_pua_space_ligature(num_cells);
else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER
index_type left, right; index_type left, right;
split_run_at_offset(cursor_offset, &left, &right); split_run_at_offset(cursor_offset, &left, &right);
if (right > left) { if (right > left) {
if (left) { if (left) {
shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[rf.font_idx], rf, false, tc); shape_run(first_cpu_cell, first_gpu_cell, left, &fg->fonts[rf.font_idx], rf, fg, false, tc);
render_groups(fg, rf, center_glyph, tc); render_groups(fg, rf, center_glyph, tc);
} }
shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[rf.font_idx], rf, true, tc); shape_run(first_cpu_cell + left, first_gpu_cell + left, right - left, &fg->fonts[rf.font_idx], rf, fg, true, tc);
render_groups(fg, rf, center_glyph, tc); render_groups(fg, rf, center_glyph, tc);
if (right < num_cells) { if (right < num_cells) {
shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[rf.font_idx], rf, false, tc); shape_run(first_cpu_cell + right, first_gpu_cell + right, num_cells - right, &fg->fonts[rf.font_idx], rf, fg, false, tc);
render_groups(fg, rf, center_glyph, tc); render_groups(fg, rf, center_glyph, tc);
} }
break; break;
@@ -1636,6 +1747,7 @@ initialize_font_group(FontGroup *fg) {
fg->fonts_count = 1; // the 0 index font is the box font fg->fonts_count = 1; // the 0 index font is the box font
if (!init_hash_tables(fg->fonts)) fatal("Out of memory"); if (!init_hash_tables(fg->fonts)) fatal("Out of memory");
vt_init(&fg->fallback_font_map); vt_init(&fg->fallback_font_map);
vt_init(&fg->scaled_font_map);
#define I(attr) if (descriptor_indices.attr) fg->attr##_font_idx = initialize_font(fg, descriptor_indices.attr, #attr); else fg->attr##_font_idx = -1; #define I(attr) if (descriptor_indices.attr) fg->attr##_font_idx = initialize_font(fg, descriptor_indices.attr, #attr); else fg->attr##_font_idx = -1;
fg->medium_font_idx = initialize_font(fg, 0, "medium"); fg->medium_font_idx = initialize_font(fg, 0, "medium");
I(bold); I(italic); I(bi); I(bold); I(italic); I(bi);
@@ -1647,12 +1759,16 @@ initialize_font_group(FontGroup *fg) {
fg->first_fallback_font_idx++; fg->first_fallback_font_idx++;
} }
#undef I #undef I
calc_cell_metrics(fg); calc_cell_metrics(fg, fg->fonts[fg->medium_font_idx].face);
ensure_canvas_can_fit(fg, 8, 1);
sprite_tracker_set_layout(&fg->sprite_tracker, fg->fcm.cell_width, fg->fcm.cell_height);
// rescale the symbol_map faces for the desired cell height, this is how fallback fonts are sized as well // rescale the symbol_map faces for the desired cell height, this is how fallback fonts are sized as well
for (size_t i = 0; i < descriptor_indices.num_symbol_fonts; i++) { for (size_t i = 0; i < descriptor_indices.num_symbol_fonts; i++) {
Font *font = fg->fonts + i + fg->first_symbol_font_idx; Font *font = fg->fonts + i + fg->first_symbol_font_idx;
set_size_for_face(font->face, fg->fcm.cell_height, true, (FONTS_DATA_HANDLE)fg); set_size_for_face(font->face, fg->fcm.cell_height, true, (FONTS_DATA_HANDLE)fg);
} }
ScaledFontData sfd = {.fcm=fg->fcm, .font_sz_in_pts=fg->font_sz_in_pts};
vt_insert(&fg->scaled_font_map, 1.f, sfd);
} }

View File

@@ -31,12 +31,6 @@ typedef struct ParsedFontFeature {
bool hash_computed; bool hash_computed;
} ParsedFontFeature; } ParsedFontFeature;
typedef struct RunFont {
unsigned scale, subscale_n, subscale_d, vertical_align, multicell_y;
ssize_t font_idx;
} RunFont;
ParsedFontFeature* parse_font_feature(const char *spec); ParsedFontFeature* parse_font_feature(const char *spec);
// API that font backends need to implement // API that font backends need to implement
@@ -62,7 +56,7 @@ void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len);
typedef void (*free_extra_data_func)(void*); typedef void (*free_extra_data_func)(void*);
StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline); StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline);
StringCanvas render_simple_text(FONTS_DATA_HANDLE fg_, const char *text); StringCanvas render_simple_text(FONTS_DATA_HANDLE fg_, const char *text);
static inline void face_apply_scaling(PyObject*face, RunFont rf) {(void)face; (void)rf;} bool face_apply_scaling(PyObject*face, const FONTS_DATA_HANDLE fg);
bool bool
add_font_name_record(PyObject *table, uint16_t platform_id, uint16_t encoding_id, uint16_t language_id, uint16_t name_id, const char *string, uint16_t string_len); add_font_name_record(PyObject *table, uint16_t platform_id, uint16_t encoding_id, uint16_t language_id, uint16_t name_id, const char *string, uint16_t string_len);

View File

@@ -30,15 +30,20 @@ typedef union FaceIndex {
FT_Long val; FT_Long val;
} FaceIndex; } FaceIndex;
typedef struct FaceMetrics {
float size_in_pts;
unsigned int units_per_EM;
// The following are in font units use font_units_to_pixels_x/y to convert to pixels
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
} FaceMetrics;
typedef struct { typedef struct {
PyObject_HEAD PyObject_HEAD
FT_Face face; FT_Face face;
unsigned int units_per_EM; FaceMetrics metrics;
int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness;
int hinting, hintstyle; int hinting, hintstyle;
bool is_scalable, has_color, is_variable, has_svg; bool is_scalable, has_color, is_variable, has_svg;
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;
@@ -46,7 +51,6 @@ typedef struct {
hb_codepoint_t space_glyph_id; hb_codepoint_t space_glyph_id;
void *extra_data; void *extra_data;
free_extra_data_func free_extra_data; free_extra_data_func free_extra_data;
float apple_leading;
PyObject *name_lookup_table; PyObject *name_lookup_table;
FontFeatures font_features; FontFeatures font_features;
} Face; } Face;
@@ -128,7 +132,7 @@ get_height_for_char(Face *self, char ch) {
unsigned int ans = 0; unsigned int ans = 0;
int glyph_index = FT_Get_Char_Index(self->face, ch); int glyph_index = FT_Get_Char_Index(self->face, ch);
if (load_glyph(self, glyph_index, FT_LOAD_DEFAULT)) { if (load_glyph(self, glyph_index, FT_LOAD_DEFAULT)) {
unsigned int baseline = font_units_to_pixels_y(self, self->ascender); unsigned int baseline = font_units_to_pixels_y(self, self->metrics.ascender);
FT_GlyphSlotRec *glyph = self->face->glyph; FT_GlyphSlotRec *glyph = self->face->glyph;
FT_Bitmap *bm = &glyph->bitmap; FT_Bitmap *bm = &glyph->bitmap;
if (glyph->bitmap_top <= 0 || (glyph->bitmap_top > 0 && (unsigned int)glyph->bitmap_top < baseline)) { if (glyph->bitmap_top <= 0 || (glyph->bitmap_top > 0 && (unsigned int)glyph->bitmap_top < baseline)) {
@@ -140,7 +144,7 @@ get_height_for_char(Face *self, char ch) {
static unsigned int static unsigned int
calc_cell_height(Face *self, bool for_metrics) { calc_cell_height(Face *self, bool for_metrics) {
unsigned int ans = font_units_to_pixels_y(self, self->height); unsigned int ans = font_units_to_pixels_y(self, self->metrics.height);
if (for_metrics) { if (for_metrics) {
unsigned int underscore_height = get_height_for_char(self, '_'); unsigned int underscore_height = get_height_for_char(self, '_');
if (underscore_height > ans) { if (underscore_height > ans) {
@@ -193,7 +197,7 @@ set_size_for_face(PyObject *s, unsigned int desired_height, bool force, FONTS_DA
FT_F26Dot6 w = (FT_F26Dot6)(ceil(fg->font_sz_in_pts * 64.0)); FT_F26Dot6 w = (FT_F26Dot6)(ceil(fg->font_sz_in_pts * 64.0));
FT_UInt xdpi = (FT_UInt)fg->logical_dpi_x, ydpi = (FT_UInt)fg->logical_dpi_y; FT_UInt xdpi = (FT_UInt)fg->logical_dpi_x, ydpi = (FT_UInt)fg->logical_dpi_y;
if (!force && (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi)) return true; if (!force && (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi)) return true;
((Face*)self)->size_in_pts = (float)fg->font_sz_in_pts; self->metrics.size_in_pts = (float)fg->font_sz_in_pts;
return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->fcm.cell_height); return set_font_size(self, w, w, xdpi, ydpi, desired_height, fg->fcm.cell_height);
} }
@@ -204,16 +208,32 @@ set_size(Face *self, PyObject *args) {
FT_F26Dot6 w = (FT_F26Dot6)(ceil(font_sz_in_pts * 64.0)); FT_F26Dot6 w = (FT_F26Dot6)(ceil(font_sz_in_pts * 64.0));
FT_UInt xdpi = (FT_UInt)dpi_x, ydpi = (FT_UInt)dpi_y; FT_UInt xdpi = (FT_UInt)dpi_x, ydpi = (FT_UInt)dpi_y;
if (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi) { Py_RETURN_NONE; } if (self->char_width == w && self->char_height == w && self->xdpi == xdpi && self->ydpi == ydpi) { Py_RETURN_NONE; }
self->size_in_pts = (float)font_sz_in_pts; self->metrics.size_in_pts = (float)font_sz_in_pts;
if (!set_font_size(self, w, w, xdpi, ydpi, 0, 0)) return NULL; if (!set_font_size(self, w, w, xdpi, ydpi, 0, 0)) return NULL;
Py_RETURN_NONE; Py_RETURN_NONE;
} }
static bool static void
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) { copy_face_metrics(Face *self) {
#define CPY(n) self->n = self->face->n; #define CPY(n) self->metrics.n = self->face->n;
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
}
bool
face_apply_scaling(PyObject *f, const FONTS_DATA_HANDLE fg) {
Face *self = (Face*)f;
if (set_size_for_face(f, 0, false, fg)) {
if (self->harfbuzz_font) hb_ft_font_changed(self->harfbuzz_font);
copy_face_metrics(self);
return true;
}
return false;
}
static bool
init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) {
copy_face_metrics(self);
self->is_scalable = FT_IS_SCALABLE(self->face); self->is_scalable = FT_IS_SCALABLE(self->face);
self->has_color = FT_HAS_COLOR(self->face); self->has_color = FT_HAS_COLOR(self->face);
self->is_variable = FT_HAS_MULTIPLE_MASTERS(self->face); self->is_variable = FT_HAS_MULTIPLE_MASTERS(self->face);
@@ -230,8 +250,8 @@ init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_
TT_OS2 *os2 = (TT_OS2*)FT_Get_Sfnt_Table(self->face, FT_SFNT_OS2); TT_OS2 *os2 = (TT_OS2*)FT_Get_Sfnt_Table(self->face, FT_SFNT_OS2);
if (os2 != NULL) { if (os2 != NULL) {
self->strikethrough_position = os2->yStrikeoutPosition; self->metrics.strikethrough_position = os2->yStrikeoutPosition;
self->strikethrough_thickness = os2->yStrikeoutSize; self->metrics.strikethrough_thickness = os2->yStrikeoutSize;
} }
self->path = path; Py_INCREF(self->path); self->path = path; Py_INCREF(self->path);
@@ -397,17 +417,17 @@ cell_metrics(PyObject *s) {
FontCellMetrics ans = {0}; FontCellMetrics ans = {0};
ans.cell_width = calc_cell_width(self); ans.cell_width = calc_cell_width(self);
ans.cell_height = calc_cell_height(self, true); ans.cell_height = calc_cell_height(self, true);
ans.baseline = font_units_to_pixels_y(self, self->ascender); ans.baseline = font_units_to_pixels_y(self, self->metrics.ascender);
ans.underline_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->ascender - self->underline_position))); ans.underline_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->metrics.ascender - self->metrics.underline_position)));
ans.underline_thickness = MAX(1, font_units_to_pixels_y(self, self->underline_thickness)); ans.underline_thickness = MAX(1, font_units_to_pixels_y(self, self->metrics.underline_thickness));
if (self->strikethrough_position != 0) { if (self->metrics.strikethrough_position != 0) {
ans.strikethrough_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->ascender - self->strikethrough_position))); ans.strikethrough_position = MIN(ans.cell_height - 1, (unsigned int)font_units_to_pixels_y(self, MAX(0, self->metrics.ascender - self->metrics.strikethrough_position)));
} else { } else {
ans.strikethrough_position = (unsigned int)floor(ans.baseline * 0.65); ans.strikethrough_position = (unsigned int)floor(ans.baseline * 0.65);
} }
if (self->strikethrough_thickness > 0) { if (self->metrics.strikethrough_thickness > 0) {
ans.strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->strikethrough_thickness)); ans.strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->metrics.strikethrough_thickness));
} else { } else {
ans.strikethrough_thickness = ans.underline_thickness; ans.strikethrough_thickness = ans.underline_thickness;
} }
@@ -1037,22 +1057,26 @@ end:
static PyMemberDef members[] = { static PyMemberDef members[] = {
#define MEM(name, type) {#name, type, offsetof(Face, name), READONLY, #name} #define MEM(name, type) {#name, type, offsetof(Face, name), READONLY, #name}
MEM(units_per_EM, T_UINT), #define MMEM(name, type) {#name, type, offsetof(Face, metrics) + offsetof(FaceMetrics, name), READONLY, #name}
MEM(ascender, T_INT), MMEM(units_per_EM, T_UINT),
MEM(descender, T_INT), MMEM(size_in_pts, T_FLOAT),
MEM(height, T_INT), MMEM(ascender, T_INT),
MEM(max_advance_width, T_INT), MMEM(descender, T_INT),
MEM(max_advance_height, T_INT), MMEM(height, T_INT),
MEM(underline_position, T_INT), MMEM(max_advance_width, T_INT),
MEM(underline_thickness, T_INT), MMEM(max_advance_height, T_INT),
MEM(strikethrough_position, T_INT), MMEM(underline_position, T_INT),
MEM(strikethrough_thickness, T_INT), MMEM(underline_thickness, T_INT),
MMEM(strikethrough_position, T_INT),
MMEM(strikethrough_thickness, T_INT),
MEM(is_scalable, T_BOOL), MEM(is_scalable, T_BOOL),
MEM(is_variable, T_BOOL), MEM(is_variable, T_BOOL),
MEM(has_svg, T_BOOL), MEM(has_svg, T_BOOL),
MEM(has_color, T_BOOL), MEM(has_color, T_BOOL),
MEM(path, T_OBJECT_EX), MEM(path, T_OBJECT_EX),
{NULL} /* Sentinel */ {NULL} /* Sentinel */
#undef MEM
#undef MMEM
}; };
static PyMethodDef methods[] = { static PyMethodDef methods[] = {

View File

@@ -18,6 +18,8 @@ vt_hash_bytes(const void *data, const size_t size) {
return hash; return hash;
} }
static inline uint64_t vt_hash_float(float x) { return vt_hash_integer((uint64_t)x); }
static inline bool vt_cmpr_float(float a, float b) { return a == b; }
#define vt_create_for_loop(itr_type, itr, table) for (itr_type itr = vt_first(table); !vt_is_end(itr); itr = vt_next(itr)) #define vt_create_for_loop(itr_type, itr, table) for (itr_type itr = vt_first(table); !vt_is_end(itr); itr = vt_next(itr))
#endif #endif