diff --git a/kitty/fonts.c b/kitty/fonts.c index fa0c8e26a..b720d3785 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -34,6 +34,10 @@ typedef struct { unsigned int x, y, z, xnum, ynum; } 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_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 #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 { FONTS_DATA_HEAD id_type id; @@ -84,6 +100,7 @@ typedef struct { Canvas canvas; GPUSpriteTracker sprite_tracker; fallback_font_map_t fallback_font_map; + scaled_font_map_t scaled_font_map; } FontGroup; 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}; fg->sprite_map = free_sprite_map(fg->sprite_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); 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 -calc_cell_metrics(FontGroup *fg) { - fg->fcm = cell_metrics(fg->fonts[fg->medium_font_idx].face); +calc_cell_metrics(FontGroup *fg, PyObject *face) { + fg->fcm = cell_metrics(face); 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 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.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 @@ -718,15 +734,58 @@ ensure_glyph_render_scratch_space(size_t sz) { #undef a } -static void -scaled_cell_dimensions(RunFont rf, unsigned *width, unsigned *height) { - *width *= rf.scale; - *height *= rf.scale; +static float +effective_scale(RunFont rf) { + float ans = MAX(1u, rf.scale); if (rf.subscale_n && rf.subscale_d && rf.subscale_n < rf.subscale_d) { - double frac = ((double)rf.subscale_n) / rf.subscale_d; - *width = (unsigned)ceil(frac * *width); - *height = (unsigned)ceil(frac * *height); + ans *= ((float)rf.subscale_n) / rf.subscale_d; } + 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)©)) { + 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(©, 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)©)) { + 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(©, 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* @@ -738,6 +797,28 @@ extract_cell_from_canvas(FontGroup *fg, unsigned int i, unsigned int num_cells) 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 calculate_regions_for_line(RunFont rf, unsigned cell_height, Region *src, Region *dest) { 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)); ensure_canvas_can_fit(fg, 2, rf.scale); Region src = { .right = width, .bottom = height }, dest = src; - unsigned dest_stride = rf.scale * fg->fcm.cell_width, src_stride = width; - calculate_regions_for_line(rf, fg->fcm.cell_height, &src, &dest); - render_alpha_mask(alpha_mask, fg->canvas.buf, &src, &dest, src_stride, dest_stride, 0xffffff); + render_alpha_mask(alpha_mask, fg->canvas.buf, &src, &dest, width, width, 0xffffff); + /*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"); }*/ 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); } 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++) { - 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); } } @@ -835,7 +917,11 @@ set_cell_sprite(GPUCell *cell, const SpritePosition *sp) { } 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 int error = 0; 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); if (PyErr_Occurred()) PyErr_Print(); - for (unsigned i = 0; i < num_cells; i++) { - if (!sp[i]->rendered) { - sp[i]->rendered = true; - sp[i]->colored = was_colored; - 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); + if (scale == 1.f) { + for (unsigned i = 0; i < num_cells; i++) { + if (!sp[i]->rendered) { + sp[i]->rendered = true; + sp[i]->colored = was_colored; + 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 } @@ -1269,8 +1367,9 @@ group_normal(Font *font, hb_font_t *hbf, const TextCache *tc) { 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) { - face_apply_scaling(font->face, rf); +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) { + 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); 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); @@ -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); printf("\n%s\n", dbuf); #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 @@ -1321,6 +1424,9 @@ split_run_at_offset(index_type cursor_offset, index_type *left, index_type *righ static void render_groups(FontGroup *fg, RunFont rf, bool center_glyph, const TextCache *tc) { 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)) { Group *group = G(groups) + idx; 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) { 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; - 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++; } + 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* @@ -1353,6 +1464,7 @@ test_shape(PyObject UNUSED *self, PyObject *args) { PyObject *face = NULL; Font *font; if (!num_font_groups) { PyErr_SetString(PyExc_RuntimeError, "must create at least one font group first"); return NULL; } + FontGroup *fg = font_groups; if (path) { face = face_from_path(path, index, (FONTS_DATA_HANDLE)font_groups); if (face == NULL) return NULL; @@ -1360,11 +1472,10 @@ test_shape(PyObject UNUSED *self, PyObject *args) { font->face = face; if (!init_hash_tables(font)) return NULL; } else { - FontGroup *fg = font_groups; font = fg->fonts + fg->medium_font_idx; } 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); 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) { switch(rf.font_idx) { 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); else if (cursor_offset > -1) { // false if DISABLE_LIGATURES_NEVER index_type left, right; split_run_at_offset(cursor_offset, &left, &right); if (right > 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); } - 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); 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); } break; @@ -1636,6 +1747,7 @@ initialize_font_group(FontGroup *fg) { fg->fonts_count = 1; // the 0 index font is the box font if (!init_hash_tables(fg->fonts)) fatal("Out of memory"); 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; fg->medium_font_idx = initialize_font(fg, 0, "medium"); I(bold); I(italic); I(bi); @@ -1647,12 +1759,16 @@ initialize_font_group(FontGroup *fg) { fg->first_fallback_font_idx++; } #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 for (size_t i = 0; i < descriptor_indices.num_symbol_fonts; i++) { 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); } + ScaledFontData sfd = {.fcm=fg->fcm, .font_sz_in_pts=fg->font_sz_in_pts}; + vt_insert(&fg->scaled_font_map, 1.f, sfd); } diff --git a/kitty/fonts.h b/kitty/fonts.h index 8e37acf51..3b70dfb5d 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -31,12 +31,6 @@ typedef struct ParsedFontFeature { bool hash_computed; } 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); // 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*); StringCanvas render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline); 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 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); diff --git a/kitty/freetype.c b/kitty/freetype.c index 29b421806..2dbb8b56c 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -30,15 +30,20 @@ typedef union FaceIndex { FT_Long val; } 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 { PyObject_HEAD FT_Face face; - unsigned int units_per_EM; - int ascender, descender, height, max_advance_width, max_advance_height, underline_position, underline_thickness, strikethrough_position, strikethrough_thickness; + FaceMetrics metrics; int hinting, hintstyle; bool is_scalable, has_color, is_variable, has_svg; - float size_in_pts; FT_F26Dot6 char_width, char_height; FT_UInt xdpi, ydpi; PyObject *path; @@ -46,7 +51,6 @@ typedef struct { hb_codepoint_t space_glyph_id; void *extra_data; free_extra_data_func free_extra_data; - float apple_leading; PyObject *name_lookup_table; FontFeatures font_features; } Face; @@ -128,7 +132,7 @@ get_height_for_char(Face *self, char ch) { unsigned int ans = 0; int glyph_index = FT_Get_Char_Index(self->face, ch); 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_Bitmap *bm = &glyph->bitmap; 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 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) { unsigned int underscore_height = get_height_for_char(self, '_'); 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_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; - ((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); } @@ -204,16 +208,32 @@ set_size(Face *self, PyObject *args) { FT_F26Dot6 w = (FT_F26Dot6)(ceil(font_sz_in_pts * 64.0)); 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; } - 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; Py_RETURN_NONE; } -static bool -init_ft_face(Face *self, PyObject *path, int hinting, int hintstyle, FONTS_DATA_HANDLE fg) { -#define CPY(n) self->n = self->face->n; +static void +copy_face_metrics(Face *self) { +#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); #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->has_color = FT_HAS_COLOR(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); if (os2 != NULL) { - self->strikethrough_position = os2->yStrikeoutPosition; - self->strikethrough_thickness = os2->yStrikeoutSize; + self->metrics.strikethrough_position = os2->yStrikeoutPosition; + self->metrics.strikethrough_thickness = os2->yStrikeoutSize; } self->path = path; Py_INCREF(self->path); @@ -397,17 +417,17 @@ cell_metrics(PyObject *s) { FontCellMetrics ans = {0}; ans.cell_width = calc_cell_width(self); ans.cell_height = calc_cell_height(self, true); - ans.baseline = font_units_to_pixels_y(self, self->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_thickness = MAX(1, font_units_to_pixels_y(self, self->underline_thickness)); + 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->metrics.ascender - self->metrics.underline_position))); + ans.underline_thickness = MAX(1, font_units_to_pixels_y(self, self->metrics.underline_thickness)); - if (self->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))); + 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->metrics.ascender - self->metrics.strikethrough_position))); } else { ans.strikethrough_position = (unsigned int)floor(ans.baseline * 0.65); } - if (self->strikethrough_thickness > 0) { - ans.strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->strikethrough_thickness)); + if (self->metrics.strikethrough_thickness > 0) { + ans.strikethrough_thickness = MAX(1, font_units_to_pixels_y(self, self->metrics.strikethrough_thickness)); } else { ans.strikethrough_thickness = ans.underline_thickness; } @@ -1037,22 +1057,26 @@ end: static PyMemberDef members[] = { #define MEM(name, type) {#name, type, offsetof(Face, name), READONLY, #name} - MEM(units_per_EM, T_UINT), - MEM(ascender, T_INT), - MEM(descender, T_INT), - MEM(height, T_INT), - MEM(max_advance_width, T_INT), - MEM(max_advance_height, T_INT), - MEM(underline_position, T_INT), - MEM(underline_thickness, T_INT), - MEM(strikethrough_position, T_INT), - MEM(strikethrough_thickness, T_INT), +#define MMEM(name, type) {#name, type, offsetof(Face, metrics) + offsetof(FaceMetrics, name), READONLY, #name} + MMEM(units_per_EM, T_UINT), + MMEM(size_in_pts, T_FLOAT), + MMEM(ascender, T_INT), + MMEM(descender, T_INT), + MMEM(height, T_INT), + MMEM(max_advance_width, T_INT), + MMEM(max_advance_height, T_INT), + MMEM(underline_position, T_INT), + MMEM(underline_thickness, T_INT), + MMEM(strikethrough_position, T_INT), + MMEM(strikethrough_thickness, T_INT), MEM(is_scalable, T_BOOL), MEM(is_variable, T_BOOL), MEM(has_svg, T_BOOL), MEM(has_color, T_BOOL), MEM(path, T_OBJECT_EX), {NULL} /* Sentinel */ +#undef MEM +#undef MMEM }; static PyMethodDef methods[] = { diff --git a/kitty/kitty-verstable.h b/kitty/kitty-verstable.h index c8101a3ce..f64153b42 100644 --- a/kitty/kitty-verstable.h +++ b/kitty/kitty-verstable.h @@ -18,6 +18,8 @@ vt_hash_bytes(const void *data, const size_t size) { 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)) #endif