[vte] widget: Add support for increased line and character spacing



commit 26c79c6dc93aae4d4f787f36b594cac151df0f07
Author: Egmont Koblinger <egmont gmail com>
Date:   Mon Dec 4 23:08:44 2017 +0100

    widget: Add support for increased line and character spacing
    
    New API methods vte_terminal_set_cell_{height,width}_scale take values
    from 1.0 (default) to 2.0 (double spacing) to push the letters further
    apart from each other, without affecting the font size.
    
    https://bugzilla.gnome.org/show_bug.cgi?id=781479
    https://bugzilla.gnome.org/show_bug.cgi?id=738781

 doc/reference/vte-sections.txt |    4 +
 src/app/app.cc                 |    8 ++
 src/vte.cc                     |  193 +++++++++++++++++++++++++++++-----------
 src/vte/vteterminal.h          |   12 +++
 src/vtedefines.hh              |    2 +
 src/vtedraw.cc                 |  124 ++++++++++++++++++--------
 src/vtedraw.hh                 |   11 ++-
 src/vtegtk.cc                  |  130 ++++++++++++++++++++++++++-
 src/vtegtk.hh                  |    2 +
 src/vteinternal.hh             |   35 ++++++--
 10 files changed, 416 insertions(+), 105 deletions(-)
---
diff --git a/doc/reference/vte-sections.txt b/doc/reference/vte-sections.txt
index 0b6cd47..f73b3da 100644
--- a/doc/reference/vte-sections.txt
+++ b/doc/reference/vte-sections.txt
@@ -33,6 +33,10 @@ vte_terminal_set_scroll_on_keystroke
 vte_terminal_get_scroll_on_keystroke
 vte_terminal_set_rewrap_on_resize
 vte_terminal_get_rewrap_on_resize
+vte_terminal_set_cell_height_scale
+vte_terminal_get_cell_height_scale
+vte_terminal_set_cell_width_scale
+vte_terminal_get_cell_width_scale
 vte_terminal_set_color_bold
 vte_terminal_set_color_foreground
 vte_terminal_set_color_background
diff --git a/src/app/app.cc b/src/app/app.cc
index e882681..2770477 100644
--- a/src/app/app.cc
+++ b/src/app/app.cc
@@ -88,6 +88,8 @@ public:
         int scrollback_lines{-1 /* infinite */};
         int transparency_percent{-1};
         int verbosity{0};
+        double cell_height_scale{1.0};
+        double cell_width_scale{1.0};
         VteCursorBlinkMode cursor_blink_mode{VTE_CURSOR_BLINK_SYSTEM};
         VteCursorShape cursor_shape{VTE_CURSOR_SHAPE_BLOCK};
 
@@ -338,6 +340,10 @@ public:
                           "Set background image extend", "EXTEND" },
                         { "background-operator", 0, 0, G_OPTION_ARG_CALLBACK, 
(void*)parse_background_operator,
                           "Set background draw operator", "OPERATOR" },
+                        { "cell-height-scale", 0, 0, G_OPTION_ARG_DOUBLE, &cell_height_scale,
+                          "Add extra line spacing", "1.0..2.0" },
+                        { "cell-width-scale", 0, 0, G_OPTION_ARG_DOUBLE, &cell_width_scale,
+                          "Add extra letter spacing", "1.0..2.0" },
                         { "cjk-width", 0, 0, G_OPTION_ARG_CALLBACK, (void*)parse_cjk_width,
                           "Specify the cjk ambiguous width to use for UTF-8 encoding", "NARROW|WIDE" },
                         { "cursor-blink", 0, 0, G_OPTION_ARG_CALLBACK, (void*)parse_cursor_blink,
@@ -1830,6 +1836,8 @@ vteapp_window_constructed(GObject *object)
 
         vte_terminal_set_allow_hyperlink(window->terminal, !options.no_hyperlink);
         vte_terminal_set_audible_bell(window->terminal, options.audible_bell);
+        vte_terminal_set_cell_height_scale(window->terminal, options.cell_height_scale);
+        vte_terminal_set_cell_width_scale(window->terminal, options.cell_width_scale);
         vte_terminal_set_cjk_ambiguous_width(window->terminal, options.cjk_ambiguous_width);
         vte_terminal_set_cursor_blink_mode(window->terminal, options.cursor_blink_mode);
         vte_terminal_set_cursor_shape(window->terminal, options.cursor_shape);
diff --git a/src/vte.cc b/src/vte.cc
index d16b580..9976cf3 100644
--- a/src/vte.cc
+++ b/src/vte.cc
@@ -635,12 +635,10 @@ VteTerminalPrivate::invalidate_cell(vte::grid::column_t col,
                        }
                        columns = cell->attr.columns;
                        style = _vte_draw_get_style(cell->attr.bold, cell->attr.italic);
-                       if (cell->c != 0 &&
-                                       _vte_draw_get_char_width (
-                                                                  m_draw,
-                                               cell->c, columns, style) >
-                                       m_cell_width * columns) {
-                               columns++;
+                        if (cell->c != 0) {
+                                int right;
+                                _vte_draw_get_char_edges(m_draw, cell->c, columns, style, NULL, &right);
+                                columns = MAX(columns, howmany(right, m_cell_width));
                        }
                }
        }
@@ -679,13 +677,10 @@ VteTerminalPrivate::invalidate_cursor_once(bool periodic)
                if (cell != NULL) {
                        columns = cell->attr.columns;
                        auto style = _vte_draw_get_style(cell->attr.bold, cell->attr.italic);
-                       if (cell->c != 0 &&
-                                       _vte_draw_get_char_width (
-                                               m_draw,
-                                               cell->c,
-                                               columns, style) >
-                           m_cell_width * columns) {
-                               columns++;
+                        if (cell->c != 0) {
+                                int right;
+                                _vte_draw_get_char_edges(m_draw, cell->c, columns, style, NULL, &right);
+                                columns = MAX(columns, howmany(right, m_cell_width));
                        }
                }
                columns = MAX(columns, preedit_width);
@@ -7526,45 +7521,63 @@ VteTerminalPrivate::widget_visibility_notify(GdkEventVisibility *event)
        }
 }
 
-/* Apply the changed metrics, and queue a resize if need be. */
+/* Apply the changed metrics, and queue a resize if need be.
+ *
+ * The cell's height consists of 4 parts, from top to bottom:
+ * - char_spacing.top: half of the extra line spacing,
+ * - char_ascent: the font's ascent,
+ * - char_descent: the font's descent,
+ * - char_spacing.bottom: the other half of the extra line spacing.
+ * Extra line spacing is typically 0, beef up cell_height_scale to get actual pixels
+ * here. Similarly, increase cell_width_scale to get nonzero char_spacing.{left,right}.
+ */
 void
-VteTerminalPrivate::apply_font_metrics(int width,
-                                       int height,
-                                       int ascent,
-                                       int descent)
+VteTerminalPrivate::apply_font_metrics(int cell_width,
+                                       int cell_height,
+                                       int char_ascent,
+                                       int char_descent,
+                                       GtkBorder char_spacing)
 {
+        int char_height;
        bool resize = false, cresize = false;
 
        /* Sanity check for broken font changes. */
-       width = MAX(width, 1);
-       height = MAX(height, 2);
-       ascent = MAX(ascent, 1);
-       descent = MAX(descent, 1);
+        cell_width = MAX(cell_width, 1);
+        cell_height = MAX(cell_height, 2);
+        char_ascent = MAX(char_ascent, 1);
+        char_descent = MAX(char_descent, 1);
+
+        /* For convenience only. */
+        char_height = char_ascent + char_descent;
 
        /* Change settings, and keep track of when we've changed anything. */
-       if (width != m_cell_width) {
+        if (cell_width != m_cell_width) {
                resize = cresize = true;
-               m_cell_width = width;
+                m_cell_width = cell_width;
        }
-       if (height != m_cell_height) {
+        if (cell_height != m_cell_height) {
                resize = cresize = true;
-               m_cell_height = height;
+                m_cell_height = cell_height;
        }
-       if (ascent != m_char_ascent) {
+        if (char_ascent != m_char_ascent) {
                resize = true;
-               m_char_ascent = ascent;
+                m_char_ascent = char_ascent;
        }
-       if (descent != m_char_descent) {
+        if (char_descent != m_char_descent) {
                resize = true;
-               m_char_descent = descent;
+                m_char_descent = char_descent;
        }
-       m_line_thickness = MAX (MIN ((height - ascent) / 2, height / 14), 1);
+        if (memcmp(&char_spacing, &m_char_padding, sizeof(GtkBorder)) != 0) {
+                resize = true;
+                m_char_padding = char_spacing;
+        }
+        m_line_thickness = MAX (MIN (char_descent / 2, char_height / 14), 1);
         /* FIXME take these from pango_font_metrics_get_{underline,strikethrough}_{position,thickness} */
-       m_underline_position = MIN (ascent + m_line_thickness, height - m_line_thickness);
+        m_underline_position = MIN (char_spacing.top + char_ascent + m_line_thickness, cell_height - 
m_line_thickness);
         m_underline_thickness = m_line_thickness;
-       m_strikethrough_position =  ascent - height / 4;
+        m_strikethrough_position = char_spacing.top + char_ascent - char_height / 4;
         m_strikethrough_thickness = m_line_thickness;
-        m_regex_underline_position = height - 1;  /* FIXME */
+        m_regex_underline_position = char_spacing.top + char_height - 1;  /* FIXME */
         m_regex_underline_thickness = 1;  /* FIXME */
 
        /* Queue a resize if anything's changed. */
@@ -7590,14 +7603,22 @@ VteTerminalPrivate::ensure_font()
                        set_font_desc(m_unscaled_font_desc);
                }
                if (m_fontdirty) {
-                       gint width, height, ascent;
+                        int cell_width, cell_height;
+                        int char_ascent, char_descent;
+                        GtkBorder char_spacing;
                        m_fontdirty = FALSE;
                        _vte_draw_set_text_font (m_draw,
                                                  m_widget,
-                                       m_fontdesc);
+                                                 m_fontdesc,
+                                                 m_cell_width_scale,
+                                                 m_cell_height_scale);
                        _vte_draw_get_text_metrics (m_draw,
-                                                   &width, &height, &ascent);
-                       apply_font_metrics(width, height, ascent, height - ascent);
+                                                    &cell_width, &cell_height,
+                                                    &char_ascent, &char_descent,
+                                                    &char_spacing);
+                        apply_font_metrics(cell_width, cell_height,
+                                           char_ascent, char_descent,
+                                           char_spacing);
                }
        }
 }
@@ -7704,6 +7725,40 @@ VteTerminalPrivate::set_font_scale(gdouble scale)
         return true;
 }
 
+bool
+VteTerminalPrivate::set_cell_width_scale(double scale)
+{
+        /* FIXME: compare old and new scale in pixel space */
+        if (_vte_double_equal(scale, m_cell_width_scale))
+                return false;
+
+        m_cell_width_scale = scale;
+        /* Set the drawing font. */
+        m_fontdirty = TRUE;
+        if (widget_realized()) {
+                ensure_font();
+        }
+
+        return true;
+}
+
+bool
+VteTerminalPrivate::set_cell_height_scale(double scale)
+{
+        /* FIXME: compare old and new scale in pixel space */
+        if (_vte_double_equal(scale, m_cell_height_scale))
+                return false;
+
+        m_cell_height_scale = scale;
+        /* Set the drawing font. */
+        m_fontdirty = TRUE;
+        if (widget_realized()) {
+                ensure_font();
+        }
+
+        return true;
+}
+
 /* Read and refresh our perception of the size of the PTY. */
 void
 VteTerminalPrivate::refresh_size()
@@ -8034,6 +8089,7 @@ VteTerminalPrivate::VteTerminalPrivate(VteTerminal *t) :
        m_cell_height = 1;
        m_char_ascent = 1;
        m_char_descent = 1;
+       m_char_padding = {0, 0, 0, 0};
        m_line_thickness = 1;
        m_underline_position = 1;
         m_underline_thickness = 1;
@@ -8162,6 +8218,8 @@ VteTerminalPrivate::VteTerminalPrivate(VteTerminal *t) :
         m_unscaled_font_desc = nullptr;
         m_fontdesc = nullptr;
         m_font_scale = 1.;
+        m_cell_width_scale = 1.;
+        m_cell_height_scale = 1.;
        m_has_fonts = FALSE;
 
         m_allow_hyperlink = FALSE;
@@ -9574,7 +9632,8 @@ VteTerminalPrivate::paint_cursor()
        struct _vte_draw_text_request item;
         vte::grid::row_t drow;
         vte::grid::column_t col;
-       long width, height, cursor_width;
+        int width, height, cursor_width;
+        guint style = 0;
        guint fore, back;
        vte::color::rgb bg;
        int x, y;
@@ -9614,14 +9673,8 @@ VteTerminalPrivate::paint_cursor()
        item.columns = item.c == '\t' ? 1 : cell ? cell->attr.columns : 1;
        item.x = col * width;
        item.y = row_to_pixel(drow);
-       cursor_width = item.columns * width;
        if (cell && cell->c != 0) {
-               guint style;
-               gint cw;
                style = _vte_draw_get_style(cell->attr.bold, cell->attr.italic);
-               cw = _vte_draw_get_char_width (m_draw, cell->c,
-                                       cell->attr.columns, style);
-               cursor_width = MAX(cursor_width, cw);
        }
 
        selected = cell_is_selected(col, drow);
@@ -9634,33 +9687,65 @@ VteTerminalPrivate::paint_cursor()
         switch (decscusr_cursor_shape()) {
 
                case VTE_CURSOR_SHAPE_IBEAM: {
+                        /* Draw at the very left of the cell (before the spacing), even in case of CJK.
+                         * IMO (egmont) not overrunning the letter improves readability, vertical movement
+                         * looks good (no zigzag even when a somewhat wider glyph that starts filling up
+                         * the left spacing, or CJK that begins further to the right is encountered),
+                         * and also this is where it looks good if background colors change, including
+                         * Shift+arrows highlighting experience in some editors. As per the behavior of
+                         * word processors, don't increase the height by the line spacing. */
                         int stem_width;
 
-                        stem_width = (int) (((float) height) * m_cursor_aspect_ratio + 0.5);
-                        stem_width = CLAMP (stem_width, VTE_LINE_WIDTH, cursor_width);
+                        stem_width = (int) (((float) (m_char_ascent + m_char_descent)) * 
m_cursor_aspect_ratio + 0.5);
+                        stem_width = CLAMP (stem_width, VTE_LINE_WIDTH, m_cell_width);
 
                         _vte_draw_fill_rectangle(m_draw,
-                                                    x, y, stem_width, height,
+                                                 x, y + m_char_padding.top, stem_width, m_char_ascent + 
m_char_descent,
                                                  &bg, VTE_DRAW_OPAQUE);
                        break;
                 }
 
                case VTE_CURSOR_SHAPE_UNDERLINE: {
-                        int line_height;
+                        /* The width is at least the overall width of the cell (or two cells) minus the two
+                         * half spacings on the two edges. That is, underlines under a CJK are more than 
twice
+                         * as wide as narrow characters in case of letter spacing. Plus, if necessary, the 
width
+                         * is increased to span under the entire glyph. Vertical position is not affected by
+                         * line spacing. */
+
+                        int line_height, left, right;
 
                        /* use height (not width) so underline and ibeam will
                         * be equally visible */
-                        line_height = (int) (((float) height) * m_cursor_aspect_ratio + 0.5);
-                        line_height = CLAMP (line_height, VTE_LINE_WIDTH, height);
+                        line_height = (int) (((float) (m_char_ascent + m_char_descent)) * 
m_cursor_aspect_ratio + 0.5);
+                        line_height = CLAMP (line_height, VTE_LINE_WIDTH, m_char_ascent + m_char_descent);
+
+                        left = m_char_padding.left;
+                        right = item.columns * m_cell_width - m_char_padding.right;
+
+                        if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
+                                int l, r;
+                                _vte_draw_get_char_edges (m_draw, cell->c, cell->attr.columns, style, &l, 
&r);
+                                left = MIN(left, l);
+                                right = MAX(right, r);
+                        }
 
                         _vte_draw_fill_rectangle(m_draw,
-                                                    x, y + height - line_height,
-                                                 cursor_width, line_height,
+                                                 x + left, y + m_cell_height - m_char_padding.bottom - 
line_height,
+                                                 right - left, line_height,
                                                  &bg, VTE_DRAW_OPAQUE);
                        break;
                 }
 
                case VTE_CURSOR_SHAPE_BLOCK:
+                        /* Include the spacings in the cursor, see bug 781479 comments 39-44.
+                         * Make the cursor even wider if the glyph is wider. */
+
+                        cursor_width = item.columns * width;
+                        if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
+                                int r;
+                                _vte_draw_get_char_edges (m_draw, cell->c, cell->attr.columns, style, NULL, 
&r);
+                                cursor_width = MAX(cursor_width, r);
+                       }
 
                        if (focus) {
                                /* just reverse the character under the cursor */
@@ -9669,7 +9754,7 @@ VteTerminalPrivate::paint_cursor()
                                                          cursor_width, height,
                                                          &bg, VTE_DRAW_OPAQUE);
 
-                                if (cell && cell->c != 0 && cell->c != ' ') {
+                                if (cell && cell->c != 0 && cell->c != ' ' && cell->c != '\t') {
                                         draw_cells(
                                                         &item, 1,
                                                         fore, back, TRUE, FALSE,
diff --git a/src/vte/vteterminal.h b/src/vte/vteterminal.h
index 2cb3024..755dc8e 100644
--- a/src/vte/vteterminal.h
+++ b/src/vte/vteterminal.h
@@ -209,6 +209,18 @@ void vte_terminal_set_font_scale(VteTerminal *terminal,
 _VTE_PUBLIC
 gdouble vte_terminal_get_font_scale(VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
 
+_VTE_PUBLIC
+void vte_terminal_set_cell_width_scale(VteTerminal *terminal,
+                                       double scale) _VTE_GNUC_NONNULL(1);
+_VTE_PUBLIC
+double vte_terminal_get_cell_width_scale(VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
+
+_VTE_PUBLIC
+void vte_terminal_set_cell_height_scale(VteTerminal *terminal,
+                                        double scale) _VTE_GNUC_NONNULL(1);
+_VTE_PUBLIC
+double vte_terminal_get_cell_height_scale(VteTerminal *terminal) _VTE_GNUC_NONNULL(1);
+
 /* Set various on-off settings. */
 _VTE_PUBLIC
 void vte_terminal_set_audible_bell(VteTerminal *terminal,
diff --git a/src/vtedefines.hh b/src/vtedefines.hh
index 3afc532..89e6416 100644
--- a/src/vtedefines.hh
+++ b/src/vtedefines.hh
@@ -95,6 +95,8 @@
 
 #define VTE_FONT_SCALE_MIN (.25)
 #define VTE_FONT_SCALE_MAX (4.)
+#define VTE_CELL_SCALE_MIN (1.)
+#define VTE_CELL_SCALE_MAX (2.)
 
 /* Maximum length of a URI in the OSC 8 escape sequences. There's no de jure limit,
  * 2000-ish the de facto standard, and Internet Explorer supports 2083.
diff --git a/src/vtedraw.cc b/src/vtedraw.cc
index c713d44..c99d02e 100644
--- a/src/vtedraw.cc
+++ b/src/vtedraw.cc
@@ -241,7 +241,7 @@ struct font_info {
        struct unistr_info ascii_unistr_info[128];
        GHashTable *other_unistr_info;
 
-       /* cell metrics */
+        /* cell metrics as taken from the font, not yet scaled by cell_{width,height}_scale */
        gint width, height, ascent;
 
        /* reusable string for UTF-8 conversion */
@@ -399,7 +399,6 @@ font_info_measure_font (struct font_info *info)
         * info for them */
        font_info_cache_ascii (info);
 
-
        if (info->height == 0) {
                info->height = PANGO_PIXELS_CEIL (logical.height);
        }
@@ -412,7 +411,6 @@ font_info_measure_font (struct font_info *info)
                          info, info->width, info->height, info->ascent);
 }
 
-
 static struct font_info *
 font_info_allocate (PangoContext *context)
 {
@@ -755,6 +753,9 @@ guint _vte_draw_get_style(gboolean bold, gboolean italic) {
 
 struct _vte_draw {
        struct font_info *fonts[4];
+        /* cell metrics, already adjusted by cell_{width,height}_scale */
+        int cell_width, cell_height;
+        GtkBorder char_spacing;
 
        cairo_t *cr;
 };
@@ -836,7 +837,9 @@ _vte_draw_clear (struct _vte_draw *draw, gint x, gint y, gint width, gint height
 void
 _vte_draw_set_text_font (struct _vte_draw *draw,
                          GtkWidget *widget,
-                         const PangoFontDescription *fontdesc)
+                         const PangoFontDescription *fontdesc,
+                         double cell_width_scale,
+                         double cell_height_scale)
 {
        PangoFontDescription *bolddesc   = NULL;
        PangoFontDescription *italicdesc = NULL;
@@ -896,33 +899,73 @@ _vte_draw_set_text_font (struct _vte_draw *draw,
                font_info_destroy (draw->fonts[bold]);
                draw->fonts[bold] = draw->fonts[normal];
        }
+
+        /* Apply letter spacing and line spacing. */
+        draw->cell_width = draw->fonts[VTE_DRAW_NORMAL]->width * cell_width_scale;
+        draw->char_spacing.left = (draw->cell_width - draw->fonts[VTE_DRAW_NORMAL]->width) / 2;
+        draw->char_spacing.right = (draw->cell_width - draw->fonts[VTE_DRAW_NORMAL]->width + 1) / 2;
+        draw->cell_height = draw->fonts[VTE_DRAW_NORMAL]->height * cell_height_scale;
+        draw->char_spacing.top = (draw->cell_height - draw->fonts[VTE_DRAW_NORMAL]->height + 1) / 2;
+        draw->char_spacing.bottom = (draw->cell_height - draw->fonts[VTE_DRAW_NORMAL]->height) / 2;
 }
 
 void
 _vte_draw_get_text_metrics(struct _vte_draw *draw,
-                          gint *width, gint *height, gint *ascent)
+                           int *cell_width, int *cell_height,
+                           int *char_ascent, int *char_descent,
+                           GtkBorder *char_spacing)
 {
        g_return_if_fail (draw->fonts[VTE_DRAW_NORMAL] != NULL);
 
-       if (width)
-               *width  = draw->fonts[VTE_DRAW_NORMAL]->width;
-       if (height)
-               *height = draw->fonts[VTE_DRAW_NORMAL]->height;
-       if (ascent)
-               *ascent = draw->fonts[VTE_DRAW_NORMAL]->ascent;
+        if (cell_width)
+                *cell_width = draw->cell_width;
+        if (cell_height)
+                *cell_height = draw->cell_height;
+        if (char_ascent)
+                *char_ascent = draw->fonts[VTE_DRAW_NORMAL]->ascent;
+        if (char_descent)
+                *char_descent = draw->fonts[VTE_DRAW_NORMAL]->height - draw->fonts[VTE_DRAW_NORMAL]->ascent;
+        if (char_spacing)
+                *char_spacing = draw->char_spacing;
 }
 
 
-int
-_vte_draw_get_char_width (struct _vte_draw *draw, vteunistr c, int columns,
-                                                 guint style)
+/* Stores the left and right edges of the given glyph, relative to the cell's left edge. */
+void
+_vte_draw_get_char_edges (struct _vte_draw *draw, vteunistr c, int columns, guint style,
+                          int *left, int *right)
 {
-       struct unistr_info *uinfo;
+        int l, w, normal_width, fits_width;
+
+        if (G_UNLIKELY (draw->fonts[VTE_DRAW_NORMAL] == NULL)) {
+                if (left)
+                        *left = 0;
+                if (right)
+                        *right = 0;
+                return;
+        }
 
-       g_return_val_if_fail (draw->fonts[VTE_DRAW_NORMAL] != NULL, 0);
+        w = font_info_get_unistr_info (draw->fonts[style], c)->width;
+        normal_width = draw->fonts[VTE_DRAW_NORMAL]->width * columns;
+        fits_width = draw->cell_width * columns;
+
+        if (G_LIKELY (w <= normal_width)) {
+                /* The regular case: The glyph is not wider than one (CJK: two) regular character(s).
+                 * Align to the left, after applying half (CJK: one) letter spacing. */
+                l = draw->char_spacing.left + (columns == 2 ? draw->char_spacing.right : 0);
+        } else if (G_UNLIKELY (w <= fits_width)) {
+                /* Slightly wider glyph, but still fits in the cell (spacing included). This case can
+                 * only happen with nonzero letter spacing. Center the glyph in the cell(s). */
+                l = (fits_width - w) / 2;
+        } else {
+                /* Even wider glyph: doesn't fit in the cell. Align at left and overflow on the right. */
+                l = 0;
+        }
 
-       uinfo = font_info_get_unistr_info (draw->fonts[style], c);
-       return uinfo->width;
+        if (left)
+                *left = l;
+        if (right)
+                *right = l + w;
 }
 
 gboolean
@@ -947,9 +990,9 @@ _vte_draw_unichar_is_local_graphic(vteunistr c)
 static void
 _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color::rgb const* fg,
                                 gint x, gint y,
-                                gint column_width, gint columns, gint row_height)
+                                gint font_width, gint columns, gint font_height)
 {
-        gint width, xcenter, xright, ycenter, ybottom;
+        gint width, height, xcenter, xright, ycenter, ybottom;
         int upper_half, lower_half, left_half, right_half;
         int light_line_width, heavy_line_width;
         double adjust;
@@ -957,9 +1000,10 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
 
         cairo_save (cr);
 
-        width = column_width * columns;
-        upper_half = row_height / 2;
-        lower_half = row_height - upper_half;
+        width = draw->cell_width * columns;
+        height = draw->cell_height;
+        upper_half = height / 2;
+        lower_half = height - upper_half;
         left_half = width / 2;
         right_half = width - left_half;
 
@@ -971,7 +1015,8 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
            k_eights; \
         })
 
-        light_line_width = column_width / 5;
+        /* Exclude the spacing for line width computation. */
+        light_line_width = font_width / 5;
         light_line_width = MAX (light_line_width, 1);
 
         if (c >= 0x2550 && c <= 0x256c) {
@@ -983,7 +1028,7 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
         xcenter = x + left_half;
         ycenter = y + upper_half;
         xright = x + width;
-        ybottom = y + row_height;
+        ybottom = y + height;
 
         switch (c) {
 
@@ -1110,7 +1155,7 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
                                        upper_half - light_line_width / 2,
                                        upper_half - light_line_width / 2 + light_line_width,
                                        upper_half - heavy_line_width / 2 + heavy_line_width,
-                                       row_height};
+                                       height};
                 int xi, yi;
                 cairo_set_line_width(cr, 0);
                 for (yi = 4; yi >= 0; yi--) {
@@ -1145,7 +1190,7 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
                 const guint v = c - 0x2500;
                 int size, line_width;
 
-                size = (v & 2) ? row_height : width;
+                size = (v & 2) ? height : width;
 
                 switch (v >> 2) {
                 case 1: /* triple dash */
@@ -1178,7 +1223,7 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
                 cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT);
                 if (v & 2) {
                         cairo_move_to(cr, xcenter + adjust, y);
-                        cairo_line_to(cr, xcenter + adjust, y + row_height);
+                        cairo_line_to(cr, xcenter + adjust, y + height);
                 } else {
                         cairo_move_to(cr, x, ycenter + adjust);
                         cairo_line_to(cr, x + width, ycenter + adjust);
@@ -1202,7 +1247,7 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
                 adjust = (line_width & 1) ? .5 : 0.;
                 cairo_set_line_width(cr, line_width);
 
-                radius = (column_width + 2) / 3;
+                radius = (font_width + 2) / 3;
                 radius = MAX(radius, heavy_line_width);
 
                 if (v & 2) {
@@ -1273,9 +1318,9 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
                 /* Use the number of eights from the top, so that
                  * U+2584 aligns with U+2596..U+259f.
                  */
-                const int h = EIGHTS (row_height, 8 - v);
+                const int h = EIGHTS (height, 8 - v);
 
-                cairo_rectangle(cr, x, y + h, width, row_height - h);
+                cairo_rectangle(cr, x, y + h, width, height - h);
                 cairo_fill (cr);
                 break;
         }
@@ -1295,13 +1340,13 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
                  */
                 const int w = EIGHTS (width, 8 - v);
 
-                cairo_rectangle(cr, x, y, w, row_height);
+                cairo_rectangle(cr, x, y, w, height);
                 cairo_fill (cr);
                 break;
         }
 
         case 0x2590: /* right half block */
-                cairo_rectangle(cr, x + left_half, y, right_half, row_height);
+                cairo_rectangle(cr, x + left_half, y, right_half, height);
                 cairo_fill (cr);
                 break;
 
@@ -1313,13 +1358,13 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
                                        fg->green / 65535.,
                                        fg->blue / 65535.,
                                        (c - 0x2590) / 4.);
-                cairo_rectangle(cr, x, y, width, row_height);
+                cairo_rectangle(cr, x, y, width, height);
                 cairo_fill (cr);
                 break;
 
         case 0x2594: /* upper one eighth block */
         {
-                const int h = EIGHTS (row_height, 1); /* Align with U+2587 */
+                const int h = EIGHTS (height, 1); /* Align with U+2587 */
                 cairo_rectangle(cr, x, y, width, h);
                 cairo_fill (cr);
                 break;
@@ -1328,7 +1373,7 @@ _vte_draw_terminal_draw_graphic(struct _vte_draw *draw, vteunistr c, vte::color:
         case 0x2595: /* right one eighth block */
         {
                 const int w = EIGHTS (width, 7);  /* Align with U+2589 */
-                cairo_rectangle(cr, x + w, y, width - w, row_height);
+                cairo_rectangle(cr, x + w, y, width - w, height);
                 cairo_fill (cr);
                 break;
         }
@@ -1421,10 +1466,13 @@ _vte_draw_text_internal (struct _vte_draw *draw,
 
        for (i = 0; i < n_requests; i++) {
                vteunistr c = requests[i].c;
-               int x = requests[i].x;
-               int y = requests[i].y + font->ascent;
                struct unistr_info *uinfo = font_info_get_unistr_info (font, c);
                union unistr_font_info *ufi = &uinfo->ufi;
+                int x, y;
+
+                _vte_draw_get_char_edges(draw, c, requests[i].columns, style, &x, NULL);
+                x += requests[i].x;
+                y = requests[i].y + draw->char_spacing.top + font->ascent;
 
                 if (_vte_draw_unichar_is_local_graphic(c)) {
                         _vte_draw_terminal_draw_graphic(draw, c, color,
diff --git a/src/vtedraw.hh b/src/vtedraw.hh
index c45cb53..505d71b 100644
--- a/src/vtedraw.hh
+++ b/src/vtedraw.hh
@@ -63,11 +63,14 @@ void _vte_draw_clear(struct _vte_draw *draw,
 
 void _vte_draw_set_text_font(struct _vte_draw *draw,
                              GtkWidget *widget,
-                            const PangoFontDescription *fontdesc);
+                             const PangoFontDescription *fontdesc,
+                             double cell_width_scale, double cell_height_scale);
 void _vte_draw_get_text_metrics(struct _vte_draw *draw,
-                               gint *width, gint *height, gint *ascent);
-int _vte_draw_get_char_width(struct _vte_draw *draw, vteunistr c, int columns,
-                            guint style);
+                                int *cell_width, int *cell_height,
+                                int *char_ascent, int *char_descent,
+                                GtkBorder *char_spacing);
+void _vte_draw_get_char_edges (struct _vte_draw *draw, vteunistr c, int columns, guint style,
+                               int *left, int *right);
 gboolean _vte_draw_has_bold (struct _vte_draw *draw, guint style);
 
 void _vte_draw_text(struct _vte_draw *draw,
diff --git a/src/vtegtk.cc b/src/vtegtk.cc
index 6e9924b..1098bf5 100644
--- a/src/vtegtk.cc
+++ b/src/vtegtk.cc
@@ -436,6 +436,12 @@ vte_terminal_get_property (GObject *object,
                 case PROP_BACKSPACE_BINDING:
                         g_value_set_enum (value, impl->m_backspace_binding);
                         break;
+                case PROP_CELL_HEIGHT_SCALE:
+                        g_value_set_double (value, vte_terminal_get_cell_height_scale (terminal));
+                        break;
+                case PROP_CELL_WIDTH_SCALE:
+                        g_value_set_double (value, vte_terminal_get_cell_width_scale (terminal));
+                        break;
                 case PROP_CJK_AMBIGUOUS_WIDTH:
                         g_value_set_int (value, vte_terminal_get_cjk_ambiguous_width (terminal));
                         break;
@@ -537,6 +543,12 @@ vte_terminal_set_property (GObject *object,
                 case PROP_BACKSPACE_BINDING:
                         vte_terminal_set_backspace_binding (terminal, (VteEraseBinding)g_value_get_enum 
(value));
                         break;
+                case PROP_CELL_HEIGHT_SCALE:
+                        vte_terminal_set_cell_height_scale (terminal, g_value_get_double (value));
+                        break;
+                case PROP_CELL_WIDTH_SCALE:
+                        vte_terminal_set_cell_width_scale (terminal, g_value_get_double (value));
+                        break;
                 case PROP_CJK_AMBIGUOUS_WIDTH:
                         vte_terminal_set_cjk_ambiguous_width (terminal, g_value_get_int (value));
                         break;
@@ -889,8 +901,10 @@ vte_terminal_class_init(VteTerminalClass *klass)
          * @width: the new character cell width
          * @height: the new character cell height
          *
-         * Emitted whenever selection of a new font causes the values of the
-         * %char_width or %char_height fields to change.
+         * Emitted whenever the cell size changes, e.g. due to a change in
+         * font, font-scale or cell-width/height-scale.
+         *
+         * Note that this signal should rather be called "cell-size-changed".
          */
         signals[SIGNAL_CHAR_SIZE_CHANGED] =
                 g_signal_new(I_("char-size-changed"),
@@ -1302,6 +1316,34 @@ vte_terminal_class_init(VteTerminalClass *klass)
                                    (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
 
         /**
+         * VteTerminal:cell-height-scale:
+         *
+         * Scale factor for the cell height, to increase line spacing. (The font's height is not affected.)
+         *
+         * Since: 0.52
+         */
+        pspecs[PROP_CELL_HEIGHT_SCALE] =
+                g_param_spec_double ("cell-height-scale", NULL, NULL,
+                                     VTE_CELL_SCALE_MIN,
+                                     VTE_CELL_SCALE_MAX,
+                                     1.,
+                                     (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
+
+        /**
+         * VteTerminal:cell-width-scale:
+         *
+         * Scale factor for the cell width, to increase letter spacing. (The font's width is not affected.)
+         *
+         * Since: 0.52
+         */
+        pspecs[PROP_CELL_WIDTH_SCALE] =
+                g_param_spec_double ("cell-width-scale", NULL, NULL,
+                                     VTE_CELL_SCALE_MIN,
+                                     VTE_CELL_SCALE_MAX,
+                                     1.,
+                                     (GParamFlags) (G_PARAM_READWRITE | G_PARAM_STATIC_STRINGS | 
G_PARAM_EXPLICIT_NOTIFY));
+
+        /**
          * VteTerminal:cjk-ambiguous-width:
          *
          * This setting controls whether ambiguous-width characters are narrow or wide
@@ -2971,6 +3013,9 @@ vte_terminal_set_backspace_binding(VteTerminal *terminal,
  * @terminal: a #VteTerminal
  *
  * Returns: the height of a character cell
+ *
+ * Note that this method should rather be called vte_terminal_get_cell_height,
+ * because the return value takes cell-height-scale into account.
  */
 glong
 vte_terminal_get_char_height(VteTerminal *terminal)
@@ -2984,6 +3029,9 @@ vte_terminal_get_char_height(VteTerminal *terminal)
  * @terminal: a #VteTerminal
  *
  * Returns: the width of a character cell
+ *
+ * Note that this method should rather be called vte_terminal_get_cell_width,
+ * because the return value takes cell-width-scale into account.
  */
 glong
 vte_terminal_get_char_width(VteTerminal *terminal)
@@ -3511,6 +3559,84 @@ vte_terminal_set_font_scale(VteTerminal *terminal,
                 g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_FONT_SCALE]);
 }
 
+/**
+ * vte_terminal_get_cell_height_scale:
+ * @terminal: a #VteTerminal
+ *
+ * Returns: the terminal's cell height scale
+ *
+ * Since: 0.52
+ */
+double
+vte_terminal_get_cell_height_scale(VteTerminal *terminal)
+{
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), 1.);
+
+        return IMPL(terminal)->m_cell_height_scale;
+}
+
+/**
+ * vte_terminal_set_cell_height_scale:
+ * @terminal: a #VteTerminal
+ * @scale: the cell height scale
+ *
+ * Sets the terminal's cell height scale to @scale.
+ *
+ * This can be used to increase the line spacing. (The font's height is not affected.)
+ * Valid values go from 1.0 (default) to 2.0 ("double spacing").
+ *
+ * Since: 0.52
+ */
+void
+vte_terminal_set_cell_height_scale(VteTerminal *terminal,
+                                   double scale)
+{
+        g_return_if_fail(VTE_IS_TERMINAL(terminal));
+
+        scale = CLAMP(scale, VTE_CELL_SCALE_MIN, VTE_CELL_SCALE_MAX);
+        if (IMPL(terminal)->set_cell_height_scale(scale))
+                g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_CELL_HEIGHT_SCALE]);
+}
+
+/**
+ * vte_terminal_get_cell_width_scale:
+ * @terminal: a #VteTerminal
+ *
+ * Returns: the terminal's cell width scale
+ *
+ * Since: 0.52
+ */
+double
+vte_terminal_get_cell_width_scale(VteTerminal *terminal)
+{
+        g_return_val_if_fail(VTE_IS_TERMINAL(terminal), 1.);
+
+        return IMPL(terminal)->m_cell_width_scale;
+}
+
+/**
+ * vte_terminal_set_cell_width_scale:
+ * @terminal: a #VteTerminal
+ * @scale: the cell width scale
+ *
+ * Sets the terminal's cell width scale to @scale.
+ *
+ * This can be used to increase the letter spacing. (The font's width is not affected.)
+ * Valid values go from 1.0 (default) to 2.0.
+ *
+ * Since: 0.52
+ */
+void
+vte_terminal_set_cell_width_scale(VteTerminal *terminal,
+                                  double scale)
+{
+        g_return_if_fail(VTE_IS_TERMINAL(terminal));
+
+        scale = CLAMP(scale, VTE_CELL_SCALE_MIN, VTE_CELL_SCALE_MAX);
+        if (IMPL(terminal)->set_cell_width_scale(scale))
+                g_object_notify_by_pspec(G_OBJECT(terminal), pspecs[PROP_CELL_WIDTH_SCALE]);
+}
+
 /* Just some arbitrary minimum values */
 #define MIN_COLUMNS (16)
 #define MIN_ROWS    (2)
diff --git a/src/vtegtk.hh b/src/vtegtk.hh
index d1ade94..ea970a4 100644
--- a/src/vtegtk.hh
+++ b/src/vtegtk.hh
@@ -67,6 +67,8 @@ enum {
         PROP_ALLOW_HYPERLINK,
         PROP_AUDIBLE_BELL,
         PROP_BACKSPACE_BINDING,
+        PROP_CELL_HEIGHT_SCALE,
+        PROP_CELL_WIDTH_SCALE,
         PROP_CJK_AMBIGUOUS_WIDTH,
         PROP_CURSOR_BLINK_MODE,
         PROP_CURSOR_SHAPE,
diff --git a/src/vteinternal.hh b/src/vteinternal.hh
index e9c18b4..bff62e0 100644
--- a/src/vteinternal.hh
+++ b/src/vteinternal.hh
@@ -573,9 +573,27 @@ public:
         PangoFontDescription *m_fontdesc;
         gdouble m_font_scale;
         gboolean m_fontdirty;
-        glong m_char_ascent;
-        glong m_char_descent;
-        /* dimensions of character cells */
+
+        /* First, the dimensions of ASCII characters are measured. The result
+         * could probably be called char_{width,height} or font_{width,height}
+         * but these aren't stored directly here, not to accidentally be confused
+         * with m_cell_{width_height}. The values are stored in vtedraw's font_info.
+         *
+         * Then in case of nondefault m_cell_{width,height}_scale an additional
+         * m_char_padding is added, resulting in m_cell_{width,height} which are
+         * hence potentially larger than the characters. This is to implement
+         * line spacing and letter spacing, primarly for accessibility (bug 781479).
+         *
+         * Char width/height, if really needed, can be computed by subtracting
+         * the char padding from the cell dimensions. Char height can also be
+         * reconstructed from m_char_{ascent,descent}, one of which is redundant,
+         * stored for convenience only.
+         */
+        long m_char_ascent;
+        long m_char_descent;
+        double m_cell_width_scale;
+        double m_cell_height_scale;
+        GtkBorder m_char_padding;
         glong m_cell_width;
         glong m_cell_height;
 
@@ -1002,10 +1020,11 @@ public:
 
         void ensure_font();
         void update_font();
-        void apply_font_metrics(int width,
-                                int height,
-                                int ascent,
-                                int descent);
+        void apply_font_metrics(int cell_width,
+                                int cell_height,
+                                int char_ascent,
+                                int char_descent,
+                                GtkBorder char_spacing);
 
         void refresh_size();
         void screen_set_size(VteScreen *screen_,
@@ -1197,6 +1216,8 @@ public:
         bool set_allow_hyperlink(bool setting);
         bool set_backspace_binding(VteEraseBinding binding);
         bool set_background_alpha(double alpha);
+        bool set_cell_height_scale(double scale);
+        bool set_cell_width_scale(double scale);
         bool set_cjk_ambiguous_width(int width);
         void set_color_background(vte::color::rgb const &color);
         void set_color_bold(vte::color::rgb const& color);



[Date Prev][Date Next]   [Thread Prev][Thread Next]   [Thread Index] [Date Index] [Author Index]