From 0c7dd7edb22c8591237c926e3abbb1c2b64ba59e Mon Sep 17 00:00:00 2001 From: Kovid Goyal Date: Wed, 19 Feb 2025 09:04:22 +0530 Subject: [PATCH] Implement horizontal alignment for font based rendering Still need to do it for box chars --- kitty/core_text.m | 19 +++++++------------ kitty/fonts.c | 24 +++++++++++++++++++++++- kitty/fonts.h | 7 ++++++- kitty/freetype.c | 13 ++++--------- 4 files changed, 40 insertions(+), 23 deletions(-) diff --git a/kitty/core_text.m b/kitty/core_text.m index 8990a4086..681db92ed 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -1001,7 +1001,7 @@ render_single_ascii_char_as_mask(const char ch, size_t *result_width, size_t *re static bool -do_render(CTFontRef ct_font, unsigned int units_per_em, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, bool center_glyph) { +do_render(CTFontRef ct_font, unsigned int units_per_em, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, bool allow_resize, FONTS_DATA_HANDLE fg, GlyphRenderInfo *ri) { unsigned int canvas_width = cell_width * num_cells; ensure_render_space(canvas_width, cell_height, num_glyphs); CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, buffers.glyphs, buffers.boxes, num_glyphs); @@ -1015,7 +1015,7 @@ do_render(CTFontRef ct_font, unsigned int units_per_em, bool bold, bool italic, CGFloat sz = CTFontGetSize(ct_font); sz *= canvas_width / right; CTFontRef new_font = CTFontCreateCopyWithAttributes(ct_font, sz, NULL, NULL); - bool ret = do_render(new_font, CTFontGetUnitsPerEm(new_font), bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, center_glyph); + bool ret = do_render(new_font, CTFontGetUnitsPerEm(new_font), bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, false, fg, ri); CFRelease(new_font); return ret; } @@ -1037,23 +1037,18 @@ do_render(CTFontRef ct_font, unsigned int units_per_em, bool bold, bool italic, Region src = {.bottom=cell_height, .right=canvas_width}, dest = {.bottom=cell_height, .right=canvas_width}; render_alpha_mask(buffers.render_buf, canvas, &src, &dest, canvas_width, canvas_width, 0xffffff); } - if (num_cells && (center_glyph || (num_cells == 2 && *was_colored))) { - if (debug_rendering) printf("centering glyphs: center_glyph: %d\n", center_glyph); - // center glyphs (two cell emoji, PUA glyphs, ligatures, etc) - CGFloat delta = (((CGFloat)canvas_width - br.size.width) / 2.f); - // FiraCode ligatures result in negative origins - if (br.origin.x > 0) delta -= br.origin.x; - if (delta >= 1.f) right_shift_canvas(canvas, canvas_width, cell_height, (unsigned)(delta)); - } + ri->canvas_width = canvas_width; ri->rendered_width = (unsigned)ceil(br.size.width); ri->x = 0; + // FiraCode ligatures result in negative origins + if (br.origin.x > 0) ri->x = (int)br.origin.x; return true; } bool -render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) { +render_glyphs_in_cells(PyObject *s, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *hb_positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, GlyphRenderInfo *ri) { CTFace *self = (CTFace*)s; ensure_render_space(128, 128, num_glyphs); for (unsigned i=0; i < num_glyphs; i++) buffers.glyphs[i] = info[i].codepoint; - return do_render(self->ct_font, self->units_per_em, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph); + return do_render(self->ct_font, self->units_per_em, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, ri); } // Font tables {{{ diff --git a/kitty/fonts.c b/kitty/fonts.c index a4fe0c061..076ac65b6 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -1135,6 +1135,24 @@ render_filled_sprite(pixel *buf, unsigned num_glyphs, FontCellMetrics scaled_met } else memset(buf, 0xff, sizeof(buf[0]) * num_glyphs * scaled_metrics.cell_height * scaled_metrics.cell_width ); } +static void +apply_horizontal_alignment(pixel *canvas, RunFont rf, bool center_glyph, GlyphRenderInfo ri, unsigned canvas_height, unsigned num_cells, unsigned num_glyphs, bool was_colored) { + int delta = 0; + (void)was_colored; +#ifdef __APPLE__ + if (num_cells == 2 && was_colored) center_glyph = true; +#endif + if (rf.subscale_n && rf.subscale_d && rf.align.horizontal) { + delta = ri.canvas_width - ri.rendered_width; + if (rf.align.horizontal == 2) delta /= 2; + } else if (center_glyph && num_glyphs && num_cells > 1 && ri.rendered_width < ri.canvas_width) { + unsigned half = (ri.canvas_width - ri.rendered_width) / 2; + if (half > 1) delta = half; + } + delta -= ri.x; + if (delta > 0) right_shift_canvas(canvas, ri.canvas_width, canvas_height, delta); +} + static void render_group( FontGroup *fg, unsigned int num_cells, unsigned int num_glyphs, CPUCell *cpu_cells, GPUCell *gpu_cells, @@ -1181,7 +1199,11 @@ render_group( render_filled_sprite(fg->canvas.buf, num_glyphs, scaled_metrics, num_scaled_cells); was_colored = false; /*dump_sprite(fg->canvas.buf, scaled_metrics.cell_width * num_scaled_cells, scaled_metrics.cell_height);*/ - } else render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas.buf, scaled_metrics.cell_width, scaled_metrics.cell_height, num_scaled_cells, scaled_metrics.baseline, &was_colored, (FONTS_DATA_HANDLE)fg, center_glyph); + } else { + GlyphRenderInfo ri = {0}; + render_glyphs_in_cells(font->face, font->bold, font->italic, info, positions, num_glyphs, fg->canvas.buf, scaled_metrics.cell_width, scaled_metrics.cell_height, num_scaled_cells, scaled_metrics.baseline, &was_colored, (FONTS_DATA_HANDLE)fg, &ri); + apply_horizontal_alignment(fg->canvas.buf, rf, center_glyph, ri, scaled_metrics.cell_height, num_scaled_cells, num_glyphs, was_colored); + } if (PyErr_Occurred()) PyErr_Print(); fg->fcm = unscaled_metrics; // needed for current_send_sprite_to_gpu() diff --git a/kitty/fonts.h b/kitty/fonts.h index 36a6933ba..dd5eeaae1 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -31,6 +31,11 @@ typedef struct ParsedFontFeature { bool hash_computed; } ParsedFontFeature; +typedef struct GlyphRenderInfo { + unsigned canvas_width, rendered_width; + int x; +} GlyphRenderInfo; + ParsedFontFeature* parse_font_feature(const char *spec); // API that font backends need to implement @@ -40,7 +45,7 @@ bool is_glyph_empty(PyObject *, glyph_index); hb_font_t* harfbuzz_font_for_face(PyObject*); bool set_size_for_face(PyObject*, unsigned int, bool, FONTS_DATA_HANDLE); FontCellMetrics cell_metrics(PyObject*); -bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, bool center_glyph); +bool render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE, GlyphRenderInfo*); PyObject* create_fallback_face(PyObject *base_face, const ListOfChars *lc, bool bold, bool italic, bool emoji_presentation, FONTS_DATA_HANDLE fg); PyObject* specialize_font_descriptor(PyObject *base_descriptor, double, double, double); PyObject* face_from_path(const char *path, int index, FONTS_DATA_HANDLE); diff --git a/kitty/freetype.c b/kitty/freetype.c index 3dd9ca5e1..8458722eb 100644 --- a/kitty/freetype.c +++ b/kitty/freetype.c @@ -960,7 +960,7 @@ place_bitmap_in_canvas(pixel *cell, ProcessedBitmap *bm, size_t cell_width, size static const ProcessedBitmap EMPTY_PBM = {.factor = 1}; bool -render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, bool center_glyph) { +render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *info, hb_glyph_position_t *positions, unsigned int num_glyphs, pixel *canvas, unsigned int cell_width, unsigned int cell_height, unsigned int num_cells, unsigned int baseline, bool *was_colored, FONTS_DATA_HANDLE fg, GlyphRenderInfo *ri) { Face *self = (Face*)f; bool is_emoji = *was_colored; *was_colored = is_emoji && self->has_color; float x = 0.f, y = 0.f, x_offset = 0.f; @@ -1003,14 +1003,9 @@ render_glyphs_in_cells(PyObject *f, bool bold, bool italic, hb_glyph_info_t *inf free_processed_bitmap(&bm); } - if (center_glyph && num_glyphs) { - unsigned int right_edge = (unsigned int)x, delta; - // x_advance is wrong for colored bitmaps that have been downsampled - if (*was_colored) right_edge = num_glyphs == 1 ? bm.right_edge : canvas_width; - if (num_cells > 1 && right_edge < canvas_width && (delta = (canvas_width - right_edge) / 2) && delta > 1) { - right_shift_canvas(canvas, canvas_width, cell_height, delta); - } - } + ri->canvas_width = canvas_width; ri->rendered_width = (unsigned)x; ri->x = 0; + // x_advance is wrong for colored bitmaps that have been downsampled + if (*was_colored) ri->rendered_width = num_glyphs == 1 ? bm.right_edge : canvas_width; return true; }