In the setup below, fractionally scaled headings show large gaps between Han-character runs and clipped Latin glyphs, even with a stationary cursor. The sample uses Han characters shared by Chinese and Japanese; neither repeated-character heading contains spaces.

At full size, compare the right edge of the 8th, 16th, and 24th M with its neighbors.
Reproduction
Tested on unmodified 00a83b8a25b9d2d50dd15efaf4c701c19de21688: Kitty 0.47.1, Neovim 0.12.5, Fedora 46 prerelease; direct Kitty/X11 without tmux. At 12 pt, cells were 19 × 44 px. Font logs confirmed Noto Sans Mono with Noto Sans Mono CJK TC fallback. A direct OSC 66 rerun on Kitty 0.48.2 reproduced the 12/10 pt measurements below. Current upstream HEAD, later Kitty releases, and other operating systems remain untested.
Save as /tmp/md-render-repro.lua:
-- Start Neovim from the md-render.nvim checkout being tested.
vim.opt.runtimepath:prepend(vim.fn.getcwd())
vim.opt.termguicolors = true
vim.opt.cursorline = false
Save as /tmp/heading-spacing.md:
## 漢字表示間隔確認
Body after the shared Han-character heading.
## 田田田田田田田田田田田田
Body after the repeated Han characters.
## MMMMMMMMMMMMMMMMMMMMMMMM
Body after the English heading.
From the plugin checkout, open Kitty with:
env -u TMUX -u TMUX_PANE -u TERM_PROGRAM -u TERM_PROGRAM_VERSION \
kitty --config NONE --debug-font-fallback \
-o remember_window_size=no -o initial_window_width=80c \
-o initial_window_height=16c -o font_size=12 \
-o linux_display_server=x11 --directory "$PWD" \
nvim --clean -n -u /tmp/md-render-repro.lua /tmp/heading-spacing.md \
2>/tmp/md-render-kitty-fonts.log
Run :MdRender toggle. --clean excludes personal plugins; disabling cursorline isolates this from #64. --config NONE does not fix fallback fonts across machines; the debug log records the fonts actually selected.
Experiments
The same h2 runs reproduce the artifacts directly in Kitty, without Neovim. Disabling scaling removes them in the same document.
Direct reproduction, measurements, and unscaled control
Run this Python script in Kitty; no third-party packages are needed. Each run contains four 田 or eight M:
import sys
for label, chunk in (("Han characters", "田" * 4), ("ASCII", "M" * 8)):
sys.stdout.write(label + "\r\n\x1b[1m")
for _ in range(3):
sys.stdout.write(f"\x1b]66;s=2:n=7:d=8:w=7:v=2;{chunk}\x1b\\")
sys.stdout.write("\x1b[0m\r\n\r\n\r\n")
sys.stdout.flush()
All rows below use h2 scaling. Backgrounds mark run boundaries; centered adds h=2, and per-character uses w=0.

| Screenshot measurement |
12 pt |
10 pt |
田 left-edge advance, within/across runs |
56 / 98 px |
47 / 83 px |
田 blank gap, within/across runs |
10 / 52 px |
8 / 44 px |
M visible ink width, ordinary/run-end |
29 / 26 px |
24 / 24 px |
These are visible-pixel bounds, not font-internal metrics. At 10 pt, all 24 M glyphs have equal ink widths and advances, while the Han-character gaps remain.
Tracing Kitty 0.48.2's hb_shape() output confirmed that, at 12 pt, a 266 px run contains 224 px of Han advances (4 × 56) but 272 px of Latin advances (8 × 34). Changing only the M advance from 34 to 33 px restored all ink widths to 29 px, with Han measurements unchanged. This was a diagnostic intervention, not a proposed fix.
For the unscaled control, add require("md-render.text_size").setup { enabled = false } to the Lua config and restart:

| Experiment / idea |
Result / limitation |
Center runs (h=2) |
At 12 and 10 pt, shifts each run equally without changing spacing or clipping; the 12 pt Han shift is 21 px. |
Per-character placement (w=0) |
Kept all six scales and corrected wrapping to use reserved cells (s=2), then checked 80/40-column previews. Boundary artifacts disappeared in the samples, but spacing became too wide. At 80 columns, a one-line mixed Chinese/English h3 wrapped to two. Rejected for readability. |
Reduce h2–h6 widths to max(1, w - 1) |
Kept text, run boundaries, scales, and fonts unchanged; h1 was the control. Noto glyphs joined/overlapped at h2/h3/h5 and clipped at h4. Droid Sans Japanese retained all glyphs and ink widths, but gaps remained. |
| Integer sizes / scaling off |
An exploratory preview used h2 at 2× and h3–h6 at ordinary size. This and disabling scaling lose the six-size hierarchy. |
| Standalone image reference |
Standalone references using Pango (text layout library) and Cairo (2D graphics library) preserved the six scales, with no missing glyphs or horizontal overflow in the 80/40-column samples. No plugin integration; font matching, redraw, and interactions remain untested. |
| Glyph-aware native packing (untested) |
Use actual terminal glyph layout to guide splitting/allocation; no portable metric source or validated algorithm yet. |
Experiment images and readability comparison
The original scales are 2, 7/4, 3/2, 7/5, 5/4, 7/6. The Han-character diagnostic rows below each contain twelve 田.
Original Noto packing, followed by per-character placement:


| Level |
Ink width |
Advance |
Blank gap |
| h1, unchanged |
54 px |
76 px |
22 px |
| h2 |
46 px |
76 px |
30 px |
| h3 |
40 px |
76 px |
36 px |
| h4 |
37 px |
76 px |
39 px |
| h5 |
34 px |
76 px |
42 px |
| h6 |
31 px |
76 px |
45 px |
At h4–h6, blank gaps exceed glyph widths. Uniform spacing alone does not ensure readability.
Reduced widths with Noto; joined ink regions do not mean input characters were deleted:

Droid Sans Japanese (confirmed in Kitty's font log), before/after reducing widths. Its cells are 32 × 42 px versus Noto's 19 × 44 px; compare within each font, not absolute distances across fonts.


Integer-scale preview and Pango/Cairo reference, using an earlier Chinese/English fixture:


The Pango image is a standalone typography reference, not an MdRender fix or Kitty rendering. It uses the same Noto font families; keeping source text alone would not establish working image-based interactions.
For the 12 pt sample, Han fallback advances leave unused space, while hinted Latin advances exceed the allocated width. The OSC 66 specification separates font scale from allocation and allows truncation of text that does not fit. A general native packing rule still needs validation across fonts and sizes. These tests do not cover all Unicode wrapping or Neovim redraw/interaction behavior.
Discussion
I hope these examples and experiments are useful. My interest is in improving readability while keeping the six heading sizes and familiar text interactions: search/highlights, selection/copying in Neovim and the terminal, and links. I'm also open to image rendering if it can preserve the original Neovim text and those interactions.
I'm curious whether you've seen similar behavior, and would welcome any thoughts or context you'd like to share.
In the setup below, fractionally scaled headings show large gaps between Han-character runs and clipped Latin glyphs, even with a stationary cursor. The sample uses Han characters shared by Chinese and Japanese; neither repeated-character heading contains spaces.
At full size, compare the right edge of the 8th, 16th, and 24th
Mwith its neighbors.Reproduction
Tested on unmodified
00a83b8a25b9d2d50dd15efaf4c701c19de21688: Kitty 0.47.1, Neovim 0.12.5, Fedora 46 prerelease; direct Kitty/X11 without tmux. At 12 pt, cells were 19 × 44 px. Font logs confirmed Noto Sans Mono with Noto Sans Mono CJK TC fallback. A direct OSC 66 rerun on Kitty 0.48.2 reproduced the 12/10 pt measurements below. Current upstream HEAD, later Kitty releases, and other operating systems remain untested.Save as
/tmp/md-render-repro.lua:Save as
/tmp/heading-spacing.md:From the plugin checkout, open Kitty with:
Run
:MdRender toggle.--cleanexcludes personal plugins; disablingcursorlineisolates this from #64.--config NONEdoes not fix fallback fonts across machines; the debug log records the fonts actually selected.Experiments
The same h2 runs reproduce the artifacts directly in Kitty, without Neovim. Disabling scaling removes them in the same document.
Direct reproduction, measurements, and unscaled control
Run this Python script in Kitty; no third-party packages are needed. Each run contains four
田or eightM:All rows below use h2 scaling. Backgrounds mark run boundaries;
centeredaddsh=2, andper-characterusesw=0.田left-edge advance, within/across runs田blank gap, within/across runsMvisible ink width, ordinary/run-endThese are visible-pixel bounds, not font-internal metrics. At 10 pt, all 24
Mglyphs have equal ink widths and advances, while the Han-character gaps remain.Tracing Kitty 0.48.2's
hb_shape()output confirmed that, at 12 pt, a 266 px run contains 224 px of Han advances (4 × 56) but 272 px of Latin advances (8 × 34). Changing only theMadvance from 34 to 33 px restored all ink widths to 29 px, with Han measurements unchanged. This was a diagnostic intervention, not a proposed fix.For the unscaled control, add
require("md-render.text_size").setup { enabled = false }to the Lua config and restart:h=2)w=0)s=2), then checked 80/40-column previews. Boundary artifacts disappeared in the samples, but spacing became too wide. At 80 columns, a one-line mixed Chinese/English h3 wrapped to two. Rejected for readability.max(1, w - 1)Experiment images and readability comparison
The original scales are
2,7/4,3/2,7/5,5/4,7/6. The Han-character diagnostic rows below each contain twelve田.Original Noto packing, followed by per-character placement:
At h4–h6, blank gaps exceed glyph widths. Uniform spacing alone does not ensure readability.
Reduced widths with Noto; joined ink regions do not mean input characters were deleted:
Droid Sans Japanese (confirmed in Kitty's font log), before/after reducing widths. Its cells are 32 × 42 px versus Noto's 19 × 44 px; compare within each font, not absolute distances across fonts.
Integer-scale preview and Pango/Cairo reference, using an earlier Chinese/English fixture:
The Pango image is a standalone typography reference, not an MdRender fix or Kitty rendering. It uses the same Noto font families; keeping source text alone would not establish working image-based interactions.
For the 12 pt sample, Han fallback advances leave unused space, while hinted Latin advances exceed the allocated width. The OSC 66 specification separates font scale from allocation and allows truncation of text that does not fit. A general native packing rule still needs validation across fonts and sizes. These tests do not cover all Unicode wrapping or Neovim redraw/interaction behavior.
Discussion
I hope these examples and experiments are useful. My interest is in improving readability while keeping the six heading sizes and familiar text interactions: search/highlights, selection/copying in Neovim and the terminal, and links. I'm also open to image rendering if it can preserve the original Neovim text and those interactions.
I'm curious whether you've seen similar behavior, and would welcome any thoughts or context you'd like to share.