Get\SetCurrentConsoleFontEx doesn't work with font names of 16 or more characters #381

Open
opened 2026-01-30 21:50:22 +00:00 by claunia · 0 comments
Owner

Originally created by @Jaykul on GitHub (Sep 20, 2018).

Originally assigned to: @miniksa on GitHub.

The console itself obviously doesn't have this limitation, but SetCurrentConsoleFontEx cannot set the font to fonts with 16 or more characters in the font name (note: the True Type name, nothing to do with the file name). The built-in "Lucida Sans Typewriter" and the open source "Fira Code Retina" work fine from the property dialog, but not from the API.

In fact, GetCurrentConsoleFontEx doesn't return the full name for fonts with more than 16 characters, and adds a garbage character to 16 character font names (looks like a "whatever was in memory there" bug).

I'd love to hear that I've implemented the PInvoke struct wrong, but I'm pretty sure that's not the case (I can define it several different ways, but none of them work any better than this does).

Microsoft Windows [Version 10.0.17760.1]

This following code works ...

I have copied PInvoke code from various places, particularly the PowerShell console host, and the Microsoft Docs

Note that the relevant docs for CONSOLE_FONT_INFOEX and SetCurrentConsoleFontEx do not mention any restrictions on length, and the struct defines the font face as a WCHAR field of size 32...

However, in the API, the font name has to have less than 16 characters in the name in order to work.

    using System;
    using System.Runtime.InteropServices;

    public static class ConsoleHelper
    {
        private const int FixedWidthTrueType = 54;
        private const int StandardOutputHandle = -11;

        [DllImport("kernel32.dll", SetLastError = true)]
        internal static extern IntPtr GetStdHandle(int nStdHandle);

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern bool SetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool MaximumWindow, ref FontInfo ConsoleCurrentFontEx);

        [return: MarshalAs(UnmanagedType.Bool)]
        [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
        internal static extern bool GetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool MaximumWindow, ref FontInfo ConsoleCurrentFontEx);


        private static readonly IntPtr ConsoleOutputHandle = GetStdHandle(StandardOutputHandle);

        [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
        public struct FontInfo
        {
            internal int cbSize;
            internal int FontIndex;
            internal short FontWidth;
            public short FontSize;
            public int FontFamily;
            public int FontWeight;
            [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
            //[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.wc, SizeConst = 32)]
            public string FontName;
        }

        public static FontInfo[] SetCurrentFont(string font, short fontSize = 0)
        {
            Console.WriteLine("Set Current Font: " + font);

            FontInfo before = new FontInfo
            {
                cbSize = Marshal.SizeOf<FontInfo>()
            };

            if (GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref before))
            {

                FontInfo set = new FontInfo
                {
                    cbSize = Marshal.SizeOf<FontInfo>(),
                    FontIndex = 0,
                    FontFamily = FixedWidthTrueType,
                    FontName = font,
                    FontWeight = 400,
                    FontSize = fontSize > 0 ? fontSize : before.FontSize
                };

                // Get some settings from current font.
                if (!SetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref set))
                {
                    var ex = Marshal.GetLastWin32Error();
                    Console.WriteLine("Set error " + ex);
                    throw new System.ComponentModel.Win32Exception(ex);
                }

                FontInfo after = new FontInfo
                {
                    cbSize = Marshal.SizeOf<FontInfo>()
                };
                GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref after);

                return new[] { before, set, after };
            }
            else
            {
                var er = Marshal.GetLastWin32Error();
                Console.WriteLine("Get error " + er);
                throw new System.ComponentModel.Win32Exception(er);
            }
        }
    }

I played with it in PowerShell window by using Add-Type with that code, and then doing something like this:

    [ConsoleHelper]::SetCurrentFont("Consolas", 16)
    [ConsoleHelper]::SetCurrentFont("Lucida Console", 12)

You can try setting "Lucida Sans Typewriter" but it won't take.

Try this: set the font to Lucida Sans Typewriter via the property dialog. Then Get it and Set it back, or just try to change the font size, without touching anything (else) and it will fail -- the font changes to the original Courier New....

    [ConsoleHelper]::SetCurrentFont("Lucida Sans Typewriter", 12)

Will give you output like this (showing the three settings: before, what we tried, and what we got):

    Set Current Font: Lucida Sans Typewriter

    FontSize FontFamily FontWeight FontName
    -------- ---------- ---------- --------
          14         54        400 Lucida Sans Typeʈ
          12         54        400 Lucida Sans Typewriter
          12         54        400 Courier New

You see that weird character on the end of the "before" value? That's happens whenever the font is longer than 16 characters -- I assume the garbage data is the result of something is truncating the name (and not null-terminating the string).

For what it's worth, I discovered this problem with Fira Code Retina, a font that has exactly 16 characters in the name -- and I have a little bit more code than what's above in a gist here if you care to experiment.

Originally created by @Jaykul on GitHub (Sep 20, 2018). Originally assigned to: @miniksa on GitHub. The console itself obviously doesn't have this limitation, but `SetCurrentConsoleFontEx` cannot set the font to fonts with 16 or more characters in the font name (note: the True Type name, nothing to do with the file name). The built-in "Lucida Sans Typewriter" and the open source "Fira Code Retina" work fine from the property dialog, but not from the API. In fact, `GetCurrentConsoleFontEx` doesn't return the full name for fonts with more than 16 characters, and adds a garbage character to 16 character font names (looks like a "whatever was in memory there" bug). I'd love to hear that I've implemented the PInvoke struct wrong, but I'm pretty sure that's not the case (I can define it several different ways, but none of them work any better than this does). `Microsoft Windows [Version 10.0.17760.1]` ## This following code works ... I have copied PInvoke code from various places, particularly [the PowerShell console host](https://github.com/PowerShell/PowerShell/blob/master/src/Microsoft.PowerShell.ConsoleHost/host/msh/ConsoleControl.cs), and the [Microsoft Docs](https://docs.microsoft.com/en-us/dotnet/api/system.console?view=netframework-4.7.2#unicode-support-for-the-console) Note that the relevant docs for [CONSOLE_FONT_INFOEX](https://docs.microsoft.com/en-us/windows/console/console-font-infoex) and [SetCurrentConsoleFontEx](https://docs.microsoft.com/en-us/windows/console/setcurrentconsolefontex) do not mention any restrictions on length, and the struct defines the font face as a WCHAR field of size 32... However, in the API, the font name has to have *less than* 16 characters in the name in order to work. ```csharp using System; using System.Runtime.InteropServices; public static class ConsoleHelper { private const int FixedWidthTrueType = 54; private const int StandardOutputHandle = -11; [DllImport("kernel32.dll", SetLastError = true)] internal static extern IntPtr GetStdHandle(int nStdHandle); [return: MarshalAs(UnmanagedType.Bool)] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool SetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool MaximumWindow, ref FontInfo ConsoleCurrentFontEx); [return: MarshalAs(UnmanagedType.Bool)] [DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)] internal static extern bool GetCurrentConsoleFontEx(IntPtr hConsoleOutput, bool MaximumWindow, ref FontInfo ConsoleCurrentFontEx); private static readonly IntPtr ConsoleOutputHandle = GetStdHandle(StandardOutputHandle); [StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)] public struct FontInfo { internal int cbSize; internal int FontIndex; internal short FontWidth; public short FontSize; public int FontFamily; public int FontWeight; [MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)] //[MarshalAs(UnmanagedType.ByValArray, ArraySubType = UnmanagedType.wc, SizeConst = 32)] public string FontName; } public static FontInfo[] SetCurrentFont(string font, short fontSize = 0) { Console.WriteLine("Set Current Font: " + font); FontInfo before = new FontInfo { cbSize = Marshal.SizeOf<FontInfo>() }; if (GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref before)) { FontInfo set = new FontInfo { cbSize = Marshal.SizeOf<FontInfo>(), FontIndex = 0, FontFamily = FixedWidthTrueType, FontName = font, FontWeight = 400, FontSize = fontSize > 0 ? fontSize : before.FontSize }; // Get some settings from current font. if (!SetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref set)) { var ex = Marshal.GetLastWin32Error(); Console.WriteLine("Set error " + ex); throw new System.ComponentModel.Win32Exception(ex); } FontInfo after = new FontInfo { cbSize = Marshal.SizeOf<FontInfo>() }; GetCurrentConsoleFontEx(ConsoleOutputHandle, false, ref after); return new[] { before, set, after }; } else { var er = Marshal.GetLastWin32Error(); Console.WriteLine("Get error " + er); throw new System.ComponentModel.Win32Exception(er); } } } ``` I played with it in PowerShell window by using `Add-Type` with that code, and then doing something like this: ```posh [ConsoleHelper]::SetCurrentFont("Consolas", 16) [ConsoleHelper]::SetCurrentFont("Lucida Console", 12) ``` You can try setting "Lucida Sans Typewriter" but it won't take. Try this: set the font to Lucida Sans Typewriter via the property dialog. Then `Get` it and `Set` it back, or just try to change the font size, without touching anything (else) and it will fail -- the font changes to the original Courier New.... ```posh [ConsoleHelper]::SetCurrentFont("Lucida Sans Typewriter", 12) ``` Will give you output like this (showing the three settings: before, what we tried, and what we got): ```posh Set Current Font: Lucida Sans Typewriter FontSize FontFamily FontWeight FontName -------- ---------- ---------- -------- 14 54 400 Lucida Sans Typeʈ 12 54 400 Lucida Sans Typewriter 12 54 400 Courier New ``` You see that weird character on the end of the "before" value? That's happens whenever the font is longer than 16 characters -- I assume the garbage data is the result of something is truncating the name (and not null-terminating the string). For what it's worth, I discovered this problem with [Fira Code Retina](https://github.com/tonsky/FiraCode), a font that has exactly 16 characters in the name -- and I have a little bit more code than what's above in [a gist here](https://gist.github.com/Jaykul/af95aeece9c3e49815a266bcd8594f1f) if you care to experiment.
claunia added the Product-ConhostResolution-Fix-AvailableWork-Item labels 2026-01-30 21:50:22 +00:00
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: starred/terminal#381