Subject: bug#53924: 26.1; fontification sometimes fails for some characters despite available glyphs

From: "Greg A. Woods" <[email-masked]>
Date: Mon, 14 Feb 2022 18:01:43 -0800

At Sun, 13 Feb 2022 13:53:27 +0200, Eli Zaretskii <[email-masked]> wrote:
Subject: Re: bug#53924: 26.1; fontification sometimes fails for some characters despite available glyphs
>
> > 	(font-families (cl-remove-duplicates
> > 			(sort (font-family-list)
> > 			      (lambda(x y) (string> (upcase x) (upcase y))))
> > 			:test 'cl-equalp)))
>
> This is not recommended as a way to get useful fonts.

Perhaps you can explain that better or differently so I can try to
understand?

I chose font-family-list because it is (supposedly) window-system
agnostic.  Indeed it works fine on macOS too, and it transparently even
does approximately the right thing on a TTY.

>  My suggestion
> is to use the following instead:
>
>   (delete-dups
>    (x-list-fonts "-*-*-medium-r-normal-*-*-*-*-*-*-iso10646-1"
> 		 'default (selected-frame)))

BTW, delete-dups is wrong, but I finally found seq-uniq (to avoid
cl-lib):

	(seq-uniq
	 (sort (font-family-list)
	       (lambda(x y) (string> (upcase x) (upcase y))))
	 (lambda(x y) (string= (upcase x) (upcase y))))

> Indeed, using x-list-fonts indiscriminately could very well include
> fonts that Emacs cannot use or even those which will crash Emacs.

Indeed -- that's another reason why I chose not to use x-list-fonts!

> Is this with the above Lisp program that tries all the fonts collected
> by font-family-list, or is this with some other recipe to reproduce
> the crash?  Also, was that in "emacs -Q"?

It turns out the crash happens with one font in particular,
"Inconsolata".  I have some cleanup of the versions of that font in
particular that I need to do to be sure I know which one is being used.

I can also now confirm the same crash occurs with both GTK+2.0 and with
the Lucid (xaw3d) toolkits, and in both 26.1 and today's Git "master".

The strange behaviour with glyph selection in some fonts also occurs
with the Lucid toolkit, and on today's Git "master".

The Lucid toolkit though seems to do font-scaling bass-ackwards
w.r.t. handling display resolution, and it also chooses "fixed" as the
default base font, which is not scalable (though that makes the effects
of the incorrect glyph choices extremely obvious on a hi-res display
since the bad glyphs are now 1/4 the size they should be).

--
					Greg A. Woods <[email-masked]>

Kelowna, BC     +1 250 762-7675           RoboHack <[email-masked]>
Planix, Inc. <[email-masked]>     Avoncote Farms <[email-masked]>

Latest show-all-font-families:

(defun show-all-font-families (&optional sample-text mono-only)
  "Show SAMPLE-TEXT (by default `list-faces-sample-text') in
'roman', 'bold', 'italic', and 'bold italic' for each font family
known to `font-family-list'.

If MONO-ONLY, include ':spacing m' in the `font-spec' parameters.
Interactively, a negative prefix does the same.

Note you can seem some strange results for fonts that don't have
each of the requested weight and slant forms.  Sometimes
proportional glyphs will even be substituted for mono-spaced
glyphs!  Sometimes a glyph from a different font (the default
font?) will be substituted even when the requested font is
available with all the requested attributes, e.g. 'office code
pro'.  (Perhaps because it is _only_ avaliable with the requested
attributes, but no others?)

Also some proportional fonts will show up despite only
mono-spaced fonts being selected.  Perhaps spacing is ignored
when requesting a font?  Note even `x-list-fonts' warns that:

    Fonts Emacs can't use may or may not be excluded...

N.B. WARNING:  This code may try to display fonts that cannot be
opened and as a result will crash Emacs!  Worse yet it can get
stuck rendering fonts and if killed cause the X11 server to go
into a CPU-bound loop that may take hours to resolve!  Kill it
sooner than later!  This is particularly true for Emacs-26.1 when
the MONO-ONLY parameter is non-nil.  See also the exclusion of
the 'droid' fonts.

Derived from an example on the EmacsWiki."
  (interactive
   (list
    ;; optional `sample-text':
    (if current-prefix-arg
	(read-string
	 "Sample text: "
	 ascii-sample-text
	 'sample-text-history))
    ;; optional `mono-only':
    (cond
     ((eq current-prefix-arg '-)
      t)
     ((and (numberp current-prefix-arg)
           (< current-prefix-arg 0))
      t)
     ((and (listp current-prefix-arg)
           (numberp (car current-prefix-arg))
           (< (car current-prefix-arg) 0))
      t)
     (t nil))))
  (let ((str (if sample-text
		 sample-text
	       list-faces-sample-text))
	(font-families (seq-uniq
			;; note:  the list is reversed so that font text is
			;; inserted in the demo buffer the right order
			(sort (font-family-list)
			      (lambda(x y) (string> (upcase x) (upcase y))))
			(lambda(x y) (string= (upcase x) (upcase y))))))
    (with-help-window "*Font Families*"
      (with-current-buffer standard-output
	(dolist (ff font-families)
	  (let* ((fs (font-spec :family ff
;				:weight 'medium :slant 'r
				;; xxx :spacing is confusing
				;; see below for additional test to confirm!
				(if mono-only :spacing) (if mono-only 'm)
				:dpi (round (/ (display-pixel-height)
					       (/ (display-mm-height) 25.4)))
				:registry 'iso10646-1))
		 (fl (list-fonts fs))
		 (fe (car fl))			; usually the cdr is non-scalable?
		 (xlfd (if fe
			   (font-xlfd-name fe)
			 (font-xlfd-name fs)))	; xxx font may not be useable
		 (fn (if (eq window-system 'x)
			 (condition-case nil
			     (x-resolve-font-name xlfd)
			   (error (message "Invalid font family: %s" ff)
				  nil))
		       nil))
		 (xlfd-fields (if fn
				  (x-decompose-font-name fn) ; xxx not really an X11 function
				nil))
		 )
	    ;; XXX the "droid*" fonts are broken???
	    (if (and (not (string-match "droid" ff))
		     (not (string-equal "inconsolata" ff)) ; xxx may crash Emacs!!!
		     (not (string-equal "nil" ff)) ; xxx never a useful font
;		     (not (string-match "italic" ff)) ; xxx rarely in all other styles
;		     (not (string-match "bold" ff)) ; xxx rarely in all other styles
		     (not (string-match ".pcf" ff)) ; xxx usually bitmap cursors
		     ;;
		     ;; xxx some fonts, e.g. "inconsolata" (which xfontsel says
		     ;; is both 'm' and 'p'), will return the "wrong" spacing in
		     ;; the XLFD if the request has a wild-card (i.e. :spacing
		     ;; unspecified), but then it can't be found with
		     ;; `font-info' with the explicit ":spacing 'p".
		     ;;
		     (condition-case nil
			 (if (font-info xlfd) ; xxx can this also return nil?
			     t
			   (message "Can't get font info for: %s" xlfd)
			   nil)
		       (error (message "Bad font: %s" xlfd)
			      nil))
		     (if mono-only
			 (if xlfd-fields
			     ;; because `x-resolve-font-name' sometimes ignores
			     ;; `:spacing' we must confirm the font matches
			     ;; xxx `xlfd-regexp-spacing-subnum' is WRONG, for 20 years!
			     (if (string-equal (aref xlfd-fields 9) "m")
				 t
			       (message "Font not actually monospaced: %s" xlfd)
			       nil)
			   ;; xxx try `font-get' on `fe', but that doesn't seem to
			   ;; work properly for getting `:spacing'!?!?!?!?
			   (if (eq window-system 'x)
			       (message "Can't resolve X font for: %s" xlfd)))
		       t))
		(progn
		  (message "Inserting text for font: %s" xlfd)
		  ;; xxx XXX N.B.:  without escaping the semicolons emacs can't
		  ;; parse these expressions backwards!!!
		  (insert "\; " ff ":" xlfd "\n\;\n"
			  "\;\t" ff " (plain):\n\;\n"
			  (propertize str 'font-lock-face
				      `(:family ,ff :weight medium :slant r))
			  "\n\;\n\;\t" ff " [bold]:\n\;\n"
			  (propertize str 'font-lock-face
				      `(:family ,ff :weight bold :slant r))
			  "\n\;\n\;\t " ff " [italic]:\n\;\n"
			  (propertize str 'font-lock-face
				      `(:family ,ff :weight medium :slant italic))
			  "\n\;\n\;\t " ff " [bold italic]:\n\;\n"
			  (propertize str 'font-lock-face
				      `(:family ,ff :weight bold :slant italic))
			  "\n\;\n\n"))
	      (message "Not showing font-family: %s" ff)))
	  (goto-char (point-min))
	  (setq case-fold-search t)
	  (if (fboundp 'form-feed-mode)
	      (form-feed-mode nil)))))))