Fixing fallback font glyph width jitter in Emacs for Claude Code

I run Claude Code inside Emacs via vterm. Claude Code's TUI uses various Unicode symbols for its thinking spinner and status indicators. My monospace font doesn't have all of them, so Emacs falls back to a proportional font like Arial Unicode MS. The fallback glyphs get their native width instead of the monospace cell width, and you end up with jittery columns and spinner animations that wobble around.

iTerm2 doesn't have this issue because terminal emulators force every glyph into a fixed cell grid. Emacs GUI just… doesn't.

I wrote a two-part patch for emacs-macport to fix this.

What's going on

You can check which font Emacs picks for any character:

1
2
(internal-char-font nil #x2722)
;; your main font for ASCII, but Arial Unicode MS for ✢

Most coding fonts, even Nerd Font patched ones, don't cover the Dingbats block (U+2700-U+27BF) and a bunch of other ranges. Emacs falls back to whatever has the glyph, and that font's advance width won't match your cell grid.

Part 1: fix the layout (xdisp.c)

The function gui_produce_glyphs computes pixel width for each character. Right after it sets it->pixel_width from the font metrics, I clamp it to the cell grid when a fallback font is in use:

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
it->pixel_width = pcm->width;
{
  struct face *ascii_face = face->ascii_face;
  if (ascii_face && ascii_face->font
      && font != ascii_face->font)
    {
      Lisp_Object spacing
        = ascii_face->font->props[FONT_SPACING_INDEX];
      Lisp_Object fb_spacing
        = font->props[FONT_SPACING_INDEX];
      bool face_is_mono
        = FIXNUMP (spacing)
          && XFIXNUM (spacing) >= FONT_SPACING_MONO;
      bool fallback_is_proportional
        = !FIXNUMP (fb_spacing)
          || XFIXNUM (fb_spacing) < FONT_SPACING_MONO;
      if (face_is_mono && fallback_is_proportional)
        {
          int cell = ascii_face->font->space_width;
          int cols = CHARACTER_WIDTH (it->char_to_display);
          if (cols == 1 && cell > 0)
            it->pixel_width = cell * cols;
        }
    }
}

A few things I learned the hard way:

  • Use face->ascii_face->font, not the global default face. Otherwise you break mixed-pitch-mode where some faces are supposed to be proportional.
  • Check both fonts' spacing: the face must be mono, and the fallback must be proportional. CJK fonts report spacing=0 (proportional) even though they're intentionally assigned by the fontset - so we also restrict to single-width chars (cols = 1=) to avoid clamping CJK.
  • Don't touch pcm->width. That pointer goes to the font's shared glyph cache. If you modify it, you corrupt every future render of that character across all buffers.

This alone gets rid of the jitter for single-width fallback glyphs. They now occupy exactly one cell regardless of their native advance width. They might visually overflow into the next cell, but the grid is stable.

Part 2: scale the glyphs (macfont.m)

To deal with the overflow, I scale down oversized fallback glyphs in macfont_draw, which is where Core Text actually renders things.

Two things bit me here:

GCD threading. The macport dispatches drawing to a GCD block on a separate thread. You can't read Lisp_Object values from there because the garbage collector runs on the main thread. I hit a SIGSEGV before figuring this out. The fix: compute everything you need before MAC_BEGIN_DRAW_TO_FRAME and pass plain C scalars into the block.

CTM leaking. Any CGContextScaleCTM you apply inside the drawing block persists for the rest of that draw call. Wrap it in CGContextSaveGState / CGContextRestoreGState.

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
// Before MAC_BEGIN_DRAW_TO_FRAME (safe to read Lisp_Objects here):
bool should_scale = false;
CGFloat scale_factor = 1.0;
CGRect scale_clip_rect = CGRectNull;
{
  Lisp_Object spc = s->font->props[FONT_SPACING_INDEX];
  bool font_is_proportional
    = !FIXNUMP (spc) || XFIXNUM (spc) < FONT_SPACING_MONO;
  if (font_is_proportional && total_width > 0 && s->width > 0
      && total_width > s->width)
    {
      should_scale = true;
      scale_factor = (CGFloat) s->width / total_width;
      scale_clip_rect = CGRectMake (x, -y - FONT_DESCENT (s->font),
                                    s->width, FONT_HEIGHT (s->font));
    }
}

// Inside the drawing block:
if (should_scale)
  {
    CGContextSaveGState (context);
    CGContextClipToRect (context, scale_clip_rect);
    CGContextTranslateCTM (context, text_position.x,
                           text_position.y);
    CGContextScaleCTM (context, scale_factor, scale_factor);
    CGContextTranslateCTM (context, -text_position.x,
                           -text_position.y);
  }
// ... normal glyph drawing ...
if (should_scale)
  CGContextRestoreGState (context);

The translate-scale-translate trick scales uniformly around the text origin. Don't manually scale the positions array on top of this - the CTM already handles it and you'd end up double-scaling.

The clip rect prevents sub-pixel bleeding when the cursor redraws the glyph. Without it, you get visual artifacts on cursor movement. The rect and all Lisp_Object accesses are precomputed on the Lisp thread because the drawing block runs on a separate GCD thread where the garbage collector could invalidate Lisp_Objects.

I also only scale proportional fonts. Monospace fonts should never need it.

Applying with Nix

1
2
3
4
5
6
7
myEmacs = (pkgs.emacs-macport.override {
  withNativeCompilation = false;  # faster rebuilds
}).overrideAttrs (old: {
  patches = (old.patches or []) ++ [
    ./fix-fallback-font-width.patch
  ];
});

Limitations

The xdisp.c part works on all platforms since gui_produce_glyphs is shared code. The macfont.m scaling only works on macport. The NS/Cocoa build has a different code path in the same file, and Linux (X11/GTK/PGTK) uses completely different font backends (xftfont.c, ftcrfont.c). Those would need their own versions of the scaling patch.

Also the uniform scaling makes fallback glyphs a bit smaller than surrounding text. iTerm2 seems to handle it slightly differently but I haven't dug into exactly how yet.

Full patch

The full patch (for emacs-macport 30.x):

--- a/src/xdisp.c	2026-08-30 06:56:17.853239362 +0900
+++ b/src/xdisp.c	2026-08-30 17:16:24.807600073 +0900
@@ -32724,6 +32724,30 @@
 	      it->phys_ascent = pcm->ascent + boff;
 	      it->phys_descent = pcm->descent - boff;
 	      it->pixel_width = pcm->width;
+	      {
+		struct face *ascii_face = face->ascii_face;
+		if (ascii_face && ascii_face->font
+		    && font != ascii_face->font)
+		  {
+		    Lisp_Object spacing
+		      = ascii_face->font->props[FONT_SPACING_INDEX];
+		    Lisp_Object fb_spacing
+		      = font->props[FONT_SPACING_INDEX];
+		    bool face_is_mono
+		      = FIXNUMP (spacing)
+			&& XFIXNUM (spacing) >= FONT_SPACING_MONO;
+		    bool fallback_is_proportional
+		      = !FIXNUMP (fb_spacing)
+			|| XFIXNUM (fb_spacing) < FONT_SPACING_MONO;
+		    if (face_is_mono && fallback_is_proportional)
+		      {
+			int cell = ascii_face->font->space_width;
+			int cols = CHARACTER_WIDTH (it->char_to_display);
+			if (cols == 1 && cell > 0)
+			  it->pixel_width = cell * cols;
+		      }
+		  }
+	      }
 	      /* Don't use font-global values for ascent and descent
 		 if they result in an exceedingly large line height.  */
 	      if (it->override_ascent < 0)
--- a/src/macfont.m	2026-08-30 14:43:00.724157934 +0900
+++ b/src/macfont.m	2026-08-30 17:04:48.065168085 +0900
@@ -2906,11 +2906,11 @@
     background_rect = CGRectNull;
 
   text_position = CGPointMake (x, -y);
+  CGFloat total_width = 0;
   glyphs = xmalloc (sizeof (CGGlyph) * len);
   {
     CGFloat advance_delta = 0;
     int i;
-    CGFloat total_width = 0;
 
     positions = xmalloc (sizeof (CGPoint) * len);
     for (i = 0; i < len; i++)
@@ -2928,6 +2928,23 @@
       }
   }
 
+  bool should_scale = false;
+  CGFloat scale_factor = 1.0;
+  CGRect scale_clip_rect = CGRectNull;
+  {
+    Lisp_Object spc = s->font->props[FONT_SPACING_INDEX];
+    bool font_is_proportional
+      = !FIXNUMP (spc) || XFIXNUM (spc) < FONT_SPACING_MONO;
+    if (font_is_proportional && total_width > 0 && s->width > 0
+	&& total_width > s->width)
+      {
+	should_scale = true;
+	scale_factor = (CGFloat) s->width / total_width;
+	scale_clip_rect = CGRectMake (x, -y - FONT_DESCENT (s->font),
+				      s->width, FONT_HEIGHT (s->font));
+      }
+  }
+
   /* We assume `macfont_info' is pointing to valid data during the
      execution of the code between MAC_BEGIN_DRAW_TO_FRAME and
      MAC_END_DRAW_TO_FRAME in a non-main thread, because the thread
@@ -2974,6 +2991,17 @@
       CGContextSetTextMatrix (context, atfm);
       CGContextSetTextPosition (context, text_position.x, text_position.y);
 
+      if (should_scale)
+	{
+	  CGContextSaveGState (context);
+	  CGContextClipToRect (context, scale_clip_rect);
+	  CGContextTranslateCTM (context, text_position.x,
+				 text_position.y);
+	  CGContextScaleCTM (context, scale_factor, scale_factor);
+	  CGContextTranslateCTM (context, -text_position.x,
+				 -text_position.y);
+	}
+
       if (macfont_info->color_bitmap_p || macfont_info->svg_p)
 	{
 	  if (len > 0)
@@ -2986,6 +3014,9 @@
 	  CGContextSetFontSize (context, font_size);
 	  CGContextShowGlyphsAtPositions (context, glyphs, positions, len);
 	}
+
+      if (should_scale)
+	CGContextRestoreGState (context);
     }
 
 #if defined (XMALLOC_BLOCK_INPUT_CHECK) && DRAWING_USE_GCD

Last modified on 2026-08-30