Subject: bug#74866: 29.4; fit-frame-to-window + no vertical scroll bar + ch

From: martin rudalics <[email-masked]>
Date: Mon, 23 Dec 2024 18:59:50 +0100

> Whether 'set-face-attribute' DTRT and how it is involved in the scenario
 > at hand is a question I'd prefer not to answer.  If it affects the
 > canonical line height or column width of a frame, it should find its way
 > into the size hints for each frame.  Otherwise, chaos may result.

I now analyzed the problem and came to the conclusion below.  Note that
the scenario below is interesting only for window systems that employ
size hints as is the case for most GNU/Linux based systems.  The problem
cannot be seen on Windows, for example, because it does not use size
hints.

With emacs -Q of the release version put the following code into
*scratch* (which is the code from the OP plus two commented lines).

(defun fit (lin col)
   "Fit some text (LIN lines and COL columns) into a newly created frame."
   (interactive)
   (let ((buf (get-buffer-create "*bar*"))
         (frm (make-frame '((z-group . above)))))
     (with-current-buffer buf
       (erase-buffer)
       (setq mode-line-format '(""))
       (dotimes (_ lin) (insert (format "%s\n" (make-string col ?x))))
       (goto-char (point-min)))
     (with-selected-frame frm
       (switch-to-buffer buf)
       (fit-frame-to-buffer))))
;;; LIN and COL are arbitrary, make them sufficiently small.

;; (setq frame-resize-pixelwise t)
(setq-default vertical-scroll-bar nil)
(set-face-attribute 'default nil :height 75)
(fit 9 23)
;; (eval-buffer)

Now first evaluate the

(set-face-attribute 'default nil :height 75)

line to shrink the selected frame.  Note that if you call 'eval-buffer'
immediately, total chaos will ensue because the face attribute handling
code will annihilate any previous attempt to fit the frame.

Next evaluate the buffer.  You should see a new frame with wrapped
lines.  Now uncomment the

;; (setq frame-resize-pixelwise t)

line and eval the buffer again.  You should see a new frame fitting the
buffer.

What goes wrong when you see wrapped lines?

'fit-frame-to-buffer-1' inherently (assuming a fixed width font and
other simplifications) calculates the frame width by multiplying the
frame's character width (6 pixels) with the number of characters on the
longest line (23) resulting in 138 pixels.  It adds 16 pixels for the
fringes resulting in a requested width of 154 pixels.  Emacs asks the
window manager via xg_frame_set_char_size to set the width to 154 pixels
but the window manager allots us only 152 pixels.  Emacs concurs and
wraps the lines.

The reason for this is deeply rooted in the way we calculate size hints.
Emacs sets the base width of the frame to 32 pixels here (16 for the
widths of the frame's fringes, 16 for the scroll bar width).  So we have
a "variable" frame width of 154 - 32 = 122 pixels.  At the same time
Emacs sets the width increment to the frame's default character width.
Now the nearest multiple of 6 to 122 is 120 and that's the number of
pixels the window manager gives us.

I now installed a workaround in 'fit-frame-to-buffer-1' on master to
make the frame at least as large as needed to not wrap lines in such a
case (hopefully, it does not break anything on systems that do not use
size hints).

On the Emacs I daily use I do not see the problem because I calculate
size hints in a way that adds the "missing" 2 pixels to the base width -
resulting in a base width of 34 pixels and a "variable" width of 120
pixels.  If and when I feel that it's save to install it, I will do that
on master.  Till then either live with the workaround or either remove
the scroll bar from the frame (and not from the buffer) or set
'frame-resize-pixelwise' to t.

martin