Subject: bug#76390: [PATCH] Center lines vertically with line-spacing-vertical-center

From: Daniel Mendler <[email-masked]>
Date: Tue, 18 Feb 2025 11:41:26 +0100

Tags: patch

A few years ago there was a discussion on emacs-devel started by Jesse
Nazario about vertically centering text for line-spacing > 0. A patch
was provided back then by Jesse. I hope to revive the discussion and I
like to provide an updated patch.

Centering text would be particularly useful if the text has a background
color (e.g., hl-line or hi-lock) as demonstrated by the following steps:

1. (setq-local line-spacing 0.5)
2. (hl-line-mode 1)
3. M-x hi-lock-face-symbol-at-point on a symbol at point

The proposal was to add a new variable `line-spacing-vertical-center' to
enable text centering. Maybe it makes sense to use a more general
approach which allows finer control over the alignment, e.g., a variable
`line-spacing-align' which can be set to a floating point number in the
interval [0.0, 1.0]. The value 0.5 means to center the text, 0.0 means
to align the text at the top of the line and 1.0 means to align the text
at the bottom of the line. Fine control over the alignment allows to to
adjust to fine differences of the font and its baseline alignment.

The old discussion can be found at:

https://lists.gnu.org/archive/html/emacs-devel/2019-09/msg00660.html

I attached the latest patch from the old discussion to this report.

>From 9bc5cbb727e5dcac9f774028858e2608d1a23033 Mon Sep 17 00:00:00 2001
From: Jesse Nazario <address@hidden>
Date: Sun, 8 Sep 2019 20:06:15 -0300
Subject: [PATCH] Center lines vertically with line-spacing-vertical-center

When using line-spacing, the new variable line-spacing-vertical-center
can be set to non-nil to center the line content vertically.
---
 doc/lispref/display.texi |  5 +++++
 etc/NEWS                 |  4 ++++
 src/buffer.c             | 14 ++++++++++++++
 src/buffer.h             |  4 ++++
 src/xdisp.c              | 10 +++++++++-
 5 files changed, 36 insertions(+), 1 deletion(-)

diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index fd6820897f..875c50c6ef 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -2195,6 +2195,11 @@ Line Height
 number of pixels put below lines.  A floating-point number specifies
 the spacing relative to the frame's default line height.
 
+  The @code{line-spacing} parameter creates the space by putting some
+margin only below the line. If you wish to center the line vertically
+instead, you can set the variable @code{line-spacing-vertical-center}
+as non-nin.
+
 @vindex line-spacing
   You can specify the line spacing for all lines in a buffer via the
 buffer-local @code{line-spacing} variable.  An integer specifies
diff --git a/etc/NEWS b/etc/NEWS
index cb8b6fcac1..3209f2535f 100644
--- a/etc/NEWS
+++ b/etc/NEWS
@@ -445,6 +445,10 @@ RGB triplets with a single hexadecimal digit per component.
 ---
 ** The toolbar now shows the equivalent key binding in its tooltips.
 
+** New variable line-spacing-vertical-center.
+This variable, if non-nil, centers the line content vertically when
+using the 'line-spacing' variable.
+
 
 * Editing Changes in Emacs 27.1
 
diff --git a/src/buffer.c b/src/buffer.c
index 77e8b6bb77..854b3924c0 100644
--- a/src/buffer.c
+++ b/src/buffer.c
@@ -219,6 +219,11 @@ bset_extra_line_spacing (struct buffer *b, Lisp_Object val)
   b->extra_line_spacing_ = val;
 }
 static void
+bset_line_spacing_vertical_center (struct buffer *b, Lisp_Object val)
+{
+  b->line_spacing_vertical_center_ = val;
+}
+static void
 bset_file_format (struct buffer *b, Lisp_Object val)
 {
   b->file_format_ = val;
@@ -962,6 +967,8 @@ reset_buffer (register struct buffer *b)
     (b, BVAR (&buffer_defaults, enable_multibyte_characters));
   bset_cursor_type (b, BVAR (&buffer_defaults, cursor_type));
   bset_extra_line_spacing (b, BVAR (&buffer_defaults, extra_line_spacing));
+  bset_line_spacing_vertical_center (b, BVAR (&buffer_defaults,
+                                              line_spacing_vertical_center));
 
   b->display_error_modiff = 0;
 }
@@ -5196,6 +5203,7 @@ init_buffer_once (void)
   XSETFASTINT (BVAR (&buffer_local_flags, header_line_format), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_type), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, extra_line_spacing), idx); ++idx;
+  XSETFASTINT (BVAR (&buffer_local_flags, line_spacing_vertical_center), idx); ++idx;
   XSETFASTINT (BVAR (&buffer_local_flags, cursor_in_non_selected_windows), idx); ++idx;
 
   /* buffer_local_flags contains no pointers, so it's safe to treat it
@@ -5265,6 +5273,7 @@ init_buffer_once (void)
   bset_bidi_paragraph_separate_re (&buffer_defaults, Qnil);
   bset_cursor_type (&buffer_defaults, Qt);
   bset_extra_line_spacing (&buffer_defaults, Qnil);
+  bset_line_spacing_vertical_center (&buffer_defaults, Qnil);
   bset_cursor_in_non_selected_windows (&buffer_defaults, Qt);
 
   bset_enable_multibyte_characters (&buffer_defaults, Qt);
@@ -6254,6 +6263,11 @@ from (abs POSITION).  If POSITION is positive, point was at the front
 If value is a floating point number, it specifies the spacing relative
 to the default frame line height.  A value of nil means add no extra space.  */);
 
+  DEFVAR_PER_BUFFER ("line-spacing-vertical-center",
+		     &BVAR (current_buffer, line_spacing_vertical_center), Qnil,
+                     doc: /* Non-nil means center the line content vertically
+when using `line-spacing' variable.  */);
+
   DEFVAR_PER_BUFFER ("cursor-in-non-selected-windows",
 		     &BVAR (current_buffer, cursor_in_non_selected_windows), Qnil,
 		     doc: /* Non-nil means show a cursor in non-selected windows.
diff --git a/src/buffer.h b/src/buffer.h
index 280d4e9098..d7cb27305e 100644
--- a/src/buffer.h
+++ b/src/buffer.h
@@ -549,6 +549,10 @@ #define BVAR(buf, field) ((buf)->field ## _)
      in the display of this buffer.  */
   Lisp_Object extra_line_spacing_;
 
+  /* Non-nil means center the line content vertically. To be used
+     along with `line-spacing'.  */
+  Lisp_Object line_spacing_vertical_center_;
+
   /* Cursor type to display in non-selected windows.
      t means to use hollow box cursor.
      See `cursor-type' for other values.  */
diff --git a/src/xdisp.c b/src/xdisp.c
index 95895ec3ac..e2c7309f32 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -29307,7 +29307,15 @@ gui_produce_glyphs (struct it *it)
 
   if (extra_line_spacing > 0)
     {
-      it->descent += extra_line_spacing;
+      if (! NILP (BVAR (XBUFFER (it->w->contents),
+                        line_spacing_vertical_center)))
+        it->descent += extra_line_spacing;
+      else
+        {
+          int spacing = extra_line_spacing / 2;
+          it->ascent += spacing;
+          it->descent += spacing;
+        }
       if (extra_line_spacing > it->max_extra_line_spacing)
 	it->max_extra_line_spacing = extra_line_spacing;
     }
-- 
2.23.0