diff --git a/NStack.sln b/NStack.sln index 43ed1a6..0b4bfed 100644 --- a/NStack.sln +++ b/NStack.sln @@ -12,6 +12,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Files", "Solution .github\workflows\build.yml = .github\workflows\build.yml .github\workflows\publish.yml = .github\workflows\publish.yml README.md = README.md + testenvironments.json = testenvironments.json EndProjectSection EndProject Global diff --git a/NStack/unicode/Rune.ColumnWidth.cs b/NStack/unicode/Rune.ColumnWidth.cs index b1df9ea..381d72b 100644 --- a/NStack/unicode/Rune.ColumnWidth.cs +++ b/NStack/unicode/Rune.ColumnWidth.cs @@ -4,11 +4,9 @@ // using NStack; -namespace System -{ - public partial struct Rune - { - static uint[,] combining = new uint[,] { +namespace System { + public partial struct Rune { + static uint [,] combining = new uint [,] { { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, @@ -48,16 +46,16 @@ public partial struct Rune { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x2e9a, 0x2e9a }, - { 0x2ef4, 0x2eff }, { 0x2fd6, 0x2fef }, { 0x2ffc, 0x2fff }, - { 0x31e4, 0x31ef }, { 0x321f, 0x321f }, { 0xA48D, 0xA48F }, + { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x2E9A, 0x2E9A }, + { 0x2EF4, 0x2EFF }, { 0x2FD6, 0x2FEF }, { 0x2FFC, 0x2FFF }, + { 0x31E4, 0x31EF }, { 0x321F, 0x321F }, { 0xA48D, 0xA48F }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, { 0xFE1A, 0xFE1F }, { 0xFE20, 0xFE23 }, { 0xFE53, 0xFE53 }, { 0xFE67, 0xFE67 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, }; - static uint[,] combiningWideChars = new uint[,] { + static uint [,] combiningWideChars = new uint [,] { /* Hangul Jamo init. consonants - 0x1100, 0x11ff */ /* Miscellaneous Technical - 0x2300, 0x23ff */ /* Hangul Syllables - 0x11a8, 0x11c2 */ @@ -84,22 +82,21 @@ public partial struct Rune { 0x3131, 0x318e }, { 0x3190, 0x3247 }, { 0x3250, 0x4dbf }, { 0x4e00, 0xa4c6 }, { 0xa960, 0xa97c }, { 0xac00 ,0xd7a3 }, { 0xf900, 0xfaff }, { 0xfe10, 0xfe1f }, { 0xfe30 ,0xfe6b }, - { 0xff01, 0xff60 }, { 0xffe0, 0xffe6 } + { 0xff01, 0xff60 }, { 0xffe0, 0xffe6 }, { 0x10000, 0x10ffff } }; - static int bisearch(uint rune, uint[,] table, int max) + static int bisearch (uint rune, uint [,] table, int max) { int min = 0; int mid; - if (rune < table[0, 0] || rune > table[max, 1]) + if (rune < table [0, 0] || rune > table [max, 1]) return 0; - while (max >= min) - { + while (max >= min) { mid = (min + max) / 2; - if (rune > table[mid, 1]) + if (rune > table [mid, 1]) min = mid + 1; - else if (rune < table[mid, 0]) + else if (rune < table [mid, 0]) max = mid - 1; else return 1; @@ -127,21 +124,14 @@ static int bisearch(uint rune, uint[,] table, int max) // return false; //} - static uint gethexaformat(uint rune, int length) - { - var hex = rune.ToString($"x{length}"); - var hexstr = hex.Substring(hex.Length - length, length); - return (uint)int.Parse(hexstr, System.Globalization.NumberStyles.HexNumber); - } - /// /// Check if the rune is a non-spacing character. /// /// The rune. /// True if is a non-spacing character, false otherwise. - public static bool IsNonSpacingChar(uint rune) + public static bool IsNonSpacingChar (uint rune) { - return bisearch(rune, combining, combining.GetLength(0) - 1) != 0; + return bisearch (rune, combining, combining.GetLength (0) - 1) != 0; } /// @@ -149,60 +139,29 @@ public static bool IsNonSpacingChar(uint rune) /// /// The rune. /// True if is a wide character, false otherwise. - public static bool IsWideChar(uint rune) + public static bool IsWideChar (uint rune) { - return bisearch(gethexaformat(rune, 4), combiningWideChars, combiningWideChars.GetLength(0) - 1) != 0; + return bisearch (rune, combiningWideChars, combiningWideChars.GetLength (0) - 1) != 0; } - static char firstSurrogatePairChar = '\0'; - /// /// Number of column positions of a wide-character code. This is used to measure runes as displayed by text-based terminals. /// /// The width in columns, 0 if the argument is the null character, -1 if the value is not printable, otherwise the number of columns that the rune occupies. /// The rune. - public static int ColumnWidth(Rune rune) + public static int ColumnWidth (Rune rune) { - if (firstSurrogatePairChar != '\0') - firstSurrogatePairChar = '\0'; uint irune = (uint)rune; if (irune < 0x20 || (irune >= 0x7f && irune < 0xa0)) return -1; if (irune < 0x7f) return 1; /* binary search in table of non-spacing characters */ - if (bisearch(gethexaformat(irune, 4), combining, combining.GetLength(0) - 1) != 0) + if (bisearch (irune, combining, combining.GetLength (0) - 1) != 0) return 0; /* if we arrive here, ucs is not a combining or C0/C1 control character */ return 1 + - (bisearch(gethexaformat(irune, 4), combiningWideChars, combiningWideChars.GetLength(0) - 1) != 0 ? 1 : 0); - } - - /// - /// Number of column positions of a wide-character code. This is used to measure runes as displayed by text-based terminals. - /// - /// The width in columns, 0 if the argument is the null character, -1 if the value is not printable, otherwise the number of columns that the rune occupies. - /// The char. - public static int ColumnWidth(char c) - { - if (!((Rune)c).IsValid) - { - if (firstSurrogatePairChar == '\0') - { - firstSurrogatePairChar = c; - return 0; - } - else if (firstSurrogatePairChar != '\0') - { - var r = new Rune(firstSurrogatePairChar, c); - firstSurrogatePairChar = '\0'; - return ColumnWidth(r); - } - } - if (firstSurrogatePairChar != '\0') - firstSurrogatePairChar = '\0'; - - return ColumnWidth((Rune)c); + (bisearch (irune, combiningWideChars, combiningWideChars.GetLength (0) - 1) != 0 ? 1 : 0); } } } diff --git a/NStack/unicode/Rune.cs b/NStack/unicode/Rune.cs index b8a77d1..092756e 100644 --- a/NStack/unicode/Rune.cs +++ b/NStack/unicode/Rune.cs @@ -8,7 +8,7 @@ namespace System { /// /// /// - [StructLayout(LayoutKind.Sequential)] + [StructLayout (LayoutKind.Sequential)] public partial struct Rune { // Stores the rune uint value; @@ -54,9 +54,8 @@ public partial struct Rune { /// public Rune (uint rune) { - if (rune > maxRune) - { - throw new ArgumentOutOfRangeException("Value is beyond the supplementary range!"); + if (rune > maxRune) { + throw new ArgumentOutOfRangeException ("Value is beyond the supplementary range!"); } this.value = rune; } @@ -77,17 +76,12 @@ public Rune (char ch) /// The low surrogate code point. public Rune (uint highSurrogate, uint lowSurrogate) { - if (EncodeSurrogatePair(highSurrogate, lowSurrogate, out Rune rune)) - { + if (EncodeSurrogatePair (highSurrogate, lowSurrogate, out Rune rune)) { this.value = rune; - } - else if (highSurrogate < highSurrogateMin || lowSurrogate > lowSurrogateMax) - { - throw new ArgumentOutOfRangeException($"Must be between {highSurrogateMin:x} and {lowSurrogateMax:x} inclusive!"); - } - else - { - throw new ArgumentOutOfRangeException($"Resulted rune must be less or equal to {(uint)MaxRune:x}!"); + } else if (highSurrogate < highSurrogateMin || lowSurrogate > lowSurrogateMax) { + throw new ArgumentOutOfRangeException ($"Must be between {highSurrogateMin:x} and {lowSurrogateMax:x} inclusive!"); + } else { + throw new ArgumentOutOfRangeException ($"Resulted rune must be less or equal to {(uint)MaxRune:x}!"); } } @@ -95,25 +89,35 @@ public Rune (uint highSurrogate, uint lowSurrogate) /// Gets a value indicating whether this can be encoded as UTF-8 /// /// true if is valid; otherwise, false. - public bool IsValid => ValidRune(value); + public bool IsValid => ValidRune (value); /// /// Gets a value indicating whether this is a surrogate code point. /// /// trueIf is a surrogate code point, falseotherwise. - public bool IsSurrogate => IsSurrogateRune(value); + public bool IsSurrogate => IsSurrogateRune (value); /// /// Gets a value indicating whether this is a valid surrogate pair. /// /// trueIf is a valid surrogate pair, falseotherwise. - public bool IsSurrogatePair => DecodeSurrogatePair(value, out _); + public bool IsSurrogatePair => DecodeSurrogatePair (value, out _); + + /// + /// Gets a value indicating whether this is a high surrogate. + /// + public bool IsHighSurrogate => value >= highSurrogateMin && value <= highSurrogateMax; + + /// + /// Gets a value indicating whether this is a low surrogate. + /// + public bool IsLowSurrogate => value >= lowSurrogateMin && value <= lowSurrogateMax; /// /// Check if the rune is a non-spacing character. /// /// True if is a non-spacing character, false otherwise. - public bool IsNonSpacing => IsNonSpacingChar(value); + public bool IsNonSpacing => IsNonSpacingChar (value); // Code points in the surrogate range are not valid for UTF-8. const uint highSurrogateMin = 0xd800; @@ -539,8 +543,7 @@ public static int InvalidIndex (byte [] buffer) public static bool ValidRune (Rune rune) { if ((0 <= (int)rune.value && rune.value < highSurrogateMin) || - (lowSurrogateMax < rune.value && rune.value <= MaxRune.value)) - { + (lowSurrogateMax < rune.value && rune.value <= MaxRune.value)) { return true; } @@ -552,7 +555,7 @@ public static bool ValidRune (Rune rune) /// /// The rune. /// trueIf is a surrogate code point, falseotherwise. - public static bool IsSurrogateRune(uint rune) + public static bool IsSurrogateRune (uint rune) { return rune >= highSurrogateMin && rune <= lowSurrogateMax; } @@ -564,12 +567,11 @@ public static bool IsSurrogateRune(uint rune) /// The low surrogate code point. /// The returning rune. /// Trueif the returning rune is greater than 0 Falseotherwise. - public static bool EncodeSurrogatePair(uint highsurrogate, uint lowSurrogate, out Rune rune) + public static bool EncodeSurrogatePair (uint highsurrogate, uint lowSurrogate, out Rune rune) { rune = 0; if (highsurrogate >= highSurrogateMin && highsurrogate <= highSurrogateMax && - lowSurrogate >= lowSurrogateMin && lowSurrogate <= lowSurrogateMax) - { + lowSurrogate >= lowSurrogateMin && lowSurrogate <= lowSurrogateMax) { //return 0x10000 + ((highsurrogate - highSurrogateMin) * 0x0400) + (lowSurrogate - lowSurrogateMin); return (rune = 0x10000 + ((highsurrogate - highSurrogateMin) << 10) + (lowSurrogate - lowSurrogateMin)) > 0; } @@ -582,14 +584,13 @@ public static bool EncodeSurrogatePair(uint highsurrogate, uint lowSurrogate, ou /// The rune /// The chars if is valid. Empty otherwise. /// trueIf is a valid surrogate pair, falseotherwise. - public static bool DecodeSurrogatePair(uint rune, out char [] chars) + public static bool DecodeSurrogatePair (uint rune, out char [] chars) { uint s = rune - 0x10000; uint h = highSurrogateMin + (s >> 10); uint l = lowSurrogateMin + (s & 0x3FF); - if (EncodeSurrogatePair (h, l, out Rune dsp) && dsp == rune) - { + if (EncodeSurrogatePair (h, l, out Rune dsp) && dsp == rune) { chars = new char [] { (char)h, (char)l }; return true; } @@ -603,13 +604,11 @@ public static bool DecodeSurrogatePair(uint rune, out char [] chars) /// The string. /// The chars if is valid. Empty otherwise. /// trueIf is a valid surrogate pair, falseotherwise. - public static bool DecodeSurrogatePair(string str, out char [] chars) + public static bool DecodeSurrogatePair (string str, out char [] chars) { - if (str.Length == 2) - { - chars = str.ToCharArray(); - if (EncodeSurrogatePair(chars[0], chars[1], out _)) - { + if (str.Length == 2) { + chars = str.ToCharArray (); + if (EncodeSurrogatePair (chars [0], chars [1], out _)) { return true; } } @@ -622,9 +621,9 @@ public static bool DecodeSurrogatePair(string str, out char [] chars) /// /// The number of UTF8 bytes expected given the first prefix. /// Is the first byte of a UTF8 sequence. - public static int ExpectedSizeFromFirstByte(byte firstByte) + public static int ExpectedSizeFromFirstByte (byte firstByte) { - var x = first[firstByte]; + var x = first [firstByte]; // Invalid runes, just return 1 for byte, and let higher level pass to print if (x == xx) @@ -806,7 +805,7 @@ public static Rune To (Case toCase, Rune rune) { uint rval = rune.value; switch (toCase) { - case Case.Lower: + case Case.Lower: return new Rune (NStack.Unicode.To (NStack.Unicode.Case.Lower, rval)); case Case.Title: return new Rune (NStack.Unicode.To (NStack.Unicode.Case.Title, rval)); @@ -874,6 +873,20 @@ public static Rune To (Case toCase, Rune rune) /// Rune. public static implicit operator uint (Rune rune) => rune.value; + /// + /// Implicit operator conversion from a C# integer into a rune. + /// + /// Rune representing the C# integer + /// 32-bit Integer. + public static implicit operator Rune (int value) => new Rune ((uint)value); + + /// + /// Implicit operator conversion from a byte to an unsigned integer + /// + /// The unsigned integer representation. + /// Byte. + public static implicit operator Rune (byte byt) => new Rune (byt); + /// /// Implicit operator conversion from a C# char into a rune. /// @@ -905,7 +918,7 @@ public override string ToString () { var buff = new byte [4]; var size = EncodeRune (this, buff, 0); - return System.Text.Encoding.UTF8.GetString(buff, 0, size); + return System.Text.Encoding.UTF8.GetString (buff, 0, size); } /// diff --git a/NStackTests/NStackTests.csproj b/NStackTests/NStackTests.csproj index 125f663..8fdb4af 100644 --- a/NStackTests/NStackTests.csproj +++ b/NStackTests/NStackTests.csproj @@ -10,10 +10,10 @@ 0.20.0 - + - - + + all runtime; build; native; contentfiles; analyzers; buildtransitive diff --git a/NStackTests/RuneTest.cs b/NStackTests/RuneTest.cs index cf54695..65bec72 100644 --- a/NStackTests/RuneTest.cs +++ b/NStackTests/RuneTest.cs @@ -94,7 +94,7 @@ public void TestColumnWidth () var ui = ustring.Make (i); (var runei, var sizei) = ui.DecodeRune (); - Assert.AreEqual (1, Rune.ColumnWidth (runei)); + Assert.AreEqual (2, Rune.ColumnWidth (runei)); Assert.AreEqual ("๓ ฟก", i); Assert.AreEqual (2, runei.ToString ().Length); Assert.AreEqual (4, Rune.RuneLen (runei)); @@ -106,13 +106,13 @@ public void TestColumnWidth () Assert.True (Rune.Valid (ui.ToByteArray ())); Assert.True (Rune.FullRune (ui.ToByteArray ())); (var runeli, var sizeli) = ui.DecodeLastRune (); - Assert.AreEqual (1, Rune.ColumnWidth (runeli)); + Assert.AreEqual (2, Rune.ColumnWidth (runeli)); Assert.AreEqual (2, runeli.ToString ().Length); Assert.AreEqual (4, Rune.RuneLen (runeli)); Assert.AreEqual (sizeli, Rune.RuneLen (runeli)); Assert.IsTrue (Rune.ValidRune (runeli)); - Assert.AreNotEqual (Rune.ColumnWidth (runeh), Rune.ColumnWidth (runei)); + Assert.AreEqual (Rune.ColumnWidth (runeh), Rune.ColumnWidth (runei)); Assert.AreNotEqual (h, i); Assert.AreEqual (runeh.ToString ().Length, runei.ToString ().Length); Assert.AreEqual (Rune.RuneLen (runeh), Rune.RuneLen (runei)); @@ -141,16 +141,16 @@ public void TestColumnWidth () Assert.AreEqual (1, m.ToString ().Length); Assert.AreEqual (3, Rune.RuneLen (m)); var rn = ustring.Make (n).DecodeRune ().rune; - Assert.AreEqual (1, Rune.ColumnWidth (rn)); + Assert.AreEqual (2, Rune.ColumnWidth (rn)); Assert.AreEqual ("๐Ÿ•", rn.ToString ()); Assert.AreEqual (2, rn.ToString ().Length); Assert.AreEqual (4, Rune.RuneLen (rn)); - Assert.AreEqual (1, Rune.ColumnWidth (o)); + Assert.AreEqual (2, Rune.ColumnWidth (o)); Assert.AreEqual ("๐Ÿ•", o.ToString ()); Assert.AreEqual (2, o.ToString ().Length); Assert.AreEqual (4, Rune.RuneLen (o)); var rp = ustring.Make (p).DecodeRune ().rune; - Assert.AreEqual (1, Rune.ColumnWidth (rp)); + Assert.AreEqual (2, Rune.ColumnWidth (rp)); Assert.AreEqual ("๐Ÿ•", p); Assert.AreEqual (2, p.Length); Assert.AreEqual (4, Rune.RuneLen (rp)); @@ -254,25 +254,25 @@ public void TestRune () Assert.AreEqual (1, c.ToString ().Length); Assert.AreEqual ("a", c.ToString ()); Rune d = new Rune (0x10421); - Assert.AreEqual (1, Rune.ColumnWidth (d)); + Assert.AreEqual (2, Rune.ColumnWidth (d)); Assert.AreEqual (2, d.ToString ().Length); Assert.AreEqual ("๐ก", d.ToString ()); Assert.False (Rune.EncodeSurrogatePair ('\ud799', '\udc21', out _)); Assert.Throws (() => new Rune ('\ud799', '\udc21')); Rune e = new Rune ('\ud801', '\udc21'); - Assert.AreEqual (1, Rune.ColumnWidth (e)); + Assert.AreEqual (2, Rune.ColumnWidth (e)); Assert.AreEqual (2, e.ToString ().Length); Assert.AreEqual ("๐ก", e.ToString ()); Assert.False (new Rune ('\ud801').IsValid); Rune f = new Rune ('\ud83c', '\udf39'); - Assert.AreEqual (1, Rune.ColumnWidth (f)); + Assert.AreEqual (2, Rune.ColumnWidth (f)); Assert.AreEqual (2, f.ToString ().Length); Assert.AreEqual ("๐ŸŒน", f.ToString ()); Assert.DoesNotThrow (() => new Rune (0x10ffff)); Rune g = new Rune (0x10ffff); string s = "\U0010ffff"; - Assert.AreEqual (1, Rune.ColumnWidth (g)); - Assert.AreEqual (1, ustring.Make (s).ConsoleWidth); + Assert.AreEqual (2, Rune.ColumnWidth (g)); + Assert.AreEqual (2, ustring.Make (s).ConsoleWidth); Assert.AreEqual (2, g.ToString ().Length); Assert.AreEqual (2, s.Length); Assert.AreEqual ("๔ฟฟ", g.ToString ()); @@ -292,15 +292,15 @@ public void TestRune () Assert.AreEqual (1, j.ToString ().Length); Assert.AreEqual ("ๅฅฝ", j.ToString ()); var k = new Rune ('\ud83d', '\udc02'); - Assert.AreEqual (1, Rune.ColumnWidth (k)); + Assert.AreEqual (2, Rune.ColumnWidth (k)); Assert.AreEqual (2, k.ToString ().Length); Assert.AreEqual ("๐Ÿ‚", k.ToString ()); var l = new Rune ('\ud801', '\udcbb'); - Assert.AreEqual (1, Rune.ColumnWidth (l)); + Assert.AreEqual (2, Rune.ColumnWidth (l)); Assert.AreEqual (2, l.ToString ().Length); Assert.AreEqual ("๐’ป", l.ToString ()); var m = new Rune ('\ud801', '\udccf'); - Assert.AreEqual (1, Rune.ColumnWidth (m)); + Assert.AreEqual (2, Rune.ColumnWidth (m)); Assert.AreEqual (2, m.ToString ().Length); Assert.AreEqual ("๐“", m.ToString ()); var n = new Rune ('\u00e1'); @@ -308,7 +308,7 @@ public void TestRune () Assert.AreEqual (1, n.ToString ().Length); Assert.AreEqual ("รก", n.ToString ()); var o = new Rune ('\ud83d', '\udd2e'); - Assert.AreEqual (1, Rune.ColumnWidth (o)); + Assert.AreEqual (2, Rune.ColumnWidth (o)); Assert.AreEqual (2, o.ToString ().Length); Assert.AreEqual ("๐Ÿ”ฎ", o.ToString ()); var p = new Rune ('\u2329'); @@ -328,11 +328,11 @@ public void TestRune () PrintTextElementCount (ustring.Make ('\u0061', '\u0301'), "aฬ", 1, 2, 2, 1); PrintTextElementCount (ustring.Make ('\u0065', '\u0301'), "eฬ", 1, 2, 2, 1); PrintTextElementCount (ustring.Make (new Rune [] { new Rune (0x1f469), new Rune (0x1f3fd), new Rune ('\u200d'), new Rune (0x1f692) }), - "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš’", 3, 4, 7, 1); + "๐Ÿ‘ฉ๐Ÿฝโ€๐Ÿš’", 6, 4, 7, 1); PrintTextElementCount (ustring.Make (new Rune [] { new Rune (0x1f469), new Rune (0x1f3fd), new Rune ('\u200d'), new Rune (0x1f692) }), - "\U0001f469\U0001f3fd\u200d\U0001f692", 3, 4, 7, 1); + "\U0001f469\U0001f3fd\u200d\U0001f692", 6, 4, 7, 1); PrintTextElementCount (ustring.Make (new Rune ('\ud801', '\udccf')), - "\ud801\udccf", 1, 1, 2, 1); + "\ud801\udccf", 2, 1, 2, 1); } void PrintTextElementCount (ustring us, string s, int consoleWidth, int runeCount, int stringCount, int txtElementCount) @@ -631,18 +631,18 @@ public void Test_Right_To_Left_Runes () Rune r1b = 0x02001b; Rune r9b = 0x02009b; - Assert.AreEqual (1, Rune.ColumnWidth (r0)); - Assert.AreEqual (1, Rune.ColumnWidth (r7)); - Assert.AreEqual (1, Rune.ColumnWidth (r1b)); - Assert.AreEqual (1, Rune.ColumnWidth (r9b)); + Assert.AreEqual (2, Rune.ColumnWidth (r0)); + Assert.AreEqual (2, Rune.ColumnWidth (r7)); + Assert.AreEqual (2, Rune.ColumnWidth (r1b)); + Assert.AreEqual (2, Rune.ColumnWidth (r9b)); Rune.DecodeSurrogatePair ("๐จ", out char [] chars); var rtl = new Rune (chars [0], chars [1]); var rtlp = new Rune ('\ud802', '\ude01'); var s = "\U00010a01"; - Assert.AreEqual (0, Rune.ColumnWidth (rtl)); - Assert.AreEqual (0, Rune.ColumnWidth (rtlp)); + Assert.AreEqual (2, Rune.ColumnWidth (rtl)); + Assert.AreEqual (2, Rune.ColumnWidth (rtlp)); Assert.AreEqual (2, s.Length); } @@ -886,6 +886,22 @@ public void Rune_ColumnWidth_Versus_Ustring_ConsoleWidth () sumRuneWidth = us.Sum (x => Rune.ColumnWidth (x)); Assert.AreEqual (199, sumRuneWidth); } + + [Test] + public void Rune_IsHighSurrogate_IsLowSurrogate () + { + Rune r = '\ud800'; + Assert.IsTrue (r.IsHighSurrogate); + + r = '\udbff'; + Assert.IsTrue (r.IsHighSurrogate); + + r = '\udc00'; + Assert.IsTrue (r.IsLowSurrogate); + + r = '\udfff'; + Assert.IsTrue (r.IsLowSurrogate); + } } } // A Unicode character is considered a bidirectional text control character if it falls into any of the following ranges: U+061c, U+200e-U+200f, U+202a-U+202e, U+2066-U+2069. \ No newline at end of file diff --git a/NStackTests/ustringTest.cs b/NStackTests/ustringTest.cs index e5680de..3e74fd1 100644 --- a/NStackTests/ustringTest.cs +++ b/NStackTests/ustringTest.cs @@ -723,9 +723,9 @@ public void TestConsoleWidth () Assert.AreEqual (1, Rune.ColumnWidth (r)); var fr = new Rune (sc, r); Assert.False (Rune.IsNonSpacingChar (fr)); - Assert.AreEqual (1, Rune.ColumnWidth (fr)); + Assert.AreEqual (2, Rune.ColumnWidth (fr)); var us = ustring.Make (fr); - Assert.AreEqual (1, us.ConsoleWidth); + Assert.AreEqual (2, us.ConsoleWidth); } [Test] @@ -887,5 +887,31 @@ public void Operator_Not_Equal_Ustring_Versus_String () Assert.False (ustr != " "); Assert.False (str != " "); } + + [Test] + public void Ustring_Array_Is_Not_Equal_ToRunes_Array_And_String_Array () + { + var text = "New Test ไฝ "; + ustring us = text; + string s = text; + Assert.AreEqual (10, us.RuneCount); + Assert.AreEqual (10, s.Length); + // The reason is ustring index is related to byte length and not rune length + Assert.AreEqual (12, us.Length); + Assert.AreNotEqual (20320, us [9]); + Assert.AreEqual (20320, s [9]); + Assert.AreEqual (228, us [9]); + Assert.AreEqual ("รค", ((Rune)us [9]).ToString ()); + Assert.AreEqual ("ไฝ ", s [9].ToString ()); + + // Rune array is equal to string array + var usToRunes = us.ToRunes (); + Assert.AreEqual (10, usToRunes.Length); + Assert.AreEqual (10, s.Length); + Assert.AreEqual (20320, usToRunes [9]); + Assert.AreEqual (20320, s [9]); + Assert.AreEqual ("ไฝ ", ((Rune)usToRunes [9]).ToString ()); + Assert.AreEqual ("ไฝ ", s [9].ToString ()); + } } } diff --git a/testenvironments.json b/testenvironments.json new file mode 100644 index 0000000..70dbd0b --- /dev/null +++ b/testenvironments.json @@ -0,0 +1,15 @@ +{ + "version": "1", + "environments": [ + { + "name": "WSL-Ubuntu", + "type": "wsl", + "wslDistribution": "Ubuntu" + }, + { + "name": "WSL-Debian", + "type": "wsl", + "wslDistribution": "Debian" + } + ] +} \ No newline at end of file