diff --git a/CHANGELOG.md b/CHANGELOG.md index f919972d..1df82ec3 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -61,6 +61,13 @@ * Crash when `foot.ini` contains an invalid section name (https://codeberg.org/dnkl/foot/issues/159). * Background opacity when in _reverse video_ mode. +* Crash when writing a sixel image that extends outside the terminal's + right margin (https://codeberg.org/dnkl/foot/issues/151). +* Sixel image at non-zero column positions getting sheared at + seemingly random occasions + (https://codeberg.org/dnkl/foot/issues/151). +* Crash after either resizing a window or changing the font size if + there were sixels present in the scrollback while doing so. ### Security diff --git a/grid.c b/grid.c index a79b4493..a779c1b3 100644 --- a/grid.c +++ b/grid.c @@ -83,9 +83,9 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, * at the output that is *oldest* */ int offset = grid->offset + old_screen_rows; - tll(struct sixel) old_sixels = tll_init(); + tll(struct sixel) untranslated_sixels = tll_init(); tll_foreach(grid->sixel_images, it) - tll_push_back(old_sixels, it->item); + tll_push_back(untranslated_sixels, it->item); tll_free(grid->sixel_images); /* Turn cursor coordinates into grid absolute coordinates */ @@ -108,7 +108,6 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, for (size_t i = 0; i < tracking_points_count; i++) tll_push_back(tracking_points, _tracking_points[i]); - /* * Walk the old grid */ @@ -122,56 +121,15 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, continue; /* Map sixels on current "old" row to current "new row" */ - tll_foreach(old_sixels, it) { + tll_foreach(untranslated_sixels, it) { if (it->item.pos.row != old_row_idx) continue; struct sixel sixel = it->item; sixel.pos.row = new_row_idx; - /* Make sure it doesn't cross the wrap-around after being re-based */ - int end = (sixel.pos.row + sixel.rows - 1) & (new_rows - 1); - if (end < sixel.pos.row) { - /* TODO: split instead of destroying */ - sixel_destroy(&it->item); - } else { - - /* Insert sixel into the *sorted* list. */ - - /* Based on rebase_row() in sixel.c */ - /* Uses 'old' offset to ensure old sixels are treated as such */ -#define rebase_row(t, row) \ - (((row) - (grid->offset + new_screen_rows) + new_rows) & (new_rows - 1)) - - int end_row = rebase_row(term, sixel.pos.row + sixel.rows - 1); - - /* - * TODO: this is basically sixel_insert(), except we - * cannot use it since: - * - * a) we don't have a 'term' reference - * b) the grid hasn't been fully * updated yet - * (e.g. grid->num_rows is invalid etc). - */ - - bool inserted = false; - tll_foreach(grid->sixel_images, it2) { - const struct sixel *s = &it2->item; - if (rebase_row(term, s->pos.row + s->rows - 1) < end_row) { - tll_insert_before(grid->sixel_images, it2, sixel); - inserted = true; - break; - } - } - - if (!inserted) - tll_push_back(grid->sixel_images, sixel); - - } - - /* Sixel has been either re-mapped, or destroyed */ - tll_remove(old_sixels, it); -#undef rebase_row + tll_push_back(grid->sixel_images, sixel); + tll_remove(untranslated_sixels, it); } #define line_wrap() \ @@ -372,9 +330,9 @@ grid_reflow(struct grid *grid, int new_rows, int new_cols, grid->saved_cursor.lcf = false; /* Free sixels we failed to "map" to the new grid */ - tll_foreach(old_sixels, it) + tll_foreach(untranslated_sixels, it) sixel_destroy(&it->item); - tll_free(old_sixels); + tll_free(untranslated_sixels); tll_free(tracking_points); } diff --git a/render.c b/render.c index 427c8d44..4a006e10 100644 --- a/render.c +++ b/render.c @@ -20,6 +20,7 @@ #include "grid.h" #include "quirks.h" #include "selection.h" +#include "sixel.h" #include "shm.h" #include "util.h" #include "xmalloc.h" @@ -2197,6 +2198,8 @@ maybe_resize(struct terminal *term, int width, int height, bool force) term->cols = new_cols; term->rows = new_rows; + sixel_reflow(term); + LOG_DBG("resize: %dx%d, grid: cols=%d, rows=%d " "(left-margin=%d, right-margin=%d, top-margin=%d, bottom-margin=%d)", term->width, term->height, term->cols, term->rows, diff --git a/sixel.c b/sixel.c index c6fe1a10..898b8713 100644 --- a/sixel.c +++ b/sixel.c @@ -99,6 +99,15 @@ sixel_erase(struct terminal *term, struct sixel *sixel) sixel_destroy(sixel); } +/* + * Calculates the scrollback relative row number, given an absolute row number. + * + * The scrollback relative row number 0 is the *first*, and *oldest* + * row in the scrollback history (and thus the *first* row to be + * scrolled out). Thus, a higher number means further *down* in the + * scrollback, with the *highest* number being at the bottom of the + * screen, where new input appears. + */ static int rebase_row(const struct terminal *term, int abs_row) { @@ -109,22 +118,29 @@ rebase_row(const struct terminal *term, int abs_row) return rebased_row; } -static bool -verify_sixel_list_order(const struct terminal *term) +/* + * Verify the sixels are sorted correctly. + * + * The sixels are sorted on their *end* row, in descending order. This + * invariant means the most recent sixels appear first in the list. + */ +static void +verify_list_order(const struct terminal *term) { #if defined(_DEBUG) int prev_row = INT_MAX; int prev_col = -1; int prev_col_count = 0; + /* To aid debugging */ + size_t idx = 0; + tll_foreach(term->grid->sixel_images, it) { int row = rebase_row(term, it->item.pos.row + it->item.rows - 1); int col = it->item.pos.col; int col_count = it->item.cols; assert(row <= prev_row); - if (row > prev_row) - return false; if (row == prev_row) { /* Allowed to be on the same row only if their columns @@ -132,18 +148,109 @@ verify_sixel_list_order(const struct terminal *term) assert(col + col_count <= prev_col || prev_col + prev_col_count <= col); - - if (!(col + col_count <= prev_col || - prev_col + prev_col_count <= col)) - return false; } prev_row = row; prev_col = col; prev_col_count = col_count; + idx++; } #endif - return true; +} + +/* + * Verifies there aren't any sixels that cross the scrollback + * wrap-around. This invariant means a sixel's absolute row numbers + * are strictly increasing. + */ +static void +verify_no_wraparound_crossover(const struct terminal *term) +{ +#if defined(_DEBUG) + tll_foreach(term->grid->sixel_images, it) { + const struct sixel *six = &it->item; + + assert(six->pos.row >= 0); + assert(six->pos.row < term->grid->num_rows); + + int end = (six->pos.row + six->rows - 1) & (term->grid->num_rows - 1); + assert(end >= six->pos.row); + } +#endif +} + +/* + * Verify there aren't any sixels that cross the scrollback end. This + * invariant means a sixel's rebased row numbers are strictly + * increasing. + */ +static void +verify_scrollback_consistency(const struct terminal *term) +{ +#if defined(_DEBUG) + tll_foreach(term->grid->sixel_images, it) { + const struct sixel *six = &it->item; + + int last_row = -1; + for (int i = 0; i < six->rows; i++) { + int row_no = rebase_row(term, six->pos.row + i); + + if (last_row != -1) + assert(last_row < row_no); + + last_row = row_no; + } + } +#endif +} + +/* + * Verifies no sixel overlap with any other sixels. + */ +static void +verify_no_overlap(const struct terminal *term) +{ +#if defined(_DEBUG) + tll_foreach(term->grid->sixel_images, it) { + const struct sixel *six1 = &it->item; + + pixman_region32_t rect1; + pixman_region32_init_rect( + &rect1, six1->pos.col, six1->pos.row, six1->cols, six1->rows); + + tll_foreach(term->grid->sixel_images, it2) { + const struct sixel *six2 = &it2->item; + + if (six1 == six2) + continue; + + pixman_region32_t rect2; + pixman_region32_init_rect( + &rect2, six2->pos.col, + six2->pos.row, six2->cols, six2->rows); + + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect(&intersection, &rect1, &rect2); + + assert(!pixman_region32_not_empty(&intersection)); + + pixman_region32_fini(&intersection); + pixman_region32_fini(&rect2); + } + + pixman_region32_fini(&rect1); + } +#endif +} + +static void +verify_sixels(const struct terminal *term) +{ + verify_no_wraparound_crossover(term); + verify_scrollback_consistency(term); + verify_no_overlap(term); + verify_list_order(term); } static void @@ -166,31 +273,46 @@ out: tll_foreach(term->grid->sixel_images, it) { LOG_DBG(" rows=%d+%d", it->item.pos.row, it->item.rows); } -#else - verify_sixel_list_order(term); #endif + verify_sixels(term); } void sixel_scroll_up(struct terminal *term, int rows) { + if (likely(tll_length(term->grid->sixel_images) == 0)) + return; + tll_rforeach(term->grid->sixel_images, it) { struct sixel *six = &it->item; int six_start = rebase_row(term, six->pos.row); + if (six_start < rows) { - sixel_destroy(six); + sixel_erase(term, six); tll_remove(term->grid->sixel_images, it); - } else - break; + } else { + /* + * Unfortunately, we cannot break here. + * + * The sixels are sorted on their *end* row. This means + * there may be a sixel with a top row that will be + * scrolled out *anywhere* in the list (think of a huuuuge + * sixel that covers the entire scrollback) + */ + //break; + } } - verify_sixel_list_order(term); + verify_sixels(term); } void sixel_scroll_down(struct terminal *term, int rows) { + if (likely(tll_length(term->grid->sixel_images) == 0)) + return; + assert(term->grid->num_rows >= rows); tll_foreach(term->grid->sixel_images, it) { @@ -198,116 +320,105 @@ sixel_scroll_down(struct terminal *term, int rows) int six_end = rebase_row(term, six->pos.row + six->rows - 1); if (six_end >= term->grid->num_rows - rows) { - sixel_destroy(six); + sixel_erase(term, six); tll_remove(term->grid->sixel_images, it); } else break; } - verify_sixel_list_order(term); + verify_sixels(term); } static void sixel_overwrite(struct terminal *term, struct sixel *six, int row, int col, int height, int width) { - assert(row >= 0); - assert(row + height <= term->grid->num_rows); - assert(col >= 0); - assert(col + width <= term->grid->num_cols); + pixman_region32_t six_rect; + pixman_region32_init_rect( + &six_rect, + six->pos.col * term->cell_width, six->pos.row * term->cell_height, + six->width, six->height); - int rel_above = min(max(row - six->pos.row, 0), six->rows); - int rel_below = max(min(row + height - six->pos.row, six->rows), 0); - int rel_left = min(max(col - six->pos.col, 0), six->cols); - int rel_right = max(min(col + width - six->pos.col, six->cols), 0); + pixman_region32_t overwrite_rect; + pixman_region32_init_rect( + &overwrite_rect, + col * term->cell_width, row * term->cell_height, + width * term->cell_width, height * term->cell_height); - assert(rel_above >= 0); - assert(rel_below >= 0); - assert(rel_left >= 0); - assert(rel_right >= 0); +#if defined(_DEBUG) + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect(&intersection, &six_rect, &overwrite_rect); + assert(pixman_region32_not_empty(&intersection)); + pixman_region32_fini(&intersection); +#endif - LOG_DBG("SPLIT: six (%p): %dx%d-%dx%d, %dx%d-%dx%d, rel: above=%d, below=%d, left=%d, right=%d", - (void *)six, six->pos.row, six->pos.col, six->rows, six->cols, - row, col, height, width, - rel_above, rel_below, rel_left, rel_right); + pixman_region32_t diff; + pixman_region32_init(&diff); + pixman_region32_subtract(&diff, &six_rect, &overwrite_rect); - struct sixel imgs[4] = {0}; + pixman_region32_fini(&six_rect); + pixman_region32_fini(&overwrite_rect); - if (rel_above > 0) { - imgs[0] = (struct sixel){ - .width = six->width, - .height = rel_above * term->cell_height, - .rows = rel_above, - .cols = six->cols, - .pos = six->pos, - }; - imgs[0].data = xmalloc(imgs[0].width * imgs[0].height * sizeof(uint32_t)); - memcpy(imgs[0].data, six->data, imgs[0].width * imgs[0].height * sizeof(uint32_t)); - } + int n_rects = -1; + pixman_box32_t *boxes = pixman_region32_rectangles(&diff, &n_rects); - if (rel_below < six->rows) { - imgs[1] = (struct sixel){ - .width = six->width, - .height = six->height - rel_below * term->cell_height, - .rows = six->rows - rel_below, - .cols = six->cols, - .pos = (struct coord){ - six->pos.col, - (six->pos.row + rel_below) & (term->grid->num_rows - 1)}, - }; - imgs[1].data = xmalloc(imgs[1].width * imgs[1].height * sizeof(uint32_t)); - memcpy( - imgs[1].data, - &((const uint32_t *)six->data)[rel_below * term->cell_height * six->width], - imgs[1].width * imgs[1].height * sizeof(uint32_t)); - } + for (int i = 0; i < n_rects; i++) { + LOG_DBG("box #%d: x1=%d, y1=%d, x2=%d, y2=%d", i, + boxes[i].x1, boxes[i].y1, boxes[i].x2, boxes[i].y2); - if (rel_left > 0) { - imgs[2] = (struct sixel){ - .width = rel_left * term->cell_width, - .height = min(term->cell_height, six->height - rel_above * term->cell_height), - .rows = 1, - .cols = rel_left, - .pos = (struct coord){ - six->pos.col, - (six->pos.row + rel_above) & (term->grid->num_rows - 1)}, - }; - imgs[2].data = xmalloc(imgs[2].width * imgs[2].height * sizeof(uint32_t)); - for (size_t i = 0; i < imgs[2].height; i++) + assert(boxes[i].x1 % term->cell_width == 0); + assert(boxes[i].y1 % term->cell_height == 0); + + /* New image's position, in cells */ + const int new_col = boxes[i].x1 / term->cell_width; + const int new_row = boxes[i].y1 / term->cell_height; + + assert(new_row < term->grid->num_rows); + + /* New image's width and height, in pixels */ + const int new_width = boxes[i].x2 - boxes[i].x1; + const int new_height = boxes[i].y2 - boxes[i].y1; + + uint32_t *new_data = xmalloc(new_width * new_height * sizeof(uint32_t)); + const uint32_t *old_data = six->data; + + /* Pixel offsets into old image backing memory */ + const int x_ofs = boxes[i].x1 - six->pos.col * term->cell_width; + const int y_ofs = boxes[i].y1 - six->pos.row * term->cell_height; + + /* Copy image data, one row at a time */ + for (size_t j = 0; j < new_height; j++) { memcpy( - &((uint32_t *)imgs[2].data)[i * imgs[2].width], - &((const uint32_t *)six->data)[(rel_above * term->cell_height + i) * six->width], - imgs[2].width * sizeof(uint32_t)); - } + &new_data[(0 + j) * new_width], + &old_data[(y_ofs + j) * six->width + x_ofs], + new_width * sizeof(uint32_t)); + } - if (rel_right < six->cols) { - imgs[3] = (struct sixel){ - .width = six->width - rel_right * term->cell_width, - .height = min(term->cell_height, six->height - rel_above * term->cell_height), - .rows = 1, - .cols = six->cols - rel_right, - .pos = (struct coord){ - six->pos.col + rel_right, - (six->pos.row + rel_above) & (term->grid->num_rows - 1)}, - }; - imgs[3].data = xmalloc(imgs[3].width * imgs[3].height * sizeof(uint32_t)); - for (size_t i = 0; i < imgs[3].height; i++) - memcpy( - &((uint32_t *)imgs[3].data)[i * imgs[3].width], - &((const uint32_t *)six->data)[(rel_above * term->cell_height + i) * six->width + rel_right * term->cell_width], - imgs[3].width * sizeof(uint32_t)); - } - - for (size_t i = 0; i < sizeof(imgs) / sizeof(imgs[0]); i++) { - if (imgs[i].data == NULL) - continue; - - imgs[i].pix = pixman_image_create_bits_no_clear( + pixman_image_t *new_pix = pixman_image_create_bits_no_clear( PIXMAN_a8r8g8b8, - imgs[i].width, imgs[i].height, - imgs[i].data, imgs[i].width * sizeof(uint32_t)); - sixel_insert(term, imgs[i]); + new_width, new_height, new_data, new_width * sizeof(uint32_t)); + + struct sixel new_six = { + .data = new_data, + .pix = new_pix, + .width = new_width, + .height = new_height, + .pos = {.col = new_col, .row = new_row}, + .cols = (new_width + term->cell_width - 1) / term->cell_width, + .rows = (new_height + term->cell_height - 1) / term->cell_height, + }; + +#if defined(_DEBUG) + /* Assert we don't cross the scrollback wrap-around */ + const int new_end = new_six.pos.row + new_six.rows - 1; + assert(new_end < term->grid->num_rows); +#endif + + sixel_insert(term, new_six); } + + pixman_region32_fini(&diff); } /* Row numbers are absolute */ @@ -315,30 +426,51 @@ static void _sixel_overwrite_by_rectangle( struct terminal *term, int row, int col, int height, int width) { - assert(row >= 0); - assert(row + height <= term->grid->num_rows); - assert(col >= 0); - assert(col + width <= term->grid->num_cols); + verify_sixels(term); + +#if defined(_DEBUG) + pixman_region32_t overwrite_rect; + pixman_region32_init_rect(&overwrite_rect, col, row, width, height); +#endif const int start = row; const int end = row + height - 1; + /* We should never generate scrollback wrapping sixels */ + assert(end < term->grid->num_rows); + const int scrollback_rel_start = rebase_row(term, start); + bool UNUSED would_have_breaked = false; + tll_foreach(term->grid->sixel_images, it) { struct sixel *six = &it->item; const int six_start = six->pos.row; - const int six_end = (six_start + six->rows - 1) & (term->grid->num_rows - 1); + const int six_end = (six_start + six->rows - 1); const int six_scrollback_rel_end = rebase_row(term, six_end); + /* We should never generate scrollback wrapping sixels */ + assert(six_end < term->grid->num_rows); + if (six_scrollback_rel_end < scrollback_rel_start) { /* All remaining sixels are *before* our rectangle */ + would_have_breaked = true; break; } - /* We should never generate scrollback wrapping sixels */ - assert(six_end >= six_start); +#if defined(_DEBUG) + pixman_region32_t six_rect; + pixman_region32_init_rect(&six_rect, six->pos.col, six->pos.row, six->cols, six->rows); + + pixman_region32_t intersection; + pixman_region32_init(&intersection); + pixman_region32_intersect(&intersection, &six_rect, &overwrite_rect); + + const bool collides = pixman_region32_not_empty(&intersection); +#else + const bool UNUSED collides = false; +#endif if ((start <= six_start && end >= six_start) || /* Crosses sixel start boundary */ (start <= six_end && end >= six_end) || /* Crosses sixel end boundary */ @@ -351,14 +483,27 @@ _sixel_overwrite_by_rectangle( (col <= col_end && col + width - 1 >= col_end) || (col >= col_start && col + width - 1 <= col_end)) { + assert(!would_have_breaked); + struct sixel to_be_erased = *six; tll_remove(term->grid->sixel_images, it); sixel_overwrite(term, &to_be_erased, start, col, height, width); sixel_erase(term, &to_be_erased); - } - } + } else + assert(!collides); + } else + assert(!collides); + +#if defined(_DEBUG) + pixman_region32_fini(&intersection); + pixman_region32_fini(&six_rect); +#endif } + +#if defined(_DEBUG) + pixman_region32_fini(&overwrite_rect); +#endif } void @@ -441,6 +586,99 @@ sixel_overwrite_at_cursor(struct terminal *term, int width) term, term->grid->cursor.point.row, term->grid->cursor.point.col, width); } +void +sixel_cell_size_changed(struct terminal *term) +{ + struct grid *g = term->grid; + + term->grid = &term->normal; + tll_foreach(term->normal.sixel_images, it) { + struct sixel *six = &it->item; + six->rows = (six->height + term->cell_height - 1) / term->cell_height; + six->cols = (six->width + term->cell_width - 1) / term->cell_width; + } + + term->grid = &term->alt; + tll_foreach(term->alt.sixel_images, it) { + struct sixel *six = &it->item; + six->rows = (six->height + term->cell_height - 1) / term->cell_height; + six->cols = (six->width + term->cell_width - 1) / term->cell_width; + } + + term->grid = g; +} + +void +sixel_reflow(struct terminal *term) +{ + struct grid *g = term->grid; + + for (size_t i = 0; i < 2; i++) { + struct grid *grid = i == 0 ? &term->normal : &term->alt; + + term->grid = grid; + + /* Need the “real” list to be empty from the beginning */ + tll(struct sixel) copy = tll_init(); + tll_foreach(grid->sixel_images, it) + tll_push_back(copy, it->item); + tll_free(grid->sixel_images); + + tll_rforeach(copy, it) { + struct sixel *six = &it->item; + int start = six->pos.row; + int end = (start + six->rows - 1) & (grid->num_rows - 1); + + if (end < start) { + /* Crosses scrollback wrap-around */ + /* TODO: split image */ + sixel_destroy(six); + continue; + } + + if (six->rows > grid->num_rows) { + /* Image too large */ + /* TODO: keep bottom part? */ + sixel_destroy(six); + continue; + } + + /* Drop sixels that now cross the current scrollback end + * border. This is similar to a sixel that have been + * scrolled out */ + /* TODO: should be possible to optimize this */ + bool sixel_destroyed = false; + int last_row = -1; + + for (int j = 0; j < six->rows; j++) { + int row_no = rebase_row(term, six->pos.row + j); + if (last_row != -1 && last_row >= row_no) { + sixel_destroy(six); + sixel_destroyed = true; + break; + } + + last_row = row_no; + } + + if (sixel_destroyed) { + LOG_WARN("destroyed sixel that now crossed history"); + continue; + } + + /* Sixels that didn’t overlap may now do so, which isn’t + * allowed of course */ + _sixel_overwrite_by_rectangle( + term, six->pos.row, six->pos.col, six->rows, six->cols); + sixel_insert(term, it->item); + } + + tll_free(copy); + } + + term->grid = g; +} + void sixel_unhook(struct terminal *term) { @@ -448,6 +686,16 @@ sixel_unhook(struct terminal *term) int pixel_rows_left = term->sixel.image.height; const int stride = term->sixel.image.width * sizeof(uint32_t); + /* + * Need to 'remember' current cursor column. + * + * If we split up the sixel (to avoid scrollback wrap-around), we + * will emit a carriage-return (after several linefeeds), which + * will reset the cursor column to 0. If we use _that_ column for + * the subsequent image parts, the image will look sheared. + */ + const int start_col = term->grid->cursor.point.col; + /* We do not allow sixels to cross the scrollback wrap-around, as * this makes intersection calculations much more complicated */ while (pixel_rows_left > 0) { @@ -478,24 +726,28 @@ sixel_unhook(struct terminal *term) .height = height, .rows = (height + term->cell_height - 1) / term->cell_height, .cols = (width + term->cell_width - 1) / term->cell_width, - .pos = (struct coord){cursor->col, cur_row}, + .pos = (struct coord){start_col, cur_row}, }; - sixel_overwrite_by_rectangle( - term, cursor->row, image.pos.col, image.rows, image.cols); + assert(image.rows < term->grid->num_rows); + assert(image.pos.row + image.rows - 1 < term->grid->num_rows); - LOG_DBG("generating %dx%d pixman image at %d-%d", image.width, image.height, image.pos.row, image.pos.row + image.rows); + LOG_DBG("generating %dx%d pixman image at %d-%d", + image.width, image.height, + image.pos.row, image.pos.row + image.rows); image.pix = pixman_image_create_bits_no_clear( PIXMAN_a8r8g8b8, image.width, image.height, img_data, stride); - + /* Allocate space *first*, then insert */ for (size_t i = 0; i < image.rows; i++) term_linefeed(term); term_carriage_return(term); - render_refresh(term); + + _sixel_overwrite_by_rectangle( + term, image.pos.row, image.pos.col, image.rows, image.cols); sixel_insert(term, image); @@ -508,6 +760,11 @@ sixel_unhook(struct terminal *term) term->sixel.image.height = 0; term->sixel.max_col = 0; term->sixel.pos = (struct coord){0, 0}; + + LOG_DBG("you now have %zu sixels in current grid", + tll_length(term->grid->sixel_images)); + + render_refresh(term); } static unsigned diff --git a/sixel.h b/sixel.h index 666b8c40..6cd7324e 100644 --- a/sixel.h +++ b/sixel.h @@ -16,6 +16,9 @@ void sixel_destroy_all(struct terminal *term); void sixel_scroll_up(struct terminal *term, int rows); void sixel_scroll_down(struct terminal *term, int rows); +void sixel_cell_size_changed(struct terminal *term); +void sixel_reflow(struct terminal *term); + /* * Remove sixel data from the specified location. Used when printing * or erasing characters, and when emitting new sixel images, to diff --git a/terminal.c b/terminal.c index 077d15f0..0afe39d5 100644 --- a/terminal.c +++ b/terminal.c @@ -605,12 +605,38 @@ term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) term->fonts[i] = fonts[i]; } + const int old_cell_width = term->cell_width; + const int old_cell_height = term->cell_height; + term->cell_width = term->fonts[0]->space_advance.x > 0 ? term->fonts[0]->space_advance.x : term->fonts[0]->max_advance.x; term->cell_height = max(term->fonts[0]->height, term->fonts[0]->ascent + term->fonts[0]->descent); LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height); + if (term->cell_width < old_cell_width || + term->cell_height < old_cell_height) + { + /* + * The cell size has decreased. + * + * This means sixels, which we cannot resize, no longer fit + * into their "allocated" grid space. + * + * To be able to fit them, we would have to change the grid + * content. Inserting empty lines _might_ seem acceptable, but + * we'd also need to insert empty columns, which would break + * existing layout completely. + * + * So we delete them. + */ + sixel_destroy_all(term); + } else if (term->cell_width != old_cell_width || + term->cell_height != old_cell_height) + { + sixel_cell_size_changed(term); + } + /* Use force, since cell-width/height may have changed */ render_resize_force(term, term->width / term->scale, term->height / term->scale); return true; @@ -1503,31 +1529,7 @@ term_font_size_adjust(struct terminal *term, double amount) term->font_sizes[i].px_size = -1; } - const int old_cell_width = term->cell_width; - const int old_cell_height = term->cell_height; - - if (!reload_fonts(term)) - return false; - - if (term->cell_width < old_cell_width || - term->cell_height < old_cell_height) - { - /* - * The cell size has decreased. - * - * This means sixels, which we cannot resize, no longer fit - * into their "allocated" grid space. - * - * To be able to fit them, we would have to change the grid - * content. Inserting empty lines _might_ seem acceptable, but - * we'd also need to insert empty columns, which would break - * existing layout completely. - * - * So we delete them. - */ - sixel_destroy_all(term); - } - return true; + return reload_fonts(term); } bool