Subject: bug#71039: :box :line-width and :underline :position should accept fractional sizes

From: JD Smith <[email-masked]>
Date: Sat, 18 May 2024 21:32:23 -0400

> On May 18, 2024, at 12:06 PM, Eli Zaretskii <[email-masked]> wrote:
> 
>> From: JD Smith <[email-masked]>
>> Date: Sat, 18 May 2024 10:52:03 -0400
>> 
>> There are, however, two face size attributes which are hard-coded in pixels: :box :linewidth and :underline :
>> position.  It would be very convenient if these also accepted fractional floating point values.  E.g. a face
>> attribute of:
>> 
>> :box (:line-width (0.5 . -0.25)) 
>> 
>> would indicate a box with half a char width outside padding left & right, and one-quarter char height padding
>> above and below.
> 
> Are you sure this is a good idea?  

Definitely!  I've spent a good bit of time working around the absence of this feature (and, while I found solutions, they were too convoluted to be practical).

> What would you like this to do when
> two adjacent runs of text are shown using different-size fonts (which
> AFAIU is the main use case for this feature)?  

They'd have different height boxes, and that would be the desired effect.  Sort of the same idea as something like:

(insert "\n"
        (propertize " Short " 'face '(:box (:line-width (0 . -4)) :inverse-video t))
        (propertize " BOX " 'face '(:box (:line-width (0 . -2)) :inverse-video t))
        (propertize " tall box " 'face '(:box (:line-width (1 . 1))))
        "\n")

except also adaptive to the text height.  Normally of course you would not do this for random text.  Instead you'd apply targeted floating :box widths to short stretches of text, like labels.

> We currently force the
> thickness and position of the underline to be identical for all the
> characters of a stretch of underlined text, even if they are displayed
> using different fonts, and we take those values from the first part of
> the underlined text's stretch.  This is because having the underline
> break or show differently in the middle of an underlined text has ugly
> appearance.

I wan't aware of this behavior, but I do see the value in that special rule for underlines.  It's possible that pixel position indeed makes the most sense for underline, given the face-merging behavior you describe.  I included it mostly for completeness.

More important is :box.  For various effects which cover short stretches of text like label boxes, etc., the ability to have the box thickness scale with char size would be very useful.  Often these use inverse-video, so that the "box" is in the background color.  Many packages reach for SVG images in this context, but with a little help, many of those uses could be avoided.

> OTOH, calculating the thickness and position in pixels from the face
> font's dimensions is easy enough if your code needs that.

Sure, but upon text-scaling, this calculation needs to be performed again (probably in a temporary buffer, and across many faces).  Then, per-buffer face remaps must be applied and managed, etc.   I've done that, but it's a lot to juggle for a simple box.

> Given these two facts, I'm not sure supporting float values here will
> be worth the effort.

I'm confident they'd be used quite a lot.  Do you have a sense of how difficult these improvements would be?  Since you can already now apply fractional specified space like (space :width (0.25 . width)), I was hopeful the calculations would be close at hand.

>> In addition, :box would be even more powerful, and obviate the use of SVG styling in many situations, if :box :
>> line-width optionally accepted a list of four parameters for box dimensions, one for each side:
>> 
>> :line-width (left right top bottom)
>> 
>> naturally as either pixel or floating point fractions.
> 
> How is this different from specifying the thickness and the position,
> as we have today?

AFAIU there is no :position offset for :box?  Right now, :box can specify thickness as a fixed number of pixels outside (or inside, if negative) the character(s), and in recent versions these widths can differ vertically and horizontally.  But boxes are always centered horizontally on the boxed text and vertically on the line (including any line-spacing).

What the above proposal would do is allow you to specify the box width separately on all 4 sides of the affected text: left, right, top, and bottom.  So for example:

	:line-width (4 0 -0.25 -0.1)

would push the box 4 pixel to the left, and pull the box down from the top more than up from the bottom. As a related idea, following your suggestion, you could keep :line-width as-is and add a :box :position attribute that shifts the box vertically (which would be the same except symmetric left/right).  

The floating point width is probably more important, but this would enable some useful effects like simple text-based progress bars, etc.