aboutsummaryrefslogblamecommitdiffstats
path: root/src/display.c
blob: 3b506d2fab7a5f12db82c332881910470b414682 (plain) (tree)
1
2
3
4
5
6
7
8
9
                  
                   
                   
 
                  
                    
 
                   
                 
 
                                    
                                            
                                                       




                                                                         

                                                        
                                             


                                                                      





                                                                      
                                                                       



                                                                             
 
                                                
 


                                              
                                                

                   
 
 









                                                                  
                                                         
 
                                       
 
                     

                                                                

          

                                        




                                   

                                      
                                                                             
 


                                                                      
 












                                                                           

                                               
     

 
                                            
 
                                       
                
 

                                          
                                                   
         
 
                                      
 



                                                    





                                                                             
 
                              







                                                                        









                                                                            




                                             
             




                                                                
         


                                                       
     
 
                                    
 
                      
                       

 
                                            
 
                                       
 

                                                                            
                                
 

                                            

         
                                                                           




                               

                                                              
                                 
                                                 
                            
                            

                                                                           
                                               
                                      

              
                            
                            

                                                                     
                                       
                                    

                                                  
                                                        
         

                                               


     






                                                                 







                                              







                                                                                     






                                                                     





                                                      



                                  



                                                                   
                    




                                                                
                                                
 
                                           
                             
                                                    
                                           
                                    

                  

 
                                    
                                           
 


                               

                          
                        
 
                          
                                                                         

                                                            
                                                      

          
                                                             

                                                         
     
 

                                                             
                                  
                                                                 

                                                       
                                                    





                                                            
 
                         
                                      
                           
                                          

                                

 









                                                       








                                                   




                                                         




                                                                         
 
                                       
                            




                                                                  
                                   
                                           
                                           

                 



                 



















                                                                          
                                                     
 

















                                                       
 





                                                   
 












                                                                      
         
 









                                                         
         




                                  





                                      


                                                                          
     







                                  

 

                                                        
                                            
 
                                       
 


                                  

                                                         

                      

 


                                                                      
 
                                                      
                         







                                                
 

                                



















                                                                         

             
                               
                                                      

                                
                                                                 


                                           
                                                                    





                                                                               

                                                                      
     




                                                                           
                                                            
                               












                                                                      
                         
                               

                                                             
                                           


                                                        
                                                                   















                                                                            
                               









                                                                                   

              
 









                                                                      



                                      



                 

























































                                                                               
#include <stdio.h>
#include <stdlib.h>
#include <string.h>

#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;
}