]> git.ipfire.org Git - thirdparty/vim.git/commitdiff
patch 9.2.0321: MS-Windows: No OpenType font support v9.2.0321
authorYasuhiro Matsumoto <mattn.jp@gmail.com>
Tue, 7 Apr 2026 21:07:46 +0000 (21:07 +0000)
committerChristian Brabandt <cb@256bit.org>
Tue, 7 Apr 2026 21:07:46 +0000 (21:07 +0000)
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>
runtime/doc/gui.txt
runtime/doc/version9.txt
src/gui_dwrite.cpp
src/gui_dwrite.h
src/gui_w32.c
src/os_mswin.c
src/version.c

index a255af48bf2abae6f7ae4750d863880e3c7c93ba..9965e3dd402fed78f1ac4a9364b94023c9671656 100644 (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|.
 
index 8adff066f5ef9f7ee3030698ded45797ef27e3c4..acce37226a89e0401cba3b7988fb0c9770e46753 100644 (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
index c71358717ab1de383c2184c3636d82dbf8cb94aa..673b6d539cce30e296ff20b0ca706c9f02a84610 100644 (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);
+}
index 5de06f92b199e45f58e163687f9ec8c03c9bfd71..4c4777764e0020e232c90c484d0a4a4bfe6e0b1d 100644 (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
index 605897fb2b2c63cc5f4dd8b46ca159b4ff3f0c21..093c84176e3a57c44385d62e9b42e79c995ea9be 100644 (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
index be71fac73fe4952f25b148a345995ab565306892..6eaf84f6ced485e2e0ad0e02bf49c3a231df736c 100644 (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)
                {
index 81519ea17bc6e2472472d2c0595d37df69eb7c64..baacac399d555c73a5341d9a1abed622a278b2fa 100644 (file)
@@ -734,6 +734,8 @@ static char *(features[]) =
 
 static int included_patches[] =
 {   /* Add new patch number below this line */
+/**/
+    321,
 /**/
     320,
 /**/