#include #include #include #include "runes.h" #include "display.h" #include "config.h" #include "term.h" static void runes_display_load_font( RunesDisplay *display, char *font_name); static void runes_display_repaint_screen(RunesTerm *t); static int runes_display_continue_string( struct vt100_cell *old, struct vt100_cell *new); static void runes_display_draw_string( RunesTerm *t, int row, int col, int width, struct vt100_cell **cells, size_t len); static void runes_display_paint_rectangle( RunesTerm *t, cairo_t *cr, cairo_pattern_t *pattern, int row, int col, int width, int height); static void runes_display_draw_glyphs( RunesTerm *t, cairo_pattern_t *pattern, struct vt100_cell **cells, size_t len, int row, int col); static void runes_display_draw_glyphs_fast( RunesTerm *t, cairo_pattern_t *pattern, struct vt100_cell **cells, size_t len, int row, int col); static void runes_display_draw_glyphs_slow( RunesTerm *t, cairo_pattern_t *pattern, struct vt100_cell **cells, size_t len, int row, int col, size_t buflen); static int runes_display_glyphs_are_monospace(RunesTerm *t, int width); static int runes_display_loc_is_selected(RunesTerm *t, struct vt100_loc loc); static int runes_display_loc_is_between( struct vt100_loc loc, struct vt100_loc start, struct vt100_loc end); RunesDisplay *runes_display_new(char *font_name) { RunesDisplay *display; display = calloc(1, sizeof(RunesDisplay)); runes_display_load_font(display, font_name); return display; } void runes_display_set_window_size(RunesTerm *t, int row, int col) { RunesDisplay *display = t->display; UNUSED(row); free(display->glyph_buf); display->glyph_buf = malloc(col * sizeof(cairo_glyph_t)); } void runes_display_set_context(RunesTerm *t, cairo_t *cr) { RunesDisplay *display = t->display; display->cr = cr; if (display->layout) { pango_cairo_update_layout(display->cr, display->layout); } else { PangoAttrList *attrs; PangoFontDescription *font_desc; PangoLayoutLine *line; PangoGlyphItem *glyph_item; PangoGlyphItemIter iter; PangoGlyph glyph; int i; attrs = pango_attr_list_new(); font_desc = pango_font_description_from_string(t->config->font_name); display->layout = pango_cairo_create_layout(display->cr); pango_layout_set_attributes(display->layout, attrs); pango_layout_set_font_description(display->layout, font_desc); free(display->ascii_glyph_index_cache); display->ascii_glyph_index_cache = calloc(128, sizeof(PangoGlyph)); for (i = 32; i < 128; ++i) { char c = i; pango_layout_set_text(display->layout, &c, 1); line = pango_layout_get_line_readonly(display->layout, 0); glyph_item = line->runs->data; pango_glyph_item_iter_init_start(&iter, glyph_item, &c); glyph = glyph_item->glyphs->glyphs[iter.start_glyph].glyph; display->ascii_glyph_index_cache[i] = glyph; } pango_attr_list_unref(attrs); pango_font_description_free(font_desc); } } void runes_display_draw_screen(RunesTerm *t) { RunesDisplay *display = t->display; int r, rows; if (t->scr->dirty || display->dirty) { if (t->scr->dirty) { runes_display_maybe_clear_selection(t); } cairo_push_group(display->cr); /* XXX quite inefficient */ rows = t->scr->grid->max.row; for (r = 0; r < rows; ++r) { int c = 0, cols = t->scr->grid->max.col; int vr = r + t->scr->grid->row_top - display->row_visible_offset; int start = c; struct vt100_cell *cell = NULL, *prev_cell = NULL; GPtrArray *cells; cells = g_ptr_array_new(); while (c < cols) { cell = &t->scr->grid->rows[vr].cells[c]; if (!runes_display_continue_string(prev_cell, cell)) { runes_display_draw_string( t, r, start, c - start, (struct vt100_cell **)cells->pdata, cells->len); g_ptr_array_set_size(cells, 0); start = c; while (c < cols && cell->len == 0) { cell = &t->scr->grid->rows[vr].cells[++c]; } if (c > start) { runes_display_draw_string( t, r, start, c - start, (struct vt100_cell **)cells->pdata, cells->len); start = c; } prev_cell = NULL; } g_ptr_array_add(cells, cell); c += cell->is_wide ? 2 : 1; prev_cell = cell; } runes_display_draw_string( t, r, start, cols - start, (struct vt100_cell **)cells->pdata, cells->len); g_ptr_array_unref(cells); } cairo_pattern_destroy(display->buffer); display->buffer = cairo_pop_group(display->cr); } runes_display_repaint_screen(t); t->scr->dirty = 0; display->dirty = 0; } void runes_display_draw_cursor(RunesTerm *t) { RunesDisplay *display = t->display; if (!t->scr->hide_cursor) { int row = t->scr->grid->cur.row, col = t->scr->grid->cur.col, width; struct vt100_cell *cell; if (col >= t->scr->grid->max.col) { col = t->scr->grid->max.col - 1; } cell = &t->scr->grid->rows[t->scr->grid->row_top + row].cells[col]; width = display->fontx; if (cell->is_wide) { width *= 2; } cairo_push_group(display->cr); cairo_set_source(display->cr, t->config->cursorcolor); if (display->unfocused) { cairo_set_line_width(display->cr, 1); cairo_rectangle( display->cr, col * display->fontx + 0.5, (row + display->row_visible_offset) * display->fonty + 0.5, width - 1, display->fonty - 1); cairo_stroke(display->cr); } else { cairo_rectangle( display->cr, col * display->fontx, (row + display->row_visible_offset) * display->fonty, width, display->fonty); cairo_fill(display->cr); runes_display_draw_glyphs( t, t->config->bgdefault, &cell, 1, row + display->row_visible_offset, col); } cairo_pop_group_to_source(display->cr); cairo_paint(display->cr); } } void runes_display_set_selection( RunesTerm *t, struct vt100_loc *start, struct vt100_loc *end) { RunesDisplay *display = t->display; display->has_selection = 1; display->selection_start = *start; display->selection_end = *end; if (t->display->selection_contents) { free(t->display->selection_contents); t->display->selection_contents = NULL; } if (end->row < start->row || (end->row == start->row && end->col < start->col)) { struct vt100_loc *tmp; tmp = start; start = end; end = tmp; } vt100_screen_get_string_plaintext( t->scr, start, end, &t->display->selection_contents, &t->display->selection_len); t->display->dirty = 1; } void runes_display_maybe_clear_selection(RunesTerm *t) { RunesDisplay *display = t->display; char *contents; size_t len; if (!display->has_selection) { return; } vt100_screen_get_string_plaintext( t->scr, &display->selection_start, &display->selection_end, &contents, &len); if (len != display->selection_len || !contents || memcmp(contents, display->selection_contents, len)) { display->has_selection = 0; } } void runes_display_delete(RunesDisplay *display) { free(display->ascii_glyph_index_cache); free(display->glyph_buf); cairo_scaled_font_destroy(display->scaled_font); cairo_pattern_destroy(display->buffer); g_object_unref(display->layout); free(display); } static void runes_display_load_font( RunesDisplay *display, char *font_name) { PangoFontDescription *desc; PangoContext *context; PangoFontMetrics *metrics; PangoFontMap *fontmap; PangoFont *font; int ascent, descent; if (display->layout) { desc = (PangoFontDescription *)pango_layout_get_font_description( display->layout); context = pango_layout_get_context(display->layout); fontmap = pango_context_get_font_map(context); } else { desc = pango_font_description_from_string(font_name); fontmap = pango_cairo_font_map_get_default(); context = pango_font_map_create_context(fontmap); } metrics = pango_context_get_metrics(context, desc, NULL); display->fontx = PANGO_PIXELS( pango_font_metrics_get_approximate_digit_width(metrics)); ascent = pango_font_metrics_get_ascent(metrics); descent = pango_font_metrics_get_descent(metrics); display->fonty = PANGO_PIXELS(ascent + descent); display->font_descent = PANGO_PIXELS(descent); font = pango_font_map_load_font(fontmap, context, desc); display->scaled_font = pango_cairo_font_get_scaled_font( (PangoCairoFont *)font); cairo_scaled_font_reference(display->scaled_font); g_object_unref(font); pango_font_metrics_unref(metrics); if (!display->layout) { pango_font_description_free(desc); g_object_unref(context); } } static void runes_display_repaint_screen(RunesTerm *t) { RunesDisplay *display = t->display; if (display->buffer) { cairo_set_source(display->cr, display->buffer); cairo_paint(display->cr); } } static int runes_display_continue_string( struct vt100_cell *old, struct vt100_cell *new) { if (!old) { return 1; } if (!old->len || !new->len) { return 0; } return !( (old->attrs.fgcolor.id - new->attrs.fgcolor.id) | (old->attrs.bgcolor.id - new->attrs.bgcolor.id) | (old->attrs.attrs - new->attrs.attrs) ); } static void runes_display_draw_string( RunesTerm *t, int row, int col, int width, struct vt100_cell **cells, size_t len) { RunesDisplay *display = t->display; struct vt100_loc loc = { row + t->scr->grid->row_top - display->row_visible_offset, col }; struct vt100_loc eloc = { row + t->scr->grid->row_top - display->row_visible_offset, col + width }; struct vt100_cell_attrs *attrs; cairo_pattern_t *bg = NULL, *fg = NULL; int bg_is_custom = 0, fg_is_custom = 0; int selected; if (!width) { return; } if (len > 1 && display->has_selection && (display->selection_start.row != display->selection_end.row || display->selection_start.col != display->selection_end.col) && (runes_display_loc_is_between( display->selection_start, loc, eloc) || runes_display_loc_is_between( display->selection_end, loc, eloc))) { size_t i; int c = col; for (i = 0; i < len; ++i) { int width = cells[i]->is_wide ? 2 : 1; runes_display_draw_string(t, row, c, width, &cells[i], 1); c += width; } return; } selected = runes_display_loc_is_selected(t, loc); if (len) { attrs = &cells[0]->attrs; switch (attrs->bgcolor.type) { case VT100_COLOR_DEFAULT: bg = t->config->bgdefault; break; case VT100_COLOR_IDX: bg = t->config->colors[attrs->bgcolor.idx]; break; case VT100_COLOR_RGB: bg = cairo_pattern_create_rgb( attrs->bgcolor.r / 255.0, attrs->bgcolor.g / 255.0, attrs->bgcolor.b / 255.0); bg_is_custom = 1; break; } switch (attrs->fgcolor.type) { case VT100_COLOR_DEFAULT: fg = t->config->fgdefault; break; case VT100_COLOR_IDX: { unsigned char idx = attrs->fgcolor.idx; if (t->config->bold_is_bright && attrs->bold && idx < 8) { idx += 8; } fg = t->config->colors[idx]; break; } case VT100_COLOR_RGB: fg = cairo_pattern_create_rgb( attrs->fgcolor.r / 255.0, attrs->fgcolor.g / 255.0, attrs->fgcolor.b / 255.0); fg_is_custom = 1; break; } if (attrs->inverse ^ selected) { if (attrs->fgcolor.id == attrs->bgcolor.id) { fg = t->config->bgdefault; bg = t->config->fgdefault; } else { cairo_pattern_t *tmp = fg; fg = bg; bg = tmp; } } } else { bg = t->config->bgdefault; fg = t->config->fgdefault; if (selected) { cairo_pattern_t *tmp = fg; fg = bg; bg = tmp; } } runes_display_paint_rectangle(t, display->cr, bg, row, col, width, 1); if (len) { runes_display_draw_glyphs(t, fg, cells, len, row, col); } if (bg_is_custom) { cairo_pattern_destroy(bg); } if (fg_is_custom) { cairo_pattern_destroy(fg); } } static void runes_display_paint_rectangle( RunesTerm *t, cairo_t *cr, cairo_pattern_t *pattern, int row, int col, int width, int height) { RunesDisplay *display = t->display; cairo_save(cr); cairo_set_source(cr, pattern); cairo_rectangle( cr, col * display->fontx, row * display->fonty, width * display->fontx, height * display->fonty); cairo_fill(cr); cairo_restore(cr); } static void runes_display_draw_glyphs( RunesTerm *t, cairo_pattern_t *pattern, struct vt100_cell **cells, size_t len, int row, int col) { struct vt100_cell_attrs *attrs = &cells[0]->attrs; size_t buflen = 0, i; int fast = 1; /* we only cache the normal font for now */ if ((attrs->bold && t->config->bold_is_bold) || attrs->italic || attrs->underline) { fast = 0; } for (i = 0; i < len; ++i) { buflen += cells[i]->len; /* we only cache glyphs for ascii characters for now */ if (cells[i]->len > 1) { fast = 0; } } if (fast) { runes_display_draw_glyphs_fast(t, pattern, cells, len, row, col); } else { runes_display_draw_glyphs_slow( t, pattern, cells, len, row, col, buflen); } } static void runes_display_draw_glyphs_fast( RunesTerm *t, cairo_pattern_t *pattern, struct vt100_cell **cells, size_t len, int row, int col) { RunesDisplay *display = t->display; size_t i; for (i = 0; i < len; ++i) { cairo_glyph_t *glyph = &display->glyph_buf[i]; switch (cells[i]->len) { case 0: glyph->index = display->ascii_glyph_index_cache[' ']; break; case 1: { char c = cells[i]->contents[0]; glyph->index = display->ascii_glyph_index_cache[(int)c]; break; } default: fprintf(stderr, "runes_display_draw_glyphs_fast requires ascii\n"); return; } glyph->x = (col + i) * display->fontx; glyph->y = (row + 1) * display->fonty - display->font_descent; } cairo_save(display->cr); cairo_move_to(display->cr, col * display->fontx, row * display->fonty); cairo_set_source(display->cr, pattern); cairo_set_scaled_font(display->cr, display->scaled_font); cairo_show_glyphs(display->cr, display->glyph_buf, len); cairo_restore(display->cr); } static void runes_display_draw_glyphs_slow( RunesTerm *t, cairo_pattern_t *pattern, struct vt100_cell **cells, size_t len, int row, int col, size_t buflen) { RunesDisplay *display = t->display; struct vt100_cell_attrs *attrs = &cells[0]->attrs; PangoAttrList *pango_attrs; size_t pos = 0, i; char *buf; int width = 0; buf = malloc(buflen); for (i = 0; i < len; ++i) { memcpy(&buf[pos], cells[i]->contents, cells[i]->len); pos += cells[i]->len; width += cells[i]->is_wide ? 2 : 1; } pango_layout_set_text(display->layout, buf, buflen); if (len > 1 && !runes_display_glyphs_are_monospace(t, width)) { int c = col; for (i = 0; i < len; ++i) { runes_display_draw_glyphs(t, pattern, &cells[i], 1, row, c); c += cells[i]->is_wide ? 2 : 1; } } else { pango_attrs = pango_layout_get_attributes(display->layout); if (t->config->bold_is_bold) { pango_attr_list_change( pango_attrs, pango_attr_weight_new( attrs->bold ? PANGO_WEIGHT_BOLD : PANGO_WEIGHT_NORMAL)); } pango_attr_list_change( pango_attrs, pango_attr_style_new( attrs->italic ? PANGO_STYLE_ITALIC : PANGO_STYLE_NORMAL)); pango_attr_list_change( pango_attrs, pango_attr_underline_new( attrs->underline ? PANGO_UNDERLINE_SINGLE : PANGO_UNDERLINE_NONE)); cairo_save(display->cr); cairo_move_to(display->cr, col * display->fontx, row * display->fonty); cairo_set_source(display->cr, pattern); pango_cairo_update_layout(display->cr, display->layout); pango_cairo_show_layout(display->cr, display->layout); cairo_restore(display->cr); } free(buf); } static int runes_display_glyphs_are_monospace(RunesTerm *t, int width) { RunesDisplay *display = t->display; int w, h; if (pango_layout_get_unknown_glyphs_count(display->layout) > 0) { return 0; } pango_layout_get_pixel_size(display->layout, &w, &h); if (w != display->fontx * width) { return 0; } if (h != display->fonty) { return 0; } return 1; } static int runes_display_loc_is_selected(RunesTerm *t, struct vt100_loc loc) { RunesDisplay *display = t->display; struct vt100_loc start = display->selection_start; struct vt100_loc end = display->selection_end; if (!display->has_selection) { return 0; } if (loc.row == start.row) { int start_max_col; start_max_col = vt100_screen_row_max_col(t->scr, start.row); if (start.col > start_max_col) { start.col = t->scr->grid->max.col; } } if (loc.row == end.row) { int end_max_col; end_max_col = vt100_screen_row_max_col(t->scr, end.row); if (end.col > end_max_col) { end.col = t->scr->grid->max.col; } } return runes_display_loc_is_between(loc, start, end); } static int runes_display_loc_is_between( struct vt100_loc loc, struct vt100_loc start, struct vt100_loc end) { if (end.row < start.row || (end.row == start.row && end.col < start.col)) { struct vt100_loc tmp; tmp = start; start = end; end = tmp; } if (loc.row < start.row || loc.row > end.row) { return 0; } if (loc.row == start.row && loc.col < start.col) { return 0; } if (loc.row == end.row && loc.col >= end.col) { return 0; } return 1; }