commit 0c6a1c7823c6ff0b616142bc58719f19013e97b1
parent d893c7ab3871c86248f97d8bb4b8ae3c42a5fa2f
Author: Paul Storkman <storkman@storkman.nl>
Date: Wed, 21 Jan 2026 13:50:02 +0100
[st][patches][fontmetrics] Add new patch
An alternative way to retrieve vertical font metrics.
Diffstat:
2 files changed, 174 insertions(+), 0 deletions(-)
diff --git a/st.suckless.org/patches/fontmetrics/index.md b/st.suckless.org/patches/fontmetrics/index.md
@@ -0,0 +1,26 @@
+fontmetrics
+===========
+
+Description
+-----------
+This patch uses the font metrics provided in a TrueType font's `OS/2` table
+(see [Microsoft](https://learn.microsoft.com/en-us/typography/opentype/spec/os2)
+or [Apple](https://developer.apple.com/fonts/TrueType-Reference-Manual/RM06/Chap6OS2.html) documentation),
+if available, to determine the character cell height and underline/strike-through thickness and position.
+This may or may not make the font rendering, particularly block and box-drawing elements, better or worse.
+
+`st` normally uses the descent and ascent heights provided by FreeType,
+which seems to derive them from `hhea` table values.
+
+There is no standard for how monotype font files are supposed to communicate correct metrics to terminal emulators,
+and actual usage is inconsistent between fonts.
+
+Download
+--------
+
+- [st-fontmetrics-0.9.3.diff](st-fontmetrics-0.9.3.diff) (0.9.3)
+
+Authors
+-------
+
+- Paul Storkman - <storkman@storkman.nl>
diff --git a/st.suckless.org/patches/fontmetrics/st-fontmetrics-0.9.3.diff b/st.suckless.org/patches/fontmetrics/st-fontmetrics-0.9.3.diff
@@ -0,0 +1,148 @@
+From d0d9715e5197a5b8af810508f48db001f54817c3 Mon Sep 17 00:00:00 2001
+From: Paul Storkman <storkman@storkman.nl>
+Date: Tue, 4 Nov 2025 17:41:50 +0100
+Subject: [PATCH] Use vertical font metrics from the "OS/2" table, if possible.
+
+---
+ config.def.h | 10 +++++++
+ x.c | 73 +++++++++++++++++++++++++++++++++++++++++++++++-----
+ 2 files changed, 77 insertions(+), 6 deletions(-)
+
+diff --git a/config.def.h b/config.def.h
+index 2cd740a..e959638 100644
+--- a/config.def.h
++++ b/config.def.h
+@@ -8,6 +8,16 @@
+ static char *font = "Liberation Mono:pixelsize=12:antialias=true:autohint=true";
+ static int borderpx = 2;
+
++/* "OS/2" OpenType tables give more accurate typographic metrics for some fonts,
++ * which may or may not improve rendering of box-drawing elements.
++ */
++static int ignoreOS2metrics = 0;
++
++/* Multiplier applied to the distance between baselines.
++ * Increasing this value can misalign block or line-drawing characters.
++ */
++static float linespacing = 1.0;
++
+ /*
+ * What program is execed by st depends of these precedence rules:
+ * 1: program passed with -e
+diff --git a/x.c b/x.c
+index d73152b..ae07d57 100644
+--- a/x.c
++++ b/x.c
+@@ -15,6 +15,8 @@
+ #include <X11/Xft/Xft.h>
+ #include <X11/XKBlib.h>
+
++#include <freetype/tttables.h>
++
+ char *argv0;
+ #include "arg.h"
+ #include "st.h"
+@@ -123,6 +125,10 @@ typedef struct {
+ int width;
+ int ascent;
+ int descent;
++ int strike_y;
++ int strike_h;
++ int underline_y;
++ int underline_h;
+ int badslant;
+ int badweight;
+ short lbearing;
+@@ -969,14 +975,67 @@ xloadfont(Font *f, FcPattern *pattern)
+ f->set = NULL;
+ f->pattern = configured;
+
+- f->ascent = f->match->ascent;
+- f->descent = f->match->descent;
++ f->ascent = f->match->ascent * linespacing;
++ f->descent = f->match->descent * linespacing;
+ f->lbearing = 0;
+ f->rbearing = f->match->max_advance_width;
+
+ f->height = f->ascent + f->descent;
+ f->width = DIVCEIL(extents.xOff, strlen(ascii_printable));
+
++ f->strike_y = -2 * f->match->ascent / 3;
++ f->strike_h = 1;
++ f->underline_y = 1;
++ f->underline_h = 1;
++
++ /* Extract proper typographic metrics if available. */
++ do {
++ TT_OS2 *os2;
++ TT_Postscript *post;
++ FT_Face face;
++ double px, uppx, asc, desc, lgap;
++ if (ignoreOS2metrics)
++ break;
++ if (FcPatternGetDouble(f->match->pattern,
++ FC_PIXEL_SIZE, 0, &px) != FcResultMatch)
++ break;
++
++ face = XftLockFace(f->match);
++ /* Rerturn value may be NULL. This is not documented. */
++ if (!face)
++ break;
++ os2 = FT_Get_Sfnt_Table(face, FT_SFNT_OS2);
++ if (!os2) {
++ XftUnlockFace(f->match);
++ break;
++ }
++ uppx = face->units_per_EM / px;
++ asc = os2->sTypoAscender / uppx;
++ desc = os2->sTypoDescender / uppx;
++ lgap = os2->sTypoLineGap / uppx;
++ lgap = (asc + desc + lgap) * linespacing - asc - desc;
++ post = FT_Get_Sfnt_Table(face, FT_SFNT_POST);
++ if (!post) {
++ f->strike_y = -2 * asc / 3;
++ } else {
++ f->strike_y = -os2->yStrikeoutPosition / uppx;
++ f->strike_h = os2->yStrikeoutSize / uppx;
++ if (f->strike_h == 0)
++ f->strike_h = 1;
++ f->underline_y = post->underlinePosition / uppx;
++ f->underline_h = post->underlineThickness / uppx;
++ if (f->underline_y == 0)
++ f->underline_y = 1;
++ if (f->underline_h == 0)
++ f->underline_h = f->strike_h;
++ }
++ XftUnlockFace(f->match);
++
++ f->ascent = asc + lgap/2;
++ f->descent = -desc + lgap/2 + asc - f->ascent;
++ f->height = f->ascent + f->descent;
++ } while (0);
++
+ return 0;
+ }
+
+@@ -1496,13 +1555,15 @@ xdrawglyphfontspecs(const XftGlyphFontSpec *specs, Glyph base, int len, int x, i
+
+ /* Render underline and strikethrough. */
+ if (base.mode & ATTR_UNDERLINE) {
+- XftDrawRect(xw.draw, fg, winx, winy + dc.font.ascent * chscale + 1,
+- width, 1);
++ XftDrawRect(xw.draw, fg, winx,
++ winy + (dc.font.ascent + dc.font.underline_y) * chscale,
++ width, dc.font.underline_h);
+ }
+
+ if (base.mode & ATTR_STRUCK) {
+- XftDrawRect(xw.draw, fg, winx, winy + 2 * dc.font.ascent * chscale / 3,
+- width, 1);
++ XftDrawRect(xw.draw, fg, winx,
++ winy + (dc.font.ascent + dc.font.strike_y) * chscale - dc.font.strike_h/2,
++ width, dc.font.strike_h);
+ }
+
+ /* Reset clip to none. */
+--
+2.49.1
+