From: Yasuhiro Matsumoto Date: Tue, 7 Apr 2026 21:07:46 +0000 (+0000) Subject: patch 9.2.0321: MS-Windows: No OpenType font support X-Git-Tag: v9.2.0321^0 X-Git-Url: http://git.ipfire.org/gitweb.cgi?a=commitdiff_plain;h=ea7bf9aa8a0e338ed9163c24ae43829885a97edb;p=thirdparty%2Fvim.git 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 Signed-off-by: Christian Brabandt --- diff --git a/runtime/doc/gui.txt b/runtime/doc/gui.txt index a255af48bf..9965e3dd40 100644 --- a/runtime/doc/gui.txt +++ b/runtime/doc/gui.txt @@ -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|. diff --git a/runtime/doc/version9.txt b/runtime/doc/version9.txt index 8adff066f5..acce37226a 100644 --- a/runtime/doc/version9.txt +++ b/runtime/doc/version9.txt @@ -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 diff --git a/src/gui_dwrite.cpp b/src/gui_dwrite.cpp index c71358717a..673b6d539c 100644 --- a/src/gui_dwrite.cpp +++ b/src/gui_dwrite.cpp @@ -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); +} diff --git a/src/gui_dwrite.h b/src/gui_dwrite.h index 5de06f92b1..4c4777764e 100644 --- a/src/gui_dwrite.h +++ b/src/gui_dwrite.h @@ -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 diff --git a/src/gui_w32.c b/src/gui_w32.c index 605897fb2b..093c84176e 100644 --- a/src/gui_w32.c +++ b/src/gui_w32.c @@ -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 diff --git a/src/os_mswin.c b/src/os_mswin.c index be71fac73f..6eaf84f6ce 100644 --- a/src/os_mswin.c +++ b/src/os_mswin.c @@ -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) { diff --git a/src/version.c b/src/version.c index 81519ea17b..baacac399d 100644 --- a/src/version.c +++ b/src/version.c @@ -734,6 +734,8 @@ static char *(features[]) = static int included_patches[] = { /* Add new patch number below this line */ +/**/ + 321, /**/ 320, /**/