Have a look at
This is used by
whitespace-mode and its
whitespace-display-mappings option (noting specifically the
space-mark values for the latter), so to begin with you should just play with those.
I suspect you can map a space to a codepoint which you can control?
Edit (Kirill): This is a snippet that illustrates how this answers the question: it can be used to display spaces differently, which is what I wanted to do. It makes every space
#x20, which is very narrow in some proportional width fonts, be displayed as a different kind of space, for example as one of
#x2000..#x200a, which includes a choice of different space widths - https://www.cs.tut.fi/~jkorpela/chars/spaces.html. It also seems that the font that the new space gets displayed with can be correctly set with
(let ((tab (make-display-table))
;; try #x2000 .. #x200a, plus there are some other Unicode space characters
(aset tab ? (vector (make-glyph-code cp)))
(setq-local buffer-display-table tab)
;; Compare different fonts' space widths:
(set-fontset-font t (cons cp cp) (font-spec :family "DejaVu Sans Mono"))
;; (set-fontset-font t (cons cp cp) (font-spec :family "Source Code Pro"))