diff --git a/CHANGELOG.md b/CHANGELOG.md index 8e2283d7..76c79a2b 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -41,6 +41,10 @@ * Terminal content is now auto-scrolled when moving the mouse above or below the window while selecting (https://codeberg.org/dnkl/foot/issues/149). +* **font-bold**, **font-italic** and **font-bold-italic** options to + `foot.ini`. These options allow custom bold/italic fonts. They are + unset by default, meaning the bold/italic version of the regular + font is used (https://codeberg.org/dnkl/foot/issues/169). ### Changed diff --git a/config.c b/config.c index bc7e7426..edca071b 100644 --- a/config.c +++ b/config.c @@ -547,14 +547,28 @@ parse_section_main(const char *key, const char *value, struct config *conf, } } - else if (strcmp(key, "font") == 0) { + else if (strcmp(key, "font") == 0 || + strcmp(key, "font-bold") == 0 || + strcmp(key, "font-italic") == 0 || + strcmp(key, "font-bold-italic") == 0) + + { + size_t idx = + strcmp(key, "font") == 0 ? 0 : + strcmp(key, "font-bold") == 0 ? 1 : + strcmp(key, "font-italic") == 0 ? 2 : 3; + + tll_foreach(conf->fonts[idx], it) + config_font_destroy(&it->item); + tll_free(conf->fonts[idx]); + char *copy = xstrdup(value); for (const char *font = strtok(copy, ","); font != NULL; font = strtok(NULL, ",")) { /* Trim spaces, strictly speaking not necessary, but looks nice :) */ while (*font != '\0' && isspace(*font)) font++; if (*font != '\0') - tll_push_back(conf->fonts, config_font_parse(font)); + tll_push_back(conf->fonts[idx], config_font_parse(font)); } free(copy); } @@ -1904,7 +1918,7 @@ config_load(struct config *conf, const char *conf_path, .pad_y = 2, .bell_is_urgent = false, .startup_mode = STARTUP_WINDOWED, - .fonts = tll_init(), + .fonts = {tll_init(), tll_init(), tll_init(), tll_init()}, .scrollback = { .lines = 1000, .indicator = { @@ -2028,8 +2042,8 @@ config_load(struct config *conf, const char *conf_path, conf->colors.selection_bg >> 24 == 0; out: - if (ret && tll_length(conf->fonts) == 0) - tll_push_back(conf->fonts, config_font_parse("monospace")); + if (ret && tll_length(conf->fonts[0]) == 0) + tll_push_back(conf->fonts[0], config_font_parse("monospace")); free(conf_file.path); if (conf_file.fd >= 0) @@ -2047,9 +2061,11 @@ config_free(struct config conf) free(conf.app_id); free(conf.word_delimiters); free(conf.scrollback.indicator.text); - tll_foreach(conf.fonts, it) - config_font_destroy(&it->item); - tll_free(conf.fonts); + for (size_t i = 0; i < ALEN(conf.fonts); i++) { + tll_foreach(conf.fonts[i], it) + config_font_destroy(&it->item); + tll_free(conf.fonts[i]); + } free(conf.server_socket_path); tll_foreach(conf.bindings.key, it) { diff --git a/config.h b/config.h index d9a6f901..7313f42f 100644 --- a/config.h +++ b/config.h @@ -16,6 +16,7 @@ struct config_font { double pt_size; int px_size; }; +typedef tll(struct config_font) config_font_list_t; struct config_key_modifiers { bool shift; @@ -77,7 +78,7 @@ struct config { enum { STARTUP_WINDOWED, STARTUP_MAXIMIZED, STARTUP_FULLSCREEN } startup_mode; - tll(struct config_font) fonts; + config_font_list_t fonts[4]; struct { int lines; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 371dd7e0..979d2894 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -20,20 +20,27 @@ in this order: # SECTION: default -*font* +*font*, *font-bold*, *font-italic*, *font-bold-italic* Comma separated list of fonts to use, in fontconfig format (see *FONT FORMAT*). - The first font is the primary font. The remaining fonts are - fallback fonts that will be used whenever a glyph cannot be found - in the primary font. + For each option, the first font is the primary font. The remaining + fonts are fallback fonts that will be used whenever a glyph cannot + be found in the primary font. The fallback fonts are searched in the order they appear. If a glyph cannot be found in any of the fallback fonts, the dynamic fallback list from fontconfig (for the primary font) is searched. - Default: _monospace_. + *font-bold*, *font-italic* and *font-bold-italic* allow custom + fonts to be used for bold/italic/bold+italic fonts. If left + unconfigured, the bold/italic variants of the regular font(s) + specified in *font* are used. *Note*: you _may_ have to tweak the + size(s) of the custom bold/italic fonts to match the regular font. + + Default: _monospace_ (*font*), _not set_ (*font-bold*, + *font-italic*, *font-bold-italic*). *pad* Padding between border and glyphs, in pixels (subject to output diff --git a/foot.ini b/foot.ini index 8e78d7e7..2a5fed73 100644 --- a/foot.ini +++ b/foot.ini @@ -1,6 +1,9 @@ # -*- conf -*- # font=monospace +# font-bold= +# font-italic= +# font-bold-italic= # initial-window-size-pixels=700x500 # Or, # initial-window-size-chars= # initial-window-mode=windowed diff --git a/main.c b/main.c index 08c3b680..f34b4a5c 100644 --- a/main.c +++ b/main.c @@ -27,6 +27,7 @@ #include "server.h" #include "shm.h" #include "terminal.h" +#include "util.h" #include "version.h" #include "xmalloc.h" @@ -387,11 +388,13 @@ main(int argc, char *const *argv) if (login_shell) conf.login_shell = true; if (tll_length(conf_fonts) > 0) { - tll_foreach(conf.fonts, it) - config_font_destroy(&it->item); - tll_free(conf.fonts); + for (size_t i = 0; i < ALEN(conf.fonts); i++) { + tll_foreach(conf.fonts[i], it) + config_font_destroy(&it->item); + tll_free(conf.fonts[i]); + } tll_foreach(conf_fonts, it) - tll_push_back(conf.fonts, config_font_parse(it->item)); + tll_push_back(conf.fonts[0], config_font_parse(it->item)); tll_free(conf_fonts); } if (conf_width > 0 && conf_height > 0) { diff --git a/terminal.c b/terminal.c index c82ca4fc..303a4015 100644 --- a/terminal.c +++ b/terminal.c @@ -764,39 +764,77 @@ font_loader_thread(void *_data) static bool reload_fonts(struct terminal *term) { - const size_t count = tll_length(term->conf->fonts); - char *names[count]; + const size_t counts[4] = { + tll_length(term->conf->fonts[0]), + tll_length(term->conf->fonts[1]), + tll_length(term->conf->fonts[2]), + tll_length(term->conf->fonts[3]), + }; - size_t i = 0; - tll_foreach(term->conf->fonts, it) { - bool use_px_size = term->font_sizes[i].px_size > 0; - char size[64]; + /* Configure size (which may have been changed run-time) */ + char **names[4]; + for (size_t i = 0; i < 4; i++) { + names[i] = xmalloc(counts[i] * sizeof(names[i][0])); - if (use_px_size) - snprintf(size, sizeof(size), ":pixelsize=%d", term->font_sizes[i].px_size); - else - snprintf(size, sizeof(size), ":size=%.2f", term->font_sizes[i].pt_size); + size_t j = 0; + tll_foreach(term->conf->fonts[i], it) { + bool use_px_size = term->font_sizes[i][j].px_size > 0; + char size[64]; - size_t len = strlen(it->item.pattern) + strlen(size) + 1; - names[i] = xmalloc(len); + if (use_px_size) + snprintf(size, sizeof(size), ":pixelsize=%d", term->font_sizes[i][j].px_size); + else + snprintf(size, sizeof(size), ":size=%.2f", term->font_sizes[i][j].pt_size); - strcpy(names[i], it->item.pattern); - strcat(names[i], size); - i++; + size_t len = strlen(it->item.pattern) + strlen(size) + 1; + names[i][j] = xmalloc(len); + + strcpy(names[i][j], it->item.pattern); + strcat(names[i][j], size); + j++; + } } - char attrs0[256], attrs1[256], attrs2[256], attrs3[256]; - snprintf(attrs0, sizeof(attrs0), "dpi=%.2f", term->font_dpi); - snprintf(attrs1, sizeof(attrs1), "dpi=%.2f:weight=bold", term->font_dpi); - snprintf(attrs2, sizeof(attrs2), "dpi=%.2f:slant=italic", term->font_dpi); - snprintf(attrs3, sizeof(attrs3), "dpi=%.2f:weight=bold:slant=italic", term->font_dpi); + /* Did user configure custom bold/italic fonts? + * Or should we use the regular font, with weight/slant attributes? */ + const bool custom_bold = counts[1] > 0; + const bool custom_italic = counts[2] > 0; + const bool custom_bold_italic = counts[3] > 0; + + const size_t count_regular = counts[0]; + const char **names_regular = (const char **)names[0]; + + const size_t count_bold = custom_bold ? counts[1] : counts[0]; + const char **names_bold = (const char **)(custom_bold ? names[1] : names[0]); + + const size_t count_italic = custom_italic ? counts[2] : counts[0]; + const char **names_italic = (const char **)(custom_italic ? names[2] : names[0]); + + const size_t count_bold_italic = custom_bold_italic ? counts[3] : counts[0]; + const char **names_bold_italic = (const char **)(custom_bold_italic ? names[3] : names[0]); + + char *attrs[4] = {NULL}; + int attr_len[4] = {-1, -1, -1, -1}; /* -1, so that +1 (below) results in 0 */ + + for (size_t i = 0; i < 2; i++) { + attr_len[0] = snprintf(attrs[0], attr_len[0] + 1, "dpi=%.2f", term->font_dpi); + attr_len[1] = snprintf(attrs[1], attr_len[1] + 1, "dpi=%.2f:%s", term->font_dpi, !custom_bold ? "weight=bold" : ""); + attr_len[2] = snprintf(attrs[2], attr_len[2] + 1, "dpi=%.2f:%s", term->font_dpi, !custom_italic ? "slant=italic" : ""); + attr_len[3] = snprintf(attrs[3], attr_len[3] + 1, "dpi=%.2f:%s", term->font_dpi, !custom_bold_italic ? "weight=bold:slant=italic" : ""); + + if (i > 0) + continue; + + for (size_t i = 0; i < 4; i++) + attrs[i] = xmalloc(attr_len[i] + 1); + } struct fcft_font *fonts[4]; struct font_load_data data[4] = { - {count, (const char **)names, attrs0, &fonts[0]}, - {count, (const char **)names, attrs1, &fonts[1]}, - {count, (const char **)names, attrs2, &fonts[2]}, - {count, (const char **)names, attrs3, &fonts[3]}, + {count_regular, names_regular, attrs[0], &fonts[0]}, + {count_bold, names_bold, attrs[1], &fonts[1]}, + {count_italic, names_italic, attrs[2], &fonts[2]}, + {count_bold_italic, names_bold_italic, attrs[3], &fonts[3]}, }; thrd_t tids[4] = {0}; @@ -819,6 +857,13 @@ reload_fonts(struct terminal *term) success = false; } + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < counts[i]; j++) + free(names[i][j]); + free(names[i]); + free(attrs[i]); + } + if (!success) { LOG_ERR("failed to load primary fonts"); for (size_t i = 0; i < 4; i++) { @@ -827,19 +872,18 @@ reload_fonts(struct terminal *term) } } - for (size_t i = 0; i < count; i++) - free(names[i]); - return success ? term_set_fonts(term, fonts) : success; } static bool load_fonts_from_conf(struct terminal *term) { - size_t i = 0; - tll_foreach(term->conf->fonts, it) { - term->font_sizes[i++] = (struct config_font){ - .pt_size = it->item.pt_size, .px_size = it->item.px_size}; + for (size_t i = 0; i < 4; i++) { + size_t j = 0; + tll_foreach(term->conf->fonts[i], it) { + term->font_sizes[i][j++] = (struct config_font){ + .pt_size = it->item.pt_size, .px_size = it->item.px_size}; + } } return reload_fonts(term); @@ -922,7 +966,12 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .ptmx = ptmx, .ptmx_buffers = tll_init(), .ptmx_paste_buffers = tll_init(), - .font_sizes = xmalloc(sizeof(term->font_sizes[0]) * tll_length(conf->fonts)), + .font_sizes = { + xmalloc(sizeof(term->font_sizes[0][0]) * tll_length(conf->fonts[0])), + xmalloc(sizeof(term->font_sizes[1][0]) * tll_length(conf->fonts[1])), + xmalloc(sizeof(term->font_sizes[2][0]) * tll_length(conf->fonts[2])), + xmalloc(sizeof(term->font_sizes[3][0]) * tll_length(conf->fonts[3])), + }, .font_dpi = 0., .font_subpixel = (conf->colors.alpha == 0xffff /* Can't do subpixel rendering on transparent background */ ? FCFT_SUBPIXEL_DEFAULT @@ -1025,10 +1074,10 @@ term_init(const struct config *conf, struct fdm *fdm, struct reaper *reaper, .cwd = xstrdup(cwd), }; - { - size_t i = 0; - tll_foreach(conf->fonts, it) { - term->font_sizes[i++] = (struct config_font){ + for (size_t i = 0; i < 4; i++) { + size_t j = 0; + tll_foreach(conf->fonts[i], it) { + term->font_sizes[i][j++] = (struct config_font){ .pt_size = it->item.pt_size, .px_size = it->item.px_size}; } } @@ -1277,7 +1326,8 @@ term_destroy(struct terminal *term) for (size_t i = 0; i < sizeof(term->fonts) / sizeof(term->fonts[0]); i++) fcft_destroy(term->fonts[i]); - free(term->font_sizes); + for (size_t i = 0; i < 4; i++) + free(term->font_sizes[i]); free(term->search.buf); @@ -1518,22 +1568,24 @@ term_reset(struct terminal *term, bool hard) static bool term_font_size_adjust(struct terminal *term, double amount) { - for (size_t i = 0; i < tll_length(term->conf->fonts); i++) { - double old_pt_size = term->font_sizes[i].pt_size; + for (size_t i = 0; i < 4; i++) { + for (size_t j = 0; j < tll_length(term->conf->fonts[i]); j++) { + double old_pt_size = term->font_sizes[i][j].pt_size; - /* - * To ensure primary and user-configured fallback fonts are - * resizes by the same amount, convert pixel sizes to point - * sizes, and to the adjustment on point sizes only. - */ + /* + * To ensure primary and user-configured fallback fonts are + * resizes by the same amount, convert pixel sizes to point + * sizes, and to the adjustment on point sizes only. + */ - if (term->font_sizes[i].px_size > 0) { - double dpi = term->font_dpi; - old_pt_size = term->font_sizes[i].px_size * 72. / dpi; + if (term->font_sizes[i][j].px_size > 0) { + double dpi = term->font_dpi; + old_pt_size = term->font_sizes[i][j].px_size * 72. / dpi; + } + + term->font_sizes[i][j].pt_size = fmax(old_pt_size + amount, 0); + term->font_sizes[i][j].px_size = -1; } - - term->font_sizes[i].pt_size = fmax(old_pt_size + amount, 0); - term->font_sizes[i].px_size = -1; } return reload_fonts(term); diff --git a/terminal.h b/terminal.h index 503b5862..6a8e340b 100644 --- a/terminal.h +++ b/terminal.h @@ -225,7 +225,7 @@ struct terminal { struct composed *composed; struct fcft_font *fonts[4]; - struct config_font *font_sizes; + struct config_font *font_sizes[4]; float font_dpi; enum fcft_subpixel font_subpixel;