patch 9.2.0321: MS-Windows: No OpenType font support

Problem:  MS-Windows: No OpenType font support
Solution: Allow specifying OpenType font features directly in 'guifont'
          (Yasuhiro Matsumoto).

Allow specifying OpenType font features directly in 'guifont' using
the ':f' option (e.g., :set guifont=Cascadia_Code:h14:fss19=1:fcalt=0).
Each ':fXXXX=N' sets a single OpenType feature tag with a parameter
value.  Multiple features can be specified by repeating the ':f' option.

This only takes effect when 'renderoptions' is set to use DirectWrite
(type:directx).  Default features (calt, liga, clig, rlig, kern) are
preserved unless explicitly overridden.

closes: #19857

Signed-off-by: Yasuhiro Matsumoto <mattn.jp@gmail.com>
Signed-off-by: Christian Brabandt <cb@256bit.org>
This commit is contained in:
Yasuhiro Matsumoto
2026-04-07 21:07:46 +00:00
committed by Christian Brabandt
parent ff41e9d853
commit ea7bf9aa8a
7 changed files with 170 additions and 4 deletions
+12 -1
View File
@@ -1,4 +1,4 @@
*gui.txt* For Vim version 9.2. Last change: 2026 Feb 14
*gui.txt* For Vim version 9.2. Last change: 2026 Apr 07
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1150,11 +1150,22 @@ For the Win32 GUI *E244* *E245*
NONANTIALIASED, CLEARTYPE and DEFAULT. Normally you would use
"qDEFAULT".
Some quality values are not supported in legacy OSs.
fXX - OpenType font feature. Specify a single feature as
tag=value, where tag is a 4-character OpenType feature
tag and value is the parameter (0 to disable, 1 or
higher to enable/select variant). Multiple features
can be specified by repeating the ":f" option.
This only takes effect when 'renderoptions' is set to use
DirectWrite (type:directx). Default features (calt, liga,
etc.) are preserved unless explicitly overridden.
Example: ":fss19=1:fcalt=0" enables Stylistic Set 19
and disables Contextual Alternates.
- A '_' can be used in the place of a space, so you don't need to use
backslashes to escape the spaces.
Examples: >
:set guifont=courier_new:h12:w5:b:cRUSSIAN
:set guifont=Andale_Mono:h7.5:w4.5
:set guifont=Cascadia_Code:h14:fss19=1:fcalt=1:fliga=1
See also |font-sizes|.
+5 -1
View File
@@ -52614,12 +52614,16 @@ Other ~
- |system()| and |systemlist()| functions accept a list as first argument,
bypassing the shell completely.
Platform specific ~
-----------------
- support OpenType font features in 'guifont' for DirectWrite (Win32)
xxd ~
---
Add "-t" option to append a terminating NUL byte to C include output (-i).
*changed-9.3*
Changed~
Changed ~
-------
- Support for NeXTStep was dropped with patch v9.2.0122
- |json_decode()| is stricter: keywords must be lowercase, lone surrogates are
+79 -1
View File
@@ -313,6 +313,9 @@ struct DWriteContext {
D2D1_TEXT_ANTIALIAS_MODE mTextAntialiasMode;
DWriteFontFeature mFontFeatures[DWRITE_MAX_FONT_FEATURES];
int mFontFeatureCount;
// METHODS
DWriteContext();
@@ -357,6 +360,8 @@ struct DWriteContext {
DWriteRenderingParams *GetRenderingParams(
DWriteRenderingParams *params);
void SetFontFeatures(const DWriteFontFeature *features, int count);
};
class AdjustedGlyphRun : public DWRITE_GLYPH_RUN
@@ -648,8 +653,10 @@ DWriteContext::DWriteContext() :
mFontStyle(DWRITE_FONT_STYLE_NORMAL),
mFontSize(0.0f),
mFontAscent(0.0f),
mTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT)
mTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT),
mFontFeatureCount(0)
{
ZeroMemory(mFontFeatures, sizeof(mFontFeatures));
HRESULT hr;
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
@@ -1086,6 +1093,56 @@ DWriteContext::DrawText(const WCHAR *text, int len,
textLayout->SetFontWeight(mFontWeight, textRange);
textLayout->SetFontStyle(mFontStyle, textRange);
if (mFontFeatureCount > 0)
{
// Default OpenType features that DirectWrite normally enables.
// SetTypography() overrides all defaults, so we must
// re-add them here explicitly.
static const DWRITE_FONT_FEATURE defaultFeatures[] = {
{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES, 1 },
{ DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES, 1 },
{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES, 1 },
{ DWRITE_FONT_FEATURE_TAG_REQUIRED_LIGATURES, 1 },
{ DWRITE_FONT_FEATURE_TAG_KERNING, 1 },
};
static const int numDefaults = sizeof(defaultFeatures)
/ sizeof(defaultFeatures[0]);
IDWriteTypography *typography = NULL;
hr = mDWriteFactory->CreateTypography(&typography);
if (SUCCEEDED(hr))
{
// Add default features, skipping any that the user
// has explicitly specified (either + or -).
for (int d = 0; d < numDefaults; ++d)
{
int overridden = 0;
for (int u = 0; u < mFontFeatureCount; ++u)
{
if ((DWRITE_FONT_FEATURE_TAG)mFontFeatures[u].tag
== defaultFeatures[d].nameTag)
{
overridden = 1;
break;
}
}
if (!overridden)
typography->AddFontFeature(defaultFeatures[d]);
}
// Add user-specified features.
for (int i = 0; i < mFontFeatureCount; ++i)
{
DWRITE_FONT_FEATURE ff = {
(DWRITE_FONT_FEATURE_TAG)mFontFeatures[i].tag,
mFontFeatures[i].parameter
};
typography->AddFontFeature(ff);
}
textLayout->SetTypography(typography, textRange);
SafeRelease(&typography);
}
}
// Calculate baseline using font ascent from font metrics.
// Do NOT use GetLineMetrics() because it returns different values
// depending on text content (e.g., when CJK characters trigger
@@ -1413,3 +1470,24 @@ DWriteContext_GetRenderingParams(
else
return NULL;
}
void
DWriteContext::SetFontFeatures(
const DWriteFontFeature *features, int count)
{
if (count > DWRITE_MAX_FONT_FEATURES)
count = DWRITE_MAX_FONT_FEATURES;
mFontFeatureCount = count;
if (count > 0 && features != NULL)
memcpy(mFontFeatures, features, sizeof(DWriteFontFeature) * count);
}
void
DWriteContext_SetFontFeatures(
DWriteContext *ctx,
const DWriteFontFeature *features,
int count)
{
if (ctx != NULL)
ctx->SetFontFeatures(features, count);
}
+12
View File
@@ -51,6 +51,13 @@ typedef struct DWriteRenderingParams {
int textAntialiasMode;
} DWriteRenderingParams;
#define DWRITE_MAX_FONT_FEATURES 32
typedef struct DWriteFontFeature {
unsigned int tag; // OpenType feature tag (4 bytes)
unsigned int parameter; // Feature parameter (0 = disable, 1 = enable)
} DWriteFontFeature;
void DWrite_Init(void);
void DWrite_Final(void);
@@ -86,6 +93,11 @@ DWriteRenderingParams *DWriteContext_GetRenderingParams(
DWriteContext *ctx,
DWriteRenderingParams *params);
void DWriteContext_SetFontFeatures(
DWriteContext *ctx,
const DWriteFontFeature *features,
int count);
#ifdef __cplusplus
}
#endif
+54 -1
View File
@@ -145,7 +145,6 @@ gui_mch_set_rendering_options(char_u *s)
int dx_geom = 0;
int dx_renmode = 0;
int dx_taamode = 0;
// parse string as rendering options.
for (p = s; p != NULL && *p != NUL; )
{
@@ -3956,6 +3955,60 @@ gui_mch_init_font(char_u *font_name, int fontset UNUSED)
if (font == NOFONT)
return FAIL;
#if defined(FEAT_DIRECTX)
// Parse font features from guifont (e.g., ":fss19=1:fcalt=0:fliga=1").
{
DWriteFontFeature features[DWRITE_MAX_FONT_FEATURES];
int feat_count = 0;
char_u *fp;
if (font_name != NULL)
{
// Find each ":f" option in font_name.
for (fp = font_name; *fp != NUL; fp++)
{
if (*fp == ':' && *(fp + 1) == 'f')
{
char_u tag[5];
int ti = 0;
unsigned int param = 1;
fp += 2; // skip ":f"
while (*fp != NUL && *fp != '=' && *fp != ':'
&& ti < 4)
tag[ti++] = *fp++;
tag[ti] = NUL;
if (ti != 4)
continue; // invalid tag length
if (*fp == '=')
{
fp++;
param = (unsigned int)atoi((char *)fp);
while (*fp >= '0' && *fp <= '9')
fp++;
}
if (feat_count < DWRITE_MAX_FONT_FEATURES)
{
features[feat_count].tag =
((unsigned int)tag[0])
| ((unsigned int)tag[1] << 8)
| ((unsigned int)tag[2] << 16)
| ((unsigned int)tag[3] << 24);
features[feat_count].parameter = param;
feat_count++;
}
fp--; // adjust for loop increment
}
}
}
DWriteContext_SetFontFeatures(s_dwc, features, feat_count);
}
#endif
if (font_name == NULL)
font_name = (char_u *)"";
#ifdef FEAT_MBYTE_IME
+6
View File
@@ -3210,6 +3210,12 @@ get_logfont(
}
}
break;
case L'f':
// Font features (e.g., "fss19=1").
// Parsed separately by gui_mch_init_font(); skip here.
while (*p && *p != L':')
p++;
break;
case L'q':
for (i = 0; i < (int)ARRAY_LENGTH(quality_pairs); ++i)
{
+2
View File
@@ -734,6 +734,8 @@ static char *(features[]) =
static int included_patches[] =
{ /* Add new patch number below this line */
/**/
321,
/**/
320,
/**/