bakkeby / dmenu-flexipatch

A dmenu build with preprocessor directives to decide which patches to include during build time
MIT License
183 stars 78 forks source link

How do I make the font bold? #14

Open UtkarshVerma opened 2 years ago

UtkarshVerma commented 2 years ago

I am working on a patch which underlines matches instead of highlighting them. However, because I am not used to the Xft lib, I am stuck on how to make the font bold for matches.

Could you please help me with this?

bakkeby commented 2 years ago

The only thing you can do afaik is to use the bold version of the font.

The alternative would be to use the pango patch and the pango markup to add bold, italic and underline text.

UtkarshVerma commented 2 years ago

I would prefer to use the bold version of the font. Could you please show me a minimal example of how I would do so?

Also, how does st handle bold fonts, even though only one font family is defined in the config? I had a look at the source code and found this line, but adding this line to my patch didn't work by itself.

https://github.com/bakkeby/st-flexipatch/blob/master/x.c#L1319

bakkeby commented 2 years ago

Personally I'd consider this to be a challenging to hard task to achieve.

The st implementation would be one example of how it can be done. It may be more valuable to have an understanding of how fonts are handled in the various projects.

In dwm and dmenu the loading of fonts are handled by the drw_fontset_create and xfont_create functions in drw.c. The drw.c itself originates from a standalone library called libsl: https://git.suckless.org/libsl/files.html

In libsl the general handling of fonts is that you have a designated font and then you have fallback fonts for cases where glyphs are missing for the primary font. Fonts are loaded one by one and you end up with a linked list of fonts on the following form:

primary font > fallback font > fallback font > fallback font

In st, as it is a terminal, it only expects that a single font is used. You can have fallback fonts via the font2 patch, but that is a different topic. In st the loading of fonts is handled in the internal xloadfont + xloadfonts functions.

st will ask the Xft library to search for a font with a given font config pattern (FcPattern) based on the name you configured in the font variable in config.h. If you study the logic in the xloadfonts function then you may observe that the font found for the given font string becomes the primary font after which it continues to manipulate the pattern to look for the bold and italic versions of the same font.

Each of the fonts are stored in the drawing context as defined in st.h, see the variables font, bfont, ifont and ibfont (italic bold).

/* Drawing Context */
typedef struct {
    Color *col;
    size_t collen;
    Font font, bfont, ifont, ibfont;
    GC gc;
} DC;

As such each of the fonts are immediately accessible when rendering text and st will switch between them depending on shell escape codes.

To reproduce something like this in dmenu (or dwm for that matter) you would have to take into account how fonts are stored internally as a linked list. Presumably you'd want to have similar lists for bold, italic and italic bold. Alternative you could have a separate structure that holds the different fonts similarly to the drawing context above, then arrange those structures as a linked list.

In the end you will have to have some sort of markup to indicate that the text should be bold, and switch between fonts based on said markup. This is what the pango library handles for you.

If you are set on implementing something like this then you would be handling it all in drw.c and you should have a thorough understanding of how the drw_text function works with respect to switching between fonts as you would need to add your font switching logic in that function.

If the logic of that function is not immediately clear then I have some introductory comments here: https://github.com/bakkeby/dwm-commented/blob/master/drw.c#L609

UtkarshVerma commented 2 years ago

Thanks for the explanation. I'll try implementing this.

bakkeby commented 2 years ago

I was thinking that in the context of dwm this would be simpler to implement if you are already using the status2d patch, in which case it would just be a matter of adding new markup to switch between font sets of regular, bold, italic, and bold italic. That would just leave the loading of of the additional font sets - which would be relatively simple to handle.

As for dmenu I'm not sure how useful it would be to port the status2d patch.

bakkeby commented 1 month ago

I tried this example hack and it seems to do the trick.

diff --git a/config.def.h b/config.def.h
index a30957a..434a7f7 100644
--- a/config.def.h
+++ b/config.def.h
@@ -41,6 +41,11 @@ static const char *fonts[] =
        "monospace:size=10"
 };
 #endif // PANGO_PATCH
+
+static char *selfonts[] = {
+       "CaskaydiaMono Nerd Font Mono:size=14:style=Bold"
+};
+
 #if MANAGED_PATCH
 static char *prompt            = NULL;      /* -p  option; prompt to the left of input field */
 #else
diff --git a/dmenu.c b/dmenu.c
index 89680e0..7e9c6b3 100644
--- a/dmenu.c
+++ b/dmenu.c
@@ -100,6 +100,8 @@ struct item {
        #endif // PRINTINDEX_PATCH
 };

+Fnt *normal_fonts, *selected_fonts;
+
 static char text[BUFSIZ] = "";
 #if PIPEOUT_PATCH
 static char pipeout[8] = " | dmenu";
@@ -447,6 +448,12 @@ drawitem(struct item *item, int x, int y, int w)
        else
                drw_setscheme(drw, scheme[SchemeNorm]);

+       if (item == sel) {
+               drw->fonts = selected_fonts;
+       } else {
+               drw->fonts = normal_fonts;
+       }
+
        r = drw_text(drw
                #if EMOJI_HIGHLIGHT_PATCH
                , x + ((iscomment == 6) ? temppadding : 0)
@@ -2159,8 +2166,13 @@ main(int argc, char *argv[])
        if (!drw_font_create(drw, font))
                die("no fonts could be loaded.");
        #else
+       if (!drw_fontset_create(drw, (const char**)selfonts, LENGTH(selfonts)))
+               die("no selected fonts could be loaded.");
+       selected_fonts = drw->fonts;
+
        if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts)))
                die("no fonts could be loaded.");
+       normal_fonts = drw->fonts;
        #endif // PANGO_PATCH
        #else // !XRESOURCES_PATCH
        if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())

It will only apply for the selected item, i.e. if you ctrl+click to mark a client for output then that item will not be bold when you move away from it.

If that is desireable then one could do something like this:

@@ -447,6 +448,12 @@ drawitem(struct item *item, int x, int y, int w)
        else
                drw_setscheme(drw, scheme[SchemeNorm]);

+       if (item == sel) {
+               drw->fonts = selected_fonts;
+       } else if (item->out) {
+               drw->fonts = selected_fonts;
+       } else {
+               drw->fonts = normal_fonts;
+       }
+
        r = drw_text(drw
                #if EMOJI_HIGHLIGHT_PATCH
                , x + ((iscomment == 6) ? temppadding : 0)
@@ -2159,8 +2166,13 @@ main(int argc, char *argv[])

or alternatively even use a separate font set for output items.

diff --git a/config.def.h b/config.def.h
index a30957a..c3d8183 100644
--- a/config.def.h
+++ b/config.def.h
@@ -41,6 +41,15 @@ static const char *fonts[] =
        "monospace:size=10"
 };
 #endif // PANGO_PATCH
+
+static char *selfonts[] = {
+       "CaskaydiaMono Nerd Font Mono:size=14:style=Bold"
+};
+
+static char *outfonts[] = {
+       "CaskaydiaMono Nerd Font Mono:size=14:style=Italic"
+};
+
 #if MANAGED_PATCH
 static char *prompt            = NULL;      /* -p  option; prompt to the left of input field */
 #else
diff --git a/dmenu.c b/dmenu.c
index 89680e0..8964068 100644
--- a/dmenu.c
+++ b/dmenu.c
@@ -100,6 +100,8 @@ struct item {
        #endif // PRINTINDEX_PATCH
 };

+Fnt *normal_fonts, *selected_fonts, *output_fonts;
+
 static char text[BUFSIZ] = "";
 #if PIPEOUT_PATCH
 static char pipeout[8] = " | dmenu";
@@ -447,6 +448,14 @@ drawitem(struct item *item, int x, int y, int w)
        else
                drw_setscheme(drw, scheme[SchemeNorm]);

+       if (item == sel) {
+               drw->fonts = selected_fonts;
+       } else if (item->out) {
+               drw->fonts = output_fonts;
+       } else {
+               drw->fonts = normal_fonts;
+       }
+
        r = drw_text(drw
                #if EMOJI_HIGHLIGHT_PATCH
                , x + ((iscomment == 6) ? temppadding : 0)
@@ -2159,8 +2168,17 @@ main(int argc, char *argv[])
        if (!drw_font_create(drw, font))
                die("no fonts could be loaded.");
        #else
+       if (!drw_fontset_create(drw, (const char**)selfonts, LENGTH(selfonts)))
+               die("no selected fonts could be loaded.");
+       selected_fonts = drw->fonts;
+
+       if (!drw_fontset_create(drw, (const char**)outfonts, LENGTH(outfonts)))
+               die("no output fonts could be loaded.");
+       output_fonts = drw->fonts;
+
        if (!drw_fontset_create(drw, (const char**)fonts, LENGTH(fonts)))
                die("no fonts could be loaded.");
+       normal_fonts = drw->fonts;
        #endif // PANGO_PATCH
        #else // !XRESOURCES_PATCH
        if (!setlocale(LC_CTYPE, "") || !XSupportsLocale())