Skip to content

Commit ea7bf9a

Browse files
mattnchrisbra
authored andcommitted
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>
1 parent ff41e9d commit ea7bf9a

File tree

7 files changed

+170
-4
lines changed

7 files changed

+170
-4
lines changed

runtime/doc/gui.txt

Lines changed: 12 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
*gui.txt* For Vim version 9.2. Last change: 2026 Feb 14
1+
*gui.txt* For Vim version 9.2. Last change: 2026 Apr 07
22

33

44
VIM REFERENCE MANUAL by Bram Moolenaar
@@ -1150,11 +1150,22 @@ For the Win32 GUI *E244* *E245*
11501150
NONANTIALIASED, CLEARTYPE and DEFAULT. Normally you would use
11511151
"qDEFAULT".
11521152
Some quality values are not supported in legacy OSs.
1153+
fXX - OpenType font feature. Specify a single feature as
1154+
tag=value, where tag is a 4-character OpenType feature
1155+
tag and value is the parameter (0 to disable, 1 or
1156+
higher to enable/select variant). Multiple features
1157+
can be specified by repeating the ":f" option.
1158+
This only takes effect when 'renderoptions' is set to use
1159+
DirectWrite (type:directx). Default features (calt, liga,
1160+
etc.) are preserved unless explicitly overridden.
1161+
Example: ":fss19=1:fcalt=0" enables Stylistic Set 19
1162+
and disables Contextual Alternates.
11531163
- A '_' can be used in the place of a space, so you don't need to use
11541164
backslashes to escape the spaces.
11551165
Examples: >
11561166
:set guifont=courier_new:h12:w5:b:cRUSSIAN
11571167
:set guifont=Andale_Mono:h7.5:w4.5
1168+
:set guifont=Cascadia_Code:h14:fss19=1:fcalt=1:fliga=1
11581169
11591170
See also |font-sizes|.
11601171

runtime/doc/version9.txt

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -52614,12 +52614,16 @@ Other ~
5261452614
- |system()| and |systemlist()| functions accept a list as first argument,
5261552615
bypassing the shell completely.
5261652616

52617+
Platform specific ~
52618+
-----------------
52619+
- support OpenType font features in 'guifont' for DirectWrite (Win32)
52620+
5261752621
xxd ~
5261852622
---
5261952623
Add "-t" option to append a terminating NUL byte to C include output (-i).
5262052624

5262152625
*changed-9.3*
52622-
Changed~
52626+
Changed ~
5262352627
-------
5262452628
- Support for NeXTStep was dropped with patch v9.2.0122
5262552629
- |json_decode()| is stricter: keywords must be lowercase, lone surrogates are

src/gui_dwrite.cpp

Lines changed: 79 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -313,6 +313,9 @@ struct DWriteContext {
313313

314314
D2D1_TEXT_ANTIALIAS_MODE mTextAntialiasMode;
315315

316+
DWriteFontFeature mFontFeatures[DWRITE_MAX_FONT_FEATURES];
317+
int mFontFeatureCount;
318+
316319
// METHODS
317320

318321
DWriteContext();
@@ -357,6 +360,8 @@ struct DWriteContext {
357360

358361
DWriteRenderingParams *GetRenderingParams(
359362
DWriteRenderingParams *params);
363+
364+
void SetFontFeatures(const DWriteFontFeature *features, int count);
360365
};
361366

362367
class AdjustedGlyphRun : public DWRITE_GLYPH_RUN
@@ -648,8 +653,10 @@ DWriteContext::DWriteContext() :
648653
mFontStyle(DWRITE_FONT_STYLE_NORMAL),
649654
mFontSize(0.0f),
650655
mFontAscent(0.0f),
651-
mTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT)
656+
mTextAntialiasMode(D2D1_TEXT_ANTIALIAS_MODE_DEFAULT),
657+
mFontFeatureCount(0)
652658
{
659+
ZeroMemory(mFontFeatures, sizeof(mFontFeatures));
653660
HRESULT hr;
654661

655662
hr = D2D1CreateFactory(D2D1_FACTORY_TYPE_SINGLE_THREADED,
@@ -1086,6 +1093,56 @@ DWriteContext::DrawText(const WCHAR *text, int len,
10861093
textLayout->SetFontWeight(mFontWeight, textRange);
10871094
textLayout->SetFontStyle(mFontStyle, textRange);
10881095

1096+
if (mFontFeatureCount > 0)
1097+
{
1098+
// Default OpenType features that DirectWrite normally enables.
1099+
// SetTypography() overrides all defaults, so we must
1100+
// re-add them here explicitly.
1101+
static const DWRITE_FONT_FEATURE defaultFeatures[] = {
1102+
{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_ALTERNATES, 1 },
1103+
{ DWRITE_FONT_FEATURE_TAG_STANDARD_LIGATURES, 1 },
1104+
{ DWRITE_FONT_FEATURE_TAG_CONTEXTUAL_LIGATURES, 1 },
1105+
{ DWRITE_FONT_FEATURE_TAG_REQUIRED_LIGATURES, 1 },
1106+
{ DWRITE_FONT_FEATURE_TAG_KERNING, 1 },
1107+
};
1108+
static const int numDefaults = sizeof(defaultFeatures)
1109+
/ sizeof(defaultFeatures[0]);
1110+
1111+
IDWriteTypography *typography = NULL;
1112+
hr = mDWriteFactory->CreateTypography(&typography);
1113+
if (SUCCEEDED(hr))
1114+
{
1115+
// Add default features, skipping any that the user
1116+
// has explicitly specified (either + or -).
1117+
for (int d = 0; d < numDefaults; ++d)
1118+
{
1119+
int overridden = 0;
1120+
for (int u = 0; u < mFontFeatureCount; ++u)
1121+
{
1122+
if ((DWRITE_FONT_FEATURE_TAG)mFontFeatures[u].tag
1123+
== defaultFeatures[d].nameTag)
1124+
{
1125+
overridden = 1;
1126+
break;
1127+
}
1128+
}
1129+
if (!overridden)
1130+
typography->AddFontFeature(defaultFeatures[d]);
1131+
}
1132+
// Add user-specified features.
1133+
for (int i = 0; i < mFontFeatureCount; ++i)
1134+
{
1135+
DWRITE_FONT_FEATURE ff = {
1136+
(DWRITE_FONT_FEATURE_TAG)mFontFeatures[i].tag,
1137+
mFontFeatures[i].parameter
1138+
};
1139+
typography->AddFontFeature(ff);
1140+
}
1141+
textLayout->SetTypography(typography, textRange);
1142+
SafeRelease(&typography);
1143+
}
1144+
}
1145+
10891146
// Calculate baseline using font ascent from font metrics.
10901147
// Do NOT use GetLineMetrics() because it returns different values
10911148
// depending on text content (e.g., when CJK characters trigger
@@ -1413,3 +1470,24 @@ DWriteContext_GetRenderingParams(
14131470
else
14141471
return NULL;
14151472
}
1473+
1474+
void
1475+
DWriteContext::SetFontFeatures(
1476+
const DWriteFontFeature *features, int count)
1477+
{
1478+
if (count > DWRITE_MAX_FONT_FEATURES)
1479+
count = DWRITE_MAX_FONT_FEATURES;
1480+
mFontFeatureCount = count;
1481+
if (count > 0 && features != NULL)
1482+
memcpy(mFontFeatures, features, sizeof(DWriteFontFeature) * count);
1483+
}
1484+
1485+
void
1486+
DWriteContext_SetFontFeatures(
1487+
DWriteContext *ctx,
1488+
const DWriteFontFeature *features,
1489+
int count)
1490+
{
1491+
if (ctx != NULL)
1492+
ctx->SetFontFeatures(features, count);
1493+
}

src/gui_dwrite.h

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,13 @@ typedef struct DWriteRenderingParams {
5151
int textAntialiasMode;
5252
} DWriteRenderingParams;
5353

54+
#define DWRITE_MAX_FONT_FEATURES 32
55+
56+
typedef struct DWriteFontFeature {
57+
unsigned int tag; // OpenType feature tag (4 bytes)
58+
unsigned int parameter; // Feature parameter (0 = disable, 1 = enable)
59+
} DWriteFontFeature;
60+
5461
void DWrite_Init(void);
5562
void DWrite_Final(void);
5663

@@ -86,6 +93,11 @@ DWriteRenderingParams *DWriteContext_GetRenderingParams(
8693
DWriteContext *ctx,
8794
DWriteRenderingParams *params);
8895

96+
void DWriteContext_SetFontFeatures(
97+
DWriteContext *ctx,
98+
const DWriteFontFeature *features,
99+
int count);
100+
89101
#ifdef __cplusplus
90102
}
91103
#endif

src/gui_w32.c

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -145,7 +145,6 @@ gui_mch_set_rendering_options(char_u *s)
145145
int dx_geom = 0;
146146
int dx_renmode = 0;
147147
int dx_taamode = 0;
148-
149148
// parse string as rendering options.
150149
for (p = s; p != NULL && *p != NUL; )
151150
{
@@ -3956,6 +3955,60 @@ gui_mch_init_font(char_u *font_name, int fontset UNUSED)
39563955
if (font == NOFONT)
39573956
return FAIL;
39583957

3958+
#if defined(FEAT_DIRECTX)
3959+
// Parse font features from guifont (e.g., ":fss19=1:fcalt=0:fliga=1").
3960+
{
3961+
DWriteFontFeature features[DWRITE_MAX_FONT_FEATURES];
3962+
int feat_count = 0;
3963+
char_u *fp;
3964+
3965+
if (font_name != NULL)
3966+
{
3967+
// Find each ":f" option in font_name.
3968+
for (fp = font_name; *fp != NUL; fp++)
3969+
{
3970+
if (*fp == ':' && *(fp + 1) == 'f')
3971+
{
3972+
char_u tag[5];
3973+
int ti = 0;
3974+
unsigned int param = 1;
3975+
3976+
fp += 2; // skip ":f"
3977+
while (*fp != NUL && *fp != '=' && *fp != ':'
3978+
&& ti < 4)
3979+
tag[ti++] = *fp++;
3980+
tag[ti] = NUL;
3981+
3982+
if (ti != 4)
3983+
continue; // invalid tag length
3984+
3985+
if (*fp == '=')
3986+
{
3987+
fp++;
3988+
param = (unsigned int)atoi((char *)fp);
3989+
while (*fp >= '0' && *fp <= '9')
3990+
fp++;
3991+
}
3992+
3993+
if (feat_count < DWRITE_MAX_FONT_FEATURES)
3994+
{
3995+
features[feat_count].tag =
3996+
((unsigned int)tag[0])
3997+
| ((unsigned int)tag[1] << 8)
3998+
| ((unsigned int)tag[2] << 16)
3999+
| ((unsigned int)tag[3] << 24);
4000+
features[feat_count].parameter = param;
4001+
feat_count++;
4002+
}
4003+
4004+
fp--; // adjust for loop increment
4005+
}
4006+
}
4007+
}
4008+
DWriteContext_SetFontFeatures(s_dwc, features, feat_count);
4009+
}
4010+
#endif
4011+
39594012
if (font_name == NULL)
39604013
font_name = (char_u *)"";
39614014
#ifdef FEAT_MBYTE_IME

src/os_mswin.c

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -3210,6 +3210,12 @@ get_logfont(
32103210
}
32113211
}
32123212
break;
3213+
case L'f':
3214+
// Font features (e.g., "fss19=1").
3215+
// Parsed separately by gui_mch_init_font(); skip here.
3216+
while (*p && *p != L':')
3217+
p++;
3218+
break;
32133219
case L'q':
32143220
for (i = 0; i < (int)ARRAY_LENGTH(quality_pairs); ++i)
32153221
{

src/version.c

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -734,6 +734,8 @@ static char *(features[]) =
734734

735735
static int included_patches[] =
736736
{ /* Add new patch number below this line */
737+
/**/
738+
321,
737739
/**/
738740
320,
739741
/**/

0 commit comments

Comments
 (0)