From mboxrd@z Thu Jan 1 00:00:00 1970 Path: news.gmane.io!.POSTED.blaine.gmane.org!not-for-mail From: Jimmy Yuen Ho Wong Newsgroups: gmane.emacs.devel Subject: Re: Need `truncate-string-to-pixel-width` and `glyph-pixel-width` functions in C Date: Thu, 24 Oct 2024 22:11:36 +0100 Message-ID: References: <86sesndz8v.fsf@gnu.org> <86ed46en1q.fsf@gnu.org> <86seslddvs.fsf@gnu.org> <86msitd0oy.fsf@gnu.org> <86h691cwuv.fsf@gnu.org> <86frolcrl2.fsf@gnu.org> Mime-Version: 1.0 Content-Type: multipart/alternative; boundary="000000000000d3247c06253f7464" Injection-Info: ciao.gmane.io; posting-host="blaine.gmane.org:116.202.254.214"; logging-data="24487"; mail-complaints-to="usenet@ciao.gmane.io" Cc: emacs-devel@gnu.org To: Eli Zaretskii Original-X-From: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Thu Oct 24 23:13:28 2024 Return-path: Envelope-to: ged-emacs-devel@m.gmane-mx.org Original-Received: from lists.gnu.org ([209.51.188.17]) by ciao.gmane.io with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.92) (envelope-from ) id 1t4596-0006G8-2C for ged-emacs-devel@m.gmane-mx.org; Thu, 24 Oct 2024 23:13:28 +0200 Original-Received: from localhost ([::1] helo=lists1p.gnu.org) by lists.gnu.org with esmtp (Exim 4.90_1) (envelope-from ) id 1t4580-0001sl-2n; Thu, 24 Oct 2024 17:12:20 -0400 Original-Received: from eggs.gnu.org ([2001:470:142:3::10]) by lists.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_256_GCM_SHA384:256) (Exim 4.90_1) (envelope-from ) id 1t457x-0001sc-Im for emacs-devel@gnu.org; Thu, 24 Oct 2024 17:12:18 -0400 Original-Received: from mail-qv1-xf30.google.com ([2607:f8b0:4864:20::f30]) by eggs.gnu.org with esmtps (TLS1.2:ECDHE_RSA_AES_128_GCM_SHA256:128) (Exim 4.90_1) (envelope-from ) id 1t457v-0003By-34; Thu, 24 Oct 2024 17:12:17 -0400 Original-Received: by mail-qv1-xf30.google.com with SMTP id 6a1803df08f44-6ce3fc4e58bso8248166d6.0; Thu, 24 Oct 2024 14:12:14 -0700 (PDT) DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=gmail.com; s=20230601; t=1729804333; x=1730409133; darn=gnu.org; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:from:to:cc:subject:date:message-id:reply-to; bh=3tEQvsrf+L7yFalnjivo7IXd4KnWnm+KSHVY3cS0/GE=; b=UhP4BkiEhw0OlviGI2fTDG/pCGGhZRr+J6xoXXs392H+wSlELO/0zVVRWMcE/djIeY +o9e1FsfhOxgV0yH0YV2nX0VItKRB1XW3//DFqwl/PfP6s1I1NXN0ZssYueCWxV3OJRi rxeVX3+ePwJq0895zq/ViV/resUijJpBPcBO81WnfzkjuwxA6i7b0y1X88nf2JKtgDTJ 3E65FUPRpfhAGnAWPdoRABzv92iaboehD6y2UtBQTWvZ/3vcgMoaLiSF5VmB5FjMsubV ozzFGT84d96u1sGdJagwwc1ATyRLsvDaZ5gt4jXOez/x6jqqc6yR2SOwCOy1B1a2qN7l 9CUQ== X-Google-DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=1e100.net; s=20230601; t=1729804333; x=1730409133; h=cc:to:subject:message-id:date:from:in-reply-to:references :mime-version:x-gm-message-state:from:to:cc:subject:date:message-id :reply-to; bh=3tEQvsrf+L7yFalnjivo7IXd4KnWnm+KSHVY3cS0/GE=; b=uxpvWhnk4kagthynKn3tEtuwCTSZ+ipm8dO+WQ5peayUGvoWy6FK3yr2da5dTOv6wB JVuTFio3SPAg1RNSKfpi0JfAcpEDgAMxjH0HbLXzkh5uw++qtN5UJNoXZd3nVyDjDQKF 2EGG29MgcYqZLjI+P7g51OZUl37TMP/ADXIeP1PfQSkS7/NetMTRdqDCzJLjmg0QHYPX BZXhApX4HJ3H/DCF7+rCwl4dPEvOUvbFiCA648LCG3muzN9p0uQuT7fv+IfZ7jo4sS6s jXqdbMUJ9IdMK+S85ZqyCIm+/kXBkhvxcjYyFEgb7Qo5LDUVSKpPgJ3/4QpHyz7SeZV0 fAew== X-Gm-Message-State: AOJu0Yxt6/gGjNAaEzeUB41wtjyBKxsbtQINhvpWFzCsa/880VJoCM/S OlcGIUC40K66UimKnKe0tGrRKzEdTexsE4KtEOzWeAzTFWHNwyCGCzUXx2RMT4YAmSEwuecVcXi Z6KDPklxJyUmoaXldovXikwL84QBfDUzw X-Google-Smtp-Source: AGHT+IGGojevA8QaiKyXoqimcTvUoUs1EClIkHy+5OK/qlH2J/wX1PW/VRfVC4zjHmhlwPhgxlCqePjX4DqvluSuxRI= X-Received: by 2002:a05:6214:3114:b0:6cc:22c1:ee6e with SMTP id 6a1803df08f44-6ce342f7cd0mr80165386d6.45.1729804333423; Thu, 24 Oct 2024 14:12:13 -0700 (PDT) In-Reply-To: <86frolcrl2.fsf@gnu.org> Received-SPF: pass client-ip=2607:f8b0:4864:20::f30; envelope-from=wyuenho@gmail.com; helo=mail-qv1-xf30.google.com X-Spam_score_int: -20 X-Spam_score: -2.1 X-Spam_bar: -- X-Spam_report: (-2.1 / 5.0 requ) BAYES_00=-1.9, DKIM_SIGNED=0.1, DKIM_VALID=-0.1, DKIM_VALID_AU=-0.1, DKIM_VALID_EF=-0.1, FREEMAIL_FROM=0.001, HTML_MESSAGE=0.001, RCVD_IN_DNSWL_NONE=-0.0001, SPF_HELO_NONE=0.001, SPF_PASS=-0.001 autolearn=ham autolearn_force=no X-Spam_action: no action X-BeenThere: emacs-devel@gnu.org X-Mailman-Version: 2.1.29 Precedence: list List-Id: "Emacs development discussions." List-Unsubscribe: , List-Archive: List-Post: List-Help: List-Subscribe: , Errors-To: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Original-Sender: emacs-devel-bounces+ged-emacs-devel=m.gmane-mx.org@gnu.org Xref: news.gmane.io gmane.emacs.devel:324832 Archived-At: --000000000000d3247c06253f7464 Content-Type: text/plain; charset="UTF-8" > > Why would you need to truncate? and which part needs to be truncated? > > My suggestion is to align the s part so that it and the > following pseudo-scrollbar end exactly at the window edge. With this > arrangement, you shouldn't need to truncate at least the s part. > This can be dome by using :align-to which counts from 'right', which > is the right edge of the text area. > Actually, I don't understand why you'd need to truncate anything, > since the dimensions of the child frame can be calculated such that > the longest completion candidate will still fit without truncation. > Or what am I missing? The :align-to of the padding between candidate and annotation can only be calculated by adding the prefix column width, calculated from the longest prefix width, the candidate width, calculated the same way, and the suffix column width minus the pixel width of the suffix. The reason for this is that `string-pixel-width` will be called on all of these concatenated strings, and the maximum of which will be given as the width to a function to render the child frame. This also answers your next question, yes the pop up width is already calculated dynamically, but against a max width, this is what I mean by "confined", hence the need for truncation before blitzing the strings to a child frame. :align-to (- right suffix-width) will lead `string-pixel-width` to return a ridiculously large number that, funnily enough, is calculated from the current window's text area width, instead of the child frame's window, of which the size is unknown at this time. As to the reason for a max width, let's just say there exists language servers such as Rust Analyzer, that will return the signature of a function as the completion annotation, which can be quite long. Nobody wants their completion pop up box taking up half the screen. Of course, one can also adjusts the child frame parameters to let it deal with min/max size, margins and allow the user to resize, but the beautifully emulated scroll bar will have to be sacrificed because either I delete it from Corfu or the window will start truncating from the scroll bar when the user resizes the window to be smaller than the content width. > > There are 2 options - `truncate-string-to-width`, which is > > bugged, or set the `truncate-line` buffer local to t and let Emacs' > window system do its magic, which will be > > performant and correct, and I can even replace the arrow in an ellipsis, > but truncation will start to truncate off > > the emulated scroll bar. > > You assumed something I didn't say and didn't have in mind. There > should be no need to use truncate-lines. > I didn't assume anything. It's just a logical conclusion. Do you have it in mind now? This is not about refusal. It isn't like what you are asking is easy to do, and the "Emacs devs" are just a lazy bunch. I looked at the > code involved, and I currently have no idea how to implement what you > have in mind so that it works in all the cases, those which you > mentioned and those you didn't, and works in a way that will be useful > for Lisp programs. You may think it's easy, but it isn't. Some > things are even conceptually not simple, for example whether it is > okay or not to break a string between U+1F3C2 and the following > U+1F3FF, and if so, how to discover that subject to how the current > character-composition machinery in Emacs was designed and implemented. > You already have `string-glyph-split` which works correctly, (a job well done BTW, even more comformant than Chrome and Webkit). There are only 3 things you need to do for a hypothetical `glyph-pixel-width`: 1. With no faces, find the width of a character glyph from the default font or whatever `char-displayable-p` returns. This is easy, I can already do this from Elisp reasonably fast by going straight to the terminal using `composite-get-gstring`. 2. With faces, resolve the font from the faces, go to 1. Still no need to deal with windows. Can be done correctly in Elisp, but incredibly slowly due to anon faces, face lists and having to go through the recursive `face-attribute-merged-with`. The logic in `face-attribute-merged-with` can be duplicated and modified to deal with anon faces, and run iteratively, but still a few orders of magnitude slower than it needs to be. 3. Display properties is when you start to need a window, as a first try, you can delegate this back to the current slow path via `window-text-pixel-size`. 1 and 2 will be the vast vast majority of the use case. It's okay to be slower for 3 due to the involvement of a window. As a point of reference, this benchmark finishes in ~0.10s in my MacBook Pro M1 Pro, this is 6 FPS for measuring 10k glyph widths. ```elisp (let ((s "1")) 'face 'completions-common-part))) (benchmark-run 10000 (string-pixel-width s))) ``` The following finishes in ~0.9s. Using the procedure described in case 1 above already cuts the time down by half. ```elisp (benchmark-run 10000 (string-pixel-width "1")) ``` Getting boths cases down 1 order of magnitude is all I'm hoping for. I'm not saying emacs-devs are lazy, I'm just saying after 14 years, Emacs finally can split strings with emojis correctly, can we go one step further and give Emacs users the ability to layout in pixel precision in a more performant manner please? --000000000000d3247c06253f7464 Content-Type: text/html; charset="UTF-8" Content-Transfer-Encoding: quoted-printable
Why would you need to truncate? and whi= ch part needs to be truncated?

My suggestion is to align the <Type>s part so that it and the
following pseudo-scrollbar end exactly at the window edge.=C2=A0 With this<= br> arrangement, you shouldn't need to truncate at least the <Type>s = part.
This can be dome by using :align-to which counts from 'right', whic= h
is the right edge of the text area.=C2=A0
=C2=A0

Actually, I don't understand why you'd need to truncate anything, since the dimensions of the child frame can be calculated such that
the longest completion candidate will still fit without truncation.
Or what am I missing?

The :align-to of the padding bet= ween candidate and annotation can only be calculated by adding the prefix c= olumn width, calculated from the longest prefix width, the candidate width,= calculated the same way, and the suffix column width minus the pixel width= of the suffix. The reason for this is that `string-pixel-width` will be ca= lled on all of these concatenated strings, and the maximum of which will be= given as the width to a function to render the child frame. This also answ= ers your next question, yes the pop up width is already calculated dynamica= lly, but against a max width, this is what I mean by "confined", = hence the need for truncation before blitzing the strings to a child frame.= :align-to (- right suffix-width) will lead `string-pixel-width` to return = a ridiculously large number that, funnily enough, is calculated from the cu= rrent window's text area width, instead of the child frame's window= , of which the size is unknown at this time.

As to the reason for a = max width, let's just say there exists language servers such as Rust An= alyzer, that will return the signature of a function as the completion anno= tation, which can be quite long. Nobody wants their completion pop up box t= aking up half the screen.

Of course, one can also = adjusts the child frame parameters to let it deal with min/max size, margin= s and allow the user to resize, but the beautifully emulated scroll bar wil= l have to be sacrificed because either I delete it from Corfu or the window= will start truncating from the scroll bar when the user resizes the window= to be smaller than the content width.
=C2=A0
> There are 2 options - `truncate-string-to-width`, which is
> bugged, or set the `truncate-line` buffer local to t and let Emacs'= ; window system do its magic, which will be
> performant and correct, and I can even replace the arrow in an ellipsi= s, but truncation will start to truncate off
> the emulated scroll bar.

You assumed something I didn't say and didn't have in mind.=C2=A0 T= here
should be no need to use truncate-lines.
=C2=A0
<= div>I didn't assume anything. It's just a logical=C2=A0conclusion. = Do you have it in mind now?

This is not about refusal.=C2=A0 It isn't like w= hat you are asking is easy
to do, and the "Emacs devs" are just a lazy bunch.=C2=A0 I looked= at the
code involved, and I currently have no idea how to implement what you
have in mind so that it works in all the cases, those which you
mentioned and those you didn't, and works in a way that will be useful<= br> for Lisp programs.=C2=A0 You may think it's easy, but it isn't.=C2= =A0 Some
things are even conceptually not simple, for example whether it is
okay or not to break a string between U+1F3C2 and the following
U+1F3FF, and if so, how to discover that subject to how the current
character-composition machinery in Emacs was designed and implemented.
<= /blockquote>

You already have `string-glyph-split` which= works correctly, (a job well done BTW, even more comformant than Chrome an= d Webkit). There are only 3 things you need to do for a hypothetical `glyph= -pixel-width`:
1. With no faces, find the width of a characte= r glyph from the default font or whatever `char-displayable-p` returns. Thi= s is easy, I can already do this from Elisp reasonably fast by going straig= ht to the terminal using `composite-get-gstring`.
2. With faces, = resolve the font from the faces, go to 1. Still no need to=C2=A0deal with w= indows. Can be done correctly in Elisp, but incredibly slowly due to anon f= aces, face lists and having to go through the recursive `face-attribute-mer= ged-with`. The logic in `face-attribute-merged-with` can be duplicated and = modified to deal with anon faces, and run iteratively, but still a few orde= rs of magnitude slower than it needs to be.
3. Display properties is whe= n you start to need a window, as a first try, you can delegate this back to= the current slow path via `window-text-pixel-size`.

1 and 2 will be the vast vast majority of the use case. It's okay to= be slower for 3 due to the involvement of a window.

As a point of r= eference, this benchmark finishes in ~0.10s in my MacBook Pro M1 Pro, this = is 6 FPS for measuring 10k glyph widths.

```elisp
(let ((s "= 1")) 'face 'completions-common-part)))
=C2=A0 =C2=A0 (bench= mark-run 10000 (string-pixel-width s)))
```

The=C2=A0f= ollowing finishes in ~0.9s. Using the procedure described in case 1 above a= lready cuts the time down by half.

```elisp
(benchmark-run 10000 = (string-pixel-width "1"))
```

Getting= boths cases down 1 order of magnitude is all I'm hoping for.
=C2=A0
I'm not saying emacs-devs are lazy, I'm just saying afte= r 14 years, Emacs finally can split strings with emojis correctly, can we g= o one step further and give Emacs users the ability to layout in pixel prec= ision in a more=C2=A0performant manner please?
--000000000000d3247c06253f7464--