diff --git a/CHANGELOG.md b/CHANGELOG.md index c5d23a39..cf5ec6bb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,10 @@ top/bottom side (https://codeberg.org/dnkl/foot/issues/273). * Completions for fish shell (https://codeberg.org/dnkl/foot/issues/11) +* `line-height`, `letter-spacing`, `horizontal-letter-offset` and + `vertical-letter-offset` to `foot.ini`. These options let you tweak + cell size and glyph positioning + (https://codeberg.org/dnkl/foot/issues/244). ### Changed diff --git a/box-drawing.c b/box-drawing.c index 8640fc4c..7ec7be09 100644 --- a/box-drawing.c +++ b/box-drawing.c @@ -6,6 +6,7 @@ #define LOG_MODULE "box-drawing" #define LOG_ENABLE_DBG 0 #include "log.h" +#include "config.h" #include "macros.h" #include "stride.h" #include "terminal.h" @@ -2068,8 +2069,8 @@ box_drawing(const struct terminal *term, wchar_t wc) .wc = wc, .cols = 1, .pix = pix, - .x = 0, - .y = term->fonts[0]->ascent, + .x = -term->font_x_ofs, + .y = term->font_y_ofs + term->fonts[0]->ascent, .width = width, .height = height, .advance = { diff --git a/config.c b/config.c index 0e106584..51615389 100644 --- a/config.c +++ b/config.c @@ -387,18 +387,21 @@ str_to_double(const char *s, double *res) } static bool -str_to_color(const char *s, uint32_t *color, bool allow_alpha, const char *path, int lineno, +str_to_color(const char *s, uint32_t *color, bool allow_alpha, + struct config *conf, const char *path, int lineno, const char *section, const char *key) { unsigned long value; if (!str_to_ulong(s, 16, &value)) { - LOG_ERRNO("%s:%d: [%s]: %s: invalid color: %s", path, lineno, section, key, s); + LOG_AND_NOTIFY_ERRNO( + "%s:%d: [%s]: %s: invalid color: %s", path, lineno, section, key, s); return false; } if (!allow_alpha && (value & 0xff000000) != 0) { - LOG_ERR("%s:%d: [%s]: %s: color value must not have an alpha component: %s", - path, lineno, section, key, s); + LOG_AND_NOTIFY_ERR( + "%s:%d: [%s]: %s: color value must not have an alpha component: %s", + path, lineno, section, key, s); return false; } @@ -406,6 +409,40 @@ str_to_color(const char *s, uint32_t *color, bool allow_alpha, const char *path, return true; } +static bool +str_to_pt_or_px(const char *s, union pt_or_px *res, struct config *conf, + const char *path, int lineno, const char *section, const char *key) +{ + size_t len = s != NULL ? strlen(s) : 0; + if (len >= 2 && s[len - 2] == 'p' && s[len - 1] == 'x') { + errno = 0; + char *end = NULL; + + long value = strtol(s, &end, 10); + if (!(errno == 0 && end == s + len - 2)) { + LOG_AND_NOTIFY_ERR( + "%s:%d: [%s]: %s: " + "expected an integer directly followed by 'px', got '%s'", + path, lineno, section, key, s); + return false; + } + res->pt = 0; + res->px = value; + } else { + double value; + if (!str_to_double(s, &value)) { + LOG_AND_NOTIFY_ERR( + "%s:%d: [%s]: %s: expected a decimal value, got '%s'", + path, lineno, section, key, s); + return false; + } + res->pt = value; + res->px = 0; + } + + return true; +} + static bool parse_section_main(const char *key, const char *value, struct config *conf, const char *path, unsigned lineno) @@ -555,6 +592,32 @@ parse_section_main(const char *key, const char *value, struct config *conf, free(copy); } + else if (strcmp(key, "line-height") == 0) { + if (!str_to_pt_or_px(value, &conf->line_height, + conf, path, lineno, "default", "line-height")) + return false; + } + + else if (strcmp(key, "letter-spacing") == 0) { + if (!str_to_pt_or_px(value, &conf->letter_spacing, + conf, path, lineno, "default", "letter-spacing")) + return false; + } + + else if (strcmp(key, "horizontal-letter-offset") == 0) { + if (!str_to_pt_or_px( + value, &conf->horizontal_letter_offset, + conf, path, lineno, "default", "horizontal-letter-offset")) + return false; + } + + else if (strcmp(key, "vertical-letter-offset") == 0) { + if (!str_to_pt_or_px( + value, &conf->horizontal_letter_offset, + conf, path, lineno, "default", "vertical-letter-offset")) + return false; + } + else if (strcmp(key, "dpi-aware") == 0) { if (strcmp(value, "auto") == 0) conf->dpi_aware = DPI_AWARE_AUTO; @@ -732,7 +795,7 @@ parse_section_colors(const char *key, const char *value, struct config *conf, } uint32_t color_value; - if (!str_to_color(value, &color_value, false, path, lineno, "colors", key)) + if (!str_to_color(value, &color_value, false, conf, path, lineno, "colors", key)) return false; *color = color_value; @@ -767,8 +830,8 @@ parse_section_cursor(const char *key, const char *value, struct config *conf, uint32_t text_color, cursor_color; if (text == NULL || cursor == NULL || - !str_to_color(text, &text_color, false, path, lineno, "cursor", "color") || - !str_to_color(cursor, &cursor_color, false, path, lineno, "cursor", "color")) + !str_to_color(text, &text_color, false, conf, path, lineno, "cursor", "color") || + !str_to_color(cursor, &cursor_color, false, conf, path, lineno, "cursor", "color")) { LOG_AND_NOTIFY_ERR("%s:%d: invalid cursor colors: %s", path, lineno, value); free(value_copy); @@ -827,7 +890,7 @@ parse_section_csd(const char *key, const char *value, struct config *conf, else if (strcmp(key, "color") == 0) { uint32_t color; - if (!str_to_color(value, &color, true, path, lineno, "csd", "color")) { + if (!str_to_color(value, &color, true, conf, path, lineno, "csd", "color")) { LOG_AND_NOTIFY_ERR("%s:%d: invalid titlebar-color: %s", path, lineno, value); return false; } @@ -858,7 +921,7 @@ parse_section_csd(const char *key, const char *value, struct config *conf, else if (strcmp(key, "button-minimize-color") == 0) { uint32_t color; - if (!str_to_color(value, &color, true, path, lineno, "csd", "button-minimize-color")) { + if (!str_to_color(value, &color, true, conf, path, lineno, "csd", "button-minimize-color")) { LOG_AND_NOTIFY_ERR("%s:%d: invalid button-minimize-color: %s", path, lineno, value); return false; } @@ -869,7 +932,7 @@ parse_section_csd(const char *key, const char *value, struct config *conf, else if (strcmp(key, "button-maximize-color") == 0) { uint32_t color; - if (!str_to_color(value, &color, true, path, lineno, "csd", "button-maximize-color")) { + if (!str_to_color(value, &color, true, conf, path, lineno, "csd", "button-maximize-color")) { LOG_AND_NOTIFY_ERR("%s:%d: invalid button-maximize-color: %s", path, lineno, value); return false; } @@ -880,7 +943,7 @@ parse_section_csd(const char *key, const char *value, struct config *conf, else if (strcmp(key, "button-close-color") == 0) { uint32_t color; - if (!str_to_color(value, &color, true, path, lineno, "csd", "button-close-color")) { + if (!str_to_color(value, &color, true, conf, path, lineno, "csd", "button-close-color")) { LOG_AND_NOTIFY_ERR("%s:%d: invalid button-close-color: %s", path, lineno, value); return false; } @@ -1979,6 +2042,10 @@ config_load(struct config *conf, const char *conf_path, .bell_action = BELL_ACTION_NONE, .startup_mode = STARTUP_WINDOWED, .fonts = {tll_init(), tll_init(), tll_init(), tll_init()}, + .line_height = { .pt = 0, .px = -1, }, + .letter_spacing = { .pt = 0, .px = 0, }, + .horizontal_letter_offset = {.pt = 0, .px = 0, }, + .vertical_letter_offset = {.pt = 0, .px = 0, }, .dpi_aware = DPI_AWARE_AUTO, /* DPI-aware when scaling-factor == 1 */ .scrollback = { .lines = 1000, diff --git a/config.h b/config.h index 21b79230..18b0e0c0 100644 --- a/config.h +++ b/config.h @@ -54,6 +54,12 @@ struct config_mouse_binding { } pipe; }; +/* If px != 0 then px is valid, otherwise pt is valid */ +union pt_or_px { + int16_t px; + float pt; +}; + struct config { char *term; char *shell; @@ -84,6 +90,14 @@ struct config { enum {DPI_AWARE_AUTO, DPI_AWARE_YES, DPI_AWARE_NO} dpi_aware; config_font_list_t fonts[4]; + /* Custom font metrics (-1 = use real font metrics) */ + union pt_or_px line_height; + union pt_or_px letter_spacing; + + /* Adjusted letter x/y offsets */ + union pt_or_px horizontal_letter_offset; + union pt_or_px vertical_letter_offset; + struct { int lines; diff --git a/doc/foot.ini.5.scd b/doc/foot.ini.5.scd index 4bf40af2..4a45400a 100644 --- a/doc/foot.ini.5.scd +++ b/doc/foot.ini.5.scd @@ -18,6 +18,19 @@ in this order: # SECTION: default +*shell* + Executable to launch. Typically a shell. Default: _$SHELL_ if set, + otherwise the user's default shell (as specified in + _/etc/passwd_). You can also pass arguments. For example + */bin/bash --norc*. + +*login-shell* + Boolean. If enabled, the shell will be launched as a login shell, + by prepending a '-' to argv[0]. Default: _no_. + +*term* + Value to set the environment variable *TERM* to. Default: _foot_. + *font*, *font-bold*, *font-italic*, *font-bold-italic* Comma separated list of fonts to use, in fontconfig format. That @@ -51,6 +64,38 @@ in this order: Default: _monospace:size=8_ (*font*), _not set_ (*font-bold*, *font-italic*, *font-bold-italic*). +*line-height* + An absolute value, in _points_, that override line height from the + font metrics. + + You can specify a height in _pixels_ by using the _px_ suffix: + e.g. *line-height=12px*. + + See also: *vertical-letter-offset*. + + Default: _no set_. + +*letter-spacing* + Spacing between letters, in _points_. A positive value will + increase the cell size, and a negative value shrinks it. + + You can specify a letter spacing in _pixels_ by using the _px_ + suffix: e.g. *letter-spacing=2px*. + + See also: *horizontal-letter-offset*. + + Default: _0_. + +*horizontal-letter-offset*, *vertical-letter-offset* + Configure the horizontal and vertical offsets used when + positioning glyphs within cells, in _points_, relative to the top + left corner. + + To specify an offset in _pixels_, append _px_: + e.g. *horizontal-letter-offset=2px*. + + Default: _0_. + *dpi-aware* *auto*, *yes*, or *no*. When set to *yes*, fonts are sized using the monitor's DPI, making a font of a given size have the same @@ -111,19 +156,6 @@ in this order: *geometry* Deprecated. Alias for *initial-window-size-pixels*. -*shell* - Executable to launch. Typically a shell. Default: _$SHELL_ if set, - otherwise the user's default shell (as specified in - _/etc/passwd_). You can also pass arguments. For example - */bin/bash --norc*. - -*login-shell* - Boolean. If enabled, the shell will be launched as a login shell, - by prepending a '-' to argv[0]. Default: _no_. - -*term* - Value to set the environment variable *TERM* to. Default: _foot_. - *title* Initial window title. Default: _foot_. diff --git a/foot.ini b/foot.ini index 76491372..f13d8e63 100644 --- a/foot.ini +++ b/foot.ini @@ -1,22 +1,29 @@ # -*- conf -*- +# shell=$SHELL (if set, otherwise user's default shell from /etc/passwd) +# term=foot +# login-shell=no + # font=monospace:size=8 # font-bold= # font-italic= # font-bold-italic= +# line-height= +# letter-spacing=0 +# horizontal-letter-offset=0 +# vertical-letter-offset=0 # dpi-aware=yes + # initial-window-size-pixels=700x500 # Or, # initial-window-size-chars= # initial-window-mode=windowed # pad=2x2 # optionally append 'center' -# shell=$SHELL (if set, otherwise user's default shell from /etc/passwd) -# term=foot -# login-shell=no -# workers= + # bold-text-in-bright=no # bell=none # word-delimiters=,│`|:"'()[]{}<> # notify=notify-send -a foot -i foot ${title} ${body} +# workers= [scrollback] # lines=1000 diff --git a/render.c b/render.c index 16198589..51341365 100644 --- a/render.c +++ b/render.c @@ -256,7 +256,7 @@ color_dim_for_search(pixman_color_t *color) static inline int font_baseline(const struct terminal *term) { - return term->fonts[0]->ascent; + return term->font_y_ofs + term->fonts[0]->ascent; } static void @@ -541,18 +541,20 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_image_t *clr_pix = pixman_image_create_solid_fill(&fg); if (glyph != NULL) { + const int letter_x_ofs = term->font_x_ofs; + if (unlikely(pixman_image_get_format(glyph->pix) == PIXMAN_a8r8g8b8)) { /* Glyph surface is a pre-rendered image (typically a color emoji...) */ if (!(cell->attrs.blink && term->blink.state == BLINK_OFF)) { pixman_image_composite32( PIXMAN_OP_OVER, glyph->pix, NULL, pix, 0, 0, 0, 0, - x + glyph->x, y + font_baseline(term) - glyph->y, + x + letter_x_ofs + glyph->x, y + font_baseline(term) - glyph->y, glyph->width, glyph->height); } } else { pixman_image_composite32( PIXMAN_OP_OVER, clr_pix, glyph->pix, pix, 0, 0, 0, 0, - x + glyph->x, y + font_baseline(term) - glyph->y, + x + letter_x_ofs + glyph->x, y + font_baseline(term) - glyph->y, glyph->width, glyph->height); } @@ -589,7 +591,7 @@ render_cell(struct terminal *term, pixman_image_t *pix, pixman_image_composite32( PIXMAN_OP_OVER, clr_pix, g->pix, pix, 0, 0, 0, 0, - x + x_ofs + g->x, y + font_baseline(term) - g->y, + x + letter_x_ofs + x_ofs + g->x, y + font_baseline(term) - g->y, g->width, g->height); } } @@ -1691,6 +1693,8 @@ render_osd(struct terminal *term, struct fcft_font *font = term->fonts[0]; pixman_color_t fg = color_hex_to_pixman(_fg); + const int x_ofs = term->font_x_ofs; + for (size_t i = 0; i < wcslen(text); i++) { const struct fcft_glyph *glyph = fcft_glyph_rasterize( font, text[i], term->font_subpixel); @@ -1701,7 +1705,7 @@ render_osd(struct terminal *term, pixman_image_t *src = pixman_image_create_solid_fill(&fg); pixman_image_composite32( PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0, - x + glyph->x, y + font_baseline(term) - glyph->y, + x + x_ofs + glyph->x, y + font_baseline(term) - glyph->y, glyph->width, glyph->height); pixman_image_unref(src); @@ -2268,6 +2272,7 @@ render_search_box(struct terminal *term) struct fcft_font *font = term->fonts[0]; const int x_left = width - visible_width + margin; + const int x_ofs = term->font_x_ofs; int x = x_left; int y = margin; pixman_color_t fg = color_hex_to_pixman(term->colors.table[0]); @@ -2415,13 +2420,13 @@ render_search_box(struct terminal *term) /* Glyph surface is a pre-rendered image (typically a color emoji...) */ pixman_image_composite32( PIXMAN_OP_OVER, glyph->pix, NULL, buf->pix[0], 0, 0, 0, 0, - x + glyph->x, y + font_baseline(term) - glyph->y, + x + x_ofs + glyph->x, y + font_baseline(term) - glyph->y, glyph->width, glyph->height); } else { pixman_image_t *src = pixman_image_create_solid_fill(&fg); pixman_image_composite32( PIXMAN_OP_OVER, src, glyph->pix, buf->pix[0], 0, 0, 0, 0, - x + glyph->x, y + font_baseline(term) - glyph->y, + x + x_ofs + glyph->x, y + font_baseline(term) - glyph->y, glyph->width, glyph->height); pixman_image_unref(src); } diff --git a/terminal.c b/terminal.c index 6ba04677..a5537732 100644 --- a/terminal.c +++ b/terminal.c @@ -608,6 +608,15 @@ err_sem_destroy: return false; } +static int +pt_or_px_as_pixels(const struct terminal *term, + const union pt_or_px *pt_or_px) +{ + return pt_or_px->px == 0 + ? pt_or_px->pt * term->font_dpi / 72 + : pt_or_px->px; +} + static bool term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) { @@ -629,10 +638,22 @@ term_set_fonts(struct terminal *term, struct fcft_font *fonts[static 4]) const int old_cell_width = term->cell_width; const int old_cell_height = term->cell_height; - term->cell_width = term->fonts[0]->space_advance.x > 0 - ? term->fonts[0]->space_advance.x : term->fonts[0]->max_advance.x; - term->cell_height = max(term->fonts[0]->height, - term->fonts[0]->ascent + term->fonts[0]->descent); + const struct config *conf = term->conf; + + term->cell_width = + (term->fonts[0]->space_advance.x > 0 + ? term->fonts[0]->space_advance.x + : term->fonts[0]->max_advance.x) + + pt_or_px_as_pixels(term, &conf->letter_spacing); + + term->cell_height = conf->line_height.px >= 0 + ? pt_or_px_as_pixels(term, &conf->line_height) + : max(term->fonts[0]->height, + term->fonts[0]->ascent + term->fonts[0]->descent); + + term->font_x_ofs = pt_or_px_as_pixels(term, &conf->horizontal_letter_offset); + term->font_y_ofs = pt_or_px_as_pixels(term, &conf->vertical_letter_offset); + LOG_INFO("cell width=%d, height=%d", term->cell_width, term->cell_height); if (term->cell_width < old_cell_width || diff --git a/terminal.h b/terminal.h index 128694e7..19fff0e2 100644 --- a/terminal.h +++ b/terminal.h @@ -265,6 +265,8 @@ struct terminal { struct config_font *font_sizes[4]; float font_dpi; int font_scale; + int16_t font_x_ofs; + int16_t font_y_ofs; enum fcft_subpixel font_subpixel; /*