Subject: bug#40845: SVG rendering issues

From: Pip Cet <[email-masked]>
Date: Sat, 25 Apr 2020 19:41:31 +0000

On Sat, Apr 25, 2020 at 6:07 PM Eli Zaretskii <[email-masked]> wrote:
or these
> Then I guess I don't understand your implementation at all.

My use case is to include a glyph which is supposed to look like a
character, but doesn't actually have a unicode codepoint. (I'm sorry
if this differs from the use cases you're exclusively concerned with,
but it appeared to be relevant enough to Clément's problem that I
assumed he was having a similar issue).

That means that we want to use an image spec, not a character in a
font; but that image spec depends on face/font properties, because we
want to blend in with surrounding text. The most obvious ones are
foreground and background color and size, but slant and weight would
also affect properly-rendered character-like images.

It seems fairly obvious to me that it's a bad idea to do all the work
in the display engine or in C code: sub-pixel rendering and
anti-aliasing are hard to get right. A character-like glyph might need
a third color which provides sufficient contrast to the foreground and
background colors, and that color space calculation is complicated
enough to be moved to Lisp.

So I moved it to Lisp: there's Lisp code which is passed the font/face
properties, and returns an image spec that's appropriate for that.

The attached patch actually applies to and works with master, and
includes an example. It can no longer easily be demonstrated to do the
right thing when the same buffer is displayed in different frames,
because Emacs currently applies text scaling per buffer rather than
per frame (IMHO, that's a bug). It also doesn't properly display the
cursor as a block cursor when it's over the glyph, because I can no
longer find the code which allowed me to tell, from Lisp, whether that
was the case.

And of course it doesn't use the right font metrics, because these
currently aren't exposed to Lisp.

But all these limitations are fixable.
From 8e7aa38e098bd044a71fa20df1593d2b37347f1c Mon Sep 17 00:00:00 2001
From: Pip Cet <[email-masked]>
Date: Sat, 25 Apr 2020 18:41:02 +0000
Subject: [PATCH] support generated display specs

---
 generated-image-specs.el | 31 ++++++++++++++++++++++++++++
 src/callint.c            |  1 +
 src/xdisp.c              | 44 ++++++++++++++++++++++++++++++++++++++++
 src/xfaces.c             |  2 +-
 4 files changed, 77 insertions(+), 1 deletion(-)
 create mode 100644 generated-image-specs.el

diff --git a/generated-image-specs.el b/generated-image-specs.el
new file mode 100644
index 0000000000..b764d1999d
--- /dev/null
+++ b/generated-image-specs.el
@@ -0,0 +1,31 @@
+(require 'svg)
+
+(defun generate-circle-image (alist)
+  (let* ((vsize (font-get (alist-get :font alist) :size))
+         (foreground (apply #'color-rgb-to-hex (nconc (color-name-to-rgb (alist-get :foreground alist)) (list 2))))
+         (background (apply #'color-rgb-to-hex (nconc (color-name-to-rgb (alist-get :background alist)) (list 2))))
+         (hsize (* vsize .8))
+         (svg (svg-create hsize vsize))
+         (sw (* .1 hsize)))
+    (svg-rectangle svg 0 0 hsize vsize :fill background)
+    (svg-circle svg (* hsize .5)
+                (- (* .8 vsize) (* .5 hsize))
+                (- (* hsize .5) sw)
+                :fill "none"
+                :stroke foreground
+                :stroke-width sw)
+    (svg-line svg
+              (* hsize .5) (- (* .8 vsize) (* .5 hsize))
+              (* hsize .5) (- (* .8 vsize) sw)
+              :stroke foreground
+              :stroke-width sw)
+    (svg-circle svg (* hsize .5) (- (* .8 vsize) (* (/ 2.0 3.0) hsize))
+                sw
+                :fill foreground
+                :stroke "none")
+    (let ((ret (svg-image svg)))
+      (setcdr ret (plist-put (cdr ret) :scale 1.0))
+      (setcdr ret (plist-put (cdr ret) :ascent 80))
+      ret)))
+
+(insert (propertize " " 'display `(generated-spec ,#'generate-circle-image)))
diff --git a/src/callint.c b/src/callint.c
index eb916353a0..2c34dddbe4 100644
--- a/src/callint.c
+++ b/src/callint.c
@@ -826,6 +826,7 @@ syms_of_callint (void)
   DEFSYM (Qlet, "let");
   DEFSYM (Qif, "if");
   DEFSYM (Qwhen, "when");
+  DEFSYM (Qgenerated_spec, "generated-spec");
   DEFSYM (Qletx, "let*");
   DEFSYM (Qsave_excursion, "save-excursion");
   DEFSYM (Qprogn, "progn");
diff --git a/src/xdisp.c b/src/xdisp.c
index 3258893956..b341cbb735 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -5050,6 +5050,7 @@ handle_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
 #endif
       && !EQ (XCAR (spec), Qspace)
       && !EQ (XCAR (spec), Qwhen)
+      && !EQ (XCAR (spec), Qgenerated_spec)
       && !EQ (XCAR (spec), Qslice)
       && !EQ (XCAR (spec), Qspace_width)
       && !EQ (XCAR (spec), Qheight)
@@ -5148,6 +5149,8 @@ display_prop_end (struct it *it, Lisp_Object object, struct text_pos start_pos)
    Value is non-zero if something was found which replaces the display
    of buffer or string text.  */
 
+extern Lisp_Object *lface_id_to_name;
+
 static int
 handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
 			    Lisp_Object overlay, struct text_pos *position,
@@ -5159,6 +5162,47 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
   struct text_pos start_pos = *position;
   void *itdata = NULL;
 
+  if (it != NULL &&
+      CONSP (spec) &&
+      EQ (XCAR (spec), Qgenerated_spec))
+    {
+      spec = XCDR (spec);
+      if (!CONSP (spec))
+	return 0;
+      Lisp_Object gen = XCAR (spec);
+      struct face *face = FACE_FROM_ID (it->f, it->face_id);
+      Lisp_Object lface = Qnil;
+      Lisp_Object props[] = {
+	QCtype,
+	QCfamily,
+	QCfoundry,
+	QCwidth,
+	QCheight,
+	QCweight,
+	QCslant,
+	QCunderline,
+	QCinverse_video,
+	QCforeground,
+	QCbackground,
+	QCstipple,
+	QCoverline,
+	QCstrike_through,
+	QCbox,
+	QCfont,
+	QCinherit,
+	QCfontset,
+	QCdistant_foreground,
+	QCextend,
+      };
+      for (int i = 0; i < LFACE_VECTOR_SIZE; i++)
+	lface = Fcons (Fcons (props[i], face->lface[i]),
+		       lface);
+      Lisp_Object font = Qnil;
+      XSETFONT (font, face->font);
+      lface = Fcons (Fcons (QCfont, font), lface);
+      spec = safe_call1 (gen, lface);
+    }
+
   /* If SPEC is a list of the form `(when FORM . VALUE)', evaluate FORM.
      If the result is non-nil, use VALUE instead of SPEC.  */
   form = Qt;
diff --git a/src/xfaces.c b/src/xfaces.c
index bab142ade0..01429ac86e 100644
--- a/src/xfaces.c
+++ b/src/xfaces.c
@@ -310,7 +310,7 @@ #define FACE_CACHE_BUCKETS_SIZE 1001
 
 /* A vector mapping Lisp face Id's to face names.  */
 
-static Lisp_Object *lface_id_to_name;
+Lisp_Object *lface_id_to_name;
 static ptrdiff_t lface_id_to_name_size;
 
 #ifdef HAVE_WINDOW_SYSTEM
-- 
2.26.2