Skip to content

Commit 11ded28

Browse files
committed
support CJK with truetype font
1 parent c84dd17 commit 11ded28

File tree

2 files changed

+167
-24
lines changed

2 files changed

+167
-24
lines changed

PSReadLine/ConsoleLib.cs

Lines changed: 66 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,11 @@ public static class NativeMethods
2121
public const uint ENABLE_PROCESSED_INPUT = 0x0001;
2222
public const uint ENABLE_LINE_INPUT = 0x0002;
2323

24+
public const int FontTypeMask = 0x06;
25+
public const int TrueTypeFont = 0x04;
26+
27+
internal static readonly IntPtr INVALID_HANDLE_VALUE = new IntPtr(-1); // WinBase.h
28+
2429
[DllImport("kernel32.dll", CharSet = CharSet.Unicode, SetLastError = true)]
2530
public static extern IntPtr GetStdHandle(uint handleId);
2631

@@ -79,6 +84,21 @@ public static extern int ToUnicode(uint uVirtKey, uint uScanCode, byte[] lpKeySt
7984

8085
[DllImport("GDI32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
8186
public static extern bool GetCharWidth32(IntPtr hdc, uint first, uint last, out int width);
87+
88+
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
89+
public static extern IntPtr CreateFile
90+
(
91+
string fileName,
92+
uint desiredAccess,
93+
uint ShareModes,
94+
IntPtr securityAttributes,
95+
uint creationDisposition,
96+
uint flagsAndAttributes,
97+
IntPtr templateFileWin32Handle
98+
);
99+
100+
[DllImport("kernel32.dll", SetLastError = true, CharSet = CharSet.Unicode)]
101+
internal static extern bool GetCurrentConsoleFontEx(IntPtr consoleOutput, bool bMaximumWindow, ref CONSOLE_FONT_INFO_EX consoleFontInfo);
82102
}
83103

84104
public delegate bool BreakHandler(ConsoleBreakSignal ConsoleBreakSignal);
@@ -93,13 +113,46 @@ public enum ConsoleBreakSignal : uint
93113
None = 255,
94114
}
95115

116+
public enum CHAR_INFO_Attributes : ushort
117+
{
118+
COMMON_LVB_LEADING_BYTE = 0x0100,
119+
COMMON_LVB_TRAILING_BYTE = 0x0200
120+
121+
}
122+
96123
public enum StandardHandleId : uint
97124
{
98125
Error = unchecked((uint)-12),
99126
Output = unchecked((uint)-11),
100127
Input = unchecked((uint)-10),
101128
}
102129

130+
[Flags]
131+
public enum AccessQualifiers : uint
132+
{
133+
// From winnt.h
134+
GenericRead = 0x80000000,
135+
GenericWrite = 0x40000000
136+
}
137+
138+
public enum CreationDisposition : uint
139+
{
140+
// From winbase.h
141+
CreateNew = 1,
142+
CreateAlways = 2,
143+
OpenExisting = 3,
144+
OpenAlways = 4,
145+
TruncateExisting = 5
146+
}
147+
148+
[Flags]
149+
public enum ShareModes : uint
150+
{
151+
// From winnt.h
152+
ShareRead = 0x00000001,
153+
ShareWrite = 0x00000002
154+
}
155+
103156
public struct SMALL_RECT
104157
{
105158
public short Left;
@@ -190,6 +243,19 @@ public struct TEXTMETRIC
190243
public byte tmCharSet;
191244
}
192245

246+
[StructLayout(LayoutKind.Sequential, CharSet = CharSet.Unicode)]
247+
public struct CONSOLE_FONT_INFO_EX
248+
{
249+
internal int cbSize;
250+
internal int nFont;
251+
internal short FontWidth;
252+
internal short FontHeight;
253+
internal int FontFamily;
254+
internal int FontWeight;
255+
[MarshalAs(UnmanagedType.ByValTStr, SizeConst = 32)]
256+
internal string FontFace;
257+
}
258+
193259
public struct CHAR_INFO
194260
{
195261
public ushort UnicodeChar;

PSReadLine/Render.cs

Lines changed: 101 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,10 +3,13 @@
33
using System.Diagnostics.CodeAnalysis;
44
using System.Linq;
55
using System.Management.Automation;
6+
using System.Management.Automation.Host;
67
using System.Management.Automation.Language;
78
using System.Runtime.InteropServices;
89
using System.Security;
910
using PSConsoleUtilities.Internal;
11+
using ConsoleHandle = Microsoft.Win32.SafeHandles.SafeFileHandle;
12+
using System.ComponentModel;
1013

1114
namespace PSConsoleUtilities
1215
{
@@ -27,6 +30,30 @@ public partial class PSConsoleReadLine
2730
private uint _codePage;
2831
private bool _istmInitialized = false;
2932
private TEXTMETRIC _tm = new TEXTMETRIC();
33+
private bool _trueTypeInUse = false;
34+
35+
private readonly Lazy<ConsoleHandle> _outputHandle = new Lazy<ConsoleHandle>(() =>
36+
{
37+
// We use CreateFile here instead of GetStdWin32Handle, as GetStdWin32Handle will return redirected handles
38+
var handle = NativeMethods.CreateFile(
39+
"CONOUT$",
40+
(UInt32)(AccessQualifiers.GenericRead | AccessQualifiers.GenericWrite),
41+
(UInt32)ShareModes.ShareWrite,
42+
(IntPtr)0,
43+
(UInt32)CreationDisposition.OpenExisting,
44+
0,
45+
(IntPtr)0);
46+
47+
if (handle == NativeMethods.INVALID_HANDLE_VALUE)
48+
{
49+
int err = Marshal.GetLastWin32Error();
50+
Win32Exception innerException = new Win32Exception(err);
51+
throw new Exception("Failed to retreive the input console handle.", innerException);
52+
}
53+
54+
return new ConsoleHandle(handle, true);
55+
}
56+
);
3057

3158
private class SavedTokenState
3259
{
@@ -83,43 +110,50 @@ private void ReallyRender()
83110
var text = ParseInput();
84111
_codePage = NativeMethods.GetConsoleOutputCP();
85112
_istmInitialized = false;
113+
ConsoleHandle consoleHandle = _outputHandle.Value;
114+
CONSOLE_FONT_INFO_EX fontInfo = GetConsoleFontInfo(consoleHandle);
115+
int fontType = fontInfo.FontFamily & NativeMethods.FontTypeMask;
116+
_trueTypeInUse = (fontType & NativeMethods.TrueTypeFont) == NativeMethods.TrueTypeFont;
86117

87118
int statusLineCount = GetStatusLineCount();
88-
int bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount;
119+
int j = _initialX + (_bufferWidth * Options.ExtraPromptLineCount);
120+
var backgroundColor = _initialBackgroundColor;
121+
var foregroundColor = _initialForegroundColor;
122+
bool afterLastToken = false;
123+
int totalBytes = j;
89124
int bufferWidth = Console.BufferWidth;
90-
if (_consoleBuffer.Length != bufferLineCount * bufferWidth)
91-
{
92-
var newBuffer = new CHAR_INFO[bufferLineCount * bufferWidth];
93-
Array.Copy(_consoleBuffer, newBuffer, _initialX + (Options.ExtraPromptLineCount * _bufferWidth));
94-
if (_consoleBuffer.Length > bufferLineCount * bufferWidth)
95-
{
96-
int consoleBufferOffset = ConvertOffsetToConsoleBufferOffset(text.Length, _initialX + (Options.ExtraPromptLineCount * _bufferWidth));
97-
// Need to erase the extra lines that we won't draw again
98-
for (int i = consoleBufferOffset; i < _consoleBuffer.Length; i++)
99-
{
100-
_consoleBuffer[i] = _space;
101-
}
102-
WriteBufferLines(_consoleBuffer, ref _initialY);
103-
}
104-
_consoleBuffer = newBuffer;
105-
}
106125

107126
var tokenStack = new Stack<SavedTokenState>();
108127
tokenStack.Push(new SavedTokenState
109128
{
110-
Tokens = _tokens,
111-
Index = 0,
129+
Tokens = _tokens,
130+
Index = 0,
112131
BackgroundColor = _initialBackgroundColor,
113132
ForegroundColor = _initialForegroundColor
114133
});
115134

116-
int j = _initialX + (_bufferWidth * Options.ExtraPromptLineCount);
117-
var backgroundColor = _initialBackgroundColor;
118-
var foregroundColor = _initialForegroundColor;
119-
bool afterLastToken = false;
120-
int totalBytes = j;
135+
int bufferLineCount;
136+
121137
try
122138
{
139+
bufferLineCount = ConvertOffsetToCoordinates(text.Length).Y - _initialY + 1 + statusLineCount;
140+
if (_consoleBuffer.Length != bufferLineCount * bufferWidth)
141+
{
142+
var newBuffer = new CHAR_INFO[bufferLineCount * bufferWidth];
143+
Array.Copy(_consoleBuffer, newBuffer, _initialX + (Options.ExtraPromptLineCount * _bufferWidth));
144+
if (_consoleBuffer.Length > bufferLineCount * bufferWidth)
145+
{
146+
int consoleBufferOffset = ConvertOffsetToConsoleBufferOffset(text.Length, _initialX + (Options.ExtraPromptLineCount * _bufferWidth));
147+
// Need to erase the extra lines that we won't draw again
148+
for (int i = consoleBufferOffset; i < _consoleBuffer.Length; i++)
149+
{
150+
_consoleBuffer[i] = _space;
151+
}
152+
WriteBufferLines(_consoleBuffer, ref _initialY);
153+
}
154+
_consoleBuffer = newBuffer;
155+
}
156+
123157
for (int i = 0; i < text.Length; i++)
124158
{
125159
SavedTokenState state = null;
@@ -221,6 +255,17 @@ private void ReallyRender()
221255
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
222256

223257
}
258+
else if (size > 1 && IsCJKOutputCodePage() && _trueTypeInUse)
259+
{
260+
_consoleBuffer[j].UnicodeChar = text[i];
261+
_consoleBuffer[j].Attributes = (ushort) ((uint)_consoleBuffer[j].Attributes |
262+
(uint) CHAR_INFO_Attributes.COMMON_LVB_LEADING_BYTE);
263+
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
264+
_consoleBuffer[j].UnicodeChar = text[i];
265+
_consoleBuffer[j].Attributes = (ushort) ((uint)_consoleBuffer[j].Attributes |
266+
(uint) CHAR_INFO_Attributes.COMMON_LVB_TRAILING_BYTE);
267+
MaybeEmphasize(ref _consoleBuffer[j++], i, foregroundColor, backgroundColor);
268+
}
224269
else
225270
{
226271
_consoleBuffer[j].UnicodeChar = text[i];
@@ -476,6 +521,22 @@ private bool InRegion(int i)
476521
return i >= start && i < end;
477522
}
478523

524+
private static CONSOLE_FONT_INFO_EX GetConsoleFontInfo(ConsoleHandle consoleHandle)
525+
{
526+
527+
CONSOLE_FONT_INFO_EX fontInfo = new CONSOLE_FONT_INFO_EX();
528+
fontInfo.cbSize = Marshal.SizeOf(fontInfo);
529+
bool result = NativeMethods.GetCurrentConsoleFontEx(consoleHandle.DangerousGetHandle(), false, ref fontInfo);
530+
531+
if (result == false)
532+
{
533+
int err = Marshal.GetLastWin32Error();
534+
Win32Exception innerException = new Win32Exception(err);
535+
throw new Exception("Failed to get console font information.", innerException);
536+
}
537+
return fontInfo;
538+
}
539+
479540
private void MaybeEmphasize(ref CHAR_INFO charInfo, int i, ConsoleColor foregroundColor, ConsoleColor backgroundColor)
480541
{
481542
if (i >= _emphasisStart && i < (_emphasisStart + _emphasisLength))
@@ -564,6 +625,18 @@ private uint CodePageToCharSet()
564625
return csi.ciCharset;
565626
}
566627

628+
/// <summary>
629+
/// Check if the output buffer code page is Japanese, Simplified Chinese, Korean, or Traditional Chinese
630+
/// </summary>
631+
/// <returns>true if it is CJK code page; otherwise, false.</returns>
632+
private bool IsCJKOutputCodePage()
633+
{
634+
return _codePage == 932 || // Japanese
635+
_codePage == 936 || // Simplified Chinese
636+
_codePage == 949 || // Korean
637+
_codePage == 950; // Traditional Chinese
638+
}
639+
567640
private bool IsAvailableFarEastCodePage()
568641
{
569642
uint charSet = CodePageToCharSet();
@@ -751,6 +824,10 @@ private int ConvertOffsetToConsoleBufferOffset(int offset, int startIndex)
751824
{
752825
j += 2;
753826
}
827+
else if (LengthInBufferCells(_buffer[i]) > 1 && IsCJKOutputCodePage() && _trueTypeInUse)
828+
{
829+
j += 2;
830+
}
754831
else
755832
{
756833
j++;

0 commit comments

Comments
 (0)