diff --git a/docs/changelog.rst b/docs/changelog.rst index 852b30ec7..b0c49cc31 100644 --- a/docs/changelog.rst +++ b/docs/changelog.rst @@ -10,6 +10,9 @@ To update |kitty|, :doc:`follow the instructions `. - macOS: Fix a regression in the previous release that broke rendering of strikeout (:iss:`3717`) +- macOS: Fix a crash when rendering ligatures larger than 128 characters + (:iss:`3724`) + - Fix a regression in the previous release that could cause a crash when changing layouts and mousing (:iss:`3713`) diff --git a/kitty/core_text.m b/kitty/core_text.m index a878825da..61c48fbc3 100644 --- a/kitty/core_text.m +++ b/kitty/core_text.m @@ -415,16 +415,20 @@ specialize_font_descriptor(PyObject *base_descriptor, FONTS_DATA_HANDLE fg UNUSE return base_descriptor; } -static uint8_t *render_buf = NULL; -static size_t render_buf_sz = 0; -static CGGlyph glyphs[128]; -static CGRect boxes[128]; -static CGPoint positions[128]; -static CGSize advances[128]; +struct RenderBuffers { + uint8_t *render_buf; + size_t render_buf_sz, sz; + CGGlyph *glyphs; + CGRect *boxes; + CGPoint *positions; + CGSize *advances; +}; +static struct RenderBuffers buffers = {0}; static void finalize(void) { - free(render_buf); + free(buffers.render_buf); free(buffers.glyphs); free(buffers.boxes); free(buffers.positions); free(buffers.advances); + memset(&buffers, 0, sizeof(struct RenderBuffers)); if (all_fonts_collection_data) CFRelease(all_fonts_collection_data); } @@ -442,7 +446,7 @@ render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int widt CGContextSetTextDrawingMode(ctx, kCGTextFill); CGGlyph glyph = glyph_id; CGContextSetTextMatrix(ctx, transform); - CGContextSetTextPosition(ctx, -boxes[0].origin.x, MAX(2, height - baseline)); + CGContextSetTextPosition(ctx, -buffers.boxes[0].origin.x, MAX(2, height - baseline)); CGPoint p = CGPointMake(0, 0); CTFontDrawGlyphs(font, &glyph, &p, 1, ctx); CGContextRelease(ctx); @@ -455,21 +459,30 @@ render_color_glyph(CTFontRef font, uint8_t *buf, int glyph_id, unsigned int widt } } -static inline void -ensure_render_space(size_t width, size_t height) { - if (render_buf_sz >= width * height) return; - free(render_buf); - render_buf_sz = width * height; - render_buf = malloc(render_buf_sz); - if (render_buf == NULL) fatal("Out of memory"); +static void +ensure_render_space(size_t width, size_t height, size_t num_glyphs) { + if (buffers.render_buf_sz < width * height) { + free(buffers.render_buf); buffers.render_buf = NULL; + buffers.render_buf_sz = width * height; + buffers.render_buf = malloc(buffers.render_buf_sz); + if (buffers.render_buf == NULL) fatal("Out of memory"); + } + if (buffers.sz < num_glyphs) { + buffers.sz = MAX(128, num_glyphs * 2); + buffers.advances = calloc(sizeof(buffers.advances[0]), buffers.sz); + buffers.boxes = calloc(sizeof(buffers.boxes[0]), buffers.sz); + buffers.glyphs = calloc(sizeof(buffers.glyphs[0]), buffers.sz); + buffers.positions = calloc(sizeof(buffers.positions[0]), buffers.sz); + if (!buffers.advances || !buffers.boxes || !buffers.glyphs || !buffers.positions) fatal("Out of memory"); + } } static inline void render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned int baseline, unsigned int num_glyphs) { - memset(render_buf, 0, width * height); + memset(buffers.render_buf, 0, width * height); CGColorSpaceRef gray_color_space = CGColorSpaceCreateDeviceGray(); if (gray_color_space == NULL) fatal("Out of memory"); - CGContextRef render_ctx = CGBitmapContextCreate(render_buf, width, height, 8, width, gray_color_space, (kCGBitmapAlphaInfoMask & kCGImageAlphaNone)); + CGContextRef render_ctx = CGBitmapContextCreate(buffers.render_buf, width, height, 8, width, gray_color_space, (kCGBitmapAlphaInfoMask & kCGImageAlphaNone)); if (render_ctx == NULL) fatal("Out of memory"); CGContextSetShouldAntialias(render_ctx, true); CGContextSetShouldSmoothFonts(render_ctx, true); @@ -479,7 +492,7 @@ render_glyphs(CTFontRef font, unsigned int width, unsigned int height, unsigned CGContextSetTextDrawingMode(render_ctx, kCGTextFillStroke); CGContextSetTextMatrix(render_ctx, CGAffineTransformIdentity); CGContextSetTextPosition(render_ctx, 0, height - baseline); - CTFontDrawGlyphs(font, glyphs, positions, num_glyphs, render_ctx); + CTFontDrawGlyphs(font, buffers.glyphs, buffers.positions, num_glyphs, render_ctx); CGContextRelease(render_ctx); CGColorSpaceRelease(gray_color_space); } @@ -492,19 +505,20 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) { unichar chars[num_chars]; CGSize local_advances[num_chars]; for (size_t i = 0; i < num_chars; i++) chars[i] = text[i]; - CTFontGetGlyphsForCharacters(font, chars, glyphs, num_chars); - CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, glyphs, local_advances, num_chars); - CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, glyphs, boxes, num_chars); + ensure_render_space(0, 0, num_chars); + CTFontGetGlyphsForCharacters(font, chars, buffers.glyphs, num_chars); + CTFontGetAdvancesForGlyphs(font, kCTFontOrientationDefault, buffers.glyphs, local_advances, num_chars); + CGRect bounding_box = CTFontGetBoundingRectsForGlyphs(font, kCTFontOrientationDefault, buffers.glyphs, buffers.boxes, num_chars); CGFloat x = 0, y = 0; for (size_t i = 0; i < num_chars; i++) { - positions[i] = CGPointMake(x, y); + buffers.positions[i] = CGPointMake(x, y); x += local_advances[i].width; y += local_advances[i].height; } StringCanvas ans = { .width = (size_t)ceil(x), .height = (size_t)(2 * bounding_box.size.height) }; - ensure_render_space(ans.width, ans.height); + ensure_render_space(ans.width, ans.height, num_chars); render_glyphs(font, ans.width, ans.height, baseline, num_chars); ans.canvas = malloc(ans.width * ans.height); - if (ans.canvas) memcpy(ans.canvas, render_buf, ans.width * ans.height); + if (ans.canvas) memcpy(ans.canvas, buffers.render_buf, ans.width * ans.height); return ans; } @@ -512,12 +526,13 @@ render_simple_text_impl(PyObject *s, const char *text, unsigned int baseline) { static inline bool do_render(CTFontRef ct_font, 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) { unsigned int canvas_width = cell_width * num_cells; - CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, glyphs, boxes, num_glyphs); + ensure_render_space(canvas_width, cell_height, num_glyphs); + CGRect br = CTFontGetBoundingRectsForGlyphs(ct_font, kCTFontOrientationHorizontal, buffers.glyphs, buffers.boxes, num_glyphs); const bool debug_rendering = false; if (allow_resize) { // Resize glyphs that would bleed into neighboring cells, by scaling the font size float right = 0; - for (unsigned i=0; i < num_glyphs; i++) right = MAX(right, boxes[i].origin.x + boxes[i].size.width); + for (unsigned i=0; i < num_glyphs; i++) right = MAX(right, buffers.boxes[i].origin.x + buffers.boxes[i].size.width); if (!bold && !italic && right > canvas_width + 1) { if (debug_rendering) printf("resizing glyphs, right: %f canvas_width: %u\n", right, canvas_width); CGFloat sz = CTFontGetSize(ct_font); @@ -529,19 +544,18 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g } } CGFloat x = 0, y = 0; - CTFontGetAdvancesForGlyphs(ct_font, kCTFontOrientationDefault, glyphs, advances, num_glyphs); + CTFontGetAdvancesForGlyphs(ct_font, kCTFontOrientationDefault, buffers.glyphs, buffers.advances, num_glyphs); for (unsigned i=0; i < num_glyphs; i++) { - positions[i].x = x; positions[i].y = y; - if (debug_rendering) printf("x=%f origin=%f width=%f advance=%f\n", x, boxes[i].origin.x, boxes[i].size.width, advances[i].width); - x += advances[i].width; y += advances[i].height; + buffers.positions[i].x = x; buffers.positions[i].y = y; + if (debug_rendering) printf("x=%f origin=%f width=%f advance=%f\n", x, buffers.boxes[i].origin.x, buffers.boxes[i].size.width, buffers.advances[i].width); + x += buffers.advances[i].width; y += buffers.advances[i].height; } if (*was_colored) { render_color_glyph(ct_font, (uint8_t*)canvas, info[0].codepoint, cell_width * num_cells, cell_height, baseline); } else { - ensure_render_space(canvas_width, cell_height); render_glyphs(ct_font, canvas_width, cell_height, baseline, num_glyphs); Region src = {.bottom=cell_height, .right=canvas_width}, dest = {.bottom=cell_height, .right=canvas_width}; - render_alpha_mask(render_buf, canvas, &src, &dest, canvas_width, canvas_width); + render_alpha_mask(buffers.render_buf, canvas, &src, &dest, canvas_width, canvas_width); } if (num_cells && (center_glyph || (num_cells == 2 && *was_colored))) { if (debug_rendering) printf("centering glyphs: center_glyph: %d\n", center_glyph); @@ -557,7 +571,8 @@ do_render(CTFontRef ct_font, bool bold, bool italic, hb_glyph_info_t *info, hb_g 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) { CTFace *self = (CTFace*)s; - for (unsigned i=0; i < num_glyphs; i++) glyphs[i] = info[i].codepoint; + 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, bold, italic, info, hb_positions, num_glyphs, canvas, cell_width, cell_height, num_cells, baseline, was_colored, true, fg, center_glyph); } diff --git a/kitty/fonts.c b/kitty/fonts.c index 3182716e0..70c8cbb34 100644 --- a/kitty/fonts.c +++ b/kitty/fonts.c @@ -560,10 +560,10 @@ END_ALLOW_CASE_RANGE static PyObject* box_drawing_function = NULL, *prerender_function = NULL, *descriptor_for_idx = NULL; void -render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) { +render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride) { for (size_t sr = src_rect->top, dr = dest_rect->top; sr < src_rect->bottom && dr < dest_rect->bottom; sr++, dr++) { pixel *d = dest + dest_stride * dr; - uint8_t *s = alpha_mask + src_stride * sr; + const uint8_t *s = alpha_mask + src_stride * sr; for(size_t sc = src_rect->left, dc = dest_rect->left; sc < src_rect->right && dc < dest_rect->right; sc++, dc++) { uint8_t src_alpha = d[dc] & 0xff; uint8_t alpha = s[sc]; diff --git a/kitty/fonts.h b/kitty/fonts.h index a6a5397e2..d8bca7a60 100644 --- a/kitty/fonts.h +++ b/kitty/fonts.h @@ -33,7 +33,7 @@ PyObject* face_from_descriptor(PyObject*, FONTS_DATA_HANDLE); const char* postscript_name_for_face(const PyObject*); void sprite_tracker_current_layout(FONTS_DATA_HANDLE data, unsigned int *x, unsigned int *y, unsigned int *z); -void render_alpha_mask(uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride); +void render_alpha_mask(const uint8_t *alpha_mask, pixel* dest, Region *src_rect, Region *dest_rect, size_t src_stride, size_t dest_stride); void render_line(FONTS_DATA_HANDLE, Line *line, index_type lnum, Cursor *cursor, DisableLigature); void sprite_tracker_set_limits(size_t max_texture_size, size_t max_array_len); typedef void (*free_extra_data_func)(void*);