all messages for Emacs-related lists mirrored at yhetil.org
 help / color / mirror / code / Atom feed
From: Cecilio Pardo <cpardo@imayhem.com>
To: 73730@debbugs.gnu.org
Subject: bug#73730: 31.0.50; Support for color fonts on MS-Windows
Date: Thu, 10 Oct 2024 13:16:23 +0200	[thread overview]
Message-ID: <36a6b4d5-c719-44d6-957d-bcd7db5a854b@imayhem.com> (raw)

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


The attached patch is a preliminar implementation of a DirectWrite font
driver that allows for color fonts, tested only on Windows 11.

There is much to be refined about quality, performance (caching), OS
version conditionals, etc.

Before doing all that, I need to know that this is the right (or at
least good enough) way to do it.

The DirectWrite font driver mounts on top of a copy of the harfbuzz one,
and then replaces some of the functions, but some of the hb functions
include eassert to check that the driver for a font is harfbuzz. I don't
know how to deal with that.

I suppose that if we skip harfbuzz completely, then we would have to
reimplement a lot of stuff with DirectWrite?

Also uniscribe_open has been modified to change the driver assigned to
the font unconditionally to dwrite.

-- 
Cecilio Pardo

[-- Attachment #2: 0001-Font-driver-for-DirectWrite-MS-Windows-supporting-co.patch --]
[-- Type: text/plain, Size: 16794 bytes --]

From d004845fe6a9495af7c03ddd9d30d3ba71fc3e5d Mon Sep 17 00:00:00 2001
From: Cecilio Pardo <cpardo@imayhem.com>
Date: Wed, 9 Oct 2024 11:40:28 +0200
Subject: [PATCH] Font driver for DirectWrite (MS-Windows) supporting color
 fonts.

---
 configure.ac       |   4 +-
 src/font.h         |   1 +
 src/w32dwrite.c    | 470 +++++++++++++++++++++++++++++++++++++++++++++
 src/w32fns.c       |   7 +
 src/w32uniscribe.c |   2 +
 5 files changed, 482 insertions(+), 2 deletions(-)
 create mode 100644 src/w32dwrite.c

diff --git a/configure.ac b/configure.ac
index 8a5ba7db3d1..8a06d9d5027 100644
--- a/configure.ac
+++ b/configure.ac
@@ -3172,7 +3172,7 @@ AC_DEFUN
   AC_CHECK_TOOL([WINDRES], [windres],
                 [AC_MSG_ERROR([No resource compiler found.])])
   W32_OBJ="w32fns.o w32menu.o w32reg.o w32font.o w32term.o"
-  W32_OBJ="$W32_OBJ w32xfns.o w32select.o w32uniscribe.o w32cygwinx.o"
+  W32_OBJ="$W32_OBJ w32xfns.o w32select.o w32uniscribe.o w32dwrite.o w32cygwinx.o"
   EMACSRES="emacs.res"
   case "$canonical" in
     x86_64-*-*) EMACS_MANIFEST="emacs-x64.manifest" ;;
@@ -3202,7 +3202,7 @@ AC_DEFUN
       W32_OBJ="$W32_OBJ w32image.o"
     fi
     W32_LIBS="$W32_LIBS -lwinmm -lgdi32 -lcomdlg32"
-    W32_LIBS="$W32_LIBS -lmpr -lwinspool -lole32 -lcomctl32"
+    W32_LIBS="$W32_LIBS -lmpr -lwinspool -lole32 -lcomctl32 -ldwrite"
     W32_RES_LINK="\$(EMACSRES)"
     CLIENTRES="emacsclient.res"
     CLIENTW="emacsclientw\$(EXEEXT)"
diff --git a/src/font.h b/src/font.h
index 8ee1940be0a..7e0e96d9bfa 100644
--- a/src/font.h
+++ b/src/font.h
@@ -978,6 +978,7 @@ valid_font_driver (struct font_driver const *d)
 extern struct font_driver uniscribe_font_driver;
 #ifdef HAVE_HARFBUZZ
 extern struct font_driver harfbuzz_font_driver;
+extern struct font_driver dwrite_font_driver;
 #endif
 extern void syms_of_w32font (void);
 #endif	/* HAVE_NTGUI */
diff --git a/src/w32dwrite.c b/src/w32dwrite.c
new file mode 100644
index 00000000000..32c9c179778
--- /dev/null
+++ b/src/w32dwrite.c
@@ -0,0 +1,470 @@
+#include <config.h>
+#include <math.h>
+#include <windows.h>
+
+#include "lisp.h"
+#include "coding.h"
+#include "w32term.h"
+#include "frame.h"
+#include "composite.h"
+#include "font.h"
+#include "w32font.h"
+#include "pdumper.h"
+#include "w32common.h"
+
+#include "initguid.h"
+#include <dwrite_3.h>
+
+void syms_of_w32dwrite (void);
+
+struct font_driver dwrite_font_driver;
+
+/* Initialized on syms_of_w32dwrite_for_pdumper  */
+IDWriteFactory *dwrite_factory = NULL;
+IDWriteFactory2 *dwrite_factory2 = NULL;
+IDWriteGdiInterop *gdi_interop = NULL;
+IDWriteRenderingParams *rendering_params = NULL;
+
+static void
+verify_hr (HRESULT hr, const char *msg)
+{
+  if (FAILED (hr))
+    {
+      printf ("Error: %s\n", msg);
+      abort ();
+    }
+}
+
+static float
+get_font_and_face (HFONT hfont, LOGFONTW *logfont_out,
+		   IDWriteFont **font, IDWriteFontFace **face)
+{
+  HRESULT hr;
+  LOGFONTW logfont;
+
+  GetObjectW (hfont, sizeof (LOGFONTW), &logfont);
+  hr = gdi_interop->lpVtbl->
+    CreateFontFromLOGFONT (gdi_interop, (const LOGFONTW *) &logfont, font);
+  verify_hr (hr, "Failed to create DWriteFont");
+  hr = (*font)->lpVtbl->
+    CreateFontFace (*font, face);
+  verify_hr (hr, "Failed to create DWriteFontFace");
+
+  if (logfont_out)
+    *logfont_out = logfont;
+
+  return abs (logfont.lfHeight);
+}
+
+static IDWriteBitmapRenderTarget *
+get_bitmap_render_target (HDC hdc, int width, int height)
+{
+  HRESULT hr;
+  IDWriteBitmapRenderTarget *brt;
+  hr = gdi_interop->lpVtbl->
+    CreateBitmapRenderTarget (gdi_interop, hdc, width, height, &brt);
+  verify_hr (hr, "Failed to create DWriteBitmapRenderTarget");
+
+  IDWriteBitmapRenderTarget1 *brt1;
+  hr = brt->lpVtbl->
+    QueryInterface (brt, &IID_IDWriteBitmapRenderTarget1, (void **)&brt1);
+  verify_hr (hr, "Failed to create DWriteBitmapRenderTarget1");
+  brt1->lpVtbl->
+    SetTextAntialiasMode ( brt1, DWRITE_TEXT_ANTIALIAS_MODE_CLEARTYPE );
+  brt1->lpVtbl->Release(brt1);
+
+  return brt;
+}
+
+
+/* This function does not fill in the ascent and descent fields of
+   metrics.  */
+static void text_extents_internal(IDWriteFont *dwrite_font,
+				  IDWriteFontFace *dwrite_font_face,
+				  float font_size,
+				  const unsigned *code,
+				  int nglyphs, struct font_metrics *metrics)
+{
+  HRESULT hr;
+
+  DWRITE_FONT_METRICS dwrite_font_metrics;
+  dwrite_font->lpVtbl->
+    GetMetrics (dwrite_font, &dwrite_font_metrics);
+
+  UINT16 *indices = alloca (nglyphs * sizeof (UINT16));
+  for (int i = 0; i < nglyphs; i++)
+    indices[i] = code[i];
+
+  DWRITE_GLYPH_METRICS* gmetrics =
+    alloca (nglyphs * sizeof (DWRITE_GLYPH_METRICS));
+
+  hr = dwrite_font_face->lpVtbl->
+    GetGdiCompatibleGlyphMetrics (dwrite_font_face, font_size, 1.0, NULL,
+				  TRUE, indices, nglyphs, gmetrics, false );
+  verify_hr (hr, "Failed to GetGdiCompatibleGlyphMetrics");
+
+  float width = 0;
+  int units_per_em = dwrite_font_metrics.designUnitsPerEm;
+
+  for (int i = 0; i < nglyphs; i++)
+    {
+      width += (float)gmetrics[i].advanceWidth * font_size / units_per_em;
+      float lbearing = round ((float) gmetrics[i].leftSideBearing * font_size
+			     / units_per_em);
+      float rbearing = round ((float) gmetrics[i].rightSideBearing * font_size
+			      / units_per_em);
+      if (i==0)
+	{
+	  metrics->lbearing = lbearing;
+	  metrics->rbearing = rbearing;
+	}
+      if (metrics->lbearing > lbearing)
+	metrics->lbearing = lbearing;
+      if (metrics->rbearing < rbearing)
+	metrics->rbearing = rbearing;
+    }
+  metrics->width = round(width);
+}
+
+static Lisp_Object
+dwrite_list (struct frame *f, Lisp_Object font_spec)
+{
+  Lisp_Object fonts = w32font_list_internal (f, font_spec, true);
+  for (Lisp_Object tail = fonts; CONSP (tail); tail = XCDR (tail))
+    ASET (XCAR (tail), FONT_TYPE_INDEX, Qdwrite);
+
+  FONT_ADD_LOG ("dwrite-list", font_spec, fonts);
+  return fonts;
+}
+
+static Lisp_Object
+dwrite_match (struct frame *f, Lisp_Object font_spec)
+{
+  Lisp_Object entity = w32font_match_internal (f, font_spec, true);
+  FONT_ADD_LOG ("dwrite-match", font_spec, entity);
+
+  if (! NILP (entity))
+    ASET (entity, FONT_TYPE_INDEX, Qdwrite);
+  return entity;
+}
+
+static unsigned
+dwrite_encode_char (struct font *font, int c)
+{
+  HRESULT hr;
+  IDWriteFont *dwrite_font;
+  IDWriteFontFace *dwrite_font_face;
+
+  get_font_and_face (((struct w32font_info *) font)->hfont,
+		     NULL, &dwrite_font, &dwrite_font_face );
+  UINT16 index;
+
+  hr = dwrite_font_face->lpVtbl->
+    GetGlyphIndices (dwrite_font_face, &c, 1, &index);
+  verify_hr (hr, "Failed to GetGlyphIndices");
+
+  dwrite_font->lpVtbl->Release (dwrite_font);
+  dwrite_font_face->lpVtbl->Release (dwrite_font_face);
+
+  if (index == 0)
+    return FONT_INVALID_CODE;
+
+  return index;
+}
+
+static void
+dwrite_text_extents (struct font *font, const unsigned *code,
+		     int nglyphs, struct font_metrics *metrics)
+{
+  IDWriteFont *dwrite_font;
+  IDWriteFontFace *dwrite_font_face;
+
+  HFONT hfont = ((struct w32font_info *) font)->hfont;
+  float font_size = get_font_and_face (hfont,
+				       NULL, &dwrite_font,
+				       &dwrite_font_face );
+
+  text_extents_internal (dwrite_font, dwrite_font_face, font_size, code,
+			 nglyphs, metrics);
+
+  dwrite_font->lpVtbl->Release (dwrite_font);
+  dwrite_font_face->lpVtbl->Release (dwrite_font_face);
+
+  metrics->ascent = font->ascent;
+  metrics->descent = font->descent;
+}
+
+static
+void dwrite_draw_internal (HDC hdc, int x, int y, unsigned *glyphs, int len,
+			   int baseline_y, COLORREF color, struct font *font )
+{
+  HRESULT hr;
+  LOGFONTW logfont;
+  IDWriteFont *dwrite_font;
+  IDWriteFontFace *dwrite_font_face;
+
+  float font_size = get_font_and_face (GetCurrentObject (hdc, OBJ_FONT),
+				       &logfont, &dwrite_font,
+				       &dwrite_font_face );
+
+  IDWriteTextFormat *text_format;
+  hr = dwrite_factory->lpVtbl->
+    CreateTextFormat (dwrite_factory,
+		      logfont.lfFaceName,
+		      NULL,
+		      DWRITE_FONT_WEIGHT_NORMAL,
+		      DWRITE_FONT_STYLE_NORMAL,
+		      DWRITE_FONT_STRETCH_NORMAL,
+		      font_size, L"",
+		      &text_format);
+  verify_hr (hr, "Failed to create DWriteTextFormat");
+
+  struct font_metrics metrics;
+  text_extents_internal (dwrite_font, dwrite_font_face,
+			 font_size, glyphs, len, &metrics);
+
+  int bitmap_width = metrics.width;
+  int bitmap_height = font->ascent + font->descent;
+
+  IDWriteBitmapRenderTarget *bitmap_render_target =
+    get_bitmap_render_target (hdc, bitmap_width, bitmap_height);
+
+  HDC text_dc = bitmap_render_target->lpVtbl->
+    GetMemoryDC (bitmap_render_target);
+
+  BitBlt (text_dc, 0, 0, bitmap_width, bitmap_height, hdc, x, y, SRCCOPY);
+
+  DWRITE_GLYPH_RUN glyph_run;
+
+  UINT16 *indices = alloca (len * sizeof (UINT16));
+
+  for (int i = 0; i < len; i++)
+    indices[i] = glyphs[i];
+
+  FLOAT *advances = alloca (len * sizeof (FLOAT));
+
+  for (int i = 0; i < len; i++)
+    {
+      text_extents_internal (dwrite_font, dwrite_font_face,
+			     font_size, glyphs + i, 1, &metrics);
+      advances[i] = metrics.width;
+    }
+
+  glyph_run.fontFace = dwrite_font_face;
+  glyph_run.fontEmSize = font_size;
+  glyph_run.glyphIndices = indices;
+  glyph_run.glyphCount = len;
+  glyph_run.isSideways = false;
+  glyph_run.bidiLevel = 0;
+  glyph_run.glyphOffsets = NULL;
+  glyph_run.glyphAdvances = advances;
+
+  IDWriteColorGlyphRunEnumerator *layers;
+
+  hr = dwrite_factory2->lpVtbl->
+    TranslateColorGlyphRun (dwrite_factory2,
+			    0, baseline_y,
+			    &glyph_run,
+			    NULL,
+			    DWRITE_MEASURING_MODE_GDI_CLASSIC,
+			    NULL,
+			    0,
+			    &layers );
+  if (hr == DWRITE_E_NOCOLOR)
+    {
+      bitmap_render_target->lpVtbl->
+	DrawGlyphRun (bitmap_render_target,
+		      0, baseline_y,
+		      DWRITE_MEASURING_MODE_GDI_CLASSIC,
+		      &glyph_run,
+		      rendering_params,
+		      color,
+		      NULL);
+    }
+  else
+    {
+      verify_hr (hr, "Failed at TranslateColorGlyphRun");
+      for (;;) {
+	HRESULT hr;
+	BOOL more_layers;
+	const DWRITE_COLOR_GLYPH_RUN *layer;
+
+	hr = layers->lpVtbl->MoveNext (layers, &more_layers);
+	verify_hr (hr, "Failed at MoveNext");
+	if (!more_layers)
+	  break;
+	hr = layers->lpVtbl->GetCurrentRun (layers, &layer);
+	verify_hr (hr, "Failed at GetCurrentRun");
+	bitmap_render_target->lpVtbl->
+	  DrawGlyphRun (bitmap_render_target,
+			layer->baselineOriginX, layer->baselineOriginY,
+			DWRITE_MEASURING_MODE_GDI_CLASSIC,
+			&layer->glyphRun,
+			rendering_params,
+			RGB( layer->runColor.r * 255,
+			     layer->runColor.g * 255,
+			     layer->runColor.b * 255 ),
+			NULL);
+      }
+
+      layers->lpVtbl->Release (layers);
+    }
+  BitBlt (hdc, x, y, bitmap_width, bitmap_height, text_dc, 0, 0, SRCCOPY);
+
+  text_format->lpVtbl->Release (text_format);
+  dwrite_font->lpVtbl->Release (dwrite_font);
+  bitmap_render_target->lpVtbl->Release (bitmap_render_target);
+  dwrite_font_face->lpVtbl->Release (dwrite_font_face);
+}
+
+static int
+dwrite_draw (struct glyph_string *s, int from, int to,
+	     int x, int y, bool with_background)
+{
+  HRGN orig_clip = NULL;
+  int len = to - from;
+
+  if (s->num_clips > 0)
+    {
+      HRGN new_clip = CreateRectRgnIndirect (s->clip);
+
+      /* Save clip region for later restoration.  */
+      orig_clip = CreateRectRgn (0, 0, 0, 0);
+      if (!GetClipRgn (s->hdc, orig_clip))
+	{
+	  DeleteObject (orig_clip);
+	  orig_clip = NULL;
+	}
+
+      if (s->num_clips > 1)
+        {
+          HRGN clip2 = CreateRectRgnIndirect (s->clip + 1);
+
+          CombineRgn (new_clip, new_clip, clip2, RGN_OR);
+          DeleteObject (clip2);
+        }
+
+      SelectClipRgn (s->hdc, new_clip);
+      DeleteObject (new_clip);
+    }
+
+  /* Using OPAQUE background mode can clear more background than expected
+     when Cleartype is used.  Draw the background manually to avoid this.  */
+  SetBkMode (s->hdc, TRANSPARENT);
+  if (with_background)
+    {
+      HBRUSH brush;
+      RECT rect;
+      struct font *font = s->font;
+      int ascent = font->ascent, descent = font->descent;
+
+      /* Font's global ascent and descent values might be
+	 preposterously large for some fonts.  We fix here the case
+	 when those fonts are used for display of glyphless
+	 characters, because drawing background with font dimensions
+	 in those cases makes the display illegible.  There's only one
+	 more call to the draw method with with_background set to
+	 true, and that's in w32_draw_glyph_string_foreground, when
+	 drawing the cursor, where we have no such heuristics
+	 available.  FIXME.  */
+      if (s->first_glyph->type == GLYPHLESS_GLYPH
+	  && (s->first_glyph->u.glyphless.method == GLYPHLESS_DISPLAY_HEX_CODE
+	      || s->first_glyph->u.glyphless.method == GLYPHLESS_DISPLAY_ACRONYM))
+	{
+	  ascent =
+	    s->first_glyph->slice.glyphless.lower_yoff
+	    - s->first_glyph->slice.glyphless.upper_yoff;
+	  descent = 0;
+	}
+      brush = CreateSolidBrush (s->gc->background);
+      rect.left = x;
+      rect.top = y - ascent;
+      rect.right = x + s->width;
+      rect.bottom = y + descent;
+      FillRect (s->hdc, &rect, brush);
+      DeleteObject (brush);
+    }
+
+  if (s->padding_p)
+    {
+      int i;
+      for (i = 0; i < len; i++)
+	  dwrite_draw_internal (s->hdc, x, y - s->font->ascent,
+				s->char2b + from, 1, s->font->ascent,
+				GetTextColor(s->hdc), s->font);
+    }
+  else
+    {
+      dwrite_draw_internal( s->hdc, x, y - s->font->ascent,
+			    s->char2b + from, len, s->font->ascent,
+			    GetTextColor(s->hdc), s->font );
+
+    }
+
+  /* Restore clip region.  */
+  if (s->num_clips > 0)
+    SelectClipRgn (s->hdc, orig_clip);
+
+  if (orig_clip)
+    DeleteObject (orig_clip);
+
+  return len;
+}
+
+static void
+syms_of_w32dwrite_for_pdumper (void)
+{
+  HRESULT hr;
+
+  if (!initialized)
+    return;
+
+  DEFSYM (Qdwrite, "dwrite");
+
+  dwrite_font_driver = harfbuzz_font_driver;
+  dwrite_font_driver.type = Qdwrite;
+  dwrite_font_driver.draw = dwrite_draw;
+  dwrite_font_driver.list = dwrite_list;
+  dwrite_font_driver.match = dwrite_match;
+  dwrite_font_driver.encode_char = dwrite_encode_char;
+  dwrite_font_driver.text_extents = dwrite_text_extents;
+  register_font_driver (&dwrite_font_driver, NULL);
+
+  hr = DWriteCreateFactory (DWRITE_FACTORY_TYPE_SHARED,
+			    &IID_IDWriteFactory,
+			    (IUnknown**)&dwrite_factory);
+  verify_hr (hr, "Failed to create DWriteFactory");
+
+  hr = dwrite_factory->lpVtbl->
+    QueryInterface (dwrite_factory, &IID_IDWriteFactory2,
+		    (void **)&dwrite_factory2);
+  verify_hr (hr, "Failed to create DWriteFactory2");
+
+  hr = dwrite_factory->lpVtbl->
+    GetGdiInterop (dwrite_factory, &gdi_interop);
+  verify_hr (hr, "Failed to get DWriteGgiInterop");
+
+  IDWriteRenderingParams *def;
+
+  hr = dwrite_factory->lpVtbl->
+    CreateRenderingParams (dwrite_factory, &def);
+  verify_hr (hr, "Failed to create DWriteRenderingParams");
+
+  hr = dwrite_factory->lpVtbl->
+    CreateCustomRenderingParams (dwrite_factory,
+				 def->lpVtbl->GetGamma(def),
+				 def->lpVtbl->GetEnhancedContrast(def),
+				 def->lpVtbl->GetClearTypeLevel(def),
+				 def->lpVtbl->GetPixelGeometry(def),
+				 DWRITE_RENDERING_MODE_GDI_CLASSIC,
+				 &rendering_params);
+  verify_hr (hr, "Failed to create DWriteRenderingParams");
+
+  def->lpVtbl->Release (def);
+}
+
+void
+syms_of_w32dwrite (void)
+{
+  pdumper_do_now_and_after_load (syms_of_w32dwrite_for_pdumper);
+}
diff --git a/src/w32fns.c b/src/w32fns.c
index 3ee13dcbbdd..aba91c1d41c 100644
--- a/src/w32fns.c
+++ b/src/w32fns.c
@@ -293,6 +293,10 @@ #define MENU_FREE_DELAY 1000
 extern int uniscribe_available;
 extern int harfbuzz_available;
 
+/* From w32dwrite.c  */
+extern void syms_of_w32dwrite (void);
+
+
 #ifdef WINDOWSNT
 /* From w32inevt.c */
 extern int faked_key;
@@ -6356,6 +6360,8 @@ DEFUN ("x-create-frame", Fx_create_frame, Sx_create_frame,
       specbind (Qx_resource_name, name);
     }
 
+  register_font_driver (&dwrite_font_driver, f);
+
 #ifdef HAVE_HARFBUZZ
   if (harfbuzz_available)
     register_font_driver (&harfbuzz_font_driver, f);
@@ -11811,6 +11817,7 @@ globals_of_w32fns (void)
   InitCommonControls ();
 
   syms_of_w32uniscribe ();
+  syms_of_w32dwrite ();
 }
 
 #ifdef WINDOWSNT
diff --git a/src/w32uniscribe.c b/src/w32uniscribe.c
index b77bf56b8cf..cbe57d8490a 100644
--- a/src/w32uniscribe.c
+++ b/src/w32uniscribe.c
@@ -211,6 +211,8 @@ uniscribe_open (struct frame *f, Lisp_Object font_entity, int pixel_size)
 #endif  /* HAVE_HARFBUZZ */
     uniscribe_font->w32_font.font.driver = &uniscribe_font_driver;
 
+  uniscribe_font->w32_font.font.driver = &dwrite_font_driver;
+
   return font_object;
 }
 
-- 
2.35.1.windows.2


[-- Attachment #3: emacs_3xjaQ7etE9.png --]
[-- Type: image/png, Size: 30997 bytes --]

             reply	other threads:[~2024-10-10 11:16 UTC|newest]

Thread overview: 15+ messages / expand[flat|nested]  mbox.gz  Atom feed  top
2024-10-10 11:16 Cecilio Pardo [this message]
2024-10-10 13:08 ` bug#73730: 31.0.50; Support for color fonts on MS-Windows Eli Zaretskii
2024-10-10 15:14   ` Cecilio Pardo
2024-10-10 16:33     ` Eli Zaretskii
2024-10-10 16:46       ` Cecilio Pardo
2024-10-15 22:18   ` Cecilio Pardo
2024-10-16 11:01     ` Eli Zaretskii
2024-10-16 11:32       ` Eli Zaretskii
2024-10-16 21:35       ` Cecilio Pardo
2024-10-17  6:21         ` Eli Zaretskii
2024-10-17 10:38           ` Cecilio Pardo
2024-10-10 21:50 ` Cecilio Pardo
2024-10-11  3:36   ` Eli Zaretskii
2024-10-11  6:28     ` Eli Zaretskii
2024-10-11  7:19       ` Cecilio Pardo

Reply instructions:

You may reply publicly to this message via plain-text email
using any one of the following methods:

* Save the following mbox file, import it into your mail client,
  and reply-to-all from there: mbox

  Avoid top-posting and favor interleaved quoting:
  https://en.wikipedia.org/wiki/Posting_style#Interleaved_style

* Reply using the --to, --cc, and --in-reply-to
  switches of git-send-email(1):

  git send-email \
    --in-reply-to=36a6b4d5-c719-44d6-957d-bcd7db5a854b@imayhem.com \
    --to=cpardo@imayhem.com \
    --cc=73730@debbugs.gnu.org \
    /path/to/YOUR_REPLY

  https://kernel.org/pub/software/scm/git/docs/git-send-email.html

* If your mail client supports setting the In-Reply-To header
  via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line before the message body.
Code repositories for project(s) associated with this external index

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

This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.