diff --git a/examples/Borders/Program.cs b/examples/Borders/Program.cs index faf0eab..054f8e0 100644 --- a/examples/Borders/Program.cs +++ b/examples/Borders/Program.cs @@ -1,4 +1,3 @@ -using System; using Spectre.Console; using Spectre.Console.Rendering; diff --git a/examples/Links/Program.cs b/examples/Links/Program.cs index 6ceaa6a..68b52ed 100644 --- a/examples/Links/Program.cs +++ b/examples/Links/Program.cs @@ -1,4 +1,3 @@ -using System; using Spectre.Console; namespace Links diff --git a/src/Spectre.Console/Internal/Extensions/StringExtensions.cs b/src/Spectre.Console/Internal/Extensions/StringExtensions.cs index 567be83..1c79d0a 100644 --- a/src/Spectre.Console/Internal/Extensions/StringExtensions.cs +++ b/src/Spectre.Console/Internal/Extensions/StringExtensions.cs @@ -1,6 +1,7 @@ using System; using System.Collections.Generic; using System.Text; +using Spectre.Console.Rendering; namespace Spectre.Console.Internal { @@ -11,9 +12,9 @@ namespace Spectre.Console.Internal private static readonly bool _alreadyNormalized = Environment.NewLine.Equals("\n", StringComparison.OrdinalIgnoreCase); - public static int CellLength(this string text, Encoding encoding) + public static int CellLength(this string text, RenderContext context) { - return Cell.GetCellLength(encoding, text); + return Cell.GetCellLength(context, text); } public static string NormalizeLineEndings(this string text, bool native = false) diff --git a/src/Spectre.Console/Internal/Text/Cell.cs b/src/Spectre.Console/Internal/Text/Cell.cs index 89ef993..dad3a70 100644 --- a/src/Spectre.Console/Internal/Text/Cell.cs +++ b/src/Spectre.Console/Internal/Text/Cell.cs @@ -1,155 +1,28 @@ -// Taken and modified from NStack project by Miguel de Icaza, licensed under BSD-3 -// https://github.com/migueldeicaza/NStack/blob/3fc024fb2c2e99927d3e12991570fb54db8ce01e/NStack/unicode/Rune.ColumnWidth.cs - -using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text; +using Spectre.Console.Rendering; +using Wcwidth; namespace Spectre.Console.Internal { internal static class Cell { - [SuppressMessage("Design", "RCS1169:Make field read-only.")] - [SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1500:Braces for multi-line statements should not share line")] - private static readonly uint[,] _combining = new uint[,] + public static int GetCellLength(RenderContext context, string text) { - { 0x0300, 0x036F }, { 0x0483, 0x0486 }, { 0x0488, 0x0489 }, - { 0x0591, 0x05BD }, { 0x05BF, 0x05BF }, { 0x05C1, 0x05C2 }, - { 0x05C4, 0x05C5 }, { 0x05C7, 0x05C7 }, { 0x0600, 0x0603 }, - { 0x0610, 0x0615 }, { 0x064B, 0x065E }, { 0x0670, 0x0670 }, - { 0x06D6, 0x06E4 }, { 0x06E7, 0x06E8 }, { 0x06EA, 0x06ED }, - { 0x070F, 0x070F }, { 0x0711, 0x0711 }, { 0x0730, 0x074A }, - { 0x07A6, 0x07B0 }, { 0x07EB, 0x07F3 }, { 0x0901, 0x0902 }, - { 0x093C, 0x093C }, { 0x0941, 0x0948 }, { 0x094D, 0x094D }, - { 0x0951, 0x0954 }, { 0x0962, 0x0963 }, { 0x0981, 0x0981 }, - { 0x09BC, 0x09BC }, { 0x09C1, 0x09C4 }, { 0x09CD, 0x09CD }, - { 0x09E2, 0x09E3 }, { 0x0A01, 0x0A02 }, { 0x0A3C, 0x0A3C }, - { 0x0A41, 0x0A42 }, { 0x0A47, 0x0A48 }, { 0x0A4B, 0x0A4D }, - { 0x0A70, 0x0A71 }, { 0x0A81, 0x0A82 }, { 0x0ABC, 0x0ABC }, - { 0x0AC1, 0x0AC5 }, { 0x0AC7, 0x0AC8 }, { 0x0ACD, 0x0ACD }, - { 0x0AE2, 0x0AE3 }, { 0x0B01, 0x0B01 }, { 0x0B3C, 0x0B3C }, - { 0x0B3F, 0x0B3F }, { 0x0B41, 0x0B43 }, { 0x0B4D, 0x0B4D }, - { 0x0B56, 0x0B56 }, { 0x0B82, 0x0B82 }, { 0x0BC0, 0x0BC0 }, - { 0x0BCD, 0x0BCD }, { 0x0C3E, 0x0C40 }, { 0x0C46, 0x0C48 }, - { 0x0C4A, 0x0C4D }, { 0x0C55, 0x0C56 }, { 0x0CBC, 0x0CBC }, - { 0x0CBF, 0x0CBF }, { 0x0CC6, 0x0CC6 }, { 0x0CCC, 0x0CCD }, - { 0x0CE2, 0x0CE3 }, { 0x0D41, 0x0D43 }, { 0x0D4D, 0x0D4D }, - { 0x0DCA, 0x0DCA }, { 0x0DD2, 0x0DD4 }, { 0x0DD6, 0x0DD6 }, - { 0x0E31, 0x0E31 }, { 0x0E34, 0x0E3A }, { 0x0E47, 0x0E4E }, - { 0x0EB1, 0x0EB1 }, { 0x0EB4, 0x0EB9 }, { 0x0EBB, 0x0EBC }, - { 0x0EC8, 0x0ECD }, { 0x0F18, 0x0F19 }, { 0x0F35, 0x0F35 }, - { 0x0F37, 0x0F37 }, { 0x0F39, 0x0F39 }, { 0x0F71, 0x0F7E }, - { 0x0F80, 0x0F84 }, { 0x0F86, 0x0F87 }, { 0x0F90, 0x0F97 }, - { 0x0F99, 0x0FBC }, { 0x0FC6, 0x0FC6 }, { 0x102D, 0x1030 }, - { 0x1032, 0x1032 }, { 0x1036, 0x1037 }, { 0x1039, 0x1039 }, - { 0x1058, 0x1059 }, { 0x1160, 0x11FF }, { 0x135F, 0x135F }, - { 0x1712, 0x1714 }, { 0x1732, 0x1734 }, { 0x1752, 0x1753 }, - { 0x1772, 0x1773 }, { 0x17B4, 0x17B5 }, { 0x17B7, 0x17BD }, - { 0x17C6, 0x17C6 }, { 0x17C9, 0x17D3 }, { 0x17DD, 0x17DD }, - { 0x180B, 0x180D }, { 0x18A9, 0x18A9 }, { 0x1920, 0x1922 }, - { 0x1927, 0x1928 }, { 0x1932, 0x1932 }, { 0x1939, 0x193B }, - { 0x1A17, 0x1A18 }, { 0x1B00, 0x1B03 }, { 0x1B34, 0x1B34 }, - { 0x1B36, 0x1B3A }, { 0x1B3C, 0x1B3C }, { 0x1B42, 0x1B42 }, - { 0x1B6B, 0x1B73 }, { 0x1DC0, 0x1DCA }, { 0x1DFE, 0x1DFF }, - { 0x200B, 0x200F }, { 0x202A, 0x202E }, { 0x2060, 0x2063 }, - { 0x206A, 0x206F }, { 0x20D0, 0x20EF }, { 0x302A, 0x302F }, - { 0x3099, 0x309A }, { 0xA806, 0xA806 }, { 0xA80B, 0xA80B }, - { 0xA825, 0xA826 }, { 0xFB1E, 0xFB1E }, { 0xFE00, 0xFE0F }, - { 0xFE20, 0xFE23 }, { 0xFEFF, 0xFEFF }, { 0xFFF9, 0xFFFB }, - { 0x10A01, 0x10A03 }, { 0x10A05, 0x10A06 }, { 0x10A0C, 0x10A0F }, - { 0x10A38, 0x10A3A }, { 0x10A3F, 0x10A3F }, { 0x1D167, 0x1D169 }, - { 0x1D173, 0x1D182 }, { 0x1D185, 0x1D18B }, { 0x1D1AA, 0x1D1AD }, - { 0x1D242, 0x1D244 }, { 0xE0001, 0xE0001 }, { 0xE0020, 0xE007F }, - { 0xE0100, 0xE01EF }, - }; - - public static int GetCellLength(Encoding encoding, string text) - { - return text.Sum(c => GetCellLength(encoding, c)); - } - - public static int GetCellLength(Encoding encoding, char rune) - { - if (rune == '\r' || rune == '\n') + return text.Sum(rune => { - return 0; - } - - // Is it represented by a single byte? - // In that case we don't have to calculate the - // actual cell width. - if (encoding.GetByteCount(new[] { rune }) == 1) - { - return 1; - } - - var irune = (uint)rune; - if (irune < 32) - { - return 0; - } - - if (irune < 127) - { - return 1; - } - - if (irune >= 0x7f && irune <= 0xa0) - { - return 0; - } - - // Binary search in table of non-spacing characters - if (BinarySearch(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 + - ((irune >= 0x1100 && - (irune <= 0x115f || /* Hangul Jamo init. consonants */ - irune == 0x2329 || irune == 0x232a || - (irune >= 0x2e80 && irune <= 0xa4cf && - irune != 0x303f) || /* CJK ... Yi */ - (irune >= 0xac00 && irune <= 0xd7a3) || /* Hangul Syllables */ - (irune >= 0xf900 && irune <= 0xfaff) || /* CJK Compatibility Ideographs */ - (irune >= 0xfe10 && irune <= 0xfe19) || /* Vertical forms */ - (irune >= 0xfe30 && irune <= 0xfe6f) || /* CJK Compatibility Forms */ - (irune >= 0xff00 && irune <= 0xff60) || /* Fullwidth Forms */ - (irune >= 0xffe0 && irune <= 0xffe6) || - (irune >= 0x20000 && irune <= 0x2fffd) || - (irune >= 0x30000 && irune <= 0x3fffd))) ? 1 : 0); - } - - private static int BinarySearch(uint rune, uint[,] table, int max) - { - var min = 0; - int mid; - - if (rune < table[0, 0] || rune > table[max, 1]) - { - return 0; - } - - while (max >= min) - { - mid = (min + max) / 2; - if (rune > table[mid, 1]) + if (context.LegacyConsole) { - min = mid + 1; + // Is it represented by a single byte? + // In that case we don't have to calculate the + // actual cell width. + if (context.Encoding.GetByteCount(new[] { rune }) == 1) + { + return 1; + } } - else if (rune < table[mid, 0]) - { - max = mid - 1; - } - else - { - return 1; - } - } - return 0; + return UnicodeCalculator.GetWidth(rune); + }); } } } diff --git a/src/Spectre.Console/Rendering/Segment.cs b/src/Spectre.Console/Rendering/Segment.cs index 68cabb5..4833b88 100644 --- a/src/Spectre.Console/Rendering/Segment.cs +++ b/src/Spectre.Console/Rendering/Segment.cs @@ -2,7 +2,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; -using System.Text; using Spectre.Console.Internal; namespace Spectre.Console.Rendering @@ -82,11 +81,11 @@ namespace Spectre.Console.Rendering /// Gets the number of cells that this segment /// occupies in the console. /// - /// The encoding to use. + /// The render context. /// The number of cells that this segment occupies in the console. - public int CellLength(Encoding encoding) + public int CellLength(RenderContext context) { - return Text.CellLength(encoding); + return Text.CellLength(context); } /// @@ -266,17 +265,17 @@ namespace Spectre.Console.Rendering /// /// The segment to split. /// The overflow strategy to use. - /// The encoding to use. + /// The render context. /// The maximum width. /// A list of segments that has been split. - public static List SplitOverflow(Segment segment, Overflow? overflow, Encoding encoding, int width) + public static List SplitOverflow(Segment segment, Overflow? overflow, RenderContext context, int width) { if (segment is null) { throw new ArgumentNullException(nameof(segment)); } - if (segment.CellLength(encoding) <= width) + if (segment.CellLength(context) <= width) { return new List(1) { segment }; } @@ -288,7 +287,7 @@ namespace Spectre.Console.Rendering if (overflow == Overflow.Fold) { - var totalLength = segment.Text.CellLength(encoding); + var totalLength = segment.Text.CellLength(context); var lengthLeft = totalLength; while (lengthLeft > 0) { @@ -311,12 +310,12 @@ namespace Spectre.Console.Rendering return result; } - internal static Segment TruncateWithEllipsis(string text, Style style, Encoding encoding, int maxWidth) + internal static Segment TruncateWithEllipsis(string text, Style style, RenderContext context, int maxWidth) { return SplitOverflow( new Segment(text, style), Overflow.Ellipsis, - encoding, + context, maxWidth)[0]; } diff --git a/src/Spectre.Console/Rendering/SegmentLine.cs b/src/Spectre.Console/Rendering/SegmentLine.cs index 234ceb7..f1483d6 100644 --- a/src/Spectre.Console/Rendering/SegmentLine.cs +++ b/src/Spectre.Console/Rendering/SegmentLine.cs @@ -1,7 +1,6 @@ using System.Collections.Generic; using System.Diagnostics.CodeAnalysis; using System.Linq; -using System.Text; namespace Spectre.Console.Rendering { @@ -19,11 +18,11 @@ namespace Spectre.Console.Rendering /// /// Gets the cell width of the segment line. /// - /// The encoding to use. + /// The render context. /// The cell width of the segment line. - public int CellWidth(Encoding encoding) + public int CellWidth(RenderContext context) { - return this.Sum(line => line.CellLength(encoding)); + return this.Sum(line => line.CellLength(context)); } /// diff --git a/src/Spectre.Console/Spectre.Console.csproj b/src/Spectre.Console/Spectre.Console.csproj index 9153cb1..cdb627d 100644 --- a/src/Spectre.Console/Spectre.Console.csproj +++ b/src/Spectre.Console/Spectre.Console.csproj @@ -35,6 +35,7 @@ all runtime; build; native; contentfiles; analyzers; buildtransitive + diff --git a/src/Spectre.Console/Widgets/Padder.cs b/src/Spectre.Console/Widgets/Padder.cs index d1a33dd..6e6dff4 100644 --- a/src/Spectre.Console/Widgets/Padder.cs +++ b/src/Spectre.Console/Widgets/Padder.cs @@ -83,7 +83,7 @@ namespace Spectre.Console } // Missing space on right side? - var lineWidth = line.CellWidth(context.Encoding); + var lineWidth = line.CellWidth(context); var diff = width - lineWidth - Padding.Left - Padding.Right; if (diff > 0) { diff --git a/src/Spectre.Console/Widgets/Panel.cs b/src/Spectre.Console/Widgets/Panel.cs index 022e4ce..c4ae217 100644 --- a/src/Spectre.Console/Widgets/Panel.cs +++ b/src/Spectre.Console/Widgets/Panel.cs @@ -101,7 +101,7 @@ namespace Spectre.Console content.AddRange(line); // Do we need to pad the panel? - var length = line.Sum(segment => segment.CellLength(context.Encoding)); + var length = line.Sum(segment => segment.CellLength(context)); if (length < childWidth) { var diff = childWidth - length; @@ -138,9 +138,9 @@ namespace Spectre.Console var rightSpacing = 0; var headerWidth = panelWidth - (EdgeWidth * 2); - var header = Segment.TruncateWithEllipsis(Header.Text, Header.Style ?? borderStyle, context.Encoding, headerWidth); + var header = Segment.TruncateWithEllipsis(Header.Text, Header.Style ?? borderStyle, context, headerWidth); - var excessWidth = headerWidth - header.CellLength(context.Encoding); + var excessWidth = headerWidth - header.CellLength(context); if (excessWidth > 0) { switch (Header.Alignment ?? Justify.Left) diff --git a/src/Spectre.Console/Widgets/Paragraph.cs b/src/Spectre.Console/Widgets/Paragraph.cs index f7d83e2..4fdc1f5 100644 --- a/src/Spectre.Console/Widgets/Paragraph.cs +++ b/src/Spectre.Console/Widgets/Paragraph.cs @@ -119,8 +119,8 @@ namespace Spectre.Console return new Measurement(0, 0); } - var min = _lines.Max(line => line.Max(segment => segment.CellLength(context.Encoding))); - var max = _lines.Max(x => x.CellWidth(context.Encoding)); + var min = _lines.Max(line => line.Max(segment => segment.CellLength(context))); + var max = _lines.Max(x => x.CellWidth(context)); return new Measurement(min, Math.Min(max, maxWidth)); } @@ -144,7 +144,7 @@ namespace Spectre.Console var justification = context.Justification ?? Alignment ?? Justify.Left; foreach (var (_, _, last, line) in lines.Enumerate()) { - var length = line.Sum(l => l.StripLineEndings().CellLength(context.Encoding)); + var length = line.Sum(l => l.StripLineEndings().CellLength(context)); if (length < maxWidth) { // Justify right side @@ -193,7 +193,7 @@ namespace Spectre.Console private List SplitLines(RenderContext context, int maxWidth) { - if (_lines.Max(x => x.CellWidth(context.Encoding)) <= maxWidth) + if (_lines.Max(x => x.CellWidth(context)) <= maxWidth) { return Clone(); } @@ -244,15 +244,15 @@ namespace Spectre.Console continue; } - var length = current.CellLength(context.Encoding); + var length = current.CellLength(context); if (length > maxWidth) { // The current segment is longer than the width of the console, // so we will need to crop it up, into new segments. - var segments = Segment.SplitOverflow(current, Overflow, context.Encoding, maxWidth); + var segments = Segment.SplitOverflow(current, Overflow, context, maxWidth); if (segments.Count > 0) { - if (line.CellWidth(context.Encoding) + segments[0].CellLength(context.Encoding) > maxWidth) + if (line.CellWidth(context) + segments[0].CellLength(context) > maxWidth) { lines.Add(line); line = new SegmentLine(); @@ -272,7 +272,7 @@ namespace Spectre.Console } else { - if (line.CellWidth(context.Encoding) + length > maxWidth) + if (line.CellWidth(context) + length > maxWidth) { line.Add(Segment.Empty); lines.Add(line); diff --git a/src/Spectre.Console/Widgets/Table.cs b/src/Spectre.Console/Widgets/Table.cs index 5d5625a..827a440 100644 --- a/src/Spectre.Console/Widgets/Table.cs +++ b/src/Spectre.Console/Widgets/Table.cs @@ -250,7 +250,7 @@ namespace Spectre.Console result.AddRange(cell[cellRowIndex]); // Pad cell content right - var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context.Encoding)); + var length = cell[cellRowIndex].Sum(segment => segment.CellLength(context)); if (length < columnWidths[cellIndex]) { result.Add(new Segment(new string(' ', columnWidths[cellIndex] - length)));