Closed mankoff closed 9 years ago
;; Fix for alignment issue when using `mu4e-use-fancy-chars'
(if (equal window-system 'x)
(progn
(set-fontset-font "fontset-default" 'unicode "Dejavu Sans Mono")
(set-face-font 'default "Inconsolata-10")))
Does this not work?
Doesn't seem to work on OS X, even when I change x
to mac
. The command (set-face-font 'default "Inconsolata-10")
gives an error:
Debugger entered--Lisp error: (error "Font not available" #<font-spec nil nil Inconsolata nil nil nil nil nil 10.0 nil nil nil ((:name . "Inconsolata-10") (user-spec . "Inconsolata-10"))>)
internal-set-lisp-face-attribute(default :font "Inconsolata-10" 0)
set-face-attribute(default nil :font "Inconsolata-10")
set-face-font(default "Inconsolata-10")
eval((set-face-font (quote default) "Inconsolata-10") nil)
...
I don't know which font would be appropriate for OS X, but I come across this side which maybe helps. https://groups.google.com/forum/#!topic/carbon-emacs/1c25vYy8uxQ
Get the same error on Linux, I removed the line, seems to solve the alignment issue nevertheless.
Nothing on that comment thread seems to work. I am using a fixed-width font. The alignment error in the above screenshot appear to be due to different width of the symbols. When I use emoji, it is more obvious, and the emoji widths are not calculated correctly.
Damn it :-/! That's unfortunate, but I have no further idea which may fix this. I had the same issue today and saw this little snippet in the mu4e emacs manual http://www.djcbsoftware.nl/code/mu/mu4e/Fancy-characters-and-Inconsolata.html and thought that this will fix it for you too.
regards, Christian
This is a bit of a limitation in emacs itself, I'm afraid... you can't really have pixel-perfect alignment. But perhaps some more tweaking of that snippet above can get you further.
A work-around would be to have the option for the last field to be right-aligned. Then the alignment issue wouldn't matter.
Shameless self-promotion: I have a project to fix just this at https://github.com/cpitclaudel/monospacifier . If you use a common programming font (if not, just open an issue there), then it's just a matter of installing an extra font and adding a small tweak to your .emacs.
Here's a bit more info: even if you find a monospace font that covers all the characters you want to use, it still won't work perfectly: indeed, there's a good chance that the base width of that fallback font will not equal that of your usual programming font. Thus as long as there's the same number of flags for every line things will work, but as soon as there's more than one, things will break in all sorts of horrible ways.
The fix for this is to take two fonts, a main one (monospace) and a fallback one, and to edit the fallback to make all its characters match the base width of the main font. I have written software to do this.
Here's a screenshot (from another issue, but it happens to show alignment as well:)
@cpitclaudel I tried your fonts, that looks great, however, I can't make it work correctly, as the spacing is always bad. What did I missed? Here is what I did:
git clone https://github.com/cpitclaudel/monospacifier
cd monospacifier/fonts
cp * ~/.fonts
fc-cache
And in my .emacs I added:
(dolist (ft (fontset-list))
(set-fontset-font ft 'unicode (font-spec :name "DejaVu Sans Mono"))
(set-fontset-font ft 'unicode (font-spec :name "Asanb Math monospacified for DejaVu Sans Mono") nil 'append))
(setq mu4e-use-fancy-chars t)
Screenshot:
Thanks!
Try pressing C-u C-x = on one of the symbols; it will tell you which font it's using.
The font is xft:-PfEd-DejaVu Sans Mono-bold-normal-normal-*-13-*-*-*-m-0-iso10646-1 (#xADE)
, so notAsanb Math
... I guess that that's because the Mono font has priority over your's? What can I do to avoid to replace everything by your font? The strange thing also is that it's not shifted by a small amount of space, but it's shifted on the left completely by whole space, like if the width was exactly 0. Also, I tried with the others chars, and the problematic ones are ⏚
(a bit too big), ⚓
(way too small, with 0),⚴
(a bit too big, by a few px).
Ok, so meantime I tried to find a real solution that did not involved using some special fonts... And I found a way to precisely deal with spacing in emacs. So now, we need to adapt this code to mu4e, but I think that it shouldn't be that complicated (next step). Using the same idea, we can even use images/emoji instead of utf-8 chars!
;; https://emacs.stackexchange.com/questions/5495/how-can-i-determine-the-width-of-characters-on-the-screen
(defun my-char-width (char_str)
"Return the width in pixels of a character in the current
window's default font. More precisely, this returns the
width of the letter ‘m’. If the font is mono-spaced, this
will also be the width of all other printable characters."
(let ((window (selected-window))
(remapping face-remapping-alist))
(with-temp-buffer
(make-local-variable 'face-remapping-alist)
(setq face-remapping-alist remapping)
(set-window-buffer window (current-buffer))
(insert char_str)
(aref (aref (font-get-glyphs (font-at 1) 1 2) 0) 4))))
(defun insert_in_n_columns (cols_nb str)
"Insert as much element of list_char as possible, until it goes
out of n cols. 'Begin' is useless for the user."
(cl-labels ((aux (rem_px rem_l)
(if (null rem_l) ;; Empty list, fill with big space
(progn
;; Cannot use the local variable...
;; Better way than using a tmp variable?
(setq tmp_rem_px rem_px)
(insert
(propertize " "
'display
'(space . (:width (tmp_rem_px))))))
(progn
;; Get first element, remaining part,
;; and with of first elt
(let* ((c (car rem_l))
(r (cdr rem_l))
(w (my-char-width c)))
;; If there is enough space for the char, insert it
(if (<= w rem_px)
(progn
(insert c)
(aux (- rem_px w) r))
(progn
(if (not (eq rem_px 0))
;; Else fill with the good space
(progn
(setq tmp_rem_px rem_px)
(insert
(propertize " "
'display
'(space . (:width (tmp_rem_px)))))
)))))))))
(aux (* cols_nb (default-font-width)) (string-to-list str))))
;; Demo: this code adds a one-px space
;; Big utf-8 char
;; (insert_in_n_columns 4 "ab⏚")
;; (insert_in_n_columns 4 "abc⏚d")
;; Small utf-8 char
;; (insert_in_n_columns 4 "abc⚴d")
Ok, so I came up with this solution, it works great. It does not yet support the pictures, but I'll try to see if it's possible to implement.
@djcb : Do you think I can submit a Pull Request? It changes only one function of you actual code: mu4e~headers-field-truncate-to-width
.
Démo:
Code:
;; https://emacs.stackexchange.com/questions/5495/how-can-i-determine-the-width-of-characters-on-the-screen
(require 'memoize)
(defun my-char-width (char_str)
"Return the width in pixels of a character in the current
window's default font. More precisely, this returns the
width of the letter ‘m’. If the font is mono-spaced, this
will also be the width of all other printable characters."
(let ((window (selected-window))
(remapping face-remapping-alist))
(with-temp-buffer
(make-local-variable 'face-remapping-alist)
(setq face-remapping-alist remapping)
(set-window-buffer window (current-buffer))
(insert char_str)
(aref (aref (font-get-glyphs (font-at 1) 1 2) 0) 4))))
(memoize 'my-char-width)
(defun my-string-width (str)
"Return the width in pixels of a string in the current
window's default font. If the font is mono-spaced, this
will also be the width of all other printable characters."
(let ((window (selected-window))
(remapping face-remapping-alist))
(with-temp-buffer
(make-local-variable 'face-remapping-alist)
(setq face-remapping-alist remapping)
(set-window-buffer window (current-buffer))
(insert str)
(car (window-text-pixel-size)))))
(defun my-truncate-in-n-columns (cols_nb str)
"Insert as much element of list_char as possible,
until it goes out of n cols."
(cl-labels ((aux (rem_px rem_l)
(if (null rem_l) ;; Empty list, fill with big space
(progn
;; Cannot use the local variable...
;; Better way than using a tmp variable?
(propertize " "
'display
`(space . (:width (,rem_px)))))
(progn
;; Get first element, remaining part,
;; and with of first elt
(let* ((c (car rem_l))
(r (cdr rem_l))
(w (my-char-width c)))
;; If there is enough space for the char, insert it
(if (<= w rem_px)
(progn
(concat (string c)
(aux (- rem_px w) r)))
(progn
(if (not (eq rem_px 0))
;; Else fill with the good space
(progn
(propertize " "
'display
`(space . (:width (,rem_px))))
)))))))))
(aux (* cols_nb (default-font-width)) (string-to-list str))))
(defun my-fill-in-n-columns (cols_nb str)
"Try to put str into cols_nb columns, and pad with a propertize space.
If it's not possible, then put as much element as possible,
and add … at the end."
(let* ((col_w (default-font-width))
(str_w (my-string-width str))
(d (- (* cols_nb col_w) str_w)))
(if (eq d 0)
str
(if (> d 0)
(concat str
(propertize " "
'display
`(space . (:width (,d)))))
(concat (my-truncate-in-n-columns (- cols_nb 1) str) "…")))))
(memoize 'my-fill-in-n-columns)
(setq my-mu4e-special-fields '(:flags))
(defun mu4e~headers-field-truncate-to-width (_msg _field val width)
"Truncate VAL to WIDTH."
(if width
(if (member _field my-mu4e-special-fields)
(my-fill-in-n-columns width val)
(truncate-string-to-width val width 0 ?\s t)
)
val))
I think I found the idea solution: nearly no code modification (the code is even simpler than before), works with all unicode char, independent of the exploitation system and it even works with pictures (needs imagemagick for pictures however). The idea is that you can replace any old "utf-8" by a string, that can contain utf-8 chars... but also text with some properties, like bold... including images !
Demo
Code:
;; (setq mu4e-headers-attach-mark '("a" . "⚓"))
(setq mu4e-headers-attach-mark `("a" . ,(propertize " " 'display '(image . (:type imagemagick :file "/tmp/trombone.png" :width 16 :ascent center)))))
Then:
(require 'memoize) ;; For efficiency
(defun reduce-string-until-ok (cols str &optional final_string)
"This function reduces the string str until it fits into 'cols' columns of
the current monospace width. If the string it to big, it cuts it and add the string 'final_string' at the end. By defaut, it's '…'"
(let* ((window (selected-window))
(remapping face-remapping-alist)
(col_w (default-font-width))
(cols_w (* cols col_w)))
(with-temp-buffer
(make-local-variable 'face-remapping-alist)
(setq face-remapping-alist remapping)
(set-window-buffer window (current-buffer))
;; Get the length of the final string that needs to be
;; added when it's too big
(if (not final_string)
(setq final_string "…"))
(insert final_string)
(let ((cols_w_too_big (max 0
(- cols_w (car (window-text-pixel-size))))))
(erase-buffer)
;; Insert the wanted string...
(insert str)
;; Get the width
(let* ((w (car (window-text-pixel-size)))
(d (- cols_w w))
(is_too_big nil))
;; Remove elements until the size is good
(while (< d 0)
(delete-backward-char 1)
(setq w (car (window-text-pixel-size)))
;; Update d to make sure it let space for "…"
(setq d (- cols_w_too_big w))
(setq is_too_big t))
(concat (buffer-string)
;; Add space if needed
(if (eq d 0)
""
(propertize " "
'display
`(space . (:width (,d)))))
;; Add final string if needed
(if is_too_big final_string "")))))))
;; Memoize for efficiency
(defun reduce-string-until-ok-memo (cols str &optional final_string)
(reduce-string-until-ok cols str final_string))
(memoize 'reduce-string-until-ok-memo)
(setq my-mu4e-special-fields '(:flags))
(defun mu4e~headers-field-truncate-to-width (_msg _field val width)
"Truncate VAL to WIDTH."
(if width
(if (member _field my-mu4e-special-fields) ;; For efficiency
(reduce-string-until-ok-memo width val)
(truncate-string-to-width val width 0 ?\s t))
val))
Important: if you are trying to change pictures to see which one is the best one, you should us reduce-string-until-ok
instead of reduce-string-until-ok-memo
in the last function, else the result is cached in memory for efficiency.
For example, after installing emacs-emojify
, I can use the images in ~/.emacs.d/emojis/emojione-v2.2.6-22/
:
(defun my-emoticone (file)
(propertize " "
'display `(image . (:type imagemagick :file ,(concat (expand-file-name "~/.emacs.d/emojis/emojione-v2.2.6-22/") file) :width 16 :ascent center))))
(setq mu4e-headers-flagged-mark `("F" . ,(my-emoticone "1f49a.png")))
(setq mu4e-headers-trashed-mark `("T" . ,(my-emoticone "26d4.png")))
(setq mu4e-headers-attach-mark `("a" . ,(my-emoticone "1f4ce.png")))
(setq mu4e-headers-encrypted-mark `("x" . ,(my-emoticone "1f50f.png")))
(setq mu4e-headers-signed-mark '("s" . "☡"))
(setq mu4e-headers-unread-mark `("u" . ,(my-emoticone "1f4e9.png")))
(setq mu4e-headers-new-mark '("N" . ""))
(setq mu4e-headers-draft-mark '("D" . "⚒"))
(setq mu4e-headers-passed-mark '("P" . "❯"))
(setq mu4e-headers-replied-mark '("R" . "❮"))
(setq mu4e-headers-seen-mark '("S" . "✔"))
You know, I think I lost the context of this discussion over time. Isn't it enough to add a pad the icons column with a single space with an :align-to
display property?
Well I see two problems with :align-to
:
…
at the end when needed. And that's basically what most of the code above does.@tobiasBora About using fontset, the problem is that Emacs is first using fallbacks from fontconfig. This makes fontset usable only by specifying the precise ranges each font should be handled.
tobiasBora - just wanted to say thanks for your snippet, this really should be a part of standard emacs: usefull across mu, org-agenda, ...
I fixed the problem using (setq mu4e-headers-precise-alignment t)
and increasing the flags column width as mentioned in https://github.com/djcb/mu/issues/1062#issuecomment-1004082214
Related to #599
If I have
(setq mu4e-use-fancy-chars 't)
the header columns are mis-aligned by a few pixels. See screenshot. It would be nice to be able to use fancy chars or even emoji (like #599) and keep alignment.