Hi Eli, Thanks for the comments. Some quick responses: > + if (SYMBOLP (feature_sym) && FIXNUMP (feature_val)) > + { > > + /* Convert symbol to HarfBuzz tag. */ > + const char *feature_name > + = SSDATA (SYMBOL_NAME (feature_sym)); > + hb_tag_t tag = hb_tag_from_string (feature_name, -1); > > We will need to DEF_DLL_FN and LOAD_DLL_FN for hb_tag_from_string, for > this to work on Windows. But you can leave this to me, if you want. I don't have access to a Windows machine, it would be great if you can help me with that. > + /* Cache features array to store enabled font features. */ > + static hb_feature_t *hb_features; > + static ptrdiff_t hb_features_size; > + unsigned int num_features = 0; > + if (!NILP (font_features)) > + num_features = hb_features_from_lisp (font_features, &hb_features, > > + &hb_features_size); > + hb_bool_t success > + = hb_shape_full (hb_font, hb_buffer, > > + num_features == 0 ? NULL : hb_features, > + num_features, NULL); > > Hmm... not sure I understand what kind of caching is being used here. > AFAIU, hb_features are recomputed anew each time we call hbfont_shape, > so how does this caching help? I saw the comment a few lines below for the hb_buffer (static hb_buffer_t *hb_buffer = NULL;) to have less allocation and I followed that. It is true that the features are recomputed but the array can be reused in the next call. If new allocation is preferred I can change to that. > One more comment: did you try this code with text that we normally > don't pass to the shaping engine to display, like plain-ASCII text? > Emacs normally calls the shaping engine only for characters whose > slots in composition-function-table are non-nil. Otherwise we display > the font glyphs directly without shaping. (This is an optimization: > using the shaping engine slows down redisplay, because it is > implemented by calling to Lisp, which then calls back into C.) So, > for example, if someone places a face with :font-features attribute on > plain-ASCII text, they will probably not see any effect. > Yes you are right about it. I was confused why it did not have any effect initially. And I figured I needed to do something with composition-function-table. This is my test code #+begin_src elisp (set-char-table-range composition-function-table ?0 '(["." 0 font-shape-gstring])) (set-char-table-range composition-function-table ?! '(["\\(!==\\)" 0 font-shape-gstring])) #+end_src #+begin_src elisp (set-face-attribute 'default nil :font "JetBrains Mono" :height 100 :font-features '((zero . 0) (ss19 . 1) (zero . 1))) #+end_src #+begin_src elisp (set-face-attribute 'default nil :font "JetBrains Mono" :height 100 :font-features '((zero . 1))) #+end_src > If I'm right, then we will need to make changes in display-engine > functions like composition_compute_stop_pos, composition_reseat_it, > find_composition, and others, to force the text which has such a face > attribute to be handed to HarfBuzz for shaping. An alternative is to > require that use of this face attribute needs special setup of > composition-function-table, but that is IMO worse because it will slow > down display of the relevant characters even if they don't have the > face with this attribute. Thanks for pointing that out, I can explore that part of the code. I have not looked much into composite.c