Skip to content

Braille block (U+2800–U+28FF) renders with right-side gap when font glyphs don't fill cell — needs synthetic rendering like xterm.js / Ghostty #9696

@msaggiorato

Description

@msaggiorato

Summary

Braille pattern characters (U+2800–U+28FF) render with a visible gap on the right side of every cell when the active font's braille glyphs don't fill the full em-cell width. This breaks the visual continuity of progress bars, btop graphs, gum/charm-bracelet TUIs, and Claude Code's spinner.

This may be related to #1940 (which scopes box-drawing only) — opening as a separate issue in case the resolution path differs, but happy to have it merged if they share a fix.

Repro

  1. Use any monospace font where braille glyphs have built-in horizontal padding (Meslo, Menlo, SF Mono, Monaco, Source Code Pro — most popular fonts).
  2. Print a progress-bar string using braille:
    printf '[⣿⣿⡄⠀⠀⠀⠀⠀]\n'
  3. Compare to VS Code's integrated terminal rendering the same string with the same font.

Measured glyph metrics (canvas measureText at 32px, font="Meslo LG L DZ for Powerline"):

  • — advance: 21.88px, ink right edge: 16.69px24% empty padding on right side of every cell.
  • Same metrics for Menlo, SF Mono, Monaco (all hit the same Apple Symbols / system braille fallback).

Expected

Dots fill the cell edge-to-edge so consecutive ⣿⣿ glyphs visually merge, matching VS Code, iTerm2, Ghostty, Konsole, kitty.

Comparison

Image

VS Code Example

Image

(Left/top: current font-glyph rendering with gaps. Right/top: synthetic rendering with ctx.arc() sized to cellWidth/8. Bottom: zoomed single-char detail showing the 24% padding.)

Root cause

Warp draws the actual font glyph for braille codepoints. Most fonts ship braille glyphs that don't fill the cell. Result: visible gaps that no font choice fully fixes.

Precedent — both major terminals bundle braille with box-drawing into the same synthetic-glyph code path

xterm.js (used by VS Code, Hyper, Tabby, Wave): CustomGlyphRasterizer.ts:143-168

function drawBrailleCharacter(ctx, pattern, xOffset, yOffset, deviceCellWidth, deviceCellHeight) {
  const xEighth = deviceCellWidth / 8;
  const paddingY = deviceCellHeight * 0.1;
  const usableHeight = deviceCellHeight * 0.8;
  const yEighth = usableHeight / 8;
  const radius = Math.min(xEighth, yEighth);
  for (let bit = 0; bit < 8; bit++) {
    if (pattern & (1 << bit)) {
      const x = brailleDotPositions[bit * 2];
      const y = brailleDotPositions[bit * 2 + 1];
      const cx = xOffset + (x + 1) * xEighth;
      const cy = yOffset + paddingY + (y + 1) * yEighth;
      ctx.beginPath();
      ctx.arc(cx, cy, radius, 0, Math.PI * 2);
      ctx.fill();
    }
  }
}

Exposed as customGlyphs: true (default ON). VS Code surfaces this as terminal.integrated.customGlyphs and lists Box Drawing, Block Elements, Braille Patterns, Powerline Symbols, Progress Indicators, Git Branch Symbols, Symbols for Legacy Computing as the covered ranges.

Ghostty: src/font/sprite/draw/braille.zig — dedicated draw2800_28FF() in Zig. Sibling files cover box-drawing, powerline, geometric shapes, branch symbols, legacy computing — same architectural bundling.

Suggested scope when this lands

Match the VS Code / Ghostty bundle so progress bars, TUI dashboards, fancy prompts all become font-agnostic at once:

  • U+2500–U+257F Box Drawing
  • U+2580–U+259F Block Elements
  • U+2800–U+28FF Braille Patterns
  • U+E0A0–U+E0D4 Powerline Symbols (PUA)
  • U+1FB00–U+1FBFF Symbols for Legacy Computing

Environment

  • Warp version:
  • macOS
  • Font: Meslo LG L DZ for Powerline (problem reproduces on Menlo, SF Mono, Monaco, Source Code Pro too)

Related

Metadata

Metadata

Assignees

No one assigned

    Labels

    area:shell-terminalTerminal input/output, shell integration, prompt behavior, and block rendering.area:ui-frameworkCore Warp UI framework, rendering, layout, and windowing infrastructure.bugSomething isn't working.os:macmacOS-specific behavior, regressions, or requests.repro:highThe report includes enough evidence that the issue appears highly reproducible.triagedIssue has received an initial automated triage pass.

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions