Subject: bug#78621: Pixelwise display specified spaces less precise than min-width

From: Eli Zaretskii <[email-masked]>
Date: Fri, 30 May 2025 07:39:46 +0300

> Date: Thu, 29 May 2025 09:38:15 -0700
> Cc: [email-masked], [email-masked]
> From: Jim Porter <[email-masked]>
> 
> On 5/28/2025 11:18 PM, Eli Zaretskii wrote:
> > I don't follow this argument.  string-pixel-width uses the display
> > code, so it should return the exact same value in pixels as what the
> > actual display produces when the same characters are shown on the
> > screen.  So whatever rounding happens (which I don't think it does,
> > since font glyphs have integer advance width), it happens the same in
> > both cases and should yield the same values.  Or what am I missing?
> 
> The short version is that we should pass the current buffer to 
> 'string-pixel-width' so that we can just use the display code to compute 
> the string's width with all the remappings applied, rather than having 
> the display code compute it without remappings and then trying to guess 
> what the remapping would do to the width.

If the string's text is already in a buffer, we have
buffer-text-pixel-size and window-text-pixel-size for that purpose.
If the text is not in any buffer, then string-pixel-width should do
the job, and do it well.  In particular, it strives to turn on/off all
the features that affect display as they are in the original buffer
(which can be passed to it as an optional argument), and applies all
the buffer-local settings that could matter.  This includes face
remapping (which is how text-scaling is implemented).

So I still don't understand the nature of the problem.  It is possible
that string-pixel-width should take some additional settings into
consideration, but in that case, we need to identify which settings it
currently ignores, and add that to the function to fix it.  However,
your argument is not specific to some setting that isn't taken into
consideration, it is a general one, and that I don't understand, for
the simple reason that string-pixel-width uses the exact code used for
displaying text.

> The problem is that 'text-scale-increase' modifies the text size by a 
> particular ratio; when zooming out by one step, that defaults to 1 / 
> text-scale-mode-step, or 5/6.

text-scale-increase does that by using face-remapping-alist, which
string-pixel-width accounts for (at least is supposed to).

> My fixed-pitch font is 8 pixels wide at the default size, so zooming out 
> one step means the pixel width per char is 8 * 5/6 = 6.666.

I don't think this is true.  What face-remapping-alist does is it asks
Emacs to find a variant of the font with smaller or larger size.
Emacs then asks the font back-end to find a font that best fits that
size, and uses that.  The size of the font is not necessarily what you
compute above, it is the best fit for that number.

But this is a tangent, see below.

> Since the advance width is an integer, we round up to 7 pixels. The
> original code works for fixed-pitch fonts since
> '(default-font-width)' returns 7 in this case, so the original
> expression:
> 
>    (ceiling (* (string-pixel-width str)
>                (/ (float (default-font-width)) (frame-char-width))))
> 
> is equivalent (only in this simple case) to:
> 
>    (* (string-width str) (default-font-width))
> 
> Hopefully that all makes sense.
> 
> For variable-pitch fonts, this is more complex. Our zoom ratio is still 
> 5/6, but we apply that ratio per-character, rounding each time. For the 
> string "hi there", the pixel widths of each character are:
> 
>    Scale   h  i  _  t  h  e  r  e   Total
>        0   8  4  4  5  8  8  5  8      50
>       -1   7  3  4  4  7  7  5  7      44
> 
> Since the error introduced by rounding each character is a bit 
> different, we get a different result between the width from the display 
> engine compared to the approximation in the first expression above where 
> we round only once at the very end.

Why do you say that the result is different from the actual display?
Do you perhaps assume that string-pixel-width adds the character
widths "by hand", and thus accrues those round-off errors?  If so,
that's not what string-pixel-width does.  It actually computes the
layout of the string's text as it would be shown on display, using the
exact same code from the display engine used for actual display.  IOW,
the pixel-width of the text is measured by the display layout code,
including asking the (scaled-up or scaled-down) font for the metrics
of the glyph of each character in the string, and computing the sum of
those metrics exactly like we do for displaying them.  So any
round-off, such as it is, should be identical both on display and in
the results of string-pixel-width.  Take a look at
window-text-pixel-size (the primitive which string-pixel-width calls)
to see how it does its job by calling the display-engine functions.

The only way I can understand your argument is that the original
recipe didn't pass the buffer to string-pixel-width (that additional
argument is new in Emacs 31), which might be the reason why it
attempted to account for text-scaling "by hand".  But that's not how
this issue should be solved, which is why we added that argument in
Emacs 31.

If I'm missing something in the above reasoning, please point that
out, because I still think string-pixel-width should do its job in
this situation (barring any bugs in it that we should identify and
fix).