Skip to content

Size cells from the buffer's font, not the frame's - #590

Merged
dakra merged 2 commits into
mainfrom
fix/cell-pixel-size
Aug 1, 2026
Merged

dakra merged 2 commits into
mainfrom
fix/cell-pixel-size

Conversation

@dakra

@dakra dakra commented Jul 31, 2026

Copy link
Copy Markdown
Owner

Follow-up to #589. That PR made buffer-face-mode rescale the terminal grid; the cell pixel dimensions ghostel reports were still computed from the frame.

Cells were measured off the wrong thing

ghostel--reported-cell-width and ghostel--cell-height — the dimensions handed to libghostty and reported to programs through XTWINOPS (CSI 14 t / CSI 16 t) — used frame-char-width / frame-char-height. Both are frame-level and ignore a buffer-local default face remapping, while the row/column count comes from the remap-aware window-screen-lines and window-max-chars-per-line. Any local font change (text-scale-mode, buffer-face-mode, a ghostel-default :height) therefore left the reported cell at the frame's size while the grid tracked the new font.

The kitty graphics paths shared the base, so a placement asking for 4×2 cells was sized for the frame's cell and drew at the wrong scale inside a scaled grid.

Both now use default-font-width / default-font-height, which resolve through face-remapping-alist.

Two things deliberately keep the frame metric:

  • the float line-spacing branch, because redisplay scales a float by FRAME_LINE_HEIGHT (xdisp.c), matching default-line-height;
  • kitty slice heights, which stay at the font height rather than default-line-height — buffer line-spacing is applied per glyph, so a spacing-inclusive slice would double-pad image rows.

Measured in a real Emacs before adding any caching: the default-font-width + default-font-height pair costs ~11 µs even on the font-info path, against ~0.06 µs for frame-char-*. Not worth caching on the per-placement kitty path.

Verified live

GUI Emacs 31.0.91 driving a real bash, with a Python helper in the shell reading back the terminal's own CSI 16 t / CSI 14 t replies. frame-char-* stayed 7×14 throughout.

state buffer font term grid cell (h;w) text area kitty image (4c×2r)
baseline 7×14 34×120 22;11 748×1320 :width 28 :height 28, (slice 0 0 28 14)
text-scale-set 6 22×41 11×38 65;35 715×1330 :width 88 :height 82, (slice 0 0 88 41)
buffer-face-set '(:height 240) 14×28 17×60 44;22 748×1320 —
line-spacing 4 7×14 — cell height 18 logical — slices still 14 tall

Every text-area figure equals rows × cell-height and cols × cell-width. Before the change all four rows reported 22;11 and sized the image at 28×28, so at text-scale 6 an image drew at about a third of its declared cell footprint.

A compile terminal reset its cell to 1×1 px

Found while reviewing the above; it is an independent pre-existing bug, in the second commit.

ghostel-compile--start reconciles the VT to the output window after display-buffer may have placed the buffer somewhere other than the window --prepare-buffer measured. It called ghostel--set-size with only rows and cols, and the native binding defaults omitted cell dimensions to 1 px — wiping the dimensions ghostel--init-buffer had just seeded. ghostel--adjust-size never repaired it, because it only re-sends when the row/column count changes and the reconcile had just made those match. So a compile terminal reported a 1 px cell for the whole run.

The reconcile now goes through ghostel--set-size-with-cell-dims like the other resize sites. Verified live: M-x ghostel-compile running a probe that queries CSI 16 t on /dev/tty reports 6;22;11, where dropping the dimensions again reports 6;1;1.

Tests

Stubs in the kitty fixture and the cell-dimension tests now make the frame dimensions differ from the font dimensions, so a revert to frame-char-* fails rather than passing on coincidence. New: ghostel-test-kitty-display-image-sized-from-buffer-font.

ghostel-test-compile-reconciles-vt-size-to-outwin had been masking the compile bug — it stubbed ghostel--set-size-with-cell-dims away and recorded only the three-argument ghostel--set-size. Both reconcile tests now let the wrapper run and assert at the native boundary.

make -j8 all passes from a clean .build/tests.

Not addressed here

The float line-spacing branch rounds where xdisp.c and default-line-height truncate — up to 1 px of over-report at e.g. line-spacing 0.25 with a 14 px font. Predates this work; only the base term changed.

dakra added 2 commits August 1, 2026 00:58
`ghostel--reported-cell-width' and `ghostel--cell-height' — the cell
pixel dimensions handed to libghostty and reported to programs through
XTWINOPS (CSI 14/16 t) — were built from `frame-char-width' and
`frame-char-height'.  Both are frame-level and blind to a buffer-local
`default' face remapping, so any local font change (`text-scale-mode',
`buffer-face-mode', a `ghostel-default' `:height') left the reported
cell size at the frame's while the row/column count, which comes from
the remap-aware `window-screen-lines' and `window-max-chars-per-line',
tracked the new font.  A terminal scaled to text-scale 6 kept
reporting an 11x22 px cell for a grid whose cells were 35x65.

The kitty graphics paths had the same base: both sized the image and
its per-row slices off the frame's cell, so a placement that asked for
4x2 cells drew at the unscaled pixel size inside a scaled grid.

Use `default-font-width' / `default-font-height', which resolve
through `face-remapping-alist'.  The float `line-spacing' branch keeps
multiplying by `frame-char-height': redisplay scales a float by
FRAME_LINE_HEIGHT (xdisp.c), so the frame's char height is the right
base there.  Slices keep using the font height rather than
`default-line-height' — buffer `line-spacing' is applied per glyph, so
a spacing-inclusive slice would double-pad image rows.

Cost is not a concern on the per-placement kitty path: the pair costs
~11 us even when the default face is remapped and `font-info' runs.

Verified in a GUI Emacs driving a live bash, with a Python helper in
the shell reading back the terminal's own CSI 16 t / CSI 14 t replies.
At the default font: cell 11x22, area 1320x748 for a 34x120 grid.
After `text-scale-set 6' (font 22x41, frame char dims unchanged at
7x14): cell 35x65, area 1330x715 for 11x38, and a 4x2-cell kitty
placement grew from 28x28 px with 14 px slices to 88x82 px with 41 px
slices.  `buffer-face-set (:height 240)': cell 22x44, area 1320x748
for 17x60.  With `line-spacing' 4 the reported cell height picks up the
spacing while the slices stay at the font height.
`ghostel-compile--start' reconciles the VT to the output window after
`display-buffer' may have placed the buffer in a window other than the
one `--prepare-buffer' measured.  That call passed only rows and cols.
The native binding defaults omitted cell dimensions to 1 px, so the
resize overwrote the dimensions `ghostel--init-buffer' had just seeded
and libghostty ran the rest of the compilation with a 1x1 px cell:
CSI 14/16 t reported a 1 px cell to the command, and kitty graphics
placements derived their grid geometry from it.

Nothing repaired it afterwards.  `ghostel--adjust-size' only re-sends
dimensions when the row/column count changes, and the reconcile had
just made those match the output window, so the 1x1 cell survived
until the user resized the window.

Route the reconcile through `ghostel--set-size-with-cell-dims' like
the other resize sites.  The compile buffer is already current there,
which is what the wrapper needs to resolve the dimensions.

The wrapper's docstring claimed five resize sites; there are two.

Verified in a GUI Emacs: `M-x ghostel-compile' running a probe that
queries CSI 16 t on /dev/tty reports a 11x22 px cell, matching
`ghostel--reported-cell-width'/`-height' for the buffer's font, where
dropping the dimensions again reports 1x1.

`ghostel-test-compile-reconciles-vt-size-to-outwin' had been masking
this: it stubbed `ghostel--set-size-with-cell-dims' away and recorded
only the three-argument `ghostel--set-size'.  Both reconcile tests now
let the wrapper run and assert at the native boundary — one that the
reconcile carries the cell dimensions and still precedes the header
render and the spawn, the other that a missing output window leaves
just the `--prepare-buffer' seed.
@dakra
dakra merged commit 9af3ba8 into main Aug 1, 2026
29 checks passed
@dakra
dakra deleted the fix/cell-pixel-size branch August 1, 2026 06:24

This branch was previously deployed

1 inactive deployment
github-pages — 9af3ba8e Deployed Aug 1, 2026 by dakra via deploy #139
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant