unofficial mirror of emacs-devel@gnu.org 
 help / color / mirror / code / Atom feed
* Emacs canvas support
       [not found] <875zdikdge.fsf.ref@yahoo.com>
@ 2020-04-29  6:34 ` Po Lu via Emacs development discussions.
  2020-04-29  8:24   ` Eli Zaretskii
  2020-04-29 20:48   ` Juri Linkov
  0 siblings, 2 replies; 53+ messages in thread
From: Po Lu via Emacs development discussions. @ 2020-04-29  6:34 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 301 bytes --]


I'd appreciate some feedback on something I came up with during my spare
time: Emacs canvas support.

For now it only works on X11 + Cairo builds, and I haven't quite figured
out how to make redisplay work reliably on canvases, but it already
seems to be quite promising.  A patch is attached below.

[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: Canvas patche --]
[-- Type: text/x-patch, Size: 61624 bytes --]

diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index e53f0e9f60..2a24576620 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -27,6 +27,7 @@ Display
 * Window Dividers::     Separating windows visually.
 * Display Property::    Images, margins, text size, etc.
 * Images::              Displaying images in Emacs buffers.
+* Canvases::            Drawing areas inside Emacs buffers.
 * Xwidgets::            Displaying native widgets in Emacs buffers.
 * Buttons::             Adding clickable buttons to Emacs buffers.
 * Abstract Display::    Emacs's Widget for Object Collections.
@@ -6509,6 +6510,152 @@ Image Cache
 debugging.
 @end defvar
 
+@node Canvases
+@cindex drawing canvases
+@cindex drawing areas
+@cindex canvases
+@section Canvases
+
+This chapter describes canvases, objects that can store drawing operations
+which are then displayed inside buffer text.
+
+@menu
+* Creating Canvases::           How canvases can be created
+* Operating on Canvases::       How canvases can be used
+* Displaying Canvases::         How canvases can be displayed
+@end menu
+
+@node Creating Canvases
+@cindex creating Canvases
+@pindex make-canvas
+
+  This section describes how canvases can be created.
+To create a canvas, call the function @code{make-canvas}.
+
+@defun make-canvas width height
+This function takes 2 arguments @code{width}, and @code{height},
+and creates a canvas @code{width} wide and @code{height} tall.
+@end defun
+
+@defun canvas-from-image image &optional width height
+This function creates a canvas from the image descriptor
+@code{image}.  The created canvas will be @code{width} wide,
+and @code{height} tall, if specified.
+@end defun
+
+@node Operating on Canvases
+@cindex operating on Canvases
+@pindex canvas-rectangle
+
+  This section describes how canvases can be drawn to,
+and manipulated.
+
+@defun canvasp canvas
+Return whether @code{canvas} is a canvas or not.
+@end defun
+
+@defun canvas-dimensions canvas
+Return the dimensions of @code{canvas} as a pair.
+@end defun
+
+@defun canvas-ellipse canvas x y width height &optional color
+       hollow opacity
+Draw an ellipse centred upon @code{x}, @code{y} onto the canvas
+@code{canvas}. The drawn ellipse will be colored @code{color},
+or the current frame's foreground color if @code{color} is not
+specified or nil.  The opacity of the drawn item will be
+@code{opacity}, and the item will be hollow if @code{hollow} is
+non-nil.
+@end defun
+
+@defun canvas-rectangle canvas x y width height &optional color
+       hollow opacity
+Draw a rectangle at @code{x}, and @code{y} onto the canvas
+@code{canvas}.  The rectangle will be colored @code{color},
+or the current frame's foreground color if @code{color} is nil.
+The opacity of the drawn item will be @code{opacity}, and the item
+will be hollow if @code{hollow} is non-nil.
+@end defun
+
+@defun canvas-fill-pixel canvas x y color opacity
+Fill the pixel at @code{x}, @code{y} inside @code{canvas}
+to @code{color}, with the opacity @code{opacity}.
+@end defun
+
+@defun canvas-draw-string canvas x y string
+       &optional color opacity family size
+Draw the string @code{string} to @code{x}, @code{y}
+inside the canvas @code{canvas}.  The font family used
+will be @code{family}, the color @code{color}, the opacity
+@code{opacity}, and the size @code{size}.
+
+@code{family} can either be a string, or a list in which
+the first element should be the family as a string, the
+second element should be whether the font should be italic,
+and an optional third argument describing whether or not
+the font should be bold.
+@end defun
+
+@defun canvas-draw-image canvas image-spec x y
+       &optional width height frame opacity
+Paint @code{image-spec} into @code{canvas} at @code{x},
+@code{y}.  If @code{width} or @code{height}
+is set and the image is wider than @code{width} or @code{height} respectively,
+the image will be cropped to fit.  The alpha channel of @code{image-spec}
+will be set to @code{opacity}.
+@end defun
+
+@defun canvas-measure-string canvas string &optional family size
+Return a cons pair containing the width and height of @code{string},
+when rendered onto @code{canvas}, with the font @code{family} at
+@code{size}.
+@end defun
+
+@defun canvas-rounded-rectangle canvas x y width height radius
+       &optional color hollow opacity
+Draw a rounded rectangle at @code{x}, @code{y} onto @code{canvas}.
+The opacity of the rectangle will be @code{opacity}.
+The radius of the rectangle will be @code{radius}.
+@end defun
+
+@defun canvas-pixel-at canvas x y
+Return the pixel at @code{x}, @code{y} inside @code{canvas},
+as an ARGB list.
+@end defun
+
+@defun canvas-draw-canvas canvas canvas2 x y &optional width height opacity
+Draw @code{canvas2} onto @code{canvas} at @code{x}, @code{y}.
+@code{canvas2}'s alpha channel will be set to @code{opacity},
+if specified.
+@code{canvas2} will not be taller than @code{height} or wider than
+@code{width}, if specified.
+@end defun
+
+@defun canvas-width canvas
+Return the width of @code{canvas}.
+@end defun
+
+@defun canvas-height canvas
+Return the height of @code{canvas}.
+@end defun
+
+@defun canvas-region canvas x y width height
+Return a subsection of @code{canvas} at @code{x},
+@code{y}, that is @code{width} wide and @code{height} tall.
+@end defun
+
+@defun canvas-arc canvas x y radius angle1 angle2 &optional color opacity
+Draw an arc at @code{x}, @code{y}, with a radius of @code{radius},
+and the angles @code{angle1}, @code{angle2}.
+@end defun
+
+@node Displaying Canvases
+@cindex displaying Canvases
+
+  Canvases can be displayed by setting them as the
+@code{display} property of a string.
+
+
 @node Xwidgets
 @section Embedded Native Widgets
 @cindex xwidget
diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi
index bba1b63115..d995ec4606 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -1430,6 +1430,7 @@ Top
 * Window Dividers::         Separating windows visually.
 * Display Property::        Enabling special display features.
 * Images::                  Displaying images in Emacs buffers.
+* Canvases::                Drawing areas inside Emacs buffers.
 * Buttons::                 Adding clickable buttons to Emacs buffers.
 * Abstract Display::        Emacs's Widget for Object Collections.
 * Blinking::                How Emacs shows the matching open parenthesis.
diff --git a/lisp/canvas.el b/lisp/canvas.el
new file mode 100644
index 0000000000..d60d3efa4f
--- /dev/null
+++ b/lisp/canvas.el
@@ -0,0 +1,49 @@
+;;; canvas.el --- Canvas support for GNU Emacs
+
+;; Copyright (C) 2020 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; These are several utlity functions for canvas operations that can
+;;; be implemented in Lisp code.
+
+\f
+;;; Code:
+
+(defun canvas-fill-pixel (canvas x y color opacity)
+  "Set the pixel at X, Y inside CANVAS to COLOR, with the opacity OPACITY."
+  (canvas-rectangle canvas x y 1 1 color opacity))
+
+(defun canvas-from-image (image &optional width height)
+  "Create a canvas from IMAGE.
+The canvas will be no wider than WIDTH (if specified),
+and no taller than HEIGHT (if specified)."
+  (let ((canvas (make-canvas (or width (car (image-size image t)))
+                             (or height (cdr (image-size image t))))))
+    (prog1 canvas (canvas-draw-image canvas image 0 0))))
+
+(defun canvas-width (canvas)
+  "Return the width of CANVAS."
+  (car (canvas-dimensions canvas)))
+
+(defun canvas-height (canvas)
+  "Return the height of CANVAS."
+  (cdr (canvas-dimensions canvas)))
+
+(provide 'canvas)
+;;; canvas.el ends here
diff --git a/lisp/loadup.el b/lisp/loadup.el
index 97525b2708..1a9b1c7410 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -285,6 +285,8 @@
 (load "emacs-lisp/tabulated-list")
 (load "buff-menu")
 
+(load "canvas")
+
 (if (fboundp 'x-create-frame)
     (progn
       (load "fringe")
diff --git a/src/Makefile.in b/src/Makefile.in
index 552dd2e50a..0d70c68b04 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -407,6 +407,7 @@ .m.o:
 ## be dumped as pure by dump-emacs.
 base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o $(XMENU_OBJ) window.o \
 	charset.o coding.o category.o ccl.o character.o chartab.o bidi.o \
+	common-canvas.o \
 	$(CM_OBJ) term.o terminal.o xfaces.o $(XOBJ) $(GTK_OBJ) $(DBUS_OBJ) \
 	emacs.o keyboard.o macros.o keymap.o sysdep.o \
 	bignum.o buffer.o filelock.o insdel.o marker.o \
diff --git a/src/alloc.c b/src/alloc.c
index cc9ba8dbf5..acce0109e1 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -47,6 +47,7 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2020 Free Software
 #include "blockinput.h"
 #include "pdumper.h"
 #include "termhooks.h"		/* For struct terminal.  */
+#include "canvas.h"
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -3114,6 +3115,11 @@ cleanup_vector (struct Lisp_Vector *vector)
       module_finalize_function (function);
     }
 #endif
+  else if (PSEUDOVECTOR_TYPEP (&vector->header, PVEC_CANVAS))
+    {
+      struct canvas *cnvs = (struct canvas *) vector;
+      destroy_canvas_contents (cnvs->canvas);
+    }
 }
 
 /* Reclaim space used by unmarked vectors.  */
diff --git a/src/canvas.h b/src/canvas.h
new file mode 100644
index 0000000000..c42a15e6fd
--- /dev/null
+++ b/src/canvas.h
@@ -0,0 +1,83 @@
+/* Canvas support for GNU Emacs.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include "lisp.h"
+#include "frame.h"
+#include "window.h"
+
+#ifdef USE_CAIRO
+#include <cairo/cairo.h>
+typedef cairo_surface_t *canvas_contents_t;
+#else
+typedef void *canvas_contents_t;
+#endif
+
+struct canvas
+{
+  union vectorlike_header header;
+  Lisp_Object cnvs_objects;
+  Lisp_Object window;
+  Lisp_Object object;
+
+  canvas_contents_t canvas;
+  int width, height;
+  bool multiple_objects_seen;
+  bool changed_since_last_redisplay;
+} GCALIGNED_STRUCT;
+
+/* Test for xwidget pseudovector.  */
+#define CANVASP(x) PSEUDOVECTORP (x, PVEC_CANVAS)
+#define XCANVAS(a) \
+  (eassert (CANVASP (a)), XUNTAG (a, Lisp_Vectorlike, struct canvas))
+
+#define CHECK_CANVAS(x) CHECK_TYPE (CANVASP (x), Qcanvasp, x)
+
+#define MARK_CANVAS_CHANGED(x)                                              \
+  (((x)->changed_since_last_redisplay = true),                              \
+   (windows_or_buffers_changed = 2), (redisplay ()));                       \
+  do                                                                        \
+    {                                                                       \
+      if ((x)->multiple_objects_seen || !EQ (selected_window, (x)->window)) \
+        {                                                                   \
+          Lisp_Object tail, head;                                           \
+          FOR_EACH_FRAME (tail, head)                                       \
+          {                                                                 \
+            struct frame *f = XFRAME (head);                                \
+            SET_FRAME_GARBAGED (f);                                         \
+          }                                                                 \
+        }                                                                   \
+    }                                                                       \
+  while (false)
+
+extern Lisp_Object
+make_canvas (int width, int height);
+
+extern canvas_contents_t
+make_canvas_contents (int width, int height);
+
+extern void
+destroy_canvas_contents (canvas_contents_t contents);
+
+extern void
+syms_of_canvas (void);
+
+extern void
+canvas_end_redisplay (struct window *w,
+		      struct glyph_matrix *matrix);
diff --git a/src/common-canvas.c b/src/common-canvas.c
new file mode 100644
index 0000000000..501f4930f7
--- /dev/null
+++ b/src/common-canvas.c
@@ -0,0 +1,856 @@
+/* Canvas support for GNU Emacs.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "canvas.h"
+#include "coding.h"
+
+#include <math.h>
+
+Lisp_Object
+make_canvas (int width, int height)
+{
+#ifndef USE_CAIRO
+  error ("Canvases are not supported without cairo.")
+#endif
+  struct canvas *canvas = ALLOCATE_PSEUDOVECTOR
+    (struct canvas, object, PVEC_CANVAS);
+  canvas->width = width;
+  canvas->height = height;
+  canvas->object = Qnil;
+  canvas->multiple_objects_seen = false;
+  canvas->canvas = make_canvas_contents (width, height);
+  canvas->changed_since_last_redisplay = true;
+
+  Lisp_Object cnvs;
+  XSETCANVAS (cnvs, canvas);
+  return cnvs;
+}
+
+void
+destroy_canvas_contents (canvas_contents_t contents)
+{
+#ifdef USE_CAIRO
+  cairo_surface_destroy (contents);
+#else
+  error ("Not implemented");
+#endif
+}
+
+canvas_contents_t
+make_canvas_contents (int width, int height)
+{
+#ifdef USE_CAIRO
+  cairo_surface_t *crs =
+    cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+  return crs;
+#else
+  error ("Not implemented");
+#endif
+}
+
+void
+canvas_end_redisplay (struct window *w,
+		      struct glyph_matrix *matrix)
+{
+  int i;
+  int area;
+
+  for (i = 0; i < matrix->nrows; ++i)
+    {
+      struct glyph_row *row;
+      row = MATRIX_ROW (matrix, i);
+      if (row->enabled_p)
+        for (area = LEFT_MARGIN_AREA; area < LAST_AREA; ++area)
+          {
+            struct glyph *glyph = row->glyphs[area];
+            struct glyph *glyph_end = glyph + row->used[area];
+            for (; glyph < glyph_end; ++glyph)
+              if (glyph->type == CANVAS_GLYPH &&
+		  glyph->u.canvas->changed_since_last_redisplay)
+                {
+		  canvas_update_glyph
+		    (w, i, row, area, row->glyphs[area] -
+		     glyph, 1 + row->glyphs[area] - glyph,
+		     glyph);
+                }
+          }
+    }
+}
+
+DEFUN ("make-canvas", Fmake_canvas, Smake_canvas, 2, 2, 0,
+       doc: /* Create a canvas, WIDTH pixels wide, and HEIGHT pixels tall. */)
+  (Lisp_Object width, Lisp_Object height)
+{
+  check_integer_range (height, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+
+  return make_canvas (XFIXNUM (width), XFIXNUM (height));
+}
+
+DEFUN ("canvas-ellipse", Fcanvas_ellipse, Scanvas_ellipse, 5, 8, 0,
+       doc: /* Draw a WIDTH wide and HEIGHT tall ellipse centred at X, Y in CANVAS.
+The color of the ellipse will be COLOR (or the foreground color of COLOR is nil).
+The ellipse will be hollow if HOLLOW is non-nil.
+The opacity of the circle will be OPACITY, which should be a floating-point
+number between 1 and 0. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height, Lisp_Object color,
+   Lisp_Object hollow, Lisp_Object opacity)
+{
+    if (NILP (opacity))
+    opacity = make_float (1.0);
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+  check_integer_range (height, 0, INT_MAX);
+  CHECK_NUMBER (opacity);
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))->query_colors
+	(XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+	lg = Fnth (make_fixnum (1), color_values),
+	lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  struct canvas *cv = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (cv->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  cairo_matrix_t save_matrix;
+  cairo_get_matrix (cr, &save_matrix);
+  cairo_translate (cr, XFIXNUM (x), XFIXNUM (y));
+  cairo_scale (cr, 1, 1);
+  cairo_new_path (cr);
+  cairo_arc (cr, 0, 0,
+             XFIXNUM (width) / 2.0, 0, 2 * M_PI);
+  cairo_set_matrix (cr, &save_matrix);
+
+  cairo_set_line_width (cr, canvas_stroke_width);
+  if (!NILP (hollow))
+    cairo_stroke (cr);
+  else
+    cairo_fill (cr);
+
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (cv);
+#endif
+  return Qnil;
+}
+
+DEFUN ("canvasp", Fcanvasp, Scanvasp, 1, 1, 0,
+       doc: /* Return t if CANVAS is a canvas, else nil.  */)
+  (Lisp_Object canvas)
+{
+  return CANVASP (canvas) ? Qt : Qnil;
+}
+
+DEFUN ("canvas-rectangle", Fcanvas_rectangle, Scanvas_rectangle, 5, 8, 0,
+       doc: /* Draw a WIDTH wide and HEIGHT tall rectangle at X, Y in CANVAS.
+The color of the rectangle will be COLOR (or the foreground color of COLOR is nil).
+The rectangle will be hollow if HOLLOW is non-nil.
+The opacity of the circle will be OPACITY, which should be a floating-point
+number between 1 and 0. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height, Lisp_Object color,
+   Lisp_Object hollow, Lisp_Object opacity)
+{
+  if (NILP (opacity))
+    opacity = make_float (1.0);
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+  check_integer_range (height, 0, INT_MAX);
+  CHECK_NUMBER (opacity);
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))->query_colors
+	(XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+	lg = Fnth (make_fixnum (1), color_values),
+	lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  struct canvas *cv = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (cv->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  if (NILP (hollow))
+    {
+      cairo_rectangle (cr, XFIXNUM (x), XFIXNUM (y), XFIXNUM (width),
+		       XFIXNUM (height));
+      cairo_fill (cr);
+    }
+  else
+    {
+      cairo_rectangle (cr, XFIXNUM (x), XFIXNUM (y), XFIXNUM (width),
+		       XFIXNUM (height));
+      cairo_stroke (cr);
+    }
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (cv);
+  return Qnil;
+#else
+  error ("Not implemented");
+#endif
+}
+
+DEFUN ("canvas-rounded-rectangle", Fcanvas_rounded_rectangle, Scanvas_rounded_rectangle, 6, MANY, 0,
+       doc: /* Draw a WIDTH wide and HEIGHT tall rectangle at X, Y in CANVAS.
+The color of the rectangle will be COLOR (or the foreground color of COLOR is nil).
+The rectangle will be hollow if HOLLOW is non-nil.
+The opacity of the circle will be OPACITY, which should be a floating-point
+number between 1 and 0.
+usage: (canvas-rounded-rectangle canvas x y width height radius &optional color hollow opacity)  */)
+  (ptrdiff_t nargs, Lisp_Object *args)
+{
+  Lisp_Object canvas, x, y, width, height, radius, color, hollow, opacity;
+  canvas = args[0];
+  x = args[1];
+  y = args[2];
+  width = args[3];
+  height = args[4];
+  radius = args[5];
+  color = Qnil;
+  hollow = Qnil;
+  opacity = Qnil;
+
+  if (nargs > 6)
+    color = args[6];
+  if (nargs > 7)
+    hollow = args[7];
+  if (nargs > 8)
+    opacity = args[8];
+  if (nargs > 9)
+    xsignal2 (Qwrong_number_of_arguments,
+	      Qcanvas_rounded_rectangle,
+	      make_fixnum (nargs));
+  if (NILP (opacity))
+    opacity = make_float (1.0);
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+  check_integer_range (height, 0, INT_MAX);
+  CHECK_NUMBER (opacity);
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))->query_colors
+	(XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+	lg = Fnth (make_fixnum (1), color_values),
+	lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  struct canvas *cv = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (cv->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  {
+#define radius XFLOATINT (radius)
+    double degrees = M_PI / 180.0;
+#define width XFIXNUM (width)
+#define height XFIXNUM (height)
+#define x XFIXNUM (x)
+#define y XFIXNUM (y)
+    cairo_new_sub_path (cr);
+    cairo_arc (cr, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees);
+    cairo_arc (cr, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees);
+    cairo_arc (cr, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees);
+    cairo_arc (cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
+    cairo_close_path (cr);
+#undef radius
+#undef y
+#undef x
+#undef height
+#undef width
+  }
+#undef radius
+  cairo_set_line_width (cr, canvas_stroke_width);
+  if (NILP (hollow))
+    cairo_fill (cr);
+  else
+    cairo_stroke (cr);
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (cv);
+  return Qnil;
+#else
+  error ("Not implemented");
+#endif
+}
+
+DEFUN ("canvas-draw-string", Fcanvas_draw_string, Scanvas_draw_string, 4, 8, 0,
+       doc: /* Draw the string STRING onto the canvas CANVAS at X, Y.
+The opacity of the drawn text will be OPACITY, and the color of the drawn text will be COLOR.
+The font-family used will be FAMILY, which can be a string or a list of
+the font-family as a string, whether or not the the text should be italic,
+and whether or not the text should be bold.
+The size of the text will be SIZE, or the default text size if nil. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y, Lisp_Object string,
+   Lisp_Object color, Lisp_Object opacity, Lisp_Object family, Lisp_Object size)
+{
+  if (noninteractive)
+    error ("`canvas-draw-string' cannot be called when running in batch mode.");
+  if (NILP (opacity))
+    opacity = make_float (1.0);
+  if (NILP (size))
+    size = make_fixnum (FRAME_TEXT_HEIGHT (XFRAME (selected_frame)));
+  if (NILP (family))
+    family = build_string ("monospace");
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  CHECK_NUMBER (opacity);
+  if (!Flistp (family))
+    CHECK_STRING (family);
+  CHECK_STRING (string);
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))->query_colors
+	(XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+	lg = Fnth (make_fixnum (1), color_values),
+	lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  struct canvas *c = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (c->canvas);
+  cairo_font_slant_t slant_flags = CAIRO_FONT_SLANT_NORMAL;
+  cairo_font_weight_t weight_flags = CAIRO_FONT_WEIGHT_NORMAL;
+  const char *family_utf8;
+  if (!NILP (Flistp (family)))
+    {
+      if (!NILP (CALLN (Flss, Flength (family), make_fixnum (2))))
+	error ("Invalid font spec");
+      else
+	{
+	  int length = XFIXNUM (Flength (family));
+	  if (length > 3)
+	    error ("Invalid font spec");
+	  Lisp_Object italic, bold;
+	  CHECK_STRING_CAR (family);
+	  family_utf8 = SSDATA (ENCODE_UTF_8 (XCAR (family)));
+	  if (length == 3)
+	    bold = XCAR (XCDR (XCDR (family)));
+	  else
+	    bold = Qnil;
+	  italic = XCAR (XCDR (family));
+	  if (!NILP (italic))
+	    slant_flags = CAIRO_FONT_SLANT_ITALIC;
+	  if (!NILP (bold))
+	    weight_flags = CAIRO_FONT_WEIGHT_BOLD;
+	}
+    }
+  else
+    {
+      family_utf8 = SSDATA (ENCODE_UTF_8 (family));
+    }
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  cairo_select_font_face (cr, family_utf8,
+			  slant_flags,
+			  weight_flags);
+  cairo_set_font_size (cr, XFIXNUM (size));
+  cairo_move_to (cr, XFIXNUM (x), XFIXNUM (y) + XFIXNUM (size));
+  cairo_show_text (cr, SSDATA (ENCODE_UTF_8 (string)));
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (c);
+  return Qnil;
+#else
+  return Qnil;
+#endif
+}
+
+DEFUN ("canvas-measure-string", Fcanvas_measure_string, Scanvas_measure_string, 2, 4, 0,
+       doc: /* Return a pair containing the width and height of STRING,
+if drawn on CANVAS with the family FAMILY at SIZE.  */)
+(Lisp_Object canvas, Lisp_Object string, Lisp_Object family, Lisp_Object size)
+{
+  if (noninteractive)
+    error ("`canvas-draw-string' cannot be called when running in batch mode.");
+  if (NILP (size))
+    size = make_fixnum (FRAME_TEXT_HEIGHT (XFRAME (selected_frame)));
+  if (NILP (family))
+    family = build_string ("cairo:monospace");
+  CHECK_CANVAS (canvas);
+
+  if (!Flistp (family))
+    CHECK_STRING (family);
+  CHECK_STRING (string);
+#ifdef USE_CAIRO
+  struct canvas *c = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (c->canvas);
+  cairo_font_slant_t slant_flags = CAIRO_FONT_SLANT_NORMAL;
+  cairo_font_weight_t weight_flags = CAIRO_FONT_WEIGHT_NORMAL;
+  const char *family_utf8;
+  if (!NILP (Flistp (family)))
+    {
+      if (!NILP (CALLN (Flss, Flength (family), make_fixnum (2))))
+	error ("Invalid font spec");
+      else
+	{
+	  int length = XFIXNUM (Flength (family));
+	  if (length > 3)
+	    error ("Invalid font spec");
+	  Lisp_Object italic, bold;
+	  CHECK_STRING_CAR (family);
+	  family_utf8 = SSDATA (ENCODE_UTF_8 (XCAR (family)));
+	  if (length == 3)
+	    bold = XCAR (XCDR (XCDR (family)));
+	  else
+	    bold = Qnil;
+	  italic = XCAR (XCDR (family));
+	  if (!NILP (italic))
+	    slant_flags = CAIRO_FONT_SLANT_ITALIC;
+	  if (!NILP (bold))
+	    weight_flags = CAIRO_FONT_WEIGHT_BOLD;
+	}
+    }
+  else
+    {
+      family_utf8 = SSDATA (ENCODE_UTF_8 (family));
+    }
+  cairo_select_font_face (cr, family_utf8,
+			  slant_flags,
+			  weight_flags);
+  cairo_set_font_size (cr, XFIXNUM (size));
+  cairo_text_extents_t extents;
+  cairo_text_extents (cr, SSDATA (ENCODE_UTF_8 (string)), &extents);
+  cairo_destroy (cr);
+  return Fcons (make_fixnum (extents.width),
+		make_fixnum (extents.height));
+#else
+  return Qnil;
+#endif
+}
+
+DEFUN ("canvas-draw-image", Fcanvas_draw_image, Scanvas_draw_image, 4, 8,
+       0, doc: /* Paint IMAGE_SPEC onto CANVAS, at X, Y.
+If WIDTH or HEIGHT is set, and IMAGE is wider than WIDTH or taller than HEIGHT,
+IMAGE_SPEC will be cropped to fit WIDTH and/or HEIGHT respectively.
+FRAME should be a live frame.
+The opacity of the drawn image will be OPACITY.  */)
+  (Lisp_Object canvas, Lisp_Object image_spec,
+   Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height,
+   Lisp_Object frame, Lisp_Object opacity)
+{
+  if (valid_image_p (image_spec))
+    {
+      if (NILP (frame))
+	frame = selected_frame;
+      struct frame *f = decode_window_system_frame (frame);
+      ptrdiff_t id = lookup_image (f, image_spec);
+      struct image *img = IMAGE_FROM_ID (f, id);
+      if (!img)
+	return Qnil;
+      if (img->load_failed_p)
+	return Qnil;
+      int iwidth = img->width + 2 * img->hmargin;
+      int iheight = img->height + 2 * img->vmargin;
+      CHECK_CANVAS (canvas);
+      check_integer_range (x, 0, INT_MAX);
+      check_integer_range (y, 0, INT_MAX);
+      if (NILP (width))
+	width = make_fixnum (iwidth);
+      check_integer_range (width, 0, INT_MAX);
+      if (NILP (height))
+	height = make_fixnum (iheight);
+      check_integer_range (height, 0, INT_MAX);
+      if (NILP (width))
+	width = make_fixnum (iwidth);
+      if (NILP (opacity))
+	opacity = make_fixnum (1);
+      CHECK_NUMBER (opacity);
+#ifdef USE_CAIRO
+      cairo_surface_t *crs
+        = cairo_image_surface_create_for_data ((unsigned char *)
+                                                 img->pixmap->data,
+                                               (img->pixmap->bits_per_pixel
+                                                    == 32
+                                                  ? CAIRO_FORMAT_RGB24
+                                                  : CAIRO_FORMAT_A8),
+                                               img->pixmap->width,
+                                               img->pixmap->height,
+                                               img->pixmap->bytes_per_line);
+      struct canvas *cv = XCANVAS (canvas);
+      cairo_t *cr = cairo_create (cv->canvas);
+      cairo_save (cr);
+      cairo_translate (cr, XFIXNUM (x), XFIXNUM (y));
+      cairo_rectangle (cr, 0, 0,
+		       XFIXNUM (width), XFIXNUM (height));
+      cairo_clip (cr);
+      cairo_set_source_surface (cr, crs, 0, 0);
+      cairo_paint_with_alpha (cr, XFLOATINT (opacity));
+      cairo_restore (cr);
+      cairo_destroy (cr);
+      cairo_surface_destroy (crs);
+      MARK_CANVAS_CHANGED (cv);
+#endif
+    }
+  else
+    error ("Invalid image specification");
+  return Qnil;
+}
+
+DEFUN ("canvas-arc", Fcanvas_arc, Scanvas_arc, 6, 8, 0,
+       doc: /* Draw an arc on CANVAS starting from XC, YC,
+with a radius of RADIUS and 2 angles angle1 and angle2.
+Use the color COLOR with the alpha channel set to OPACITY, if specified. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y, Lisp_Object radius,
+   Lisp_Object angle1, Lisp_Object angle2, Lisp_Object color, Lisp_Object opacity)
+{
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  CHECK_NUMBER (angle1);
+  CHECK_NUMBER (angle2);
+  CHECK_NUMBER (radius);
+
+  if (NILP (opacity))
+    opacity = make_fixnum (1.0);
+  CHECK_NUMBER (opacity);
+
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))
+        ->query_colors (XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+                  lg = Fnth (make_fixnum (1), color_values),
+                  lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  cairo_t *cr = cairo_create (XCANVAS (canvas)->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  cairo_arc (cr, XFLOATINT (x), XFLOATINT (y), XFLOATINT (radius),
+	     XFLOATINT (angle1), XFLOATINT (angle2));
+  cairo_stroke (cr);
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (XCANVAS (canvas));
+#else
+#endif
+  return Qnil;
+}
+
+DEFUN ("canvas-filled-arc", Fcanvas_filled_arc, Scanvas_filled_arc, 6, 8, 0,
+       doc: /* Draw a filled arc on CANVAS starting from XC, YC,
+with a radius of RADIUS and 2 angles angle1 and angle2.
+Use the color COLOR with the alpha channel set to OPACITY, if specified. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y, Lisp_Object radius,
+   Lisp_Object angle1, Lisp_Object angle2, Lisp_Object color, Lisp_Object opacity)
+{
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  CHECK_NUMBER (angle1);
+  CHECK_NUMBER (angle2);
+  CHECK_NUMBER (radius);
+
+  if (NILP (opacity))
+    opacity = make_fixnum (1.0);
+  CHECK_NUMBER (opacity);
+
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))
+        ->query_colors (XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+                  lg = Fnth (make_fixnum (1), color_values),
+                  lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  cairo_t *cr = cairo_create (XCANVAS (canvas)->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  cairo_arc (cr, XFLOATINT (x), XFLOATINT (y), XFLOATINT (radius),
+	     XFLOATINT (angle1), XFLOATINT (angle2));
+  cairo_fill (cr);
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (XCANVAS (canvas));
+#else
+#endif
+  return Qnil;
+}
+
+DEFUN ("canvas-region", Fcanvas_region, Scanvas_region, 5, 5, 0,
+       doc: /* Return a canvas containing a WIDTH wide and HEIGHT tall
+subsection of CANVAS at X, Y */)
+  (Lisp_Object canvas, Lisp_Object x,
+   Lisp_Object y, Lisp_Object width, Lisp_Object height)
+{
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+  check_integer_range (height, 0, INT_MAX);
+
+#ifdef USE_CAIRO
+  int ix = XFIXNUM (x),
+    iy = XFIXNUM (y),
+    iw = XFIXNUM (width),
+    ih = XFIXNUM (height);
+  cairo_surface_t *s = cairo_surface_create_for_rectangle
+    (XCANVAS (canvas)->canvas, ix, iy, iw, ih);
+  Lisp_Object newcvs = make_canvas (iw, ih);
+  struct canvas *target = XCANVAS (newcvs);
+  cairo_t *t = cairo_create (target->canvas);
+  cairo_set_source_surface (t, s, 0, 0);
+  cairo_paint (t);
+  cairo_destroy (t);
+  cairo_surface_destroy (s);
+  return newcvs;
+#else
+  error ("Not implemented.")
+#endif
+}
+
+
+DEFUN ("canvas-draw-canvas", Fcanvas_draw_canvas, Scanvas_draw_canvas, 4, 7,
+       0, doc: /* Paint CANVAS2 onto CANVAS, at X, Y.
+If WIDTH or HEIGHT is set, and IMAGE is wider than WIDTH or taller than HEIGHT,
+IMAGE_SPEC will be cropped to fit WIDTH and/or HEIGHT respectively.
+The opacity of the drawn image will be OPACITY.  */)
+  (Lisp_Object canvas, Lisp_Object canvas2,
+   Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height, Lisp_Object opacity)
+{
+  CHECK_CANVAS (canvas);
+  CHECK_CANVAS (canvas2);
+  int iwidth = XCANVAS (canvas2)->width;
+  int iheight = XCANVAS (canvas2)->height;
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  if (NILP (width))
+    width = make_fixnum (iwidth);
+  check_integer_range (width, 0, INT_MAX);
+  if (NILP (height))
+    height = make_fixnum (iheight);
+  check_integer_range (height, 0, INT_MAX);
+  if (NILP (width))
+    width = make_fixnum (iwidth);
+  if (NILP (opacity))
+    opacity = make_fixnum (1);
+  CHECK_NUMBER (opacity);
+
+#ifdef USE_CAIRO
+  cairo_surface_t *crs = XCANVAS (canvas2)->canvas;
+  struct canvas *cv = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (cv->canvas);
+  cairo_save (cr);
+  cairo_translate (cr, XFIXNUM (x), XFIXNUM (y));
+  cairo_rectangle (cr, 0, 0, XFIXNUM (width), XFIXNUM (height));
+  cairo_clip (cr);
+  cairo_set_source_surface (cr, crs, 0, 0);
+  cairo_paint_with_alpha (cr, XFLOATINT (opacity));
+  cairo_restore (cr);
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (cv);
+#endif
+  return Qnil;
+}
+
+DEFUN ("canvas-pixel-at", Fcanvas_pixel_at, Scanvas_pixel_at, 3, 3, 0,
+       doc: /* Return the color of the pixel at X, Y inside CANVAS as an ARGB list. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y)
+{
+#ifndef USE_CAIRO
+  error ("Not implemented.");
+#else
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, XCANVAS (canvas)->width);
+  check_integer_range (y, 0, XCANVAS (canvas)->height);
+  struct {
+#ifdef WORDS_BIGENDIAN
+    uint8_t a, r, g, b;
+#else
+    uint8_t b, g, r, a;
+#endif
+  } *argb32 =
+    (void *) cairo_image_surface_get_data (XCANVAS (canvas)->canvas);
+  typeof (*argb32) res = argb32 [XFIXNUM (y) * XCANVAS (canvas)->width +
+				 XFIXNUM (x)];
+  return CALLN (Flist, make_fixnum (res.a),
+		make_fixnum (res.r),
+		make_fixnum (res.g),
+		make_fixnum (res.b));
+#endif
+}
+
+DEFUN ("canvas-dimensions", Fcanvas_dimensions, Scanvas_dimensions, 1, 1, 0,
+       doc: /* Return a cons pair containing the width and height of CANVAS. */)
+  (Lisp_Object canvas)
+{
+  CHECK_CANVAS (canvas);
+  return Fcons (make_fixnum (XCANVAS (canvas)->width),
+		make_fixnum (XCANVAS (canvas)->height));
+}
+
+void
+syms_of_canvas (void)
+{
+  defsubr (&Smake_canvas);
+  defsubr (&Scanvas_rectangle);
+  defsubr (&Scanvas_ellipse);
+  defsubr (&Scanvas_rectangle);
+  defsubr (&Scanvas_draw_string);
+  defsubr (&Scanvas_draw_image);
+  defsubr (&Scanvas_draw_canvas);
+  defsubr (&Scanvas_measure_string);
+  defsubr (&Scanvas_dimensions);
+  defsubr (&Scanvas_region);
+  defsubr (&Scanvas_pixel_at);
+  defsubr (&Scanvas_arc);
+  defsubr (&Scanvasp);
+  defsubr (&Scanvas_filled_arc);
+  defsubr (&Scanvas_rounded_rectangle);
+  DEFSYM (Qcanvas_rounded_rectangle, "canvas-rounded-rectangle");
+  DEFSYM (Qcanvasp, "canvasp");
+  DEFSYM (Qcolor_values, "color-values");
+  DEFVAR_INT ("canvas-stroke-width", canvas_stroke_width,
+	      doc: /* The stroke width to be used in canvases. */);
+  canvas_stroke_width = 4;
+}
diff --git a/src/data.c b/src/data.c
index bce2e53cfb..aefd6d7e70 100644
--- a/src/data.c
+++ b/src/data.c
@@ -263,6 +263,8 @@ DEFUN ("type-of", Ftype_of, Stype_of, 1, 1, 0,
           return Qxwidget;
         case PVEC_XWIDGET_VIEW:
           return Qxwidget_view;
+	case PVEC_CANVAS:
+	  return Qcanvas;
         /* "Impossible" cases.  */
 	case PVEC_MISC_PTR:
         case PVEC_OTHER:
@@ -3859,6 +3861,7 @@ #define PUT_ERROR(sym, tail, msg)			\
   DEFSYM (Qchar_table, "char-table");
   DEFSYM (Qbool_vector, "bool-vector");
   DEFSYM (Qhash_table, "hash-table");
+  DEFSYM (Qcanvas, "canvas");
   DEFSYM (Qthread, "thread");
   DEFSYM (Qmutex, "mutex");
   DEFSYM (Qcondition_variable, "condition-variable");
diff --git a/src/dispextern.h b/src/dispextern.h
index 0b1f3d14ae..d33b87c3df 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -382,7 +382,10 @@ #define SET_GLYPH_FROM_GLYPH_CODE(glyph, gc)				\
   STRETCH_GLYPH,
 
   /* Glyph is an external widget drawn by the GUI toolkit.  */
-  XWIDGET_GLYPH
+  XWIDGET_GLYPH,
+
+  /* Glyph is a canvas.  */
+  CANVAS_GLYPH
 };
 
 
@@ -540,6 +543,9 @@ #define FACE_ID_BITS	20
     struct xwidget *xwidget;
 #endif
 
+    /* Canvas reference (type == CANVAS_GLYPH).  */
+    struct canvas *canvas;
+
     /* Sub-structure for type == STRETCH_GLYPH.  */
     struct
     {
@@ -1405,6 +1411,9 @@ #define OVERLAPS_ERASED_CURSOR 	(1 << 2)
   /* Xwidget.  */
   struct xwidget *xwidget;
 
+  /* Canvas.  */
+  struct canvas *canvas;
+
   /* Slice */
   struct glyph_slice slice;
 
@@ -2158,7 +2167,10 @@ #define MAX_FRINGE_BITMAPS (1<<FRINGE_ID_BITS)
   IT_CONTINUATION,
 
   /* Xwidget.  */
-  IT_XWIDGET
+  IT_XWIDGET,
+
+  /* Canvas.  */
+  IT_CANVAS
 };
 
 
@@ -2223,6 +2235,7 @@ #define MAX_FRINGE_BITMAPS (1<<FRINGE_ID_BITS)
   GET_FROM_IMAGE,
   GET_FROM_STRETCH,
   GET_FROM_XWIDGET,
+  GET_FROM_CANVAS,
   NUM_IT_METHODS
 };
 
@@ -2447,6 +2460,10 @@ #define OVERLAY_STRING_CHUNK_SIZE 16
       struct {
 	Lisp_Object object;
       } xwidget;
+      /* method == GET_FROM_CANVAS */
+      struct {
+	Lisp_Object object;
+      } canvas;
     } u;
 
     /* Current text and display positions.  */
@@ -2578,6 +2595,9 @@ #define OVERLAY_STRING_CHUNK_SIZE 16
   /* If what == IT_XWIDGET.  */
   struct xwidget *xwidget;
 
+  /* If what == IT_CANVAS.  */
+  struct canvas *canvas;
+
   /* Values from `slice' property.  */
   struct it_slice slice;
 
@@ -3714,6 +3734,11 @@ #define IMAGE_BACKGROUND_TRANSPARENT(img, f, mask)			      \
 
 #endif /* HAVE_WINDOW_SYSTEM */
 
+extern void
+canvas_update_glyph (struct window *w, int x, struct glyph_row *row,
+		     enum glyph_row_area area, ptrdiff_t start, ptrdiff_t end,
+		     struct glyph *glyph);
+
 INLINE_HEADER_END
 
 #endif /* not DISPEXTERN_H_INCLUDED */
diff --git a/src/dispnew.c b/src/dispnew.c
index 5b6fa51a56..1ba639cb79 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -43,6 +43,7 @@ Copyright (C) 1985-1988, 1993-1995, 1997-2020 Free Software Foundation,
 #include "tparam.h"
 #include "xwidget.h"
 #include "pdumper.h"
+#include "canvas.h"
 
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
@@ -3697,6 +3698,7 @@ update_window (struct window *w, bool force_p)
 #endif
 
   xwidget_end_redisplay (w, w->current_matrix);
+  canvas_end_redisplay (w, w->current_matrix);
   clear_glyph_matrix (desired_matrix);
 
   return paused_p;
@@ -3782,6 +3784,7 @@ gui_update_window_end (struct window *w, bool cursor_on_p,
     FRAME_RIF (f)->update_window_end_hook (w,
                                            cursor_on_p,
                                            mouse_face_overwritten_p);
+  canvas_end_redisplay (w, w->current_matrix);
 }
 
 #endif /* HAVE_WINDOW_SYSTEM  */
@@ -4371,6 +4374,11 @@ scrolling_window (struct window *w, int tab_line_p)
     return 0;
 #endif
 
+  /* We need this to fix canvas movement detection in a reliable way.
+     FIXME. */
+  if (w->have_canvas_p)
+    return 0;
+
   /* Give up if some rows in the desired matrix are not enabled.  */
   if (! MATRIX_ROW_ENABLED_P (desired_matrix, i))
     return -1;
@@ -5565,6 +5573,10 @@ mode_line_string (struct window *w, enum window_part part,
 	      y0 -= row->ascent - glyph->ascent;
 	    }
 #endif
+	  if (glyph->type == CANVAS_GLYPH)
+	    {
+	      y0 -= row->ascent - glyph->ascent;
+	    }
 	}
       else
 	{
@@ -5654,6 +5666,10 @@ marginal_area_string (struct window *w, enum window_part part,
 	      y0 += glyph->slice.img.y;
 	    }
 #endif
+	  if (glyph->type == CANVAS_GLYPH)
+	    {
+	      y0 -= row->ascent - glyph->ascent;
+	    }
 	}
       else
 	{
diff --git a/src/emacs.c b/src/emacs.c
index ea9c4cd79d..c16af2c14c 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -94,6 +94,8 @@ #define MAIN_PROGRAM
 #include "getpagesize.h"
 #include "gnutls.h"
 
+#include "canvas.h"
+
 #ifdef PROFILING
 # include <sys/gmon.h>
 extern void moncontrol (int mode);
@@ -1567,6 +1569,8 @@ main (int argc, char **argv)
       /* Before init_window_once, because it sets up the
 	 Vcoding_system_hash_table.  */
       syms_of_coding ();	/* This should be after syms_of_fileio.  */
+
+      syms_of_canvas ();
       init_frame_once ();       /* Before init_window_once.  */
       init_window_once ();	/* Init the window system.  */
 #ifdef HAVE_WINDOW_SYSTEM
diff --git a/src/lisp.h b/src/lisp.h
index b4ac017dcf..f463399dad 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -1103,6 +1103,7 @@ DEFINE_GDB_SYMBOL_END (PSEUDOVECTOR_FLAG)
   PVEC_MUTEX,
   PVEC_CONDVAR,
   PVEC_MODULE_FUNCTION,
+  PVEC_CANVAS,
 
   /* These should be last, for internal_equal and sxhash_obj.  */
   PVEC_COMPILED,
@@ -1349,6 +1350,7 @@ #define XSETSUB_CHAR_TABLE(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_SUB_CHAR_TABLE))
 #define XSETTHREAD(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_THREAD))
 #define XSETMUTEX(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_MUTEX))
 #define XSETCONDVAR(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_CONDVAR))
+#define XSETCANVAS(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_CANVAS))
 
 /* Efficiently convert a pointer to a Lisp object and back.  The
    pointer is represented as a fixnum, so the garbage collector
diff --git a/src/pdumper.c b/src/pdumper.c
index 63424c5734..33c527c7d8 100644
--- a/src/pdumper.c
+++ b/src/pdumper.c
@@ -3036,6 +3036,8 @@ dump_vectorlike (struct dump_context *ctx,
       error_unsupported_dump_object (ctx, lv, "condvar");
     case PVEC_MODULE_FUNCTION:
       error_unsupported_dump_object (ctx, lv, "module function");
+    case PVEC_CANVAS:
+      error_unsupported_dump_object (ctx, lv, "canvas");
     default:
       error_unsupported_dump_object(ctx, lv, "weird pseudovector");
     }
diff --git a/src/print.c b/src/print.c
index bd1769144e..28a620fd20 100644
--- a/src/print.c
+++ b/src/print.c
@@ -34,6 +34,7 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2020 Free Software
 #include "blockinput.h"
 #include "xwidget.h"
 #include "dynlib.h"
+#include "canvas.h"
 
 #include <c-ctype.h>
 #include <float.h>
@@ -1833,6 +1834,16 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
       }
       break;
 #endif
+    case PVEC_CANVAS:
+      {
+	print_c_string ("#<canvas ", printcharfun);
+	const struct canvas *canvas = XCANVAS (obj);
+	print_object (make_fixnum (canvas->width), printcharfun, false);
+	printchar ('x', printcharfun);
+	print_object (make_fixnum (canvas->height), printcharfun, false);
+	print_c_string (">", printcharfun);
+      }
+      break;
 
     default:
       emacs_abort ();
diff --git a/src/window.c b/src/window.c
index e2dea8b70e..5e0ed1ab94 100644
--- a/src/window.c
+++ b/src/window.c
@@ -4289,6 +4289,7 @@ make_window (void)
   w->scroll_bar_width = -1;
   w->scroll_bar_height = -1;
   w->column_number_displayed = -1;
+  w->have_canvas_p = false;
   /* Reset window_list.  */
   Vwindow_list = Qnil;
   /* Return window.  */
diff --git a/src/window.h b/src/window.h
index 167d1be7ab..67c7417007 100644
--- a/src/window.h
+++ b/src/window.h
@@ -445,6 +445,9 @@ #define WINDOW_H_INCLUDED
        window.  */
     bool_bf suspend_auto_hscroll : 1;
 
+    /* True if we think a canvas is being displayed in this window.  */
+    bool_bf have_canvas_p : 1;
+
     /* Amount by which lines of this window are scrolled in
        y-direction (smooth scrolling).  */
     int vscroll;
diff --git a/src/xdisp.c b/src/xdisp.c
index 140d134572..e63c497100 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -462,6 +462,7 @@ Copyright (C) 1985-1988, 1993-1995, 1997-2020 Free Software Foundation,
 #include "fontset.h"
 #include "blockinput.h"
 #include "xwidget.h"
+#include "canvas.h"
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -1055,6 +1056,7 @@ #define THIN_SPACE_WIDTH 1
 static bool next_element_from_image (struct it *);
 static bool next_element_from_stretch (struct it *);
 static bool next_element_from_xwidget (struct it *);
+static bool next_element_from_canvas (struct it *);
 static void load_overlay_strings (struct it *, ptrdiff_t);
 static bool get_next_display_element (struct it *);
 static enum move_it_result
@@ -2688,7 +2690,7 @@ remember_mouse_glyph (struct frame *f, int gx, int gy, NativeRectangle *rect)
 
 	  if (g < end)
 	    {
-	      if (g->type == IMAGE_GLYPH)
+	      if (g->type == IMAGE_GLYPH || g->type == CANVAS_GLYPH)
 		{
 		  /* Don't remember when mouse is over image, as
 		     image may have hot-spots.  */
@@ -5589,7 +5591,7 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
 
   /* After this point, VALUE is the property after any
      margin prefix has been stripped.  It must be a string,
-     an image specification, or `(space ...)'.
+     an image specification, a canvas, or `(space ...)'.
 
      LOCATION specifies where to display: `left-margin',
      `right-margin' or nil.  */
@@ -5601,7 +5603,7 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
 #endif /* not HAVE_WINDOW_SYSTEM */
              || (CONSP (value) && EQ (XCAR (value), Qspace))
              || ((it ? FRAME_WINDOW_P (it->f) : frame_window_p)
-		 && valid_xwidget_spec_p (value)));
+		 && valid_xwidget_spec_p (value))) || CANVASP (value);
 
   if (valid_p && display_replaced == 0)
     {
@@ -5686,6 +5688,22 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
 	  *position = start_pos;
           it->xwidget = lookup_xwidget (value);
 	}
+      else if (CANVASP (value))
+	{
+	  it->what = IT_CANVAS;
+	  it->method = GET_FROM_CANVAS;
+	  it->position = start_pos;
+	  it->object = NILP (object) ? it->w->contents : object;
+	  *position = start_pos;
+	  it->canvas = XCANVAS (value);
+	  if ((!NILP (it->canvas->object) &&
+	       !EQ (it->canvas->object, it->object)) ||
+	      (!NILP (it->canvas->window) &&
+	       XWINDOW (it->canvas->window) != it->w))
+	    it->canvas->multiple_objects_seen = true;
+	  it->canvas->object = it->object;
+	  XSETWINDOW (it->canvas->window, it->w);
+	}
 #ifdef HAVE_WINDOW_SYSTEM
       else
 	{
@@ -6446,6 +6464,9 @@ push_it (struct it *it, struct text_pos *position)
     case GET_FROM_XWIDGET:
       p->u.xwidget.object = it->object;
       break;
+    case GET_FROM_CANVAS:
+      p->u.canvas.object = it->object;
+      break;
     case GET_FROM_BUFFER:
     case GET_FROM_DISPLAY_VECTOR:
     case GET_FROM_STRING:
@@ -6550,6 +6571,9 @@ pop_it (struct it *it)
     case GET_FROM_XWIDGET:
       it->object = p->u.xwidget.object;
       break;
+    case GET_FROM_CANVAS:
+      it->object = p->u.canvas.object;
+      break;
     case GET_FROM_STRETCH:
       it->object = p->u.stretch.object;
       break;
@@ -7236,6 +7260,7 @@ reseat_to_string (struct it *it, const char *s, Lisp_Object string,
   next_element_from_image,
   next_element_from_stretch,
   next_element_from_xwidget,
+  next_element_from_canvas,
 };
 
 #define GET_NEXT_DISPLAY_ELEMENT(it) (*get_next_element[(it)->method]) (it)
@@ -8151,6 +8176,7 @@ set_iterator_to_next (struct it *it, bool reseat_p)
     case GET_FROM_IMAGE:
     case GET_FROM_STRETCH:
     case GET_FROM_XWIDGET:
+    case GET_FROM_CANVAS:
 
       /* The position etc with which we have to proceed are on
 	 the stack.  The position may be at the end of a string,
@@ -8619,6 +8645,12 @@ next_element_from_xwidget (struct it *it)
   return true;
 }
 
+static bool
+next_element_from_canvas (struct it *it)
+{
+  it->what = IT_CANVAS;
+  return true;
+}
 
 /* Fill iterator IT with next display element from a stretch glyph
    property.  IT->object is the value of the text property.  Value is
@@ -27810,6 +27842,19 @@ fill_xwidget_glyph_string (struct glyph_string *s)
   s->xwidget = s->first_glyph->u.xwidget;
 }
 #endif
+
+static void
+fill_canvas_glyph_string (struct glyph_string *s)
+{
+  eassert (s->first_glyph->type == CANVAS_GLYPH);
+  s->w->have_canvas_p = true;
+  s->face = FACE_FROM_ID (s->f, s->first_glyph->face_id);
+  s->font = s->face->font;
+  s->width = s->first_glyph->pixel_width;
+  s->ybase += s->first_glyph->voffset;
+  s->canvas = s->first_glyph->u.canvas;
+  s->canvas->changed_since_last_redisplay = false;
+}
 /* Fill glyph string S from a sequence of stretch glyphs.
 
    START is the index of the first glyph to consider,
@@ -28227,6 +28272,18 @@ #define BUILD_IMAGE_GLYPH_STRING(START, END, HEAD, TAIL, HL, X, LAST_X) \
      while (false)
 #endif
 
+#define BUILD_CANVAS_GLYPH_STRING(START, END, HEAD, TAIL, HL, X, LAST_X) \
+  do                                                                     \
+    {                                                                    \
+      s = alloca (sizeof *s);                                            \
+      INIT_GLYPH_STRING (s, NULL, w, row, area, START, HL);              \
+      append_glyph_string (&(HEAD), &(TAIL), s);                         \
+      ++(START);                                                         \
+      s->x = (X);                                                        \
+      fill_canvas_glyph_string (s);					\
+    }                                                                    \
+  while (false)
+
 /* Add a glyph string for a sequence of character glyphs to the list
    of strings between HEAD and TAIL.  START is the index of the first
    glyph in row area AREA of glyph row ROW that is part of the new
@@ -28378,7 +28435,11 @@ #define BUILD_GLYPH_STRINGS_1(START, END, HEAD, TAIL, HL, X, LAST_X)	\
 	    case IMAGE_GLYPH:						\
 	      BUILD_IMAGE_GLYPH_STRING (START, END, HEAD, TAIL,		\
 					HL, X, LAST_X);			\
-	      break;
+	      break;							\
+	    case CANVAS_GLYPH:				        	\
+	      BUILD_CANVAS_GLYPH_STRING (START, END, HEAD, TAIL,	\
+					 HL, X, LAST_X);		\
+	      break;							\
 
 #define BUILD_GLYPH_STRINGS_XW(START, END, HEAD, TAIL, HL, X, LAST_X)	\
             case XWIDGET_GLYPH:                                         \
@@ -29086,6 +29147,116 @@ produce_image_glyph (struct it *it)
     }
 }
 
+static void
+produce_canvas_glyph (struct it *it)
+{
+  struct canvas *canvas;
+  int glyph_ascent, crop;
+
+  eassert (it->what == IT_CANVAS);
+
+  struct face *face = FACE_FROM_ID (it->f, it->face_id);
+  prepare_face_for_display (it->f, face);
+
+  canvas = it->canvas;
+  it->ascent = it->phys_ascent = glyph_ascent = canvas->height / 2;
+  it->descent = it->phys_descent = canvas->height / 2;
+  it->pixel_width = canvas->width;
+
+  if (it->descent < 0)
+    it->descent = 0;
+
+  it->nglyphs = 1;
+
+  if (face->box != FACE_NO_BOX)
+    {
+      if (face->box_horizontal_line_width > 0)
+	{
+	  it->ascent += face->box_horizontal_line_width;
+	  it->descent += face->box_horizontal_line_width;
+	}
+
+      if (face->box_vertical_line_width > 0)
+	{
+	  if (it->start_of_box_run_p)
+	    it->pixel_width += face->box_vertical_line_width;
+	  it->pixel_width += face->box_vertical_line_width;
+	}
+    }
+
+  take_vertical_position_into_account (it);
+
+  /* Automatically crop wide image glyphs at right edge so we can
+     draw the cursor on same display row.  */
+  crop = it->pixel_width - (it->last_visible_x - it->current_x);
+  if (crop > 0 && (it->hpos == 0 || it->pixel_width > it->last_visible_x / 4))
+    it->pixel_width -= crop;
+
+  if (it->glyph_row)
+    {
+      enum glyph_row_area area = it->area;
+      struct glyph *glyph
+	= it->glyph_row->glyphs[area] + it->glyph_row->used[area];
+
+      if (it->glyph_row->reversed_p)
+	{
+	  struct glyph *g;
+
+	  /* Make room for the new glyph.  */
+	  for (g = glyph - 1; g >= it->glyph_row->glyphs[it->area]; g--)
+	    g[1] = *g;
+	  glyph = it->glyph_row->glyphs[it->area];
+	}
+      if (glyph < it->glyph_row->glyphs[area + 1])
+	{
+	  glyph->charpos = CHARPOS (it->position);
+	  glyph->object = it->object;
+	  glyph->pixel_width = clip_to_bounds (-1, it->pixel_width, SHRT_MAX);
+	  glyph->ascent = glyph_ascent;
+	  glyph->descent = it->descent;
+	  glyph->voffset = it->voffset;
+	  glyph->type = CANVAS_GLYPH;
+	  glyph->avoid_cursor_p = it->avoid_cursor_p;
+	  glyph->multibyte_p = it->multibyte_p;
+	  glyph->u.canvas = it->canvas;
+	  if (it->glyph_row->reversed_p && area == TEXT_AREA)
+	    {
+	      /* In R2L rows, the left and the right box edges need to be
+		 drawn in reverse direction.  */
+	      glyph->right_box_line_p = it->start_of_box_run_p;
+	      glyph->left_box_line_p = it->end_of_box_run_p;
+	    }
+	  else
+	    {
+	      glyph->left_box_line_p = it->start_of_box_run_p;
+	      glyph->right_box_line_p = it->end_of_box_run_p;
+	    }
+          glyph->overlaps_vertically_p = 0;
+          glyph->padding_p = 0;
+	  glyph->glyph_not_available_p = 0;
+	  glyph->face_id = it->face_id;
+	  glyph->font_type = FONT_TYPE_UNKNOWN;
+	  if (it->bidi_p)
+	    {
+	      glyph->resolved_level = it->bidi_it.resolved_level;
+	      eassert ((it->bidi_it.type & 7) == it->bidi_it.type);
+	      glyph->bidi_type = it->bidi_it.type;
+	    }
+	  ++it->glyph_row->used[area];
+	}
+      else
+	IT_EXPAND_MATRIX_WIDTH (it, area);
+    }
+}
+
+void
+canvas_update_glyph (struct window *w, int x, struct glyph_row *row,
+		     enum glyph_row_area area, ptrdiff_t start, ptrdiff_t end,
+		     struct glyph *glyph)
+{
+  draw_glyphs (w, x, row, area, start, end, DRAW_NORMAL_TEXT, 0);
+}
+
 static void
 produce_xwidget_glyph (struct it *it)
 {
@@ -30608,6 +30779,8 @@ gui_produce_glyphs (struct it *it)
     produce_stretch_glyph (it);
   else if (it->what == IT_XWIDGET)
     produce_xwidget_glyph (it);
+  else if (it->what == IT_CANVAS)
+    produce_canvas_glyph (it);
 
  done:
   /* Accumulate dimensions.  Note: can't assume that it->descent > 0
@@ -31008,6 +31181,10 @@ get_window_cursor_type (struct window *w, struct glyph *glyph, int *width,
 	      cursor_type = HOLLOW_BOX_CURSOR;
 	    }
       }
+      if (glyph != NULL && glyph->type == CANVAS_GLYPH)
+	{
+	  cursor_type = HOLLOW_BOX_CURSOR;
+	}
       return cursor_type;
     }
 
diff --git a/src/xterm.c b/src/xterm.c
index 7989cecec7..716ed0ef97 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -75,6 +75,7 @@ Copyright (C) 1989, 1993-2020 Free Software Foundation, Inc.
 #include "sysselect.h"
 #include "menu.h"
 #include "pdumper.h"
+#include "canvas.h"
 
 #ifdef USE_X_TOOLKIT
 #include <X11/Shell.h>
@@ -2066,6 +2067,30 @@ x_draw_glyphless_glyph_string_foreground (struct glyph_string *s)
    }
 }
 
+static void
+x_draw_canvas_glyph_string_foreground (struct glyph_string *s)
+{
+  eassert (s->first_glyph->type == CANVAS_GLYPH);
+#ifdef USE_CAIRO
+  cairo_t *cr = x_begin_cr_clip (s->f, s->gc);
+  int x = s->x;
+  int y = s->ybase - s->first_glyph->ascent;
+
+  if (s->face->box != FACE_NO_BOX &&
+      s->first_glyph->left_box_line_p)
+    x += max (s->face->box_vertical_line_width, 0);
+
+  x_set_glyph_string_clipping (s);
+  x_clear_area (s->f, x, y, s->width, s->height);
+  cairo_set_source_surface (cr, s->canvas->canvas,
+			    s->x, s->y);
+  cairo_paint (cr);
+  x_end_cr_clip (s->f);
+#else
+  emacs_abort ();
+#endif
+}
+
 #ifdef USE_X_TOOLKIT
 
 #ifdef USE_LUCID
@@ -3811,6 +3836,11 @@ x_draw_glyph_string (struct glyph_string *s)
       x_draw_glyphless_glyph_string_foreground (s);
       break;
 
+    case CANVAS_GLYPH:
+      x_draw_glyph_string_background (s, true);
+      x_draw_canvas_glyph_string_foreground (s);
+      break;
+
     default:
       emacs_abort ();
     }

^ permalink raw reply related	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29  6:34 ` Emacs canvas support Po Lu via Emacs development discussions.
@ 2020-04-29  8:24   ` Eli Zaretskii
  2020-04-29  9:57     ` Po Lu
  2020-04-29 20:48   ` Juri Linkov
  1 sibling, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29  8:24 UTC (permalink / raw)
  To: Po Lu; +Cc: emacs-devel

> Date: Wed, 29 Apr 2020 14:34:09 +0800
> From: Po Lu via "Emacs development discussions." <emacs-devel@gnu.org>
> 
> I'd appreciate some feedback on something I came up with during my spare
> time: Emacs canvas support.

I've read the code and the docs, but I don't think I have a clear idea
of what "canvases" are and what would be their intended usage.
Perhaps consider starting the documentation with some introductory
comments and even a small tutorial.

It sounds like you are talking about a way to create images
dynamically, but then I don't understand why we need canvas-from-image,
for example.

Also, is there support for clicking the mouse on a canvas?  I don't
see it.

> For now it only works on X11 + Cairo builds, and I haven't quite figured
> out how to make redisplay work reliably on canvases

Well, that'd be my main comments.  I don't quite understand the parts
of the display code you use for this, they seem like a copy/paste from
other objects, sometimes with comments that weren't updated and still
reference images instead of canvases.  If the drawing on the canvas is
supposed to be modified by Lisp code, then we'd need a much more
elaborate machinery than just one flag to decide when a canvas needs
to be redrawn.

Thanks.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29  8:24   ` Eli Zaretskii
@ 2020-04-29  9:57     ` Po Lu
  2020-04-29 10:10       ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: Po Lu @ 2020-04-29  9:57 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1847 bytes --]

Eli Zaretskii <eliz@gnu.org> writes:

> I've read the code and the docs, but I don't think I have a clear idea
> of what "canvases" are and what would be their intended usage.
> Perhaps consider starting the documentation with some introductory
> comments and even a small tutorial.

A canvas is an object that contains memory that can be painted to, and
then displayed by setting it as the `display' text property.

> It sounds like you are talking about a way to create images
> dynamically, but then I don't understand why we need canvas-from-image,
> for example.

It's not really a way to create images dynamically, but rather paint to
a section of the screen.  `canvas-from-image' is just a convenience
function that produces a paintable canvas from a static image.

> Also, is there support for clicking the mouse on a canvas?  I don't
> see it.

You can bind mouse events as usual.

> Well, that'd be my main comments.  I don't quite understand the parts
> of the display code you use for this, they seem like a copy/paste from

Correct, glyph production and iterator code code was mostly copied from
the xwidgets code, since I was somewhat in a rush.  Thanks for the
feedback.

> other objects, sometimes with comments that weren't updated and still
> reference images instead of canvases.

I'll fix that promptly.

> If the drawing on the canvas is  supposed to be modified by Lisp code,
> then we'd need a much more elaborate machinery than just one flag to
> decide when a canvas needs to be redrawn.

Agreed, that is something I've been trying to figure out for some time
now.  Right now, I just do my best to figure out if the canvas has not
touched anything important, and if it has, it just garbages each frame
and calls redisplay.

Attached is some sample code that should hopefully demonstrate exactly
what they are capable of.


[-- Attachment #2: canvas test --]
[-- Type: text/plain, Size: 2879 bytes --]

(setq canvas (make-canvas 1800 1800))

(erase-buffer)
(insert (propertize " " 'display canvas))

(canvas-draw-image canvas (create-image "splash.png")
		   0 0 180 180 (selected-frame) 1.0)
(canvas-ellipse canvas (/ 180 2) (/ 180 2) 130 130 "green")
(canvas-rectangle canvas 0 0 180 180 "red" t)
(canvas-rectangle canvas 20 20 40 100 "white" t 0.5)
(canvas-draw-string canvas 0 0 "Hello, Emacs" "red" 1.0 "monospace" 50)
(canvas-draw-string canvas 60 60 "Hello, Emacs" "white" 1.0 '("monospace" t t) 50)
(let ((image (create-image "splash.png")))
  (canvas-draw-image canvas image 700 20))
(setq track-mouse t)
(defun mouse-movement-canvas-4-paint ()
  "The function to be called when the mouse is moved."
  (interactive "")
  (track-mouse
    (let ((event))
      (while (mouse-movement-p (setq event (read-event)))
	(let* ((position (event-start event))
	       (x (car (posn-x-y position)))
	       (y (cdr (posn-x-y position))))
	  (message (format "%d %d" x y))
	  (canvas-filled-arc canvas x y 100.0 (* 45.0 (/ pi 180.0)) pi "blue"))))))
(defun mouse-movement-canvas-paint ()
  "The function to be called when the mouse is moved."
  (interactive "")
  (track-mouse
    (let ((event))
      (while (mouse-movement-p (setq event (read-event)))
	(let* ((position (event-start event))
	       (x (car (posn-x-y position)))
	       (y (cdr (posn-x-y position))))
	  (message (format "%d %d" x y))
	  (canvas-ellipse canvas x y 10 10 "blue"))))))
(defun mouse-movement-3-canvas-paint ()
  "The function to be called when the mouse is moved."
  (interactive "")
  (track-mouse
    (let ((event))
      (while (mouse-movement-p (setq event (read-event)))
	(let* ((position (event-start event))
	       (x (car (posn-x-y position)))
	       (y (cdr (posn-x-y position))))
	  (message (format "%d %d" x y))
	  (canvas-rectangle canvas x y 80 80 "white"))))))
(defun mouse-movement-2-canvas-paint ()
  "The function to be called when the mouse is moved."
  (interactive "")
  (track-mouse
    (let ((event))
      (while (mouse-movement-p (setq event (read-event)))
	(let* ((position (event-start event))
	       (x (car (posn-x-y position)))
	       (y (cdr (posn-x-y position))))
	  (message (format "%d %d" x y))
	  (canvas-draw-string canvas x y "Welcome to Emacs" nil nil nil 22))))))
(local-set-key [down-mouse-1] #'mouse-movement-canvas-paint)
(local-set-key [down-mouse-3] #'mouse-movement-2-canvas-paint)
(local-set-key [down-mouse-2] #'mouse-movement-3-canvas-paint)
(local-set-key [C-down-mouse-1] #'mouse-movement-canvas-4-paint)
(canvas-measure-string canvas "hi")
(canvas-draw-string canvas 0 0 "Hi" "yellow")
(setq canvas2 (canvas-from-image (create-image "splash.png")))
(canvas-rectangle canvas2 0 0 40 20 "orange")
(canvas-rectangle canvas2 20 0 40 20 "black")

(canvas-rounded-rectangle canvas 20 0 40 20 10.0 "red" nil)
(canvas-draw-canvas canvas canvas2 500 500)

^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29  9:57     ` Po Lu
@ 2020-04-29 10:10       ` Eli Zaretskii
  2020-04-29 10:22         ` Po Lu
  0 siblings, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 10:10 UTC (permalink / raw)
  To: Po Lu; +Cc: emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: emacs-devel@gnu.org
> Date: Wed, 29 Apr 2020 17:57:03 +0800
> 
> > I've read the code and the docs, but I don't think I have a clear idea
> > of what "canvases" are and what would be their intended usage.
> > Perhaps consider starting the documentation with some introductory
> > comments and even a small tutorial.
> 
> A canvas is an object that contains memory that can be painted to, and
> then displayed by setting it as the `display' text property.

That's the technical description of the implementation.  It doesn't
explain when  canvases can be useful and for what purposes.

> > It sounds like you are talking about a way to create images
> > dynamically, but then I don't understand why we need canvas-from-image,
> > for example.
> 
> It's not really a way to create images dynamically, but rather paint to
> a section of the screen.

But the result of this painting is some graphical object, similar to
an image, that will be displayed within a buffer, right?

> > Also, is there support for clicking the mouse on a canvas?  I don't
> > see it.
> 
> You can bind mouse events as usual.

Did I miss the code that tells Emacs the click was on a canvas?  E.g.,
if you click on a canvas, what does posn-object return when passed the
click event as its argument?



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 10:10       ` Eli Zaretskii
@ 2020-04-29 10:22         ` Po Lu
  2020-04-29 10:27           ` Po Lu via Emacs development discussions.
  2020-04-29 10:35           ` Eli Zaretskii
  0 siblings, 2 replies; 53+ messages in thread
From: Po Lu @ 2020-04-29 10:22 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:


> That's the technical description of the implementation.  It doesn't
> explain when  canvases can be useful and for what purposes.

Canvases are useful for when I want to be able to control a portion of a
screen dynamically, in a fast way, from Lisp code.  For instance,
displaying a constantly changing bar chart or graph inside Emacs.

> But the result of this painting is some graphical object, similar to
> an image, that will be displayed within a buffer, right?

Correct.

> Did I miss the code that tells Emacs the click was on a canvas?  E.g.,
> if you click on a canvas, what does posn-object return when passed the
> click event as its argument?

Oops.  I missed that.  Thanks for bringing it up.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 10:22         ` Po Lu
@ 2020-04-29 10:27           ` Po Lu via Emacs development discussions.
  2020-04-29 11:47             ` Eli Zaretskii
  2020-04-29 10:35           ` Eli Zaretskii
  1 sibling, 1 reply; 53+ messages in thread
From: Po Lu via Emacs development discussions. @ 2020-04-29 10:27 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 896 bytes --]

Po Lu <luangruo@yahoo.com> writes:

> Eli Zaretskii <eliz@gnu.org> writes:
>
>
>> That's the technical description of the implementation.  It doesn't
>> explain when  canvases can be useful and for what purposes.
>
> Canvases are useful for when I want to be able to control a portion of a
> screen dynamically, in a fast way, from Lisp code.  For instance,
> displaying a constantly changing bar chart or graph inside Emacs.
>
>> But the result of this painting is some graphical object, similar to
>> an image, that will be displayed within a buffer, right?
>
> Correct.
>
>> Did I miss the code that tells Emacs the click was on a canvas?  E.g.,
>> if you click on a canvas, what does posn-object return when passed the
>> click event as its argument?
>
> Oops.  I missed that.  Thanks for bringing it up.

Here is a version of the patch with several of the problems you
mentioned rectified.


[-- Warning: decoded text below may be mangled, UTF-8 assumed --]
[-- Attachment #2: 0001-emacs-canvas-patches.diff --]
[-- Type: text/x-patch, Size: 62494 bytes --]

diff --git a/doc/lispref/display.texi b/doc/lispref/display.texi
index e53f0e9f60..2a24576620 100644
--- a/doc/lispref/display.texi
+++ b/doc/lispref/display.texi
@@ -27,6 +27,7 @@ Display
 * Window Dividers::     Separating windows visually.
 * Display Property::    Images, margins, text size, etc.
 * Images::              Displaying images in Emacs buffers.
+* Canvases::            Drawing areas inside Emacs buffers.
 * Xwidgets::            Displaying native widgets in Emacs buffers.
 * Buttons::             Adding clickable buttons to Emacs buffers.
 * Abstract Display::    Emacs's Widget for Object Collections.
@@ -6509,6 +6510,152 @@ Image Cache
 debugging.
 @end defvar
 
+@node Canvases
+@cindex drawing canvases
+@cindex drawing areas
+@cindex canvases
+@section Canvases
+
+This chapter describes canvases, objects that can store drawing operations
+which are then displayed inside buffer text.
+
+@menu
+* Creating Canvases::           How canvases can be created
+* Operating on Canvases::       How canvases can be used
+* Displaying Canvases::         How canvases can be displayed
+@end menu
+
+@node Creating Canvases
+@cindex creating Canvases
+@pindex make-canvas
+
+  This section describes how canvases can be created.
+To create a canvas, call the function @code{make-canvas}.
+
+@defun make-canvas width height
+This function takes 2 arguments @code{width}, and @code{height},
+and creates a canvas @code{width} wide and @code{height} tall.
+@end defun
+
+@defun canvas-from-image image &optional width height
+This function creates a canvas from the image descriptor
+@code{image}.  The created canvas will be @code{width} wide,
+and @code{height} tall, if specified.
+@end defun
+
+@node Operating on Canvases
+@cindex operating on Canvases
+@pindex canvas-rectangle
+
+  This section describes how canvases can be drawn to,
+and manipulated.
+
+@defun canvasp canvas
+Return whether @code{canvas} is a canvas or not.
+@end defun
+
+@defun canvas-dimensions canvas
+Return the dimensions of @code{canvas} as a pair.
+@end defun
+
+@defun canvas-ellipse canvas x y width height &optional color
+       hollow opacity
+Draw an ellipse centred upon @code{x}, @code{y} onto the canvas
+@code{canvas}. The drawn ellipse will be colored @code{color},
+or the current frame's foreground color if @code{color} is not
+specified or nil.  The opacity of the drawn item will be
+@code{opacity}, and the item will be hollow if @code{hollow} is
+non-nil.
+@end defun
+
+@defun canvas-rectangle canvas x y width height &optional color
+       hollow opacity
+Draw a rectangle at @code{x}, and @code{y} onto the canvas
+@code{canvas}.  The rectangle will be colored @code{color},
+or the current frame's foreground color if @code{color} is nil.
+The opacity of the drawn item will be @code{opacity}, and the item
+will be hollow if @code{hollow} is non-nil.
+@end defun
+
+@defun canvas-fill-pixel canvas x y color opacity
+Fill the pixel at @code{x}, @code{y} inside @code{canvas}
+to @code{color}, with the opacity @code{opacity}.
+@end defun
+
+@defun canvas-draw-string canvas x y string
+       &optional color opacity family size
+Draw the string @code{string} to @code{x}, @code{y}
+inside the canvas @code{canvas}.  The font family used
+will be @code{family}, the color @code{color}, the opacity
+@code{opacity}, and the size @code{size}.
+
+@code{family} can either be a string, or a list in which
+the first element should be the family as a string, the
+second element should be whether the font should be italic,
+and an optional third argument describing whether or not
+the font should be bold.
+@end defun
+
+@defun canvas-draw-image canvas image-spec x y
+       &optional width height frame opacity
+Paint @code{image-spec} into @code{canvas} at @code{x},
+@code{y}.  If @code{width} or @code{height}
+is set and the image is wider than @code{width} or @code{height} respectively,
+the image will be cropped to fit.  The alpha channel of @code{image-spec}
+will be set to @code{opacity}.
+@end defun
+
+@defun canvas-measure-string canvas string &optional family size
+Return a cons pair containing the width and height of @code{string},
+when rendered onto @code{canvas}, with the font @code{family} at
+@code{size}.
+@end defun
+
+@defun canvas-rounded-rectangle canvas x y width height radius
+       &optional color hollow opacity
+Draw a rounded rectangle at @code{x}, @code{y} onto @code{canvas}.
+The opacity of the rectangle will be @code{opacity}.
+The radius of the rectangle will be @code{radius}.
+@end defun
+
+@defun canvas-pixel-at canvas x y
+Return the pixel at @code{x}, @code{y} inside @code{canvas},
+as an ARGB list.
+@end defun
+
+@defun canvas-draw-canvas canvas canvas2 x y &optional width height opacity
+Draw @code{canvas2} onto @code{canvas} at @code{x}, @code{y}.
+@code{canvas2}'s alpha channel will be set to @code{opacity},
+if specified.
+@code{canvas2} will not be taller than @code{height} or wider than
+@code{width}, if specified.
+@end defun
+
+@defun canvas-width canvas
+Return the width of @code{canvas}.
+@end defun
+
+@defun canvas-height canvas
+Return the height of @code{canvas}.
+@end defun
+
+@defun canvas-region canvas x y width height
+Return a subsection of @code{canvas} at @code{x},
+@code{y}, that is @code{width} wide and @code{height} tall.
+@end defun
+
+@defun canvas-arc canvas x y radius angle1 angle2 &optional color opacity
+Draw an arc at @code{x}, @code{y}, with a radius of @code{radius},
+and the angles @code{angle1}, @code{angle2}.
+@end defun
+
+@node Displaying Canvases
+@cindex displaying Canvases
+
+  Canvases can be displayed by setting them as the
+@code{display} property of a string.
+
+
 @node Xwidgets
 @section Embedded Native Widgets
 @cindex xwidget
diff --git a/doc/lispref/elisp.texi b/doc/lispref/elisp.texi
index bba1b63115..d995ec4606 100644
--- a/doc/lispref/elisp.texi
+++ b/doc/lispref/elisp.texi
@@ -1430,6 +1430,7 @@ Top
 * Window Dividers::         Separating windows visually.
 * Display Property::        Enabling special display features.
 * Images::                  Displaying images in Emacs buffers.
+* Canvases::                Drawing areas inside Emacs buffers.
 * Buttons::                 Adding clickable buttons to Emacs buffers.
 * Abstract Display::        Emacs's Widget for Object Collections.
 * Blinking::                How Emacs shows the matching open parenthesis.
diff --git a/lisp/canvas.el b/lisp/canvas.el
new file mode 100644
index 0000000000..d60d3efa4f
--- /dev/null
+++ b/lisp/canvas.el
@@ -0,0 +1,49 @@
+;;; canvas.el --- Canvas support for GNU Emacs
+
+;; Copyright (C) 2020 Free Software Foundation, Inc.
+
+;; This file is part of GNU Emacs.
+
+;; GNU Emacs is free software: you can redistribute it and/or modify
+;; it under the terms of the GNU General Public License as published by
+;; the Free Software Foundation, either version 3 of the License, or
+;; (at your option) any later version.
+
+;; GNU Emacs is distributed in the hope that it will be useful,
+;; but WITHOUT ANY WARRANTY; without even the implied warranty of
+;; MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+;; GNU General Public License for more details.
+
+;; You should have received a copy of the GNU General Public License
+;; along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.
+
+;;; Commentary:
+
+;;; These are several utlity functions for canvas operations that can
+;;; be implemented in Lisp code.
+
+\f
+;;; Code:
+
+(defun canvas-fill-pixel (canvas x y color opacity)
+  "Set the pixel at X, Y inside CANVAS to COLOR, with the opacity OPACITY."
+  (canvas-rectangle canvas x y 1 1 color opacity))
+
+(defun canvas-from-image (image &optional width height)
+  "Create a canvas from IMAGE.
+The canvas will be no wider than WIDTH (if specified),
+and no taller than HEIGHT (if specified)."
+  (let ((canvas (make-canvas (or width (car (image-size image t)))
+                             (or height (cdr (image-size image t))))))
+    (prog1 canvas (canvas-draw-image canvas image 0 0))))
+
+(defun canvas-width (canvas)
+  "Return the width of CANVAS."
+  (car (canvas-dimensions canvas)))
+
+(defun canvas-height (canvas)
+  "Return the height of CANVAS."
+  (cdr (canvas-dimensions canvas)))
+
+(provide 'canvas)
+;;; canvas.el ends here
diff --git a/lisp/loadup.el b/lisp/loadup.el
index 97525b2708..1a9b1c7410 100644
--- a/lisp/loadup.el
+++ b/lisp/loadup.el
@@ -285,6 +285,8 @@
 (load "emacs-lisp/tabulated-list")
 (load "buff-menu")
 
+(load "canvas")
+
 (if (fboundp 'x-create-frame)
     (progn
       (load "fringe")
diff --git a/src/Makefile.in b/src/Makefile.in
index 552dd2e50a..0d70c68b04 100644
--- a/src/Makefile.in
+++ b/src/Makefile.in
@@ -407,6 +407,7 @@ .m.o:
 ## be dumped as pure by dump-emacs.
 base_obj = dispnew.o frame.o scroll.o xdisp.o menu.o $(XMENU_OBJ) window.o \
 	charset.o coding.o category.o ccl.o character.o chartab.o bidi.o \
+	common-canvas.o \
 	$(CM_OBJ) term.o terminal.o xfaces.o $(XOBJ) $(GTK_OBJ) $(DBUS_OBJ) \
 	emacs.o keyboard.o macros.o keymap.o sysdep.o \
 	bignum.o buffer.o filelock.o insdel.o marker.o \
diff --git a/src/alloc.c b/src/alloc.c
index cc9ba8dbf5..acce0109e1 100644
--- a/src/alloc.c
+++ b/src/alloc.c
@@ -47,6 +47,7 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2020 Free Software
 #include "blockinput.h"
 #include "pdumper.h"
 #include "termhooks.h"		/* For struct terminal.  */
+#include "canvas.h"
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -3114,6 +3115,11 @@ cleanup_vector (struct Lisp_Vector *vector)
       module_finalize_function (function);
     }
 #endif
+  else if (PSEUDOVECTOR_TYPEP (&vector->header, PVEC_CANVAS))
+    {
+      struct canvas *cnvs = (struct canvas *) vector;
+      destroy_canvas_contents (cnvs->canvas);
+    }
 }
 
 /* Reclaim space used by unmarked vectors.  */
diff --git a/src/canvas.h b/src/canvas.h
new file mode 100644
index 0000000000..036f2bf499
--- /dev/null
+++ b/src/canvas.h
@@ -0,0 +1,83 @@
+/* Canvas support for GNU Emacs.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include <config.h>
+
+#include "lisp.h"
+#include "frame.h"
+#include "window.h"
+
+#ifdef USE_CAIRO
+#include <cairo/cairo.h>
+typedef cairo_surface_t *canvas_contents_t;
+#else
+typedef void *canvas_contents_t;
+#endif
+
+struct canvas
+{
+  union vectorlike_header header;
+  Lisp_Object cnvs_objects;
+  Lisp_Object window;
+  Lisp_Object object;
+
+  canvas_contents_t canvas;
+  int width, height;
+  bool multiple_objects_seen;
+  bool changed_since_last_redisplay;
+} GCALIGNED_STRUCT;
+
+/* Test for canvas pseudovector.  */
+#define CANVASP(x) PSEUDOVECTORP (x, PVEC_CANVAS)
+#define XCANVAS(a) \
+  (eassert (CANVASP (a)), XUNTAG (a, Lisp_Vectorlike, struct canvas))
+
+#define CHECK_CANVAS(x) CHECK_TYPE (CANVASP (x), Qcanvasp, x)
+
+#define MARK_CANVAS_CHANGED(x)                                              \
+  (((x)->changed_since_last_redisplay = true),                              \
+   (windows_or_buffers_changed = 2), (redisplay ()));                       \
+  do                                                                        \
+    {                                                                       \
+      if ((x)->multiple_objects_seen || !EQ (selected_window, (x)->window)) \
+        {                                                                   \
+          Lisp_Object tail, head;                                           \
+          FOR_EACH_FRAME (tail, head)                                       \
+          {                                                                 \
+            struct frame *f = XFRAME (head);                                \
+            SET_FRAME_GARBAGED (f);                                         \
+          }                                                                 \
+        }                                                                   \
+    }                                                                       \
+  while (false)
+
+extern Lisp_Object
+make_canvas (int width, int height);
+
+extern canvas_contents_t
+make_canvas_contents (int width, int height);
+
+extern void
+destroy_canvas_contents (canvas_contents_t contents);
+
+extern void
+syms_of_canvas (void);
+
+extern void
+canvas_end_redisplay (struct window *w,
+		      struct glyph_matrix *matrix);
diff --git a/src/common-canvas.c b/src/common-canvas.c
new file mode 100644
index 0000000000..329c37daad
--- /dev/null
+++ b/src/common-canvas.c
@@ -0,0 +1,868 @@
+/* Canvas support for GNU Emacs.
+   Copyright (C) 2020 Free Software Foundation, Inc.
+
+This file is part of GNU Emacs.
+
+GNU Emacs is free software: you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 3 of the License, or (at
+your option) any later version.
+
+GNU Emacs is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with GNU Emacs.  If not, see <https://www.gnu.org/licenses/>.  */
+
+#include "canvas.h"
+#include "coding.h"
+
+#include <math.h>
+
+Lisp_Object
+make_canvas (int width, int height)
+{
+#ifndef USE_CAIRO
+  error ("Canvases are not supported without cairo.")
+#endif
+  struct canvas *canvas = ALLOCATE_PSEUDOVECTOR
+    (struct canvas, object, PVEC_CANVAS);
+  canvas->width = width;
+  canvas->height = height;
+  canvas->object = Qnil;
+  canvas->multiple_objects_seen = false;
+  canvas->canvas = make_canvas_contents (width, height);
+  canvas->changed_since_last_redisplay = true;
+
+  Lisp_Object cnvs;
+  XSETCANVAS (cnvs, canvas);
+  return cnvs;
+}
+
+void
+destroy_canvas_contents (canvas_contents_t contents)
+{
+#ifdef USE_CAIRO
+  cairo_surface_destroy (contents);
+#else
+  error ("Not implemented");
+#endif
+}
+
+canvas_contents_t
+make_canvas_contents (int width, int height)
+{
+#ifdef USE_CAIRO
+  cairo_surface_t *crs =
+    cairo_image_surface_create (CAIRO_FORMAT_ARGB32, width, height);
+  return crs;
+#else
+  error ("Not implemented");
+#endif
+}
+
+void
+canvas_end_redisplay (struct window *w,
+		      struct glyph_matrix *matrix)
+{
+  int i;
+  int area;
+
+  struct canvas **unmark_canvases;
+  ptrdiff_t unmark_size = 0;
+  unmark_canvases = xmalloc (sizeof *unmark_canvases * unmark_size);
+
+  for (i = 0; i < matrix->nrows; ++i)
+    {
+      struct glyph_row *row;
+      row = MATRIX_ROW (matrix, i);
+      if (row->enabled_p)
+        for (area = LEFT_MARGIN_AREA; area < LAST_AREA; ++area)
+          {
+            struct glyph *glyph = row->glyphs[area];
+            struct glyph *glyph_end = glyph + row->used[area];
+            for (; glyph < glyph_end; ++glyph)
+              if (glyph->type == CANVAS_GLYPH &&
+		  glyph->u.canvas->changed_since_last_redisplay)
+                {
+		  ++unmark_size;
+                  unmark_canvases
+                    = xrealloc (unmark_canvases,
+                                (sizeof *unmark_canvases * unmark_size));
+		  unmark_canvases[unmark_size - 1] = glyph->u.canvas;
+                  canvas_update_glyph
+		    (w, i, row, area, row->glyphs[area] -
+		     glyph, 1 + row->glyphs[area] - glyph,
+		     glyph);
+                }
+          }
+    }
+  for (; unmark_size; unmark_size--)
+    unmark_canvases[unmark_size - 1]->changed_since_last_redisplay = false;
+  xfree (unmark_canvases);
+}
+
+DEFUN ("make-canvas", Fmake_canvas, Smake_canvas, 2, 2, 0,
+       doc: /* Create a canvas, WIDTH pixels wide, and HEIGHT pixels tall. */)
+  (Lisp_Object width, Lisp_Object height)
+{
+  check_integer_range (height, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+
+  return make_canvas (XFIXNUM (width), XFIXNUM (height));
+}
+
+DEFUN ("canvas-ellipse", Fcanvas_ellipse, Scanvas_ellipse, 5, 8, 0,
+       doc: /* Draw a WIDTH wide and HEIGHT tall ellipse centred at X, Y in CANVAS.
+The color of the ellipse will be COLOR (or the foreground color of COLOR is nil).
+The ellipse will be hollow if HOLLOW is non-nil.
+The opacity of the circle will be OPACITY, which should be a floating-point
+number between 1 and 0. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height, Lisp_Object color,
+   Lisp_Object hollow, Lisp_Object opacity)
+{
+    if (NILP (opacity))
+    opacity = make_float (1.0);
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+  check_integer_range (height, 0, INT_MAX);
+  CHECK_NUMBER (opacity);
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))->query_colors
+	(XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+	lg = Fnth (make_fixnum (1), color_values),
+	lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  struct canvas *cv = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (cv->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  cairo_matrix_t save_matrix;
+  cairo_get_matrix (cr, &save_matrix);
+  cairo_translate (cr, XFIXNUM (x), XFIXNUM (y));
+  cairo_scale (cr, 1, 1);
+  cairo_new_path (cr);
+  cairo_arc (cr, 0, 0,
+             XFIXNUM (width) / 2.0, 0, 2 * M_PI);
+  cairo_set_matrix (cr, &save_matrix);
+
+  cairo_set_line_width (cr, canvas_stroke_width);
+  if (!NILP (hollow))
+    cairo_stroke (cr);
+  else
+    cairo_fill (cr);
+
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (cv);
+#endif
+  return Qnil;
+}
+
+DEFUN ("canvasp", Fcanvasp, Scanvasp, 1, 1, 0,
+       doc: /* Return t if CANVAS is a canvas, else nil.  */)
+  (Lisp_Object canvas)
+{
+  return CANVASP (canvas) ? Qt : Qnil;
+}
+
+DEFUN ("canvas-rectangle", Fcanvas_rectangle, Scanvas_rectangle, 5, 8, 0,
+       doc: /* Draw a WIDTH wide and HEIGHT tall rectangle at X, Y in CANVAS.
+The color of the rectangle will be COLOR (or the foreground color of COLOR is nil).
+The rectangle will be hollow if HOLLOW is non-nil.
+The opacity of the circle will be OPACITY, which should be a floating-point
+number between 1 and 0. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height, Lisp_Object color,
+   Lisp_Object hollow, Lisp_Object opacity)
+{
+  if (NILP (opacity))
+    opacity = make_float (1.0);
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+  check_integer_range (height, 0, INT_MAX);
+  CHECK_NUMBER (opacity);
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))->query_colors
+	(XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+	lg = Fnth (make_fixnum (1), color_values),
+	lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  struct canvas *cv = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (cv->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  if (NILP (hollow))
+    {
+      cairo_rectangle (cr, XFIXNUM (x), XFIXNUM (y), XFIXNUM (width),
+		       XFIXNUM (height));
+      cairo_fill (cr);
+    }
+  else
+    {
+      cairo_rectangle (cr, XFIXNUM (x), XFIXNUM (y), XFIXNUM (width),
+		       XFIXNUM (height));
+      cairo_stroke (cr);
+    }
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (cv);
+  return Qnil;
+#else
+  error ("Not implemented");
+#endif
+}
+
+DEFUN ("canvas-rounded-rectangle", Fcanvas_rounded_rectangle, Scanvas_rounded_rectangle, 6, MANY, 0,
+       doc: /* Draw a WIDTH wide and HEIGHT tall rectangle at X, Y in CANVAS.
+The color of the rectangle will be COLOR (or the foreground color of COLOR is nil).
+The rectangle will be hollow if HOLLOW is non-nil.
+The opacity of the circle will be OPACITY, which should be a floating-point
+number between 1 and 0.
+usage: (canvas-rounded-rectangle canvas x y width height radius &optional color hollow opacity)  */)
+  (ptrdiff_t nargs, Lisp_Object *args)
+{
+  Lisp_Object canvas, x, y, width, height, radius, color, hollow, opacity;
+  canvas = args[0];
+  x = args[1];
+  y = args[2];
+  width = args[3];
+  height = args[4];
+  radius = args[5];
+  color = Qnil;
+  hollow = Qnil;
+  opacity = Qnil;
+
+  if (nargs > 6)
+    color = args[6];
+  if (nargs > 7)
+    hollow = args[7];
+  if (nargs > 8)
+    opacity = args[8];
+  if (nargs > 9)
+    xsignal2 (Qwrong_number_of_arguments,
+	      Qcanvas_rounded_rectangle,
+	      make_fixnum (nargs));
+  if (NILP (opacity))
+    opacity = make_float (1.0);
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+  check_integer_range (height, 0, INT_MAX);
+  CHECK_NUMBER (opacity);
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))->query_colors
+	(XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+	lg = Fnth (make_fixnum (1), color_values),
+	lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  struct canvas *cv = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (cv->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  {
+#define radius XFLOATINT (radius)
+    double degrees = M_PI / 180.0;
+#define width XFIXNUM (width)
+#define height XFIXNUM (height)
+#define x XFIXNUM (x)
+#define y XFIXNUM (y)
+    cairo_new_sub_path (cr);
+    cairo_arc (cr, x + width - radius, y + radius, radius, -90 * degrees, 0 * degrees);
+    cairo_arc (cr, x + width - radius, y + height - radius, radius, 0 * degrees, 90 * degrees);
+    cairo_arc (cr, x + radius, y + height - radius, radius, 90 * degrees, 180 * degrees);
+    cairo_arc (cr, x + radius, y + radius, radius, 180 * degrees, 270 * degrees);
+    cairo_close_path (cr);
+#undef radius
+#undef y
+#undef x
+#undef height
+#undef width
+  }
+#undef radius
+  cairo_set_line_width (cr, canvas_stroke_width);
+  if (NILP (hollow))
+    cairo_fill (cr);
+  else
+    cairo_stroke (cr);
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (cv);
+  return Qnil;
+#else
+  error ("Not implemented");
+#endif
+}
+
+DEFUN ("canvas-draw-string", Fcanvas_draw_string, Scanvas_draw_string, 4, 8, 0,
+       doc: /* Draw the string STRING onto the canvas CANVAS at X, Y.
+The opacity of the drawn text will be OPACITY, and the color of the drawn text will be COLOR.
+The font-family used will be FAMILY, which can be a string or a list of
+the font-family as a string, whether or not the the text should be italic,
+and whether or not the text should be bold.
+The size of the text will be SIZE, or the default text size if nil. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y, Lisp_Object string,
+   Lisp_Object color, Lisp_Object opacity, Lisp_Object family, Lisp_Object size)
+{
+  if (noninteractive)
+    error ("`canvas-draw-string' cannot be called when running in batch mode.");
+  if (NILP (opacity))
+    opacity = make_float (1.0);
+  if (NILP (size))
+    size = make_fixnum (FRAME_TEXT_HEIGHT (XFRAME (selected_frame)));
+  if (NILP (family))
+    family = build_string ("monospace");
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  CHECK_NUMBER (opacity);
+  if (!Flistp (family))
+    CHECK_STRING (family);
+  CHECK_STRING (string);
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))->query_colors
+	(XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+	lg = Fnth (make_fixnum (1), color_values),
+	lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  struct canvas *c = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (c->canvas);
+  cairo_font_slant_t slant_flags = CAIRO_FONT_SLANT_NORMAL;
+  cairo_font_weight_t weight_flags = CAIRO_FONT_WEIGHT_NORMAL;
+  const char *family_utf8;
+  if (!NILP (Flistp (family)))
+    {
+      if (!NILP (CALLN (Flss, Flength (family), make_fixnum (2))))
+	error ("Invalid font spec");
+      else
+	{
+	  int length = XFIXNUM (Flength (family));
+	  if (length > 3)
+	    error ("Invalid font spec");
+	  Lisp_Object italic, bold;
+	  CHECK_STRING_CAR (family);
+	  family_utf8 = SSDATA (ENCODE_UTF_8 (XCAR (family)));
+	  if (length == 3)
+	    bold = XCAR (XCDR (XCDR (family)));
+	  else
+	    bold = Qnil;
+	  italic = XCAR (XCDR (family));
+	  if (!NILP (italic))
+	    slant_flags = CAIRO_FONT_SLANT_ITALIC;
+	  if (!NILP (bold))
+	    weight_flags = CAIRO_FONT_WEIGHT_BOLD;
+	}
+    }
+  else
+    {
+      family_utf8 = SSDATA (ENCODE_UTF_8 (family));
+    }
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  cairo_select_font_face (cr, family_utf8,
+			  slant_flags,
+			  weight_flags);
+  cairo_set_font_size (cr, XFIXNUM (size));
+  cairo_move_to (cr, XFIXNUM (x), XFIXNUM (y) + XFIXNUM (size));
+  cairo_show_text (cr, SSDATA (ENCODE_UTF_8 (string)));
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (c);
+  return Qnil;
+#else
+  return Qnil;
+#endif
+}
+
+DEFUN ("canvas-measure-string", Fcanvas_measure_string, Scanvas_measure_string, 2, 4, 0,
+       doc: /* Return a pair containing the width and height of STRING,
+if drawn on CANVAS with the family FAMILY at SIZE.  */)
+(Lisp_Object canvas, Lisp_Object string, Lisp_Object family, Lisp_Object size)
+{
+  if (noninteractive)
+    error ("`canvas-draw-string' cannot be called when running in batch mode.");
+  if (NILP (size))
+    size = make_fixnum (FRAME_TEXT_HEIGHT (XFRAME (selected_frame)));
+  if (NILP (family))
+    family = build_string ("cairo:monospace");
+  CHECK_CANVAS (canvas);
+
+  if (!Flistp (family))
+    CHECK_STRING (family);
+  CHECK_STRING (string);
+#ifdef USE_CAIRO
+  struct canvas *c = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (c->canvas);
+  cairo_font_slant_t slant_flags = CAIRO_FONT_SLANT_NORMAL;
+  cairo_font_weight_t weight_flags = CAIRO_FONT_WEIGHT_NORMAL;
+  const char *family_utf8;
+  if (!NILP (Flistp (family)))
+    {
+      if (!NILP (CALLN (Flss, Flength (family), make_fixnum (2))))
+	error ("Invalid font spec");
+      else
+	{
+	  int length = XFIXNUM (Flength (family));
+	  if (length > 3)
+	    error ("Invalid font spec");
+	  Lisp_Object italic, bold;
+	  CHECK_STRING_CAR (family);
+	  family_utf8 = SSDATA (ENCODE_UTF_8 (XCAR (family)));
+	  if (length == 3)
+	    bold = XCAR (XCDR (XCDR (family)));
+	  else
+	    bold = Qnil;
+	  italic = XCAR (XCDR (family));
+	  if (!NILP (italic))
+	    slant_flags = CAIRO_FONT_SLANT_ITALIC;
+	  if (!NILP (bold))
+	    weight_flags = CAIRO_FONT_WEIGHT_BOLD;
+	}
+    }
+  else
+    {
+      family_utf8 = SSDATA (ENCODE_UTF_8 (family));
+    }
+  cairo_select_font_face (cr, family_utf8,
+			  slant_flags,
+			  weight_flags);
+  cairo_set_font_size (cr, XFIXNUM (size));
+  cairo_text_extents_t extents;
+  cairo_text_extents (cr, SSDATA (ENCODE_UTF_8 (string)), &extents);
+  cairo_destroy (cr);
+  return Fcons (make_fixnum (extents.width),
+		make_fixnum (extents.height));
+#else
+  return Qnil;
+#endif
+}
+
+DEFUN ("canvas-draw-image", Fcanvas_draw_image, Scanvas_draw_image, 4, 8,
+       0, doc: /* Paint IMAGE_SPEC onto CANVAS, at X, Y.
+If WIDTH or HEIGHT is set, and IMAGE is wider than WIDTH or taller than HEIGHT,
+IMAGE_SPEC will be cropped to fit WIDTH and/or HEIGHT respectively.
+FRAME should be a live frame.
+The opacity of the drawn image will be OPACITY.  */)
+  (Lisp_Object canvas, Lisp_Object image_spec,
+   Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height,
+   Lisp_Object frame, Lisp_Object opacity)
+{
+  if (valid_image_p (image_spec))
+    {
+      if (NILP (frame))
+	frame = selected_frame;
+      struct frame *f = decode_window_system_frame (frame);
+      ptrdiff_t id = lookup_image (f, image_spec);
+      struct image *img = IMAGE_FROM_ID (f, id);
+      if (!img)
+	return Qnil;
+      if (img->load_failed_p)
+	return Qnil;
+      int iwidth = img->width + 2 * img->hmargin;
+      int iheight = img->height + 2 * img->vmargin;
+      CHECK_CANVAS (canvas);
+      check_integer_range (x, 0, INT_MAX);
+      check_integer_range (y, 0, INT_MAX);
+      if (NILP (width))
+	width = make_fixnum (iwidth);
+      check_integer_range (width, 0, INT_MAX);
+      if (NILP (height))
+	height = make_fixnum (iheight);
+      check_integer_range (height, 0, INT_MAX);
+      if (NILP (width))
+	width = make_fixnum (iwidth);
+      if (NILP (opacity))
+	opacity = make_fixnum (1);
+      CHECK_NUMBER (opacity);
+#ifdef USE_CAIRO
+      cairo_surface_t *crs
+        = cairo_image_surface_create_for_data ((unsigned char *)
+                                                 img->pixmap->data,
+                                               (img->pixmap->bits_per_pixel
+                                                    == 32
+                                                  ? CAIRO_FORMAT_RGB24
+                                                  : CAIRO_FORMAT_A8),
+                                               img->pixmap->width,
+                                               img->pixmap->height,
+                                               img->pixmap->bytes_per_line);
+      struct canvas *cv = XCANVAS (canvas);
+      cairo_t *cr = cairo_create (cv->canvas);
+      cairo_save (cr);
+      cairo_translate (cr, XFIXNUM (x), XFIXNUM (y));
+      cairo_rectangle (cr, 0, 0,
+		       XFIXNUM (width), XFIXNUM (height));
+      cairo_clip (cr);
+      cairo_set_source_surface (cr, crs, 0, 0);
+      cairo_paint_with_alpha (cr, XFLOATINT (opacity));
+      cairo_restore (cr);
+      cairo_destroy (cr);
+      cairo_surface_destroy (crs);
+      MARK_CANVAS_CHANGED (cv);
+#endif
+    }
+  else
+    error ("Invalid image specification");
+  return Qnil;
+}
+
+DEFUN ("canvas-arc", Fcanvas_arc, Scanvas_arc, 6, 8, 0,
+       doc: /* Draw an arc on CANVAS starting from XC, YC,
+with a radius of RADIUS and 2 angles angle1 and angle2.
+Use the color COLOR with the alpha channel set to OPACITY, if specified. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y, Lisp_Object radius,
+   Lisp_Object angle1, Lisp_Object angle2, Lisp_Object color, Lisp_Object opacity)
+{
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  CHECK_NUMBER (angle1);
+  CHECK_NUMBER (angle2);
+  CHECK_NUMBER (radius);
+
+  if (NILP (opacity))
+    opacity = make_fixnum (1.0);
+  CHECK_NUMBER (opacity);
+
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))
+        ->query_colors (XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+                  lg = Fnth (make_fixnum (1), color_values),
+                  lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  cairo_t *cr = cairo_create (XCANVAS (canvas)->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  cairo_arc (cr, XFLOATINT (x), XFLOATINT (y), XFLOATINT (radius),
+	     XFLOATINT (angle1), XFLOATINT (angle2));
+  cairo_stroke (cr);
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (XCANVAS (canvas));
+#else
+#endif
+  return Qnil;
+}
+
+DEFUN ("canvas-filled-arc", Fcanvas_filled_arc, Scanvas_filled_arc, 6, 8, 0,
+       doc: /* Draw a filled arc on CANVAS starting from XC, YC,
+with a radius of RADIUS and 2 angles angle1 and angle2.
+Use the color COLOR with the alpha channel set to OPACITY, if specified. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y, Lisp_Object radius,
+   Lisp_Object angle1, Lisp_Object angle2, Lisp_Object color, Lisp_Object opacity)
+{
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  CHECK_NUMBER (angle1);
+  CHECK_NUMBER (angle2);
+  CHECK_NUMBER (radius);
+
+  if (NILP (opacity))
+    opacity = make_fixnum (1.0);
+  CHECK_NUMBER (opacity);
+
+#ifdef USE_CAIRO
+  if (NILP (color))
+    color = Qunspecified;
+  Lisp_Object color_values = call1 (Qcolor_values, color);
+  if (!NILP (color_values))
+    CHECK_LIST (color_values);
+  double r, g, b;
+  if (NILP (color_values))
+    {
+      Emacs_Color col;
+      col.pixel = FRAME_FOREGROUND_PIXEL (XFRAME (selected_frame));
+      FRAME_TERMINAL (XFRAME (selected_frame))
+        ->query_colors (XFRAME (selected_frame), &col, 1);
+      r = col.red / 65535.0;
+      g = col.green / 65535.0;
+      b = col.blue / 65535.0;
+    }
+  else
+    {
+      Lisp_Object lr = Fnth (make_fixnum (0), color_values),
+                  lg = Fnth (make_fixnum (1), color_values),
+                  lb = Fnth (make_fixnum (2), color_values);
+
+      check_integer_range (lr, 0, 65535);
+      check_integer_range (lg, 0, 65535);
+      check_integer_range (lb, 0, 65535);
+
+      r = XFIXNUM (lr) / 65535.0;
+      g = XFIXNUM (lg) / 65535.0;
+      b = XFIXNUM (lb) / 65535.0;
+    }
+  cairo_t *cr = cairo_create (XCANVAS (canvas)->canvas);
+  cairo_set_source_rgba (cr, r, g, b, XFLOATINT (opacity));
+  cairo_arc (cr, XFLOATINT (x), XFLOATINT (y), XFLOATINT (radius),
+	     XFLOATINT (angle1), XFLOATINT (angle2));
+  cairo_fill (cr);
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (XCANVAS (canvas));
+#else
+#endif
+  return Qnil;
+}
+
+DEFUN ("canvas-region", Fcanvas_region, Scanvas_region, 5, 5, 0,
+       doc: /* Return a canvas containing a WIDTH wide and HEIGHT tall
+subsection of CANVAS at X, Y */)
+  (Lisp_Object canvas, Lisp_Object x,
+   Lisp_Object y, Lisp_Object width, Lisp_Object height)
+{
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  check_integer_range (width, 0, INT_MAX);
+  check_integer_range (height, 0, INT_MAX);
+
+#ifdef USE_CAIRO
+  int ix = XFIXNUM (x),
+    iy = XFIXNUM (y),
+    iw = XFIXNUM (width),
+    ih = XFIXNUM (height);
+  cairo_surface_t *s = cairo_surface_create_for_rectangle
+    (XCANVAS (canvas)->canvas, ix, iy, iw, ih);
+  Lisp_Object newcvs = make_canvas (iw, ih);
+  struct canvas *target = XCANVAS (newcvs);
+  cairo_t *t = cairo_create (target->canvas);
+  cairo_set_source_surface (t, s, 0, 0);
+  cairo_paint (t);
+  cairo_destroy (t);
+  cairo_surface_destroy (s);
+  return newcvs;
+#else
+  error ("Not implemented.")
+#endif
+}
+
+
+DEFUN ("canvas-draw-canvas", Fcanvas_draw_canvas, Scanvas_draw_canvas, 4, 7,
+       0, doc: /* Paint CANVAS2 onto CANVAS, at X, Y.
+If WIDTH or HEIGHT is set, and IMAGE is wider than WIDTH or taller than HEIGHT,
+IMAGE_SPEC will be cropped to fit WIDTH and/or HEIGHT respectively.
+The opacity of the drawn image will be OPACITY.  */)
+  (Lisp_Object canvas, Lisp_Object canvas2,
+   Lisp_Object x, Lisp_Object y,
+   Lisp_Object width, Lisp_Object height, Lisp_Object opacity)
+{
+  CHECK_CANVAS (canvas);
+  CHECK_CANVAS (canvas2);
+  int iwidth = XCANVAS (canvas2)->width;
+  int iheight = XCANVAS (canvas2)->height;
+  check_integer_range (x, 0, INT_MAX);
+  check_integer_range (y, 0, INT_MAX);
+  if (NILP (width))
+    width = make_fixnum (iwidth);
+  check_integer_range (width, 0, INT_MAX);
+  if (NILP (height))
+    height = make_fixnum (iheight);
+  check_integer_range (height, 0, INT_MAX);
+  if (NILP (width))
+    width = make_fixnum (iwidth);
+  if (NILP (opacity))
+    opacity = make_fixnum (1);
+  CHECK_NUMBER (opacity);
+
+#ifdef USE_CAIRO
+  cairo_surface_t *crs = XCANVAS (canvas2)->canvas;
+  struct canvas *cv = XCANVAS (canvas);
+  cairo_t *cr = cairo_create (cv->canvas);
+  cairo_save (cr);
+  cairo_translate (cr, XFIXNUM (x), XFIXNUM (y));
+  cairo_rectangle (cr, 0, 0, XFIXNUM (width), XFIXNUM (height));
+  cairo_clip (cr);
+  cairo_set_source_surface (cr, crs, 0, 0);
+  cairo_paint_with_alpha (cr, XFLOATINT (opacity));
+  cairo_restore (cr);
+  cairo_destroy (cr);
+  MARK_CANVAS_CHANGED (cv);
+#endif
+  return Qnil;
+}
+
+DEFUN ("canvas-pixel-at", Fcanvas_pixel_at, Scanvas_pixel_at, 3, 3, 0,
+       doc: /* Return the color of the pixel at X, Y inside CANVAS as an ARGB list. */)
+  (Lisp_Object canvas, Lisp_Object x, Lisp_Object y)
+{
+#ifndef USE_CAIRO
+  error ("Not implemented.");
+#else
+  CHECK_CANVAS (canvas);
+  check_integer_range (x, 0, XCANVAS (canvas)->width);
+  check_integer_range (y, 0, XCANVAS (canvas)->height);
+  struct {
+#ifdef WORDS_BIGENDIAN
+    uint8_t a, r, g, b;
+#else
+    uint8_t b, g, r, a;
+#endif
+  } *argb32 =
+    (void *) cairo_image_surface_get_data (XCANVAS (canvas)->canvas);
+  typeof (*argb32) res = argb32 [XFIXNUM (y) * XCANVAS (canvas)->width +
+				 XFIXNUM (x)];
+  return CALLN (Flist, make_fixnum (res.a),
+		make_fixnum (res.r),
+		make_fixnum (res.g),
+		make_fixnum (res.b));
+#endif
+}
+
+DEFUN ("canvas-dimensions", Fcanvas_dimensions, Scanvas_dimensions, 1, 1, 0,
+       doc: /* Return a cons pair containing the width and height of CANVAS. */)
+  (Lisp_Object canvas)
+{
+  CHECK_CANVAS (canvas);
+  return Fcons (make_fixnum (XCANVAS (canvas)->width),
+		make_fixnum (XCANVAS (canvas)->height));
+}
+
+void
+syms_of_canvas (void)
+{
+  defsubr (&Smake_canvas);
+  defsubr (&Scanvas_rectangle);
+  defsubr (&Scanvas_ellipse);
+  defsubr (&Scanvas_rectangle);
+  defsubr (&Scanvas_draw_string);
+  defsubr (&Scanvas_draw_image);
+  defsubr (&Scanvas_draw_canvas);
+  defsubr (&Scanvas_measure_string);
+  defsubr (&Scanvas_dimensions);
+  defsubr (&Scanvas_region);
+  defsubr (&Scanvas_pixel_at);
+  defsubr (&Scanvas_arc);
+  defsubr (&Scanvasp);
+  defsubr (&Scanvas_filled_arc);
+  defsubr (&Scanvas_rounded_rectangle);
+  DEFSYM (Qcanvas_rounded_rectangle, "canvas-rounded-rectangle");
+  DEFSYM (Qcanvasp, "canvasp");
+  DEFSYM (Qcolor_values, "color-values");
+  DEFVAR_INT ("canvas-stroke-width", canvas_stroke_width,
+	      doc: /* The stroke width to be used in canvases. */);
+  canvas_stroke_width = 4;
+}
diff --git a/src/data.c b/src/data.c
index bce2e53cfb..aefd6d7e70 100644
--- a/src/data.c
+++ b/src/data.c
@@ -263,6 +263,8 @@ DEFUN ("type-of", Ftype_of, Stype_of, 1, 1, 0,
           return Qxwidget;
         case PVEC_XWIDGET_VIEW:
           return Qxwidget_view;
+	case PVEC_CANVAS:
+	  return Qcanvas;
         /* "Impossible" cases.  */
 	case PVEC_MISC_PTR:
         case PVEC_OTHER:
@@ -3859,6 +3861,7 @@ #define PUT_ERROR(sym, tail, msg)			\
   DEFSYM (Qchar_table, "char-table");
   DEFSYM (Qbool_vector, "bool-vector");
   DEFSYM (Qhash_table, "hash-table");
+  DEFSYM (Qcanvas, "canvas");
   DEFSYM (Qthread, "thread");
   DEFSYM (Qmutex, "mutex");
   DEFSYM (Qcondition_variable, "condition-variable");
diff --git a/src/dispextern.h b/src/dispextern.h
index 0b1f3d14ae..d33b87c3df 100644
--- a/src/dispextern.h
+++ b/src/dispextern.h
@@ -382,7 +382,10 @@ #define SET_GLYPH_FROM_GLYPH_CODE(glyph, gc)				\
   STRETCH_GLYPH,
 
   /* Glyph is an external widget drawn by the GUI toolkit.  */
-  XWIDGET_GLYPH
+  XWIDGET_GLYPH,
+
+  /* Glyph is a canvas.  */
+  CANVAS_GLYPH
 };
 
 
@@ -540,6 +543,9 @@ #define FACE_ID_BITS	20
     struct xwidget *xwidget;
 #endif
 
+    /* Canvas reference (type == CANVAS_GLYPH).  */
+    struct canvas *canvas;
+
     /* Sub-structure for type == STRETCH_GLYPH.  */
     struct
     {
@@ -1405,6 +1411,9 @@ #define OVERLAPS_ERASED_CURSOR 	(1 << 2)
   /* Xwidget.  */
   struct xwidget *xwidget;
 
+  /* Canvas.  */
+  struct canvas *canvas;
+
   /* Slice */
   struct glyph_slice slice;
 
@@ -2158,7 +2167,10 @@ #define MAX_FRINGE_BITMAPS (1<<FRINGE_ID_BITS)
   IT_CONTINUATION,
 
   /* Xwidget.  */
-  IT_XWIDGET
+  IT_XWIDGET,
+
+  /* Canvas.  */
+  IT_CANVAS
 };
 
 
@@ -2223,6 +2235,7 @@ #define MAX_FRINGE_BITMAPS (1<<FRINGE_ID_BITS)
   GET_FROM_IMAGE,
   GET_FROM_STRETCH,
   GET_FROM_XWIDGET,
+  GET_FROM_CANVAS,
   NUM_IT_METHODS
 };
 
@@ -2447,6 +2460,10 @@ #define OVERLAY_STRING_CHUNK_SIZE 16
       struct {
 	Lisp_Object object;
       } xwidget;
+      /* method == GET_FROM_CANVAS */
+      struct {
+	Lisp_Object object;
+      } canvas;
     } u;
 
     /* Current text and display positions.  */
@@ -2578,6 +2595,9 @@ #define OVERLAY_STRING_CHUNK_SIZE 16
   /* If what == IT_XWIDGET.  */
   struct xwidget *xwidget;
 
+  /* If what == IT_CANVAS.  */
+  struct canvas *canvas;
+
   /* Values from `slice' property.  */
   struct it_slice slice;
 
@@ -3714,6 +3734,11 @@ #define IMAGE_BACKGROUND_TRANSPARENT(img, f, mask)			      \
 
 #endif /* HAVE_WINDOW_SYSTEM */
 
+extern void
+canvas_update_glyph (struct window *w, int x, struct glyph_row *row,
+		     enum glyph_row_area area, ptrdiff_t start, ptrdiff_t end,
+		     struct glyph *glyph);
+
 INLINE_HEADER_END
 
 #endif /* not DISPEXTERN_H_INCLUDED */
diff --git a/src/dispnew.c b/src/dispnew.c
index 5b6fa51a56..9e0c1a060e 100644
--- a/src/dispnew.c
+++ b/src/dispnew.c
@@ -43,6 +43,7 @@ Copyright (C) 1985-1988, 1993-1995, 1997-2020 Free Software Foundation,
 #include "tparam.h"
 #include "xwidget.h"
 #include "pdumper.h"
+#include "canvas.h"
 
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
@@ -3697,6 +3698,7 @@ update_window (struct window *w, bool force_p)
 #endif
 
   xwidget_end_redisplay (w, w->current_matrix);
+  canvas_end_redisplay (w, w->current_matrix);
   clear_glyph_matrix (desired_matrix);
 
   return paused_p;
@@ -3782,6 +3784,7 @@ gui_update_window_end (struct window *w, bool cursor_on_p,
     FRAME_RIF (f)->update_window_end_hook (w,
                                            cursor_on_p,
                                            mouse_face_overwritten_p);
+  canvas_end_redisplay (w, w->current_matrix);
 }
 
 #endif /* HAVE_WINDOW_SYSTEM  */
@@ -4371,6 +4374,11 @@ scrolling_window (struct window *w, int tab_line_p)
     return 0;
 #endif
 
+  /* We need this to fix canvas movement detection in a reliable way.
+     FIXME. */
+  if (w->have_canvas_p)
+    return 0;
+
   /* Give up if some rows in the desired matrix are not enabled.  */
   if (! MATRIX_ROW_ENABLED_P (desired_matrix, i))
     return -1;
@@ -5462,6 +5470,10 @@ buffer_posn_from_coords (struct window *w, int *x, int *y, struct display_pos *p
       if (img && !NILP (img->spec))
 	*object = img->spec;
     }
+  else if (it.what == IT_CANVAS)
+    {
+      XSETCANVAS (*object, it.canvas);
+    }
 #endif
 
   /* IT's vpos counts from the glyph row that includes the window's
@@ -5565,6 +5577,10 @@ mode_line_string (struct window *w, enum window_part part,
 	      y0 -= row->ascent - glyph->ascent;
 	    }
 #endif
+	  if (glyph->type == CANVAS_GLYPH)
+	    {
+	      y0 -= row->ascent - glyph->ascent;
+	    }
 	}
       else
 	{
@@ -5654,6 +5670,10 @@ marginal_area_string (struct window *w, enum window_part part,
 	      y0 += glyph->slice.img.y;
 	    }
 #endif
+	  if (glyph->type == CANVAS_GLYPH)
+	    {
+	      y0 -= row->ascent - glyph->ascent;
+	    }
 	}
       else
 	{
diff --git a/src/emacs.c b/src/emacs.c
index ea9c4cd79d..c16af2c14c 100644
--- a/src/emacs.c
+++ b/src/emacs.c
@@ -94,6 +94,8 @@ #define MAIN_PROGRAM
 #include "getpagesize.h"
 #include "gnutls.h"
 
+#include "canvas.h"
+
 #ifdef PROFILING
 # include <sys/gmon.h>
 extern void moncontrol (int mode);
@@ -1567,6 +1569,8 @@ main (int argc, char **argv)
       /* Before init_window_once, because it sets up the
 	 Vcoding_system_hash_table.  */
       syms_of_coding ();	/* This should be after syms_of_fileio.  */
+
+      syms_of_canvas ();
       init_frame_once ();       /* Before init_window_once.  */
       init_window_once ();	/* Init the window system.  */
 #ifdef HAVE_WINDOW_SYSTEM
diff --git a/src/lisp.h b/src/lisp.h
index b4ac017dcf..f463399dad 100644
--- a/src/lisp.h
+++ b/src/lisp.h
@@ -1103,6 +1103,7 @@ DEFINE_GDB_SYMBOL_END (PSEUDOVECTOR_FLAG)
   PVEC_MUTEX,
   PVEC_CONDVAR,
   PVEC_MODULE_FUNCTION,
+  PVEC_CANVAS,
 
   /* These should be last, for internal_equal and sxhash_obj.  */
   PVEC_COMPILED,
@@ -1349,6 +1350,7 @@ #define XSETSUB_CHAR_TABLE(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_SUB_CHAR_TABLE))
 #define XSETTHREAD(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_THREAD))
 #define XSETMUTEX(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_MUTEX))
 #define XSETCONDVAR(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_CONDVAR))
+#define XSETCANVAS(a, b) (XSETPSEUDOVECTOR (a, b, PVEC_CANVAS))
 
 /* Efficiently convert a pointer to a Lisp object and back.  The
    pointer is represented as a fixnum, so the garbage collector
diff --git a/src/pdumper.c b/src/pdumper.c
index 63424c5734..33c527c7d8 100644
--- a/src/pdumper.c
+++ b/src/pdumper.c
@@ -3036,6 +3036,8 @@ dump_vectorlike (struct dump_context *ctx,
       error_unsupported_dump_object (ctx, lv, "condvar");
     case PVEC_MODULE_FUNCTION:
       error_unsupported_dump_object (ctx, lv, "module function");
+    case PVEC_CANVAS:
+      error_unsupported_dump_object (ctx, lv, "canvas");
     default:
       error_unsupported_dump_object(ctx, lv, "weird pseudovector");
     }
diff --git a/src/print.c b/src/print.c
index bd1769144e..28a620fd20 100644
--- a/src/print.c
+++ b/src/print.c
@@ -34,6 +34,7 @@ Copyright (C) 1985-1986, 1988, 1993-1995, 1997-2020 Free Software
 #include "blockinput.h"
 #include "xwidget.h"
 #include "dynlib.h"
+#include "canvas.h"
 
 #include <c-ctype.h>
 #include <float.h>
@@ -1833,6 +1834,16 @@ print_vectorlike (Lisp_Object obj, Lisp_Object printcharfun, bool escapeflag,
       }
       break;
 #endif
+    case PVEC_CANVAS:
+      {
+	print_c_string ("#<canvas ", printcharfun);
+	const struct canvas *canvas = XCANVAS (obj);
+	print_object (make_fixnum (canvas->width), printcharfun, false);
+	printchar ('x', printcharfun);
+	print_object (make_fixnum (canvas->height), printcharfun, false);
+	print_c_string (">", printcharfun);
+      }
+      break;
 
     default:
       emacs_abort ();
diff --git a/src/window.c b/src/window.c
index e2dea8b70e..5e0ed1ab94 100644
--- a/src/window.c
+++ b/src/window.c
@@ -4289,6 +4289,7 @@ make_window (void)
   w->scroll_bar_width = -1;
   w->scroll_bar_height = -1;
   w->column_number_displayed = -1;
+  w->have_canvas_p = false;
   /* Reset window_list.  */
   Vwindow_list = Qnil;
   /* Return window.  */
diff --git a/src/window.h b/src/window.h
index 167d1be7ab..67c7417007 100644
--- a/src/window.h
+++ b/src/window.h
@@ -445,6 +445,9 @@ #define WINDOW_H_INCLUDED
        window.  */
     bool_bf suspend_auto_hscroll : 1;
 
+    /* True if we think a canvas is being displayed in this window.  */
+    bool_bf have_canvas_p : 1;
+
     /* Amount by which lines of this window are scrolled in
        y-direction (smooth scrolling).  */
     int vscroll;
diff --git a/src/xdisp.c b/src/xdisp.c
index 140d134572..758f07fec5 100644
--- a/src/xdisp.c
+++ b/src/xdisp.c
@@ -462,6 +462,7 @@ Copyright (C) 1985-1988, 1993-1995, 1997-2020 Free Software Foundation,
 #include "fontset.h"
 #include "blockinput.h"
 #include "xwidget.h"
+#include "canvas.h"
 #ifdef HAVE_WINDOW_SYSTEM
 #include TERM_HEADER
 #endif /* HAVE_WINDOW_SYSTEM */
@@ -1055,6 +1056,7 @@ #define THIN_SPACE_WIDTH 1
 static bool next_element_from_image (struct it *);
 static bool next_element_from_stretch (struct it *);
 static bool next_element_from_xwidget (struct it *);
+static bool next_element_from_canvas (struct it *);
 static void load_overlay_strings (struct it *, ptrdiff_t);
 static bool get_next_display_element (struct it *);
 static enum move_it_result
@@ -2688,7 +2690,7 @@ remember_mouse_glyph (struct frame *f, int gx, int gy, NativeRectangle *rect)
 
 	  if (g < end)
 	    {
-	      if (g->type == IMAGE_GLYPH)
+	      if (g->type == IMAGE_GLYPH || g->type == CANVAS_GLYPH)
 		{
 		  /* Don't remember when mouse is over image, as
 		     image may have hot-spots.  */
@@ -5589,7 +5591,7 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
 
   /* After this point, VALUE is the property after any
      margin prefix has been stripped.  It must be a string,
-     an image specification, or `(space ...)'.
+     an image specification, a canvas, or `(space ...)'.
 
      LOCATION specifies where to display: `left-margin',
      `right-margin' or nil.  */
@@ -5601,7 +5603,7 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
 #endif /* not HAVE_WINDOW_SYSTEM */
              || (CONSP (value) && EQ (XCAR (value), Qspace))
              || ((it ? FRAME_WINDOW_P (it->f) : frame_window_p)
-		 && valid_xwidget_spec_p (value)));
+		 && valid_xwidget_spec_p (value))) || CANVASP (value);
 
   if (valid_p && display_replaced == 0)
     {
@@ -5686,6 +5688,22 @@ handle_single_display_spec (struct it *it, Lisp_Object spec, Lisp_Object object,
 	  *position = start_pos;
           it->xwidget = lookup_xwidget (value);
 	}
+      else if (CANVASP (value))
+	{
+	  it->what = IT_CANVAS;
+	  it->method = GET_FROM_CANVAS;
+	  it->position = start_pos;
+	  it->object = NILP (object) ? it->w->contents : object;
+	  *position = start_pos;
+	  it->canvas = XCANVAS (value);
+	  if ((!NILP (it->canvas->object) &&
+	       !EQ (it->canvas->object, it->object)) ||
+	      (!NILP (it->canvas->window) &&
+	       XWINDOW (it->canvas->window) != it->w))
+	    it->canvas->multiple_objects_seen = true;
+	  it->canvas->object = it->object;
+	  XSETWINDOW (it->canvas->window, it->w);
+	}
 #ifdef HAVE_WINDOW_SYSTEM
       else
 	{
@@ -6446,6 +6464,9 @@ push_it (struct it *it, struct text_pos *position)
     case GET_FROM_XWIDGET:
       p->u.xwidget.object = it->object;
       break;
+    case GET_FROM_CANVAS:
+      p->u.canvas.object = it->object;
+      break;
     case GET_FROM_BUFFER:
     case GET_FROM_DISPLAY_VECTOR:
     case GET_FROM_STRING:
@@ -6550,6 +6571,9 @@ pop_it (struct it *it)
     case GET_FROM_XWIDGET:
       it->object = p->u.xwidget.object;
       break;
+    case GET_FROM_CANVAS:
+      it->object = p->u.canvas.object;
+      break;
     case GET_FROM_STRETCH:
       it->object = p->u.stretch.object;
       break;
@@ -7236,6 +7260,7 @@ reseat_to_string (struct it *it, const char *s, Lisp_Object string,
   next_element_from_image,
   next_element_from_stretch,
   next_element_from_xwidget,
+  next_element_from_canvas,
 };
 
 #define GET_NEXT_DISPLAY_ELEMENT(it) (*get_next_element[(it)->method]) (it)
@@ -8151,6 +8176,7 @@ set_iterator_to_next (struct it *it, bool reseat_p)
     case GET_FROM_IMAGE:
     case GET_FROM_STRETCH:
     case GET_FROM_XWIDGET:
+    case GET_FROM_CANVAS:
 
       /* The position etc with which we have to proceed are on
 	 the stack.  The position may be at the end of a string,
@@ -8619,6 +8645,12 @@ next_element_from_xwidget (struct it *it)
   return true;
 }
 
+static bool
+next_element_from_canvas (struct it *it)
+{
+  it->what = IT_CANVAS;
+  return true;
+}
 
 /* Fill iterator IT with next display element from a stretch glyph
    property.  IT->object is the value of the text property.  Value is
@@ -27810,6 +27842,19 @@ fill_xwidget_glyph_string (struct glyph_string *s)
   s->xwidget = s->first_glyph->u.xwidget;
 }
 #endif
+
+static void
+fill_canvas_glyph_string (struct glyph_string *s)
+{
+  eassert (s->first_glyph->type == CANVAS_GLYPH);
+  s->w->have_canvas_p = true;
+  s->face = FACE_FROM_ID (s->f, s->first_glyph->face_id);
+  s->font = s->face->font;
+  s->width = s->first_glyph->pixel_width;
+  s->ybase += s->first_glyph->voffset;
+  s->canvas = s->first_glyph->u.canvas;
+  s->canvas->changed_since_last_redisplay = false;
+}
 /* Fill glyph string S from a sequence of stretch glyphs.
 
    START is the index of the first glyph to consider,
@@ -28227,6 +28272,18 @@ #define BUILD_IMAGE_GLYPH_STRING(START, END, HEAD, TAIL, HL, X, LAST_X) \
      while (false)
 #endif
 
+#define BUILD_CANVAS_GLYPH_STRING(START, END, HEAD, TAIL, HL, X, LAST_X) \
+  do                                                                     \
+    {                                                                    \
+      s = alloca (sizeof *s);                                            \
+      INIT_GLYPH_STRING (s, NULL, w, row, area, START, HL);              \
+      append_glyph_string (&(HEAD), &(TAIL), s);                         \
+      ++(START);                                                         \
+      s->x = (X);                                                        \
+      fill_canvas_glyph_string (s);					\
+    }                                                                    \
+  while (false)
+
 /* Add a glyph string for a sequence of character glyphs to the list
    of strings between HEAD and TAIL.  START is the index of the first
    glyph in row area AREA of glyph row ROW that is part of the new
@@ -28378,7 +28435,11 @@ #define BUILD_GLYPH_STRINGS_1(START, END, HEAD, TAIL, HL, X, LAST_X)	\
 	    case IMAGE_GLYPH:						\
 	      BUILD_IMAGE_GLYPH_STRING (START, END, HEAD, TAIL,		\
 					HL, X, LAST_X);			\
-	      break;
+	      break;							\
+	    case CANVAS_GLYPH:				        	\
+	      BUILD_CANVAS_GLYPH_STRING (START, END, HEAD, TAIL,	\
+					 HL, X, LAST_X);		\
+	      break;							\
 
 #define BUILD_GLYPH_STRINGS_XW(START, END, HEAD, TAIL, HL, X, LAST_X)	\
             case XWIDGET_GLYPH:                                         \
@@ -29086,6 +29147,116 @@ produce_image_glyph (struct it *it)
     }
 }
 
+static void
+produce_canvas_glyph (struct it *it)
+{
+  struct canvas *canvas;
+  int glyph_ascent, crop;
+
+  eassert (it->what == IT_CANVAS);
+
+  struct face *face = FACE_FROM_ID (it->f, it->face_id);
+  prepare_face_for_display (it->f, face);
+
+  canvas = it->canvas;
+  it->ascent = it->phys_ascent = glyph_ascent = canvas->height / 2;
+  it->descent = it->phys_descent = canvas->height / 2;
+  it->pixel_width = canvas->width;
+
+  if (it->descent < 0)
+    it->descent = 0;
+
+  it->nglyphs = 1;
+
+  if (face->box != FACE_NO_BOX)
+    {
+      if (face->box_horizontal_line_width > 0)
+	{
+	  it->ascent += face->box_horizontal_line_width;
+	  it->descent += face->box_horizontal_line_width;
+	}
+
+      if (face->box_vertical_line_width > 0)
+	{
+	  if (it->start_of_box_run_p)
+	    it->pixel_width += face->box_vertical_line_width;
+	  it->pixel_width += face->box_vertical_line_width;
+	}
+    }
+
+  take_vertical_position_into_account (it);
+
+  /* Automatically crop wide canvas glyphs at right edge so we can
+     draw the cursor on same display row.  */
+  crop = it->pixel_width - (it->last_visible_x - it->current_x);
+  if (crop > 0 && (it->hpos == 0 || it->pixel_width > it->last_visible_x / 4))
+    it->pixel_width -= crop;
+
+  if (it->glyph_row)
+    {
+      enum glyph_row_area area = it->area;
+      struct glyph *glyph
+	= it->glyph_row->glyphs[area] + it->glyph_row->used[area];
+
+      if (it->glyph_row->reversed_p)
+	{
+	  struct glyph *g;
+
+	  /* Make room for the new glyph.  */
+	  for (g = glyph - 1; g >= it->glyph_row->glyphs[it->area]; g--)
+	    g[1] = *g;
+	  glyph = it->glyph_row->glyphs[it->area];
+	}
+      if (glyph < it->glyph_row->glyphs[area + 1])
+	{
+	  glyph->charpos = CHARPOS (it->position);
+	  glyph->object = it->object;
+	  glyph->pixel_width = clip_to_bounds (-1, it->pixel_width, SHRT_MAX);
+	  glyph->ascent = glyph_ascent;
+	  glyph->descent = it->descent;
+	  glyph->voffset = it->voffset;
+	  glyph->type = CANVAS_GLYPH;
+	  glyph->avoid_cursor_p = it->avoid_cursor_p;
+	  glyph->multibyte_p = it->multibyte_p;
+	  glyph->u.canvas = it->canvas;
+	  if (it->glyph_row->reversed_p && area == TEXT_AREA)
+	    {
+	      /* In R2L rows, the left and the right box edges need to be
+		 drawn in reverse direction.  */
+	      glyph->right_box_line_p = it->start_of_box_run_p;
+	      glyph->left_box_line_p = it->end_of_box_run_p;
+	    }
+	  else
+	    {
+	      glyph->left_box_line_p = it->start_of_box_run_p;
+	      glyph->right_box_line_p = it->end_of_box_run_p;
+	    }
+          glyph->overlaps_vertically_p = 0;
+          glyph->padding_p = 0;
+	  glyph->glyph_not_available_p = 0;
+	  glyph->face_id = it->face_id;
+	  glyph->font_type = FONT_TYPE_UNKNOWN;
+	  if (it->bidi_p)
+	    {
+	      glyph->resolved_level = it->bidi_it.resolved_level;
+	      eassert ((it->bidi_it.type & 7) == it->bidi_it.type);
+	      glyph->bidi_type = it->bidi_it.type;
+	    }
+	  ++it->glyph_row->used[area];
+	}
+      else
+	IT_EXPAND_MATRIX_WIDTH (it, area);
+    }
+}
+
+void
+canvas_update_glyph (struct window *w, int x, struct glyph_row *row,
+		     enum glyph_row_area area, ptrdiff_t start, ptrdiff_t end,
+		     struct glyph *glyph)
+{
+  draw_glyphs (w, x, row, area, start, end, DRAW_NORMAL_TEXT, 0);
+}
+
 static void
 produce_xwidget_glyph (struct it *it)
 {
@@ -30608,6 +30779,8 @@ gui_produce_glyphs (struct it *it)
     produce_stretch_glyph (it);
   else if (it->what == IT_XWIDGET)
     produce_xwidget_glyph (it);
+  else if (it->what == IT_CANVAS)
+    produce_canvas_glyph (it);
 
  done:
   /* Accumulate dimensions.  Note: can't assume that it->descent > 0
@@ -31008,6 +31181,10 @@ get_window_cursor_type (struct window *w, struct glyph *glyph, int *width,
 	      cursor_type = HOLLOW_BOX_CURSOR;
 	    }
       }
+      if (glyph != NULL && glyph->type == CANVAS_GLYPH)
+	{
+	  cursor_type = HOLLOW_BOX_CURSOR;
+	}
       return cursor_type;
     }
 
diff --git a/src/xterm.c b/src/xterm.c
index 7989cecec7..716ed0ef97 100644
--- a/src/xterm.c
+++ b/src/xterm.c
@@ -75,6 +75,7 @@ Copyright (C) 1989, 1993-2020 Free Software Foundation, Inc.
 #include "sysselect.h"
 #include "menu.h"
 #include "pdumper.h"
+#include "canvas.h"
 
 #ifdef USE_X_TOOLKIT
 #include <X11/Shell.h>
@@ -2066,6 +2067,30 @@ x_draw_glyphless_glyph_string_foreground (struct glyph_string *s)
    }
 }
 
+static void
+x_draw_canvas_glyph_string_foreground (struct glyph_string *s)
+{
+  eassert (s->first_glyph->type == CANVAS_GLYPH);
+#ifdef USE_CAIRO
+  cairo_t *cr = x_begin_cr_clip (s->f, s->gc);
+  int x = s->x;
+  int y = s->ybase - s->first_glyph->ascent;
+
+  if (s->face->box != FACE_NO_BOX &&
+      s->first_glyph->left_box_line_p)
+    x += max (s->face->box_vertical_line_width, 0);
+
+  x_set_glyph_string_clipping (s);
+  x_clear_area (s->f, x, y, s->width, s->height);
+  cairo_set_source_surface (cr, s->canvas->canvas,
+			    s->x, s->y);
+  cairo_paint (cr);
+  x_end_cr_clip (s->f);
+#else
+  emacs_abort ();
+#endif
+}
+
 #ifdef USE_X_TOOLKIT
 
 #ifdef USE_LUCID
@@ -3811,6 +3836,11 @@ x_draw_glyph_string (struct glyph_string *s)
       x_draw_glyphless_glyph_string_foreground (s);
       break;
 
+    case CANVAS_GLYPH:
+      x_draw_glyph_string_background (s, true);
+      x_draw_canvas_glyph_string_foreground (s);
+      break;
+
     default:
       emacs_abort ();
     }

^ permalink raw reply related	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 10:22         ` Po Lu
  2020-04-29 10:27           ` Po Lu via Emacs development discussions.
@ 2020-04-29 10:35           ` Eli Zaretskii
  2020-04-29 10:41             ` Po Lu
  1 sibling, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 10:35 UTC (permalink / raw)
  To: Po Lu; +Cc: emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: emacs-devel@gnu.org
> Date: Wed, 29 Apr 2020 18:22:13 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > That's the technical description of the implementation.  It doesn't
> > explain when  canvases can be useful and for what purposes.
> 
> Canvases are useful for when I want to be able to control a portion of a
> screen dynamically, in a fast way, from Lisp code.  For instance,
> displaying a constantly changing bar chart or graph inside Emacs.

So this _is_ a way of putting images on the Emacs display, but in a
way that creates the image dynamically from Lisp, instead of reading
it from a file or producing the binary data for that image.  Right?

Incidentally, how does this compare with functions in svg.el?



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 10:35           ` Eli Zaretskii
@ 2020-04-29 10:41             ` Po Lu
  2020-04-29 11:51               ` Eli Zaretskii
  2020-04-29 16:14               ` David Engster
  0 siblings, 2 replies; 53+ messages in thread
From: Po Lu @ 2020-04-29 10:41 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> So this _is_ a way of putting images on the Emacs display, but in a
> way that creates the image dynamically from Lisp, instead of reading
> it from a file or producing the binary data for that image.  Right?

Yes.
> Incidentally, how does this compare with functions in svg.el?

While right now svg.el seems to have more features, it isn't
particularly well suited to displaying things quickly.

In fact, my own bad experiences with using svg.el to draw rapidly
changing information inspired me to begin working on this.

In the future, I hope that canvases also become more flexible than
svg.el.  A nice goal would be to have everything cairo has.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 10:27           ` Po Lu via Emacs development discussions.
@ 2020-04-29 11:47             ` Eli Zaretskii
  0 siblings, 0 replies; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 11:47 UTC (permalink / raw)
  To: Po Lu; +Cc: emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: emacs-devel@gnu.org
> Date: Wed, 29 Apr 2020 18:27:49 +0800
> 
> >> That's the technical description of the implementation.  It doesn't
> >> explain when  canvases can be useful and for what purposes.
> >
> > Canvases are useful for when I want to be able to control a portion of a
> > screen dynamically, in a fast way, from Lisp code.  For instance,
> > displaying a constantly changing bar chart or graph inside Emacs.
> >
> >> But the result of this painting is some graphical object, similar to
> >> an image, that will be displayed within a buffer, right?
> >
> > Correct.
> >
> >> Did I miss the code that tells Emacs the click was on a canvas?  E.g.,
> >> if you click on a canvas, what does posn-object return when passed the
> >> click event as its argument?
> >
> > Oops.  I missed that.  Thanks for bringing it up.
> 
> Here is a version of the patch with several of the problems you
> mentioned rectified.

Thanks.

> +@node Canvases
> +@cindex drawing canvases
> +@cindex drawing areas
> +@cindex canvases
> +@section Canvases
> +
> +This chapter describes canvases, objects that can store drawing operations
> +which are then displayed inside buffer text.

Please consider telling something here about when this is useful.

Also, I think there should be a short subsection in "Editing Types"
with the description of the canvas type, like we have for other
editing types.

> @@ -4371,6 +4374,11 @@ scrolling_window (struct window *w, int tab_line_p)
>      return 0;
>  #endif
>  
> +  /* We need this to fix canvas movement detection in a reliable way.
> +     FIXME. */
> +  if (w->have_canvas_p)
> +    return 0;

This is sub-optimal.  Please don't follow the xwidget example of
disabling redisplay optimizations because it might be hairy to support
them.  It would make any window with canvases redisplay much slower in
many cases.

> @@ -5462,6 +5470,10 @@ buffer_posn_from_coords (struct window *w, int *x, int *y, struct display_pos *p
>        if (img && !NILP (img->spec))
>  	*object = img->spec;
>      }
> +  else if (it.what == IT_CANVAS)
> +    {
> +      XSETCANVAS (*object, it.canvas);
> +    }
>  #endif

This is just the object.  That's okay, but don't you want to support
clicks on various parts of the canvas, like we do with images?  That
would require more info to be returned.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 10:41             ` Po Lu
@ 2020-04-29 11:51               ` Eli Zaretskii
  2020-04-29 12:12                 ` Po Lu
  2020-04-29 16:14               ` David Engster
  1 sibling, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 11:51 UTC (permalink / raw)
  To: Po Lu; +Cc: emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: emacs-devel@gnu.org
> Date: Wed, 29 Apr 2020 18:41:08 +0800
> 
> > Incidentally, how does this compare with functions in svg.el?
> 
> While right now svg.el seems to have more features, it isn't
> particularly well suited to displaying things quickly.
> 
> In fact, my own bad experiences with using svg.el to draw rapidly
> changing information inspired me to begin working on this.

What exactly is slow there, and why?

> In the future, I hope that canvases also become more flexible than
> svg.el.  A nice goal would be to have everything cairo has.

If canvasses are meant to be a replacement for svg.el, we need to keep
both equivalent, functionality-wise, for the benefit of platforms that
don't use Cairo, and also to ease the transition period for those
platforms which do.

So the relation between svg.el and canvasses should be rethought, I
think.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 11:51               ` Eli Zaretskii
@ 2020-04-29 12:12                 ` Po Lu
  0 siblings, 0 replies; 53+ messages in thread
From: Po Lu @ 2020-04-29 12:12 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:


> If canvasses are meant to be a replacement for svg.el, we need to keep
> both equivalent, functionality-wise, for the benefit of platforms that
> don't use Cairo, and also to ease the transition period for those
> platforms which do.
>
> So the relation between svg.el and canvasses should be rethought, I
> think.

Yeah you indeed have a point there.  I'll think over this tonight.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 10:41             ` Po Lu
  2020-04-29 11:51               ` Eli Zaretskii
@ 2020-04-29 16:14               ` David Engster
  2020-04-29 16:54                 ` Eli Zaretskii
  2020-04-29 17:08                 ` Eli Zaretskii
  1 sibling, 2 replies; 53+ messages in thread
From: David Engster @ 2020-04-29 16:14 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, emacs-devel

> While right now svg.el seems to have more features, it isn't
> particularly well suited to displaying things quickly.
>
> In fact, my own bad experiences with using svg.el to draw rapidly
> changing information inspired me to begin working on this.
>
> In the future, I hope that canvases also become more flexible than
> svg.el.  A nice goal would be to have everything cairo has.

A while ago I played around with using Cairo primitives to draw in
Emacs. While it would be nice to have something like a 'canvas object'
to draw on, Eli is correct that you can achieve similar things already
with SVG - it may be slow, but that could be fixable.

IMHO, What would be much more exciting, is if you could draw directly
over normal text. This would enable a whole new set of
possibilities. For instance, I always liked visual diff tools like
'Meld' or 'Code Compare', but I don't see how you could do this with
Emacs. Or things like proper indentation guides, or widgets inside text
for code folding. Yes, those can be done in Emacs today, but it is very
difficult to get right and often not really satisfactory (weird
interactions between packages, too slow, etc.).

I know the tricky part is not the drawing, but redisplay. I would have
no idea how to handle this.

-David



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 16:14               ` David Engster
@ 2020-04-29 16:54                 ` Eli Zaretskii
  2020-04-29 17:16                   ` tomas
                                     ` (2 more replies)
  2020-04-29 17:08                 ` Eli Zaretskii
  1 sibling, 3 replies; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 16:54 UTC (permalink / raw)
  To: David Engster; +Cc: luangruo, emacs-devel

> From: David Engster <deng@randomsample.de>
> Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> Date: Wed, 29 Apr 2020 18:14:55 +0200
> 
> IMHO, What would be much more exciting, is if you could draw directly
> over normal text. This would enable a whole new set of
> possibilities. For instance, I always liked visual diff tools like
> 'Meld' or 'Code Compare', but I don't see how you could do this with
> Emacs. Or things like proper indentation guides, or widgets inside text
> for code folding. Yes, those can be done in Emacs today, but it is very
> difficult to get right and often not really satisfactory (weird
> interactions between packages, too slow, etc.).

AFAIU, Meld seems to draw the graphics _between_ windows, and only
highlights the source lines with background colors.  I think we can do
that if we use an extra window in-between the two being compared, and
put images in that extra window to show the graphical description of
the changes between the two versions.

Drawing over normal text, if we don't want to redesign the entire
display engine, needs some new kind of "display element" ( a sibling
to "character", "image", "stretch", etc.), one that doesn't
necessarily have any effect on the metrics of the screen lines it is
drawn upon.  I'm not sure I have a clear idea about what features such
a drawing will need to support, but it could be possible to add such
an element with not too much effort.  Would someone want to come up
with a reasonable list of requirements for such a feature?



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 16:14               ` David Engster
  2020-04-29 16:54                 ` Eli Zaretskii
@ 2020-04-29 17:08                 ` Eli Zaretskii
  2020-04-29 20:14                   ` David Engster
  1 sibling, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 17:08 UTC (permalink / raw)
  To: David Engster; +Cc: luangruo, emacs-devel

> From: David Engster <deng@randomsample.de>
> Date: Wed, 29 Apr 2020 18:14:55 +0200
> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
> 
> [...] things like proper indentation guides, or widgets inside text
> for code folding.

Would it make sense to put these on the fringes?  If yes, this could
be done without any changes on the C level, I think.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 16:54                 ` Eli Zaretskii
@ 2020-04-29 17:16                   ` tomas
  2020-04-29 17:27                     ` Eli Zaretskii
  2020-04-29 19:23                   ` David Engster
  2020-04-30  6:52                   ` Corwin Brust
  2 siblings, 1 reply; 53+ messages in thread
From: tomas @ 2020-04-29 17:16 UTC (permalink / raw)
  To: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 914 bytes --]

On Wed, Apr 29, 2020 at 07:54:02PM +0300, Eli Zaretskii wrote:

[...]

> Drawing over normal text, if we don't want to redesign the entire
> display engine, needs some new kind of "display element" ( a sibling
> to "character", "image", "stretch", etc.), one that doesn't
> necessarily have any effect on the metrics of the screen lines it is
> drawn upon.  I'm not sure I have a clear idea about what features such
> a drawing will need to support, but it could be possible to add such
> an element with not too much effort.  Would someone want to come up
> with a reasonable list of requirements for such a feature?

That sounds... exciting. Basically, Emacs would have to have a "display
list" of graphical elements to draw, each one perhaps having a bounding
box (to discard those from redisplay which aren't currently visible)
and perhaps a "layer" (more to the background or foreground).

Hmmm.

Cheers
-- t

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 17:16                   ` tomas
@ 2020-04-29 17:27                     ` Eli Zaretskii
  2020-04-29 17:38                       ` Eli Zaretskii
  2020-04-29 18:51                       ` tomas
  0 siblings, 2 replies; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 17:27 UTC (permalink / raw)
  To: tomas; +Cc: emacs-devel

> Date: Wed, 29 Apr 2020 19:16:19 +0200
> From: <tomas@tuxteam.de>
> 
> > Drawing over normal text, if we don't want to redesign the entire
> > display engine, needs some new kind of "display element" ( a sibling
> > to "character", "image", "stretch", etc.), one that doesn't
> > necessarily have any effect on the metrics of the screen lines it is
> > drawn upon.  I'm not sure I have a clear idea about what features such
> > a drawing will need to support, but it could be possible to add such
> > an element with not too much effort.  Would someone want to come up
> > with a reasonable list of requirements for such a feature?
> 
> That sounds... exciting. Basically, Emacs would have to have a "display
> list" of graphical elements to draw, each one perhaps having a bounding
> box (to discard those from redisplay which aren't currently visible)
> and perhaps a "layer" (more to the background or foreground).

That's not how Emacs controls what's on display.  It basically
represents each window as a 2D array of glyphs, each one of which has
a certain graphical representation.  The representation itself is of
no concern to the display engine (well, almost); the only thing it
cares about is the metrics of each glyph, because that's what it needs
to do layout calculations.

We need to try to fit into this framework, if possible.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 17:27                     ` Eli Zaretskii
@ 2020-04-29 17:38                       ` Eli Zaretskii
  2020-04-30 13:11                         ` Arthur Miller
  2020-04-29 18:51                       ` tomas
  1 sibling, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 17:38 UTC (permalink / raw)
  To: tomas; +Cc: emacs-devel

> Date: Wed, 29 Apr 2020 20:27:53 +0300
> From: Eli Zaretskii <eliz@gnu.org>
> Cc: emacs-devel@gnu.org
> 
> That's not how Emacs controls what's on display.  It basically
> represents each window as a 2D array of glyphs, each one of which has
> a certain graphical representation.  The representation itself is of
> no concern to the display engine (well, almost); the only thing it
> cares about is the metrics of each glyph, because that's what it needs
> to do layout calculations.

Oh, and one more important aspect: everything that winds up on display
must come from some buffer or some Lisp string.  It could be a special
text property or somesuch, but it must be found by walking some buffer
or some known-in-advance Lisp string, because that's what top-level
display functions do: they iterate over these objects, and produce the
glyphs based on what they find there.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 17:27                     ` Eli Zaretskii
  2020-04-29 17:38                       ` Eli Zaretskii
@ 2020-04-29 18:51                       ` tomas
  2020-04-29 19:03                         ` Eli Zaretskii
  1 sibling, 1 reply; 53+ messages in thread
From: tomas @ 2020-04-29 18:51 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 2044 bytes --]

On Wed, Apr 29, 2020 at 08:27:53PM +0300, Eli Zaretskii wrote:
> > Date: Wed, 29 Apr 2020 19:16:19 +0200
> > From: <tomas@tuxteam.de>
> > 
> > > Drawing over normal text, if we don't want to redesign the entire
> > > display engine, needs some new kind of "display element" ( a sibling
> > > to "character", "image", "stretch", etc.), one that doesn't
> > > necessarily have any effect on the metrics of the screen lines it is
> > > drawn upon.  I'm not sure I have a clear idea about what features such
> > > a drawing will need to support, but it could be possible to add such
> > > an element with not too much effort.  Would someone want to come up
> > > with a reasonable list of requirements for such a feature?
> > 
> > That sounds... exciting. Basically, Emacs would have to have a "display
> > list" of graphical elements to draw, each one perhaps having a bounding
> > box (to discard those from redisplay which aren't currently visible)
> > and perhaps a "layer" (more to the background or foreground).
> 
> That's not how Emacs controls what's on display.  It basically
> represents each window as a 2D array of glyphs, each one of which has
> a certain graphical representation.

...a display list of sorts.

>                                     The representation itself is of
> no concern to the display engine (well, almost); the only thing it
> cares about is the metrics of each glyph, because that's what it needs
> to do layout calculations.

A graphical overlay wouldn't have "glyph metrics", the graphical
objects would (I think) have "absolute" [1] positions (possibly
precalculated by something else).

> We need to try to fit into this framework, if possible.

I think the "interesting" problem is to know (quickly) which
graphical objects intersect the (visible) window -- something
graphics programs do as their main job.

Cheers

[1] Well, absolute is relative ;-) perhaps anchored at some text
   in the buffer, perhaps anchored to the window... I don't know.
-- tomás

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 18:51                       ` tomas
@ 2020-04-29 19:03                         ` Eli Zaretskii
  2020-04-29 19:08                           ` tomas
  0 siblings, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 19:03 UTC (permalink / raw)
  To: tomas; +Cc: emacs-devel

> Date: Wed, 29 Apr 2020 20:51:28 +0200
> From: tomas@tuxteam.de
> Cc: emacs-devel@gnu.org
> 
> > That's not how Emacs controls what's on display.  It basically
> > represents each window as a 2D array of glyphs, each one of which has
> > a certain graphical representation.
> 
> ...a display list of sorts.

It's a far cry from any "list" in my book...

> >                                     The representation itself is of
> > no concern to the display engine (well, almost); the only thing it
> > cares about is the metrics of each glyph, because that's what it needs
> > to do layout calculations.
> 
> A graphical overlay wouldn't have "glyph metrics", the graphical
> objects would (I think) have "absolute" [1] positions (possibly
> precalculated by something else).

The position is basically determined by the place in a buffer or a
string where they were found.

> I think the "interesting" problem is to know (quickly) which
> graphical objects intersect the (visible) window -- something
> graphics programs do as their main job.

That's easy, we have infrastructure for that already -- it is used in
expose_frame and its subroutines.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 19:03                         ` Eli Zaretskii
@ 2020-04-29 19:08                           ` tomas
  2020-04-29 19:25                             ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: tomas @ 2020-04-29 19:08 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1066 bytes --]

On Wed, Apr 29, 2020 at 10:03:00PM +0300, Eli Zaretskii wrote:
> > Date: Wed, 29 Apr 2020 20:51:28 +0200
> > From: tomas@tuxteam.de
> > Cc: emacs-devel@gnu.org
> > 
> > > That's not how Emacs controls what's on display.  It basically
> > > represents each window as a 2D array of glyphs, each one of which has
> > > a certain graphical representation.
> > 
> > ...a display list of sorts.
> 
> It's a far cry from any "list" in my book...

Don't take the "list" too literally. I was possibly misusing (by
extension) some now-obsolete graphics jargon.

> > I think the "interesting" problem is to know (quickly) which
> > graphical objects intersect the (visible) window -- something
> > graphics programs do as their main job.
> 
> That's easy, we have infrastructure for that already -- it is used in
> expose_frame and its subroutines.

This sounds... encouraging :-)

(TBH I was coming from the other side: index the graphics objects
in a way that one doesn't need to look at most of those which
are currently hidden).

Cheers
-- t

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 16:54                 ` Eli Zaretskii
  2020-04-29 17:16                   ` tomas
@ 2020-04-29 19:23                   ` David Engster
  2020-04-30 13:29                     ` Eli Zaretskii
  2020-04-30  6:52                   ` Corwin Brust
  2 siblings, 1 reply; 53+ messages in thread
From: David Engster @ 2020-04-29 19:23 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

>> From: David Engster <deng@randomsample.de>
>> Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
>> Date: Wed, 29 Apr 2020 18:14:55 +0200
>
>> 
>> IMHO, What would be much more exciting, is if you could draw directly
>> over normal text. This would enable a whole new set of
>> possibilities. For instance, I always liked visual diff tools like
>> 'Meld' or 'Code Compare', but I don't see how you could do this with
>> Emacs. Or things like proper indentation guides, or widgets inside text
>> for code folding. Yes, those can be done in Emacs today, but it is very
>> difficult to get right and often not really satisfactory (weird
>> interactions between packages, too slow, etc.).
>
> AFAIU, Meld seems to draw the graphics _between_ windows, and only
> highlights the source lines with background colors.  I think we can do
> that if we use an extra window in-between the two being compared, and
> put images in that extra window to show the graphical description of
> the changes between the two versions.

I actually tried exactly that, many years ago. I dimly remember that
handling the coordinates was pretty complicated and it was slow. Like,
orders of magnitude too slow, even with the simplest and fastest image
types. There was no way you would ever be able to scroll text and update
the diff visualization alongside it. It was just a flickery mess. It
would of course be possible to visualize the diff in an idle timer and
not during scrolling, but that is exactly what I mean above with "not
really satisfactory", so I gave up.

> Drawing over normal text, if we don't want to redesign the entire
> display engine, needs some new kind of "display element" ( a sibling
> to "character", "image", "stretch", etc.), one that doesn't
> necessarily have any effect on the metrics of the screen lines it is
> drawn upon.  I'm not sure I have a clear idea about what features such
> a drawing will need to support, but it could be possible to add such
> an element with not too much effort.  Would someone want to come up
> with a reasonable list of requirements for such a feature?

When I played around with it, I thought a first iteration could be to
simply expose the basic Cairo API to ELisp, which is essentially drawing
lines/arcs/rectangles and filling. I thought that maybe it would be
possible that one could handle this like a graphical transparent
overlay, but I'm afraid I'm way out of my depth here. I have no idea how
one would handle scrolling, for instance.

-David



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 19:08                           ` tomas
@ 2020-04-29 19:25                             ` Eli Zaretskii
  2020-04-29 19:59                               ` tomas
  0 siblings, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-29 19:25 UTC (permalink / raw)
  To: tomas; +Cc: emacs-devel

> Date: Wed, 29 Apr 2020 21:08:54 +0200
> From: tomas@tuxteam.de
> Cc: emacs-devel@gnu.org
> 
> > > I think the "interesting" problem is to know (quickly) which
> > > graphical objects intersect the (visible) window -- something
> > > graphics programs do as their main job.
> > 
> > That's easy, we have infrastructure for that already -- it is used in
> > expose_frame and its subroutines.
> 
> This sounds... encouraging :-)
> 
> (TBH I was coming from the other side: index the graphics objects
> in a way that one doesn't need to look at most of those which
> are currently hidden).

The current display engine works by screen lines, so if we want to
keep it, we must use the existing framework.  When a portion of a
window is exposed, we redraw all the glyphs in the exposed area(s),
and we find the glyphs that need to be redrawn by comparing their
coordinates with those of the exposed rectangle(s).



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 19:25                             ` Eli Zaretskii
@ 2020-04-29 19:59                               ` tomas
  2020-04-30  1:19                                 ` Stefan Monnier
  2020-04-30 13:33                                 ` Eli Zaretskii
  0 siblings, 2 replies; 53+ messages in thread
From: tomas @ 2020-04-29 19:59 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: emacs-devel

[-- Attachment #1: Type: text/plain, Size: 913 bytes --]

On Wed, Apr 29, 2020 at 10:25:04PM +0300, Eli Zaretskii wrote:

[...]

> The current display engine works by screen lines, so if we want to
> keep it, we must use the existing framework.

Now imagine there's a (graphical) line going from (text) display
line 3 to 7. You are not implying that we have to segment that
into four chunks, one per text line?

>                                               When a portion of a
> window is exposed, we redraw all the glyphs in the exposed area(s),
> and we find the glyphs that need to be redrawn by comparing their
> coordinates with those of the exposed rectangle(s).

... but rather that we intersect the exposed area(s) with each [1]
of the graphical objects and redraw that (be it before or after the
text)?

Cheers

[1] conceptually; in reality we'll need some index structure
   to avoid looking at most of the graphical objects. Quad-
   trees, whatever.

-- t

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 17:08                 ` Eli Zaretskii
@ 2020-04-29 20:14                   ` David Engster
  2020-04-30 13:35                     ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: David Engster @ 2020-04-29 20:14 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

>> From: David Engster <deng@randomsample.de>
>> Date: Wed, 29 Apr 2020 18:14:55 +0200
>> Cc: Eli Zaretskii <eliz@gnu.org>, emacs-devel@gnu.org
>> 
>> [...] things like proper indentation guides, or widgets inside text
>> for code folding.
>
> Would it make sense to put these on the fringes?  If yes, this could
> be done without any changes on the C level, I think.

I just found that this exists already with the 'hideshowvis' package and
it works reasonably well. So I agree this is not a good use case.

You can also do indentation guides in current Emacs, but the packages I
tried did not work great. They usually collide with packages like
company-mode which are more important to me. This is often the case: if
you have several packages that use overlays and/or text properties for
fancy stuff, you can expect them to collide in some way.

Let's take another very simple example: I often wished it would be
possible to tell Emacs "Put an image at pixel position (x,y)" without
having to anchor it to some point position. For instance, I have a block
of text and would like to have an image next to it. Last I checked, the
only way to do this is to create a "sliced image" which automatically
gets cut up into rows and columns, but this is a very brittle workaround
(it doesn't work with an increased line-spacing, for instance).

-David



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29  6:34 ` Emacs canvas support Po Lu via Emacs development discussions.
  2020-04-29  8:24   ` Eli Zaretskii
@ 2020-04-29 20:48   ` Juri Linkov
  2020-04-30  2:36     ` Eli Zaretskii
  1 sibling, 1 reply; 53+ messages in thread
From: Juri Linkov @ 2020-04-29 20:48 UTC (permalink / raw)
  To: Po Lu via Emacs development discussions.; +Cc: Po Lu

> I'd appreciate some feedback on something I came up with during my spare
> time: Emacs canvas support.

Great, thanks!  I'm going to try to use canvas-rounded-rectangle
for rounded corners of tabs.  Sometime ago I tried to draw them
with Cairo, but the implementation was too ah-hoc.  Whereas your
solution is more generally usable.  Would you put your code on a branch?



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 19:59                               ` tomas
@ 2020-04-30  1:19                                 ` Stefan Monnier
  2020-04-30  6:55                                   ` tomas
                                                     ` (3 more replies)
  2020-04-30 13:33                                 ` Eli Zaretskii
  1 sibling, 4 replies; 53+ messages in thread
From: Stefan Monnier @ 2020-04-30  1:19 UTC (permalink / raw)
  To: tomas; +Cc: Eli Zaretskii, emacs-devel

> Now imagine there's a (graphical) line going from (text) display
> line 3 to 7. You are not implying that we have to segment that
> into four chunks, one per text line?

I think it would make more sense to treat the overlaid canvas as
a completely separate pixmap: when we get an request to redraw
a particular area of the screen, we'd ask the current redisplay code to
redraw the corresponding text content and then we'd ask the canvas code
to draw on top of it.  So for rendering of the canvas code we don't need
to know which part of the canvas cover which characters, we just render
the glyph matrix into a pixmap, render the canvas into another pixmap
and then combine them onto the screen.


        Stefan




^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 20:48   ` Juri Linkov
@ 2020-04-30  2:36     ` Eli Zaretskii
  2020-04-30 20:20       ` Juri Linkov
  0 siblings, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30  2:36 UTC (permalink / raw)
  To: Juri Linkov; +Cc: luangruo, emacs-devel

> From: Juri Linkov <juri@linkov.net>
> Date: Wed, 29 Apr 2020 23:48:31 +0300
> Cc: Po Lu <luangruo@yahoo.com>
> 
> > I'd appreciate some feedback on something I came up with during my spare
> > time: Emacs canvas support.
> 
> Great, thanks!  I'm going to try to use canvas-rounded-rectangle
> for rounded corners of tabs.  Sometime ago I tried to draw them
> with Cairo, but the implementation was too ah-hoc.  Whereas your
> solution is more generally usable.

Did you try using svg.el?

> Would you put your code on a branch?

We need to finish the legal paperwork first.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 16:54                 ` Eli Zaretskii
  2020-04-29 17:16                   ` tomas
  2020-04-29 19:23                   ` David Engster
@ 2020-04-30  6:52                   ` Corwin Brust
  2 siblings, 0 replies; 53+ messages in thread
From: Corwin Brust @ 2020-04-30  6:52 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, David Engster, Emacs developers

Hi everyone!  This is conversation intersects very directly with a
project I organize that is creating an RPG game engine and
authoring/publishing enhancements based on org-mode and svg.el.  Our
project is very new and the things that work only work if one is
careful and patient.  We're targeting whatever will likely be
supported in Emacs 28 or so, meaning we would be open to help testing
new features that come out of this and similar conversations, if any,
and given we're up the tasks.  (Our application for savannah hosting
is in progress; I've no interest to discuss the project's current
non-free hosting solution.)

On Wed, Apr 29, 2020 at 11:55 AM Eli Zaretskii <eliz@gnu.org> wrote:
>
> > From: David Engster <deng@randomsample.de>
> > Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> > Date: Wed, 29 Apr 2020 18:14:55 +0200
> >
> > IMHO, What would be much more exciting, is if you could draw directly
> > over normal text. This would enable a whole new set of
> > possibilities.


> [..] not too much effort.  Would someone want to come up
> with a reasonable list of requirements for such a feature?


To the extent it may help with developing the list you suggested, I
wanted to share this SVG image from our project:

   https://raw.githubusercontent.com/dungeon-mode/game/master/Docs/battleboard/Battleboard.svg

This file mocks an important player interface that shows the vital
statistics for each of the eight player-characters that compose the
"party".   The four inner boxes inside each plus-shape create a space
for one such statistic, for example, Body Hits, Armor Hit Points, etc.
which are displayed integers.  These, like the actual text show in the
mock, should ideally be text.   It seems to me that this should work
well with such overlay methods as are discussed down-thread.

I hope the graphic provides a useful context for on "in the wild"
aspirational use for composed SVG+text in user-space. I'm happy to be
pulled into other threads/conversations related to enhancing display
options for hybrid text and SVG.

PS, For the "mapping" feature (the party controls their movement
through a maze), which is the only truly implicitly graphical
component we are creating, we have resorted.to drawing text overlays
with the "text" SVG element; however, for the battle-board, we had
been planning to use a window for each character (so eight windows)
and slices within to achieve the crisp lines and while allowing some
room for some theming.

-- 
Corwin
612-217-1742
612-298-0615 (fax)
612-695-4276 (mobile)
corwin.brust (skype)
corwin@bru.st



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30  1:19                                 ` Stefan Monnier
@ 2020-04-30  6:55                                   ` tomas
  2020-04-30 12:03                                     ` Stefan Monnier
  2020-04-30  8:04                                   ` Po Lu
                                                     ` (2 subsequent siblings)
  3 siblings, 1 reply; 53+ messages in thread
From: tomas @ 2020-04-30  6:55 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 939 bytes --]

On Wed, Apr 29, 2020 at 09:19:51PM -0400, Stefan Monnier wrote:
> > Now imagine there's a (graphical) line going from (text) display
> > line 3 to 7. You are not implying that we have to segment that
> > into four chunks, one per text line?
> 
> I think it would make more sense to treat the overlaid canvas as
> a completely separate pixmap: when we get an request to redraw
> a particular area of the screen, we'd ask the current redisplay code to
> redraw the corresponding text content and then we'd ask the canvas code
> to draw on top of it.  So for rendering of the canvas code we don't need
> to know which part of the canvas cover which characters, we just render
> the glyph matrix into a pixmap, render the canvas into another pixmap
> and then combine them onto the screen.

This is sounding more and more like a compositor :-)

Nit: perhaps we'd like to have pics below as well as above text.

Cheers
-- t

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30  1:19                                 ` Stefan Monnier
  2020-04-30  6:55                                   ` tomas
@ 2020-04-30  8:04                                   ` Po Lu
  2020-04-30 12:08                                     ` Stefan Monnier
  2020-04-30 13:55                                     ` Eli Zaretskii
  2020-04-30 13:46                                   ` Eli Zaretskii
  2020-04-30 14:27                                   ` Drew Adams
  3 siblings, 2 replies; 53+ messages in thread
From: Po Lu @ 2020-04-30  8:04 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: tomas, Eli Zaretskii, emacs-devel

Stefan Monnier <monnier@iro.umontreal.ca> writes:

> I think it would make more sense to treat the overlaid canvas as
> a completely separate pixmap: when we get an request to redraw
> a particular area of the screen, we'd ask the current redisplay code to
> redraw the corresponding text content and then we'd ask the canvas code
> to draw on top of it.  So for rendering of the canvas code we don't need
> to know which part of the canvas cover which characters, we just render
> the glyph matrix into a pixmap, render the canvas into another pixmap
> and then combine them onto the screen.

That's what I'm trying to do right now, but so far keeping track of
exactly where each canvas is displayed on the screen seems to be
difficult.  I'd appreciate some suggestions, thanks.




^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30  6:55                                   ` tomas
@ 2020-04-30 12:03                                     ` Stefan Monnier
  2020-04-30 12:50                                       ` tomas
  0 siblings, 1 reply; 53+ messages in thread
From: Stefan Monnier @ 2020-04-30 12:03 UTC (permalink / raw)
  To: tomas; +Cc: Eli Zaretskii, emacs-devel

> This is sounding more and more like a compositor :-)

Yes, the combination of the glyph matrix and the canvas is done by
"a compositor", indeed.  But it's a kind of "trivial" version of
a compositor, and it's a very small part of the work (the work taking
place mostly on the canvas side where we'll need to draw at positions
that depend on the glyph matrix).

> Nit: perhaps we'd like to have pics below as well as above text.

Not sure if the benefit is worth adding complexity for that.
But maybe it's easy and natural to make the layering between glyph
matrix and canvas into an arbitrary stack of layers.

E.g. I could imagine a design where windows can display a stack of
buffers layered one on top of the other (where typically only one of
them contains text and the rest only contains canvas objects).
But "synchronizing" the canvas in one buffer with the text in the other
buffer would likely be more difficult than if we add a canvas "overlay"
directly to the buffers.


        Stefan




^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30  8:04                                   ` Po Lu
@ 2020-04-30 12:08                                     ` Stefan Monnier
  2020-04-30 13:55                                     ` Eli Zaretskii
  1 sibling, 0 replies; 53+ messages in thread
From: Stefan Monnier @ 2020-04-30 12:08 UTC (permalink / raw)
  To: Po Lu; +Cc: Eli Zaretskii, tomas, emacs-devel

>> I think it would make more sense to treat the overlaid canvas as
>> a completely separate pixmap: when we get an request to redraw
>> a particular area of the screen, we'd ask the current redisplay code to
>> redraw the corresponding text content and then we'd ask the canvas code
>> to draw on top of it.  So for rendering of the canvas code we don't need
>> to know which part of the canvas cover which characters, we just render
>> the glyph matrix into a pixmap, render the canvas into another pixmap
>> and then combine them onto the screen.
>
> That's what I'm trying to do right now, but so far keeping track of
> exactly where each canvas is displayed on the screen seems to be
> difficult.

I was thinking of having just one canvas per (Emacs) window.

> I'd appreciate some suggestions, thanks.

I can help with type system and compiler design.
But I have no practical experience with graphical APIs, sadly.


        Stefan




^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 12:03                                     ` Stefan Monnier
@ 2020-04-30 12:50                                       ` tomas
  0 siblings, 0 replies; 53+ messages in thread
From: tomas @ 2020-04-30 12:50 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: Eli Zaretskii, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 1669 bytes --]

On Thu, Apr 30, 2020 at 08:03:35AM -0400, Stefan Monnier wrote:
> > This is sounding more and more like a compositor :-)
> 
> Yes, the combination of the glyph matrix and the canvas is done by
> "a compositor", indeed.  But it's a kind of "trivial" version of
> a compositor, and it's a very small part of the work (the work taking
> place mostly on the canvas side where we'll need to draw at positions
> that depend on the glyph matrix).

Yes, one would probably want to anchor a graphics object to some
position in the (text) buffer.

> > Nit: perhaps we'd like to have pics below as well as above text.
> 
> Not sure if the benefit is worth adding complexity for that.
> But maybe it's easy and natural to make the layering between glyph
> matrix and canvas into an arbitrary stack of layers.

AFAIK Cairo supports painting on layers; so each graphics object
could carry a layer (encoded e.g. as an int) and drawing operations
could proceed sorted by that.

> E.g. I could imagine a design where windows can display a stack of
> buffers layered one on top of the other (where typically only one of
> them contains text and the rest only contains canvas objects).
> But "synchronizing" the canvas in one buffer with the text in the other
> buffer would likely be more difficult than if we add a canvas "overlay"
> directly to the buffers.

I envision the thing rather as a collection of graphics objects which
are rendered to the same surface as the text. The Cairo backend makes
that easy, but I don't see why a plain X backend would prevent that
either (I have no idea about how things are on Windows or Mac).

Cheers
-- tomás

[-- Attachment #2: Digital signature --]
[-- Type: application/pgp-signature, Size: 198 bytes --]

^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 17:38                       ` Eli Zaretskii
@ 2020-04-30 13:11                         ` Arthur Miller
  2020-04-30 14:18                           ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: Arthur Miller @ 2020-04-30 13:11 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: tomas, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Date: Wed, 29 Apr 2020 20:27:53 +0300
>> From: Eli Zaretskii <eliz@gnu.org>
>> Cc: emacs-devel@gnu.org
>> 
>> That's not how Emacs controls what's on display.  It basically
>> represents each window as a 2D array of glyphs, each one of which has
>> a certain graphical representation.  The representation itself is of
>> no concern to the display engine (well, almost); the only thing it
>> cares about is the metrics of each glyph, because that's what it needs
>> to do layout calculations.
>
> Oh, and one more important aspect: everything that winds up on display
> must come from some buffer or some Lisp string.  It could be a special
> text property or somesuch, but it must be found by walking some buffer
> or some known-in-advance Lisp string, because that's what top-level
> display functions do: they iterate over these objects, and produce the
> glyphs based on what they find there.

I have a question here about display: when lisp engine decides to draw
the display, how difficult would it be to add a callback for pre- and
post render?

Prerender callback could be any user lisp function called
that do anything it wants, say draw something on the screen as it owned
window itself. Than render engine would it's ordinary rendering drawing
just as it does now, pretending that there was nothing already drawn, so
it would mean no change at all to current c code for rendering.
Similarly one call a post-render callback and draw over the already
rendered text so user could draw whatever on top of already draw
onverlas and what not.

It is just how z-buffer works and it would mean z order from pre-render,
normal render (as it is now) and post render as implicit z-depths.

Finally one could expose ordinary X11, GDI, Cairo drawing funcions so
thet draw to an image (or why not directly to the gui window?) that
could be displayed say in prerender hook, which would get drawn as
background below the buffer text, and woulnd't collide with overlays and
other stuff. 



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 19:23                   ` David Engster
@ 2020-04-30 13:29                     ` Eli Zaretskii
  0 siblings, 0 replies; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 13:29 UTC (permalink / raw)
  To: David Engster; +Cc: luangruo, emacs-devel

> From: David Engster <deng@randomsample.de>
> Cc: luangruo@yahoo.com,  emacs-devel@gnu.org
> Date: Wed, 29 Apr 2020 21:23:30 +0200
> 
> > AFAIU, Meld seems to draw the graphics _between_ windows, and only
> > highlights the source lines with background colors.  I think we can do
> > that if we use an extra window in-between the two being compared, and
> > put images in that extra window to show the graphical description of
> > the changes between the two versions.
> 
> I actually tried exactly that, many years ago. I dimly remember that
> handling the coordinates was pretty complicated and it was slow. Like,
> orders of magnitude too slow, even with the simplest and fastest image
> types. There was no way you would ever be able to scroll text and update
> the diff visualization alongside it. It was just a flickery mess. It
> would of course be possible to visualize the diff in an idle timer and
> not during scrolling, but that is exactly what I mean above with "not
> really satisfactory", so I gave up.

Hmm... yes, I see your point.  We'd need to find a way of somehow
synchronizing the windows, which is not very easy (witness
follow-mode).

> > Drawing over normal text, if we don't want to redesign the entire
> > display engine, needs some new kind of "display element" ( a sibling
> > to "character", "image", "stretch", etc.), one that doesn't
> > necessarily have any effect on the metrics of the screen lines it is
> > drawn upon.  I'm not sure I have a clear idea about what features such
> > a drawing will need to support, but it could be possible to add such
> > an element with not too much effort.  Would someone want to come up
> > with a reasonable list of requirements for such a feature?
> 
> When I played around with it, I thought a first iteration could be to
> simply expose the basic Cairo API to ELisp, which is essentially drawing
> lines/arcs/rectangles and filling. I thought that maybe it would be
> possible that one could handle this like a graphical transparent
> overlay, but I'm afraid I'm way out of my depth here. I have no idea how
> one would handle scrolling, for instance.

Right, the main problem is not how to draw, but how to tell the
display engine where's the drawing on the screen, and when to redraw
it.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 19:59                               ` tomas
  2020-04-30  1:19                                 ` Stefan Monnier
@ 2020-04-30 13:33                                 ` Eli Zaretskii
  2020-04-30 13:52                                   ` Arthur Miller
  1 sibling, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 13:33 UTC (permalink / raw)
  To: tomas; +Cc: emacs-devel

> Date: Wed, 29 Apr 2020 21:59:30 +0200
> From: tomas@tuxteam.de
> Cc: emacs-devel@gnu.org
> 
> > The current display engine works by screen lines, so if we want to
> > keep it, we must use the existing framework.
> 
> Now imagine there's a (graphical) line going from (text) display
> line 3 to 7. You are not implying that we have to segment that
> into four chunks, one per text line?

That's one way, but it's ugly and complicated.

> 
> >                                               When a portion of a
> > window is exposed, we redraw all the glyphs in the exposed area(s),
> > and we find the glyphs that need to be redrawn by comparing their
> > coordinates with those of the exposed rectangle(s).
> 
> ... but rather that we intersect the exposed area(s) with each [1]
> of the graphical objects and redraw that (be it before or after the
> text)?

Yes, but we examine what you call "objects" by screen lines.

> [1] conceptually; in reality we'll need some index structure
>    to avoid looking at most of the graphical objects. Quad-
>    trees, whatever.

We don't use them now.  A typical window is not large enough to make
that a worthy complication.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-29 20:14                   ` David Engster
@ 2020-04-30 13:35                     ` Eli Zaretskii
  0 siblings, 0 replies; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 13:35 UTC (permalink / raw)
  To: David Engster; +Cc: luangruo, emacs-devel

> From: David Engster <david@engster.org>
> Cc: luangruo@yahoo.com,  emacs-devel@gnu.org
> Date: Wed, 29 Apr 2020 22:14:18 +0200
> 
> Let's take another very simple example: I often wished it would be
> possible to tell Emacs "Put an image at pixel position (x,y)" without
> having to anchor it to some point position. For instance, I have a block
> of text and would like to have an image next to it. Last I checked, the
> only way to do this is to create a "sliced image" which automatically
> gets cut up into rows and columns, but this is a very brittle workaround
> (it doesn't work with an increased line-spacing, for instance).

Under the current design of the display engine, I believe anything
displayed inside a window must come from some buffer text position or
from a string.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30  1:19                                 ` Stefan Monnier
  2020-04-30  6:55                                   ` tomas
  2020-04-30  8:04                                   ` Po Lu
@ 2020-04-30 13:46                                   ` Eli Zaretskii
  2020-04-30 14:37                                     ` Stefan Monnier
  2020-04-30 14:27                                   ` Drew Adams
  3 siblings, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 13:46 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: tomas, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> Date: Wed, 29 Apr 2020 21:19:51 -0400
> 
> I think it would make more sense to treat the overlaid canvas as
> a completely separate pixmap: when we get an request to redraw
> a particular area of the screen, we'd ask the current redisplay code to
> redraw the corresponding text content and then we'd ask the canvas code
> to draw on top of it.

Unfortunately, most redisplay cycles don't start from a request to
redraw a particular area of the screen.  This only happens when we get
expose events from the WM, which is not the "normal" trigger of
redisplay.  Usually, redisplay is entered when Emacs is idle, and it's
one of the display engine's main jobs to figure out what parts of
which windows need to be redrawn, by just considering the changes in
buffer text, their properties/overlays, and window geometry and
starting point.  In doing so, the display engine need to know, up
front, which parts of the display reside where on the screen, whether
any of them needs to be redrawn because the underlying Lisp data was
modified by some command since the last redisplay.

So AFAIU it won't work to redraw the "canvas" only when the text
beneath it changes; the display engine needs to be able to redraw the
canvas itself even if the text didn't change, and it needs to know
which portions of the window are affected by the canvas in order to
make decisions regarding how to redraw the window optimally.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 13:33                                 ` Eli Zaretskii
@ 2020-04-30 13:52                                   ` Arthur Miller
  0 siblings, 0 replies; 53+ messages in thread
From: Arthur Miller @ 2020-04-30 13:52 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: tomas, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> Date: Wed, 29 Apr 2020 21:59:30 +0200
>> From: tomas@tuxteam.de
>> Cc: emacs-devel@gnu.org
>> 
>> > The current display engine works by screen lines, so if we want to
>> > keep it, we must use the existing framework.
>> 
>> Now imagine there's a (graphical) line going from (text) display
>> line 3 to 7. You are not implying that we have to segment that
>> into four chunks, one per text line?
>
> That's one way, but it's ugly and complicated.
>
>> 
>> >                                               When a portion of a
>> > window is exposed, we redraw all the glyphs in the exposed area(s),
>> > and we find the glyphs that need to be redrawn by comparing their
>> > coordinates with those of the exposed rectangle(s).
>> 
>> ... but rather that we intersect the exposed area(s) with each [1]
>> of the graphical objects and redraw that (be it before or after the
>> text)?
>
> Yes, but we examine what you call "objects" by screen lines.
>
>> [1] conceptually; in reality we'll need some index structure
>>    to avoid looking at most of the graphical objects. Quad-
>>    trees, whatever.
>
> We don't use them now.  A typical window is not large enough to make
> that a worthy complication.
If you used exactly same functionality as now, but would call it twice,
or N number of times, as a chain of render hooks, and then let every
callback in chain draw it's own buffer, wouldn't that solve the issue
with relatively small effort in changes?

If user had pre-render hook, and say created an image and put it in
first char in temp buffer and then add that buffer as pre-render hook,
then the render engine just need to be modified to act when any of
buffers in render chain changes, instead of just current buffer.

But when it comes to rendering functionality, it would stay same, just call
whatever you already have now for the buffer drawing, on each render
hook and it's associated buffer and let them draw over each other; like
in z-buffer. Is it too difficult to implement? It would draw over some
portions of screen twice or more times, but who cares. I don't think it
is a big issue in an application like Emacs, it is not like Emacs is
drawing millions of polygons at 120 FPS.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30  8:04                                   ` Po Lu
  2020-04-30 12:08                                     ` Stefan Monnier
@ 2020-04-30 13:55                                     ` Eli Zaretskii
  2020-05-01 23:27                                       ` Po Lu
  1 sibling, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 13:55 UTC (permalink / raw)
  To: Po Lu; +Cc: tomas, monnier, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: tomas@tuxteam.de,  Eli Zaretskii <eliz@gnu.org>,  emacs-devel@gnu.org
> Date: Thu, 30 Apr 2020 16:04:38 +0800
> 
> Stefan Monnier <monnier@iro.umontreal.ca> writes:
> 
> That's what I'm trying to do right now, but so far keeping track of
> exactly where each canvas is displayed on the screen seems to be
> difficult.  I'd appreciate some suggestions, thanks.

I think the information needs to be recorded in the glyph structure
that represents a canvas.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 13:11                         ` Arthur Miller
@ 2020-04-30 14:18                           ` Eli Zaretskii
  2020-04-30 14:58                             ` Arthur Miller
  0 siblings, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 14:18 UTC (permalink / raw)
  To: Arthur Miller; +Cc: tomas, emacs-devel

> From: Arthur Miller <arthur.miller@live.com>
> Date: Thu, 30 Apr 2020 15:11:10 +0200
> Cc: tomas@tuxteam.de, emacs-devel@gnu.org
> 
> I have a question here about display: when lisp engine decides to draw
> the display, how difficult would it be to add a callback for pre- and
> post render?

I don't think I understand the question.  It is trivial to add a call
to a Lisp function anywhere in Emacs, the question is what that
function will be able to do to be useful.  If the called Lisp can do
anything it wants, then how can it be useful without knowing a whole
lot about the window layout, and what was just now, or will be in a
moment, drawn on it, and in which parts?

> Prerender callback could be any user lisp function called
> that do anything it wants, say draw something on the screen as it owned
> window itself. Than render engine would it's ordinary rendering drawing
> just as it does now, pretending that there was nothing already drawn, so
> it would mean no change at all to current c code for rendering.

So how do we prevent the display engine from blissfully drawing over
what the pre-render draws?

> Similarly one call a post-render callback and draw over the already
> rendered text so user could draw whatever on top of already draw
> onverlas and what not.

How will the post-render know where it can and where it cannot draw?
Or even where to draw, for that matter (assuming you want to do
something more useful than just put the same pixels in the same pixel
coordinates)?  For example, if the window scrolls, wouldn't you want
the graphics drawn by these pre/post-renderers to move on display as
well, at least sometimes?  If you do, how can you do that without
knowing some details about the scroll?

> It is just how z-buffer works and it would mean z order from pre-render,
> normal render (as it is now) and post render as implicit z-depths.

AFIU, z-buffer doesn't care to obscure what's displayed below.  is
this what you have in mind: obscuring the "normal" display with some
graphics?



^ permalink raw reply	[flat|nested] 53+ messages in thread

* RE: Emacs canvas support
  2020-04-30  1:19                                 ` Stefan Monnier
                                                     ` (2 preceding siblings ...)
  2020-04-30 13:46                                   ` Eli Zaretskii
@ 2020-04-30 14:27                                   ` Drew Adams
  3 siblings, 0 replies; 53+ messages in thread
From: Drew Adams @ 2020-04-30 14:27 UTC (permalink / raw)
  To: Stefan Monnier, tomas; +Cc: Eli Zaretskii, emacs-devel

> I think it would make more sense to treat the overlaid canvas as
> a completely separate pixmap: when we get an request to redraw
> a particular area of the screen, we'd ask the current redisplay code to
> redraw the corresponding text content and then we'd ask the canvas code
> to draw on top of it.  So for rendering of the canvas code we don't
> need to know which part of the canvas cover which characters, we just
> render the glyph matrix into a pixmap, render the canvas into another
> pixmap and then combine them onto the screen.

Exactly what I was thinking/wondering, as someone
totally naive in this area.  If we're after a graphic
canvas (editing with pixels), why involve glyphs/chars
at all, for that space?




^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 13:46                                   ` Eli Zaretskii
@ 2020-04-30 14:37                                     ` Stefan Monnier
  2020-04-30 17:27                                       ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: Stefan Monnier @ 2020-04-30 14:37 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: tomas, emacs-devel

>> I think it would make more sense to treat the overlaid canvas as
>> a completely separate pixmap: when we get an request to redraw
>> a particular area of the screen, we'd ask the current redisplay code to
>> redraw the corresponding text content and then we'd ask the canvas code
>> to draw on top of it.
>
> Unfortunately, most redisplay cycles don't start from a request to
> redraw a particular area of the screen.

I don't think it makes a big difference.  Upon redisplay, the
glyph-matrix layer will compute which parts of the window needs updates
in the that layer (exactly like it currently does), and the canvas will
do its own computation of which part of it needs updating (presumably
this will be handled by the external canvas library such as Cairo's
rather than by Emacs's own code) and then the compositing layer will
figure out how to combine those two changes.  IIUC libraries like Cairo
already provide functionality to do compositing of layers, so that would
take care of that part as well.


        Stefan




^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 14:18                           ` Eli Zaretskii
@ 2020-04-30 14:58                             ` Arthur Miller
  2020-04-30 17:30                               ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: Arthur Miller @ 2020-04-30 14:58 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: tomas, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> From: Arthur Miller <arthur.miller@live.com>
>> Date: Thu, 30 Apr 2020 15:11:10 +0200
>> Cc: tomas@tuxteam.de, emacs-devel@gnu.org
>> 
>> I have a question here about display: when lisp engine decides to draw
>> the display, how difficult would it be to add a callback for pre- and
>> post render?
>
> I don't think I understand the question.  It is trivial to add a call

I am probably not so clear in my writing, but what I mean is, can it be
impelemnted as a hook/callback, to enter same redisplay machinery
several times. As you described in other mails how redisplay is
figuring out what draw, everything has to come from buffers that are
essentially glyph matrices etc. Whatever entry in that machinery is now,
can't we just have a list of hooks that each call into that machinery?
Normally there would be just one such so Emacs work as it is now, but
user could add one before or after, or several do do some extra drawing.

> to a Lisp function anywhere in Emacs, the question is what thats
> function will be able to do to be useful.
Essentially it would be like layered rendering. Imagine Gimp layers.
Since each hook is drawing it's own stuff it would be like a layer.

For example, if Emacs exposed some graphics stuff like say: draw-this,
fill-that, and if drawing layered, like in post-render, that wouldn't
collide with overlays and other stuff, one could draw over images etc.
Similarly a pre-render could put some graphics and images that appear as
a background below normal buffer text.

> If the called Lisp can do
> anything it wants, then how can it be useful without knowing a whole
> lot about the window layout, and what was just now, or will be in a
> moment, drawn on it, and in which parts?
Each hook woul have it's own associated buffer, which could be same as
current buffer or different one (say an temporary with an image in it).
Redisplay engine can then just do whatever it does now to figure out
that buffer so no changes to redisplay engine. Yes, it would draw in
same pixel coordinates of the window it is drawing on. The only hard
part would be to figure out how to trigger redisplay engine when some of
buffers with associated hooks changes (I think, but I am not
knowledgable enough about implementation details here).

>> Prerender callback could be any user lisp function called
>> that do anything it wants, say draw something on the screen as it owned
>> window itself. Than render engine would it's ordinary rendering drawing
>> just as it does now, pretending that there was nothing already drawn, so
>> it would mean no change at all to current c code for rendering.
>
> So how do we prevent the display engine from blissfully drawing over
> what the pre-render draws?

You don't! It is exactly my point to redraw over stuff. Render should
re-draw over what pre-render has drawn etc.

That way, if a pre-render hook, draws say a buffer with an image in it,
then ordinary buffer can draw it's text over it and image would appear
as a background in final view. 

>> Similarly one call a post-render callback and draw over the already
>> rendered text so user could draw whatever on top of already draw
>> onverlas and what not.
>
> How will the post-render know where it can and where it cannot draw?
> Or even where to draw, for that matter (assuming you want to do
> something more useful than just put the same pixels in the same pixel
> coordinates)?
Actually sometimes, you do want to just put pixels in same pixel
coordinates :-). But anyway, one could draw some lines below/above text
in buffers, rectangles, and so on. Info about where and what
to draw comes well from user code. User's elisp can figure out where and
what to draw by examining the buffer and window with usual elisp we have
now?

> For example, if the window scrolls, wouldn't you want
> the graphics drawn by these pre/post-renderers to move on display as
> well, at least sometimes?  If you do, how can you do that without
> knowing some details about the scroll?
Of course. Wouldn't came out automatically, by redisplay engine as it
already is?

>> It is just how z-buffer works and it would mean z order from pre-render,
>> normal render (as it is now) and post render as implicit z-depths.
>
> AFIU, z-buffer doesn't care to obscure what's displayed below.  is
> this what you have in mind: obscuring the "normal" display with some
> graphics?

No I didn't, I had in mind to redraw stuff, so user can draw below or
above whatever Emacs normally draws.

Yeah sure if user chooses to draw two different text buffers on top of
each other it would be a mess, but I don't think that is an issue
because, normally, probably nobody want's to draw to text files on top
of each other.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 14:37                                     ` Stefan Monnier
@ 2020-04-30 17:27                                       ` Eli Zaretskii
  2020-04-30 18:22                                         ` Stefan Monnier
  0 siblings, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 17:27 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: tomas, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: tomas@tuxteam.de,  emacs-devel@gnu.org
> Date: Thu, 30 Apr 2020 10:37:03 -0400
> 
> > Unfortunately, most redisplay cycles don't start from a request to
> > redraw a particular area of the screen.
> 
> I don't think it makes a big difference.  Upon redisplay, the
> glyph-matrix layer will compute which parts of the window needs updates
> in the that layer (exactly like it currently does), and the canvas will
> do its own computation of which part of it needs updating (presumably
> this will be handled by the external canvas library such as Cairo's
> rather than by Emacs's own code) and then the compositing layer will
> figure out how to combine those two changes.

I don't think I understand how this would fit into the current
redisplay framework.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 14:58                             ` Arthur Miller
@ 2020-04-30 17:30                               ` Eli Zaretskii
  2020-05-01 14:32                                 ` Arthur Miller
  0 siblings, 1 reply; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 17:30 UTC (permalink / raw)
  To: Arthur Miller; +Cc: tomas, emacs-devel

> From: Arthur Miller <arthur.miller@live.com>
> Cc: tomas@tuxteam.de,  emacs-devel@gnu.org
> Date: Thu, 30 Apr 2020 16:58:10 +0200
> 
> > For example, if the window scrolls, wouldn't you want
> > the graphics drawn by these pre/post-renderers to move on display as
> > well, at least sometimes?  If you do, how can you do that without
> > knowing some details about the scroll?
> Of course. Wouldn't came out automatically, by redisplay engine as it
> already is?

No, of course not.

> No I didn't, I had in mind to redraw stuff, so user can draw below or
> above whatever Emacs normally draws.
> 
> Yeah sure if user chooses to draw two different text buffers on top of
> each other it would be a mess, but I don't think that is an issue
> because, normally, probably nobody want's to draw to text files on top
> of each other.

I think what you describe doesn't fit with how the redisplay works,
but I'm probably missing something, since Stefan also thinks this
could work.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 17:27                                       ` Eli Zaretskii
@ 2020-04-30 18:22                                         ` Stefan Monnier
  2020-04-30 18:42                                           ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: Stefan Monnier @ 2020-04-30 18:22 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: tomas, emacs-devel

> I don't think I understand how this would fit into the current
> redisplay framework.

I think it should basically not require touching xdisp.c.
It should work at the level of `update_window` only.


        Stefan




^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 18:22                                         ` Stefan Monnier
@ 2020-04-30 18:42                                           ` Eli Zaretskii
  0 siblings, 0 replies; 53+ messages in thread
From: Eli Zaretskii @ 2020-04-30 18:42 UTC (permalink / raw)
  To: Stefan Monnier; +Cc: tomas, emacs-devel

> From: Stefan Monnier <monnier@iro.umontreal.ca>
> Cc: tomas@tuxteam.de,  emacs-devel@gnu.org
> Date: Thu, 30 Apr 2020 14:22:02 -0400
> 
> > I don't think I understand how this would fit into the current
> > redisplay framework.
> 
> I think it should basically not require touching xdisp.c.
> It should work at the level of `update_window` only.

I _am_ talking about update_frame and update_window.  They have their
own ideas about which parts of the screen need to be updated and which
don't, and how they should be updated (whether by scrolling or by
deletion and insertion or by overwriting).  Those ideas are based on
the assumption that they know what is currently on the glass.  That
knowledge comes from the glyph matrices.  If the canvasses are drawn
independently of the glyph matrices, then update_window will make
wrong decisions, and the result will be a royal mess on the screen.

Maybe I'm missing something, but you didn't actually explain on any
level of detail how do you propose to "compose" the "normal" display
with the canvasses.  How about if you try to describe your ideas in
two simple use cases: (1) when a canvas that was on display,
overlaying some text, is deleted (and nothing else is changed); and
(2) when update_window decides to scroll some part of a window which
has a canvas partially overlaying the text in the part to be scrolled?



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30  2:36     ` Eli Zaretskii
@ 2020-04-30 20:20       ` Juri Linkov
  2020-05-01  6:01         ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: Juri Linkov @ 2020-04-30 20:20 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: luangruo, emacs-devel

[-- Attachment #1: Type: text/plain, Size: 955 bytes --]

>> > I'd appreciate some feedback on something I came up with during my spare
>> > time: Emacs canvas support.
>>
>> Great, thanks!  I'm going to try to use canvas-rounded-rectangle
>> for rounded corners of tabs.  Sometime ago I tried to draw them
>> with Cairo, but the implementation was too ah-hoc.  Whereas your
>> solution is more generally usable.
>
> Did you try using svg.el?

I tried, but svg is not great, it has many unfixed problems with scaling
and backgrounds.  OTOH, I hope with canvas it would be easy to draw such
shapes around the tab name like in web browsers shown below, and also
to implement overlapping tabs.  At least canvas in web browsers allow
doing such custom drawings.

As for the implementation, there is already Cairo code used to draw 3D box
shapes around strings with box faces in xterm.c.  Canvas could be
implemented to generalize these hard-coded shapes and expose their
definitions to Lisp as configurable options.


[-- Attachment #2: browser_tab.png --]
[-- Type: image/png, Size: 1920 bytes --]

^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 20:20       ` Juri Linkov
@ 2020-05-01  6:01         ` Eli Zaretskii
  0 siblings, 0 replies; 53+ messages in thread
From: Eli Zaretskii @ 2020-05-01  6:01 UTC (permalink / raw)
  To: Juri Linkov; +Cc: luangruo, emacs-devel

> From: Juri Linkov <juri@linkov.net>
> Cc: emacs-devel@gnu.org,  luangruo@yahoo.com
> Date: Thu, 30 Apr 2020 23:20:18 +0300
> 
> > Did you try using svg.el?
> 
> I tried, but svg is not great, it has many unfixed problems with scaling
> and backgrounds.

Cannot these bugs be fixed?

> OTOH, I hope with canvas it would be easy to draw such shapes around
> the tab name like in web browsers shown below, and also to implement
> overlapping tabs.  At least canvas in web browsers allow doing such
> custom drawings.

The problem with canvas is that we have yet to figure out how to teach
the display engine handle them correctly and efficiently.  svg.el
doesn't have that problem.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 17:30                               ` Eli Zaretskii
@ 2020-05-01 14:32                                 ` Arthur Miller
  0 siblings, 0 replies; 53+ messages in thread
From: Arthur Miller @ 2020-05-01 14:32 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: tomas, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

>> From: Arthur Miller <arthur.miller@live.com>
>> Cc: tomas@tuxteam.de,  emacs-devel@gnu.org
>> Date: Thu, 30 Apr 2020 16:58:10 +0200
>> 
>> > For example, if the window scrolls, wouldn't you want
>> > the graphics drawn by these pre/post-renderers to move on display as
>> > well, at least sometimes?  If you do, how can you do that without
>> > knowing some details about the scroll?
>> Of course. Wouldn't came out automatically, by redisplay engine as it
>> already is?
>
> No, of course not.
>
>> No I didn't, I had in mind to redraw stuff, so user can draw below or
>> above whatever Emacs normally draws.
>> 
>> Yeah sure if user chooses to draw two different text buffers on top of
>> each other it would be a mess, but I don't think that is an issue
>> because, normally, probably nobody want's to draw to text files on top
>> of each other.
>
> I think what you describe doesn't fit with how the redisplay works,
> but I'm probably missing something, since Stefan also thinks this
> could work.
To be honest I am not sure myself if it could or not work, that is why I
posed at as a question in my first mail. I am not so familiar with Emacs
internal but I do find it intersting to look at C code from time to time :-).

My idea is to make it as least intrusive on current pipeline as
possible. I see current drawing stuff as it draws to one (implicit)
layer: the current window (I mean OS window). The idea is to wrap it in
some way (as a callback) and call in it "per layer" each time, where
each layer would draw it's own bufer. This way no new objects are
needed, no new concepts, just to redraw buffers in same window once per
callback. It does not have to be a callback. Instead it could be a
list of buffers to draw just in ordinary list; a qeue to draw from
first to last. Same code it does now, just re-enter it again per each
buffer, and draw them on top of each other.

I am not sure where and how to fit it in. What this be per-buffer, or
per-window, or even-per frame? I was looking at Window and Buffer
structures, but I don't know myself, to be honest.

Then if we could get some graphics functions that draw to say XPixmap or
Cairo surface (which should be trivial to expose), we could draw
graphics to an image, and then display image in one layer and draw text
into another layer. It would give a "Canvas" like functionality and
layered rendering. How it would be usefull is up to users, but if we
look at what people do in Emacs, all this UML with Ditaa, vertical lines
with characters etc, obviosly there is some need for graphics in Emacs.
I am sure some powerline users would be happy to draw some cool graphics
to their modeline.

While drawing to image and layering stuff below/above text buffer is not
THE most efficient way, it could still be more efficient then drawing to
SVG (xml) and rendering svg and display it as image anway or doing
canvas with "put pixel" stuff. Also, not a perfect, but as rest of Emacs
implementation, probably *good enough* for many uses?



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-04-30 13:55                                     ` Eli Zaretskii
@ 2020-05-01 23:27                                       ` Po Lu
  2020-05-02  6:51                                         ` Eli Zaretskii
  0 siblings, 1 reply; 53+ messages in thread
From: Po Lu @ 2020-05-01 23:27 UTC (permalink / raw)
  To: Eli Zaretskii; +Cc: tomas, monnier, emacs-devel

Eli Zaretskii <eliz@gnu.org> writes:

> I think the information needs to be recorded in the glyph structure
> that represents a canvas.

I have a canvas field inside `struct glyph' that points to the canvas
object.  What I'm trying to do right now is track the position of the
canvases on-screen, so I can draw them separately from redisplay.

I'd appreciate some suggestions, thanks.



^ permalink raw reply	[flat|nested] 53+ messages in thread

* Re: Emacs canvas support
  2020-05-01 23:27                                       ` Po Lu
@ 2020-05-02  6:51                                         ` Eli Zaretskii
  0 siblings, 0 replies; 53+ messages in thread
From: Eli Zaretskii @ 2020-05-02  6:51 UTC (permalink / raw)
  To: Po Lu; +Cc: tomas, monnier, emacs-devel

> From: Po Lu <luangruo@yahoo.com>
> Cc: tomas@tuxteam.de,  monnier@iro.umontreal.ca,  emacs-devel@gnu.org
> Date: Sat, 02 May 2020 07:27:07 +0800
> 
> Eli Zaretskii <eliz@gnu.org> writes:
> 
> > I think the information needs to be recorded in the glyph structure
> > that represents a canvas.
> 
> I have a canvas field inside `struct glyph' that points to the canvas
> object.

It is not a good idea to have pointers in the glyph structure.  (Yes,
xwidget uses that, but don't take example from that, please.)  A glyph
should be self-describing from the metrics POV.  If you need
additional information (beyond the basic metrics), use the example of
images and faces: we cache the objects and record their cache ID in
the glyph.

> What I'm trying to do right now is track the position of the
> canvases on-screen, so I can draw them separately from redisplay.

My suggestion is not to "draw separately from redisplay".  I don't see
how this could work, unless you also modify extensively how the second
phase of redisplay, the one entered via update_frame and
update_window, works.



^ permalink raw reply	[flat|nested] 53+ messages in thread

end of thread, other threads:[~2020-05-02  6:51 UTC | newest]

Thread overview: 53+ messages (download: mbox.gz / follow: Atom feed)
-- links below jump to the message on this page --
     [not found] <875zdikdge.fsf.ref@yahoo.com>
2020-04-29  6:34 ` Emacs canvas support Po Lu via Emacs development discussions.
2020-04-29  8:24   ` Eli Zaretskii
2020-04-29  9:57     ` Po Lu
2020-04-29 10:10       ` Eli Zaretskii
2020-04-29 10:22         ` Po Lu
2020-04-29 10:27           ` Po Lu via Emacs development discussions.
2020-04-29 11:47             ` Eli Zaretskii
2020-04-29 10:35           ` Eli Zaretskii
2020-04-29 10:41             ` Po Lu
2020-04-29 11:51               ` Eli Zaretskii
2020-04-29 12:12                 ` Po Lu
2020-04-29 16:14               ` David Engster
2020-04-29 16:54                 ` Eli Zaretskii
2020-04-29 17:16                   ` tomas
2020-04-29 17:27                     ` Eli Zaretskii
2020-04-29 17:38                       ` Eli Zaretskii
2020-04-30 13:11                         ` Arthur Miller
2020-04-30 14:18                           ` Eli Zaretskii
2020-04-30 14:58                             ` Arthur Miller
2020-04-30 17:30                               ` Eli Zaretskii
2020-05-01 14:32                                 ` Arthur Miller
2020-04-29 18:51                       ` tomas
2020-04-29 19:03                         ` Eli Zaretskii
2020-04-29 19:08                           ` tomas
2020-04-29 19:25                             ` Eli Zaretskii
2020-04-29 19:59                               ` tomas
2020-04-30  1:19                                 ` Stefan Monnier
2020-04-30  6:55                                   ` tomas
2020-04-30 12:03                                     ` Stefan Monnier
2020-04-30 12:50                                       ` tomas
2020-04-30  8:04                                   ` Po Lu
2020-04-30 12:08                                     ` Stefan Monnier
2020-04-30 13:55                                     ` Eli Zaretskii
2020-05-01 23:27                                       ` Po Lu
2020-05-02  6:51                                         ` Eli Zaretskii
2020-04-30 13:46                                   ` Eli Zaretskii
2020-04-30 14:37                                     ` Stefan Monnier
2020-04-30 17:27                                       ` Eli Zaretskii
2020-04-30 18:22                                         ` Stefan Monnier
2020-04-30 18:42                                           ` Eli Zaretskii
2020-04-30 14:27                                   ` Drew Adams
2020-04-30 13:33                                 ` Eli Zaretskii
2020-04-30 13:52                                   ` Arthur Miller
2020-04-29 19:23                   ` David Engster
2020-04-30 13:29                     ` Eli Zaretskii
2020-04-30  6:52                   ` Corwin Brust
2020-04-29 17:08                 ` Eli Zaretskii
2020-04-29 20:14                   ` David Engster
2020-04-30 13:35                     ` Eli Zaretskii
2020-04-29 20:48   ` Juri Linkov
2020-04-30  2:36     ` Eli Zaretskii
2020-04-30 20:20       ` Juri Linkov
2020-05-01  6:01         ` Eli Zaretskii

Code repositories for project(s) associated with this public inbox

	https://git.savannah.gnu.org/cgit/emacs.git

This is a public inbox, see mirroring instructions
for how to clone and mirror all data and code used for this inbox;
as well as URLs for read-only IMAP folder(s) and NNTP newsgroup(s).