From: Jean-Philippe Bruyère Date: Mon, 11 Jan 2021 23:37:48 +0000 (+0100) Subject: test span X-Git-Url: https://git.osiis.dedyn.io/?a=commitdiff_plain;h=2345e7ff09d9aadf5f686ddf3c12013e675b291b;p=jp%2Fcrow.git test span --- diff --git a/Crow/Crow.csproj b/Crow/Crow.csproj index faede56d..281e5729 100644 --- a/Crow/Crow.csproj +++ b/Crow/Crow.csproj @@ -1,7 +1,8 @@ - netstandard2.0 + + netstandard2.1 $(CrowVersion) $(CrowPackageVersion) diff --git a/Crow/src/ExtensionsMethods.cs b/Crow/src/ExtensionsMethods.cs index 0ac07c11..a40b25fc 100644 --- a/Crow/src/ExtensionsMethods.cs +++ b/Crow/src/ExtensionsMethods.cs @@ -180,7 +180,7 @@ namespace Crow } public static bool IsWhiteSpaceOrNewLine (this char c) { - return c == '\t' || c == '\r' || c == '\n' || char.IsWhiteSpace (c); + return c == '\t' || c.IsAnyLineBreakCharacter() || char.IsWhiteSpace (c); } public static object GetDefaultValue(this object obj) { @@ -193,6 +193,32 @@ namespace Crow public static FileSystemInfo [] GetFileSystemInfosOrdered (this DirectoryInfo di) => di.GetFileSystemInfos ().OrderBy (f => f.Attributes).ThenBy (f => f.Name).ToArray (); + + internal static bool IsAnyLineBreakCharacter (this char c) + => c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029'; + internal static ReadOnlySpan GetLine (this string str, LineSpan ls) { + if (ls.Start >= str.Length) + return "".AsSpan (); + return str.AsSpan ().Slice (ls.Start, ls.Length); + } + internal static ReadOnlySpan GetLine (this string str, LineSpan ls, int offset) { + int start = ls.Start + offset; + if (start >= str.Length) + return "".AsSpan (); + return str.AsSpan ().Slice (start, ls.Length); + + } + internal static ReadOnlySpan GetLineIncludingLineBreak (this string str, LineSpan ls) { + if (ls.Start >= str.Length) + return "".AsSpan (); + return str.AsSpan ().Slice (ls.Start, ls.LengthIncludingLineBreak); + } + internal static ReadOnlySpan GetLineIncludingLineBreak (this string str, LineSpan ls, int offset) { + int start = ls.Start + offset; + if (start >= str.Length) + return "".AsSpan (); + return str.AsSpan ().Slice (start, ls.LengthIncludingLineBreak); + } } } diff --git a/Crow/src/Mono.Cairo/Context.cs b/Crow/src/Mono.Cairo/Context.cs index 6463ecf0..57f6c517 100644 --- a/Crow/src/Mono.Cairo/Context.cs +++ b/Crow/src/Mono.Cairo/Context.cs @@ -878,10 +878,35 @@ namespace Crow.Cairo { public TextExtents TextExtents(string s) { TextExtents extents; - NativeMethods.cairo_text_extents (handle, s, out extents); + NativeMethods.cairo_text_extents (handle, TerminateUtf8(s), out extents); return extents; } + public void ShowText (ReadOnlySpan s, int tabSize) { + int size = s.Length * 4 + 1; + Span bytes = size > 512 ? new byte[size] : stackalloc byte[size]; + int encodedBytes = Encoding.UTF8.GetBytes(s, bytes); + bytes[encodedBytes] = 0; + NativeMethods.cairo_show_text (handle, ref bytes.Slice (0, encodedBytes + 1).GetPinnableReference ()); + } + public TextExtents TextExtents (ReadOnlySpan s, int tabSize) { + TextExtents extents; + int size = s.Length * 4 + 1; + Span bytes = size > 512 ? new byte[size] : stackalloc byte[size]; + int encodedBytes = Encoding.UTF8.GetBytes (s, bytes); + bytes[encodedBytes] = 0; + + NativeMethods.cairo_text_extents (handle, ref bytes.Slice (0, encodedBytes + 1).GetPinnableReference (), out extents); + return extents; + } + public void TextExtents (ReadOnlySpan s, int tabSize, out TextExtents extents) { + int size = s.Length * 4 + 1; + Span bytes = size > 512 ? new byte[size] : stackalloc byte[size]; + int encodedBytes = Encoding.UTF8.GetBytes (s, bytes); + bytes[encodedBytes] = 0; + + NativeMethods.cairo_text_extents (handle, ref bytes.Slice(0, encodedBytes + 1).GetPinnableReference(), out extents); + } public TextExtents GlyphExtents (Glyph[] glyphs) { diff --git a/Crow/src/Mono.Cairo/NativeMethods.cs b/Crow/src/Mono.Cairo/NativeMethods.cs index 66b528a1..4f93c411 100644 --- a/Crow/src/Mono.Cairo/NativeMethods.cs +++ b/Crow/src/Mono.Cairo/NativeMethods.cs @@ -751,9 +751,6 @@ namespace Crow.Cairo [DllImport (cairo, CallingConvention=CallingConvention.Cdecl)] internal static extern void cairo_show_page (IntPtr cr); - [DllImport (cairo, CallingConvention=CallingConvention.Cdecl)] - internal static extern void cairo_show_text (IntPtr cr, byte[] text); - [DllImport (cairo, CallingConvention=CallingConvention.Cdecl)] internal static extern Status cairo_status (IntPtr cr); @@ -839,8 +836,17 @@ namespace Crow.Cairo internal static extern IntPtr cairo_svg_surface_restrict_to_version (IntPtr surface, SvgVersion version); #endregion - [DllImport (cairo, CallingConvention=CallingConvention.Cdecl)] - internal static extern void cairo_text_extents (IntPtr cr, string text, out TextExtents extents); + [DllImport (cairo, CallingConvention = CallingConvention.Cdecl)] + internal static extern void cairo_show_text (IntPtr cr, byte[] text); + + [DllImport (cairo, CallingConvention = CallingConvention.Cdecl)] + internal static extern void cairo_text_extents (IntPtr cr, byte[] utf8, out TextExtents extents); + + [DllImport (cairo, CallingConvention = CallingConvention.Cdecl)] + internal static extern void cairo_show_text (IntPtr cr, ref byte utf8); + + [DllImport (cairo, CallingConvention = CallingConvention.Cdecl)] + internal static extern void cairo_text_extents (IntPtr cr, ref byte utf8, out TextExtents extents); [DllImport (cairo, CallingConvention=CallingConvention.Cdecl)] internal static extern void cairo_text_path (IntPtr ct, byte[] utf8); diff --git a/Crow/src/Widgets/Label.cs b/Crow/src/Widgets/Label.cs index 3b1e4ea4..369d495b 100644 --- a/Crow/src/Widgets/Label.cs +++ b/Crow/src/Widgets/Label.cs @@ -8,8 +8,38 @@ using System.Linq; using Crow.Cairo; using System.Text.RegularExpressions; using System.ComponentModel; +using System.Text; namespace Crow { + internal struct LineSpan + { + public readonly int Start; + public readonly int End; + public readonly int EndIncludingLineBreak; + public int Length => End - Start; + public int LengthIncludingLineBreak => EndIncludingLineBreak - Start; + public int LineBreakLength => EndIncludingLineBreak - End; + public LineSpan (int start, int end, int endIncludingLineBreak) { + Start = start; + End = end; + EndIncludingLineBreak = endIncludingLineBreak; + } + public LineSpan WithStartOffset (int start) => new LineSpan (Start + start, End, EndIncludingLineBreak); + /*public ReadOnlySpan GetSubString (string str) { + if (Start >= str.Length) + return "".AsSpan(); + return str.Length - Start < Length ? + str.AsSpan().Slice (Start, Length) : + str.AsSpan().Slice (Start); + } + public ReadOnlySpan GetSubStringIncludingLineBreak (string str) { + if (Start >= str.Length) + return "".AsSpan (); + return (str.Length - Start < LengthIncludingLineBreak) ? + str.AsSpan ().Slice (Start, LengthIncludingLineBreak) : + str.AsSpan ().Slice (Start); + }*/ + } public class Label : Widget { #region CTOR @@ -28,7 +58,7 @@ namespace Crow { //TODO:change protected to private #region private and protected fields - string _text = "label"; + string _text; Alignment _textAlignment; bool horizontalStretch; bool verticalStretch; @@ -112,20 +142,14 @@ namespace Crow { [DefaultValue("label")] public string Text { - get { - return lines == null ? - _text : lines.Aggregate((i, j) => i + Interface.LineBreak + j); - } + get => _text; set { - if (string.Equals (value, _text, StringComparison.Ordinal)) + if (_text.AsSpan ().SequenceEqual (value.AsSpan ())) return; _text = value; - if (string.IsNullOrEmpty(_text)) - _text = ""; - lines = getLines; OnTextChanged (this, new TextChangeEventArgs (Text)); @@ -135,7 +159,7 @@ namespace Crow { [DefaultValue(false)] public bool Selectable { - get { return _selectable; } + get => _selectable; set { if (value == _selectable) @@ -150,7 +174,7 @@ namespace Crow { [DefaultValue(false)] public bool Multiline { - get { return _multiline; } + get => _multiline; set { if (value == _multiline) @@ -198,11 +222,12 @@ namespace Crow { gr.FontOptions = Interface.FontRenderingOptions; gr.Antialias = Interface.Antialias; try { - string l = lines [_currentLine]; - if (_currentCol < l.Length) + //string l = lines [_currentLine].GetSubString (_text); + ; + /*if (_currentCol < l.Length) l = l.Remove (Math.Min (_currentCol, l.Length)); - l = l.Replace ("\t", new String (' ', Interface.TAB_SIZE)); - return (int)Math.Ceiling (gr.TextExtents (l).XAdvance); + l = l.Replace ("\t", new String (' ', Interface.TAB_SIZE));*/ + return (int)Math.Ceiling (gr.TextExtents (_text.GetLine (lines[_currentLine], _currentCol), Interface.TAB_SIZE).XAdvance); } catch { System.Diagnostics.Debug.WriteLine ("xpos measuring fault in label"); return 0; @@ -266,12 +291,7 @@ namespace Crow { /// /// return char at CurrentLine, CurrentColumn /// - [XmlIgnore]protected Char CurrentChar - { - get { - return lines [CurrentLine][CurrentColumn]; - } - } + [XmlIgnore]protected Char CurrentChar => _text[lines[CurrentLine].Start + CurrentColumn]; /// /// ordered selection start and end positions in char units /// @@ -297,25 +317,41 @@ namespace Crow { if (SelRelease < 0 || SelBegin < 0) return ""; if (selectionStart.Y == selectionEnd.Y) - return lines [selectionStart.Y].Substring (selectionStart.X, selectionEnd.X - selectionStart.X); - string tmp = ""; - tmp = lines [selectionStart.Y].Substring (selectionStart.X); - for (int l = selectionStart.Y + 1; l < selectionEnd.Y; l++) { - tmp += Interface.LineBreak + lines [l]; - } - tmp += Interface.LineBreak + lines [selectionEnd.Y].Substring (0, selectionEnd.X); - return tmp; + return _text.Substring (lines[selectionStart.Y].Start + selectionStart.X, selectionEnd.X - selectionStart.X); + + StringBuilder tmp = new StringBuilder (_text.GetLineIncludingLineBreak (lines[selectionStart.Y], selectionStart.X).ToString ()); + for (int l = selectionStart.Y + 1; l < selectionEnd.Y; l++) + tmp.Append (_text.GetLineIncludingLineBreak (lines[l]).ToString ()); + tmp.Append (_text.Substring (lines[selectionEnd.Y].Start, selectionEnd.X)); + return tmp.ToString (); } } [XmlIgnore]public bool selectionIsEmpty { get { return SelRelease < 0; } } - List lines; - List getLines { + List lines; + List getLines { get { - return _multiline ? - Regex.Split (_text, "\r\n|\r|\n|\\\\n").ToList() : - new List(new string[] { _text }); + if (string.IsNullOrEmpty(_text)) + return new List (new LineSpan[] { new LineSpan (0, 0, 0) }); + if (!_multiline) + return new List (new LineSpan[] { new LineSpan (0, _text.Length, _text.Length) }); + List lines = new List (); + bool lineBreak = false; + int start = 0, end = 0; + for (int i = 0; i < _text.Length; i++) { + if (_text[i] == '\r' || _text[i] == '\n') { + if (lineBreak) + continue; + lineBreak = true; + end = i; + }else if (lineBreak) { + lines.Add (new LineSpan (start, end, i)); + start = end = i; + lineBreak = false; + } + } + return lines; } } /// @@ -353,7 +389,7 @@ namespace Crow { //skip white spaces while (!char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) CurrentColumn--; - while (char.IsLetterOrDigit (lines [CurrentLine] [CurrentColumn]) && CurrentColumn > 0) + while (char.IsLetterOrDigit (this.CurrentChar) && CurrentColumn > 0) CurrentColumn--; if (!char.IsLetterOrDigit (this.CurrentChar)) CurrentColumn++; @@ -377,16 +413,14 @@ namespace Crow { return; CurrentLine--; CurrentColumn = lines [CurrentLine].Length; - lines [CurrentLine] += lines [CurrentLine + 1]; - lines.RemoveAt (CurrentLine + 1); - OnTextChanged (this, new TextChangeEventArgs (Text)); + Text = _text.Remove (lines[CurrentLine].End, lines[CurrentLine].LineBreakLength); return; } CurrentColumn--; - lines [CurrentLine] = lines [CurrentLine].Remove (CurrentColumn, 1); + Text = _text.Remove (lines[CurrentLine].Start + CurrentColumn, 1); } else { - int linesToRemove = selectionEnd.Y - selectionStart.Y + 1; + /*int linesToRemove = selectionEnd.Y - selectionStart.Y + 1; int l = selectionStart.Y; if (linesToRemove > 0) { @@ -401,9 +435,9 @@ namespace Crow { lines [l] = lines [l].Remove (selectionStart.X, selectionEnd.X - selectionStart.X); CurrentColumn = selectionStart.X; SelBegin = -1; - SelRelease = -1; + SelRelease = -1;*/ } - OnTextChanged (this, new TextChangeEventArgs (Text)); + //OnTextChanged (this, new TextChangeEventArgs (Text)); } /// /// Insert new string at caret position, should be sure no line break is inside. @@ -411,7 +445,7 @@ namespace Crow { /// String. protected void Insert(string str) { - if (!selectionIsEmpty) + /*if (!selectionIsEmpty) this.DeleteChar (); if (_multiline) { string[] strLines = Regex.Split (str, "\r\n|\r|\n|" + @"\\n").ToArray(); @@ -423,21 +457,26 @@ namespace Crow { CurrentColumn += strLines[i].Length; } } else { + ReadOnlySpan a = _text.AsSpan ().Slice (0 ,lines[CurrentLine].Start + CurrentColumn); + ReadOnlySpan b = _text.AsSpan ().Slice (lines[CurrentLine].Start + CurrentColumn); + + _text = string.Concat( (a, str.AsSpan(), b); + lines [CurrentLine] = lines [CurrentLine].Insert (CurrentColumn, str); CurrentColumn += str.Length; } - OnTextChanged (this, new TextChangeEventArgs (Text)); + OnTextChanged (this, new TextChangeEventArgs (Text));*/ } /// /// Insert a line break. /// protected void InsertLineBreak() { - lines.Insert(CurrentLine + 1, lines[CurrentLine].Substring(CurrentColumn)); + /*lines.Insert(CurrentLine + 1, lines[CurrentLine].Substring(CurrentColumn)); lines [CurrentLine] = lines [CurrentLine].Substring (0, CurrentColumn); CurrentLine++; CurrentColumn = 0; - OnTextChanged (this, new TextChangeEventArgs (Text)); + OnTextChanged (this, new TextChangeEventArgs (Text));*/ } bool textMeasureIsUpToDate = false; Size cachedTextSize = default(Size); @@ -462,11 +501,9 @@ namespace Crow { cachedTextSize.Height = (int)Math.Ceiling ((fe.Ascent+fe.Descent) * Math.Max (1, lines.Count)); try { - for (int i = 0; i < lines.Count; i++) { - string l = lines[i].Replace ("\t", new String (' ', Interface.TAB_SIZE)); - - TextExtents tmp = gr.TextExtents (l); - + TextExtents tmp = default; + for (int i = 0; i < lines.Count; i++) { + gr.TextExtents (_text.GetLine (lines[i]), Interface.TAB_SIZE, out tmp); if (tmp.XAdvance > te.XAdvance) te = tmp; } @@ -597,31 +634,38 @@ namespace Crow { #endregion //****** debug selection ************* -// if (SelRelease >= 0) { -// new SolidColor(Color.DarkGreen).SetAsSource(gr); -// Rectangle R = new Rectangle ( -// rText.X + (int)SelEndCursorPos - 3, -// rText.Y + (int)(SelRelease.Y * (fe.Ascent+fe.Descent)), -// 6, -// (int)(fe.Ascent+fe.Descent)); -// gr.Rectangle (R); -// gr.Fill (); -// } -// if (SelBegin >= 0) { -// new SolidColor(Color.DarkRed).SetAsSource(gr); -// Rectangle R = new Rectangle ( -// rText.X + (int)SelStartCursorPos - 3, -// rText.Y + (int)(SelBegin.Y * (fe.Ascent+fe.Descent)), -// 6, -// (int)(fe.Ascent+fe.Descent)); -// gr.Rectangle (R); -// gr.Fill (); -// } + // if (SelRelease >= 0) { + // new SolidColor(Color.DarkGreen).SetAsSource(gr); + // Rectangle R = new Rectangle ( + // rText.X + (int)SelEndCursorPos - 3, + // rText.Y + (int)(SelRelease.Y * (fe.Ascent+fe.Descent)), + // 6, + // (int)(fe.Ascent+fe.Descent)); + // gr.Rectangle (R); + // gr.Fill (); + // } + // if (SelBegin >= 0) { + // new SolidColor(Color.DarkRed).SetAsSource(gr); + // Rectangle R = new Rectangle ( + // rText.X + (int)SelStartCursorPos - 3, + // rText.Y + (int)(SelBegin.Y * (fe.Ascent+fe.Descent)), + // 6, + // (int)(fe.Ascent+fe.Descent)); + // gr.Rectangle (R); + // gr.Fill (); + // } //******************* - + if (string.IsNullOrEmpty (_text)) { + gr.Restore (); + return; + } for (int i = 0; i < lines.Count; i++) { - string l = lines [i].Replace ("\t", new String (' ', Interface.TAB_SIZE)); - int lineLength = (int)gr.TextExtents (l).XAdvance; + if (lines[i].Length == 0) + continue; + ReadOnlySpan chars = _text.GetLine (lines[i]); + + //string l = lines [i].Replace ("\t", new String (' ', Interface.TAB_SIZE)); + int lineLength = (int)gr.TextExtents (chars, Interface.TAB_SIZE).XAdvance; Rectangle lineRect = new Rectangle ( rText.X, rText.Y + i * (int)(fe.Ascent+fe.Descent), @@ -636,13 +680,11 @@ namespace Crow { // TextAlignment == Alignment.TopRight || // TextAlignment == Alignment.BottomRight) // lineRect.X += (rText.Width - lineLength); - if (string.IsNullOrWhiteSpace (l)) - continue; Foreground.SetAsSource (IFace, gr); gr.MoveTo (lineRect.X,(double)rText.Y + fe.Ascent + (fe.Ascent+fe.Descent) * i) ; - gr.ShowText (l); + gr.ShowText (chars, Interface.TAB_SIZE); gr.Fill (); if (Selectable) { @@ -672,7 +714,7 @@ namespace Crow { gr.Clip (); gr.SetSource (SelectionForeground); gr.MoveTo (lineRect.X, rText.Y + fe.Ascent + (fe.Ascent+fe.Descent) * i); - gr.ShowText (l); + gr.ShowText (chars, Interface.TAB_SIZE); gr.Fill (); gr.Restore (); } @@ -774,27 +816,31 @@ namespace Crow { double cPos = 0f; CurrentLine = (int)(mouseLocalPos.Y / (fe.Ascent+fe.Descent)); - + //fix cu if (CurrentLine >= lines.Count) CurrentLine = lines.Count - 1; + LineSpan ls = lines[CurrentLine]; + ReadOnlySpan curLine = _text.GetLine (lines[CurrentLine]); + switch (TextAlignment) { case Alignment.Center: case Alignment.Top: case Alignment.Bottom: - cPos+= ClientRectangle.Width - gr.TextExtents(lines [CurrentLine]).Width/2.0; + cPos += ClientRectangle.Width - gr.TextExtents (curLine, Interface.TAB_SIZE).Width / 2.0; break; case Alignment.Right: case Alignment.TopRight: case Alignment.BottomRight: - cPos += ClientRectangle.Width - gr.TextExtents(lines [CurrentLine]).Width; + cPos += ClientRectangle.Width - gr.TextExtents (curLine, Interface.TAB_SIZE).Width; break; } + for (int i = 0; i < lines[CurrentLine].Length; i++) { - string c = lines [CurrentLine].Substring (i, 1); + /*string c = .Substring (i, 1); if (c == "\t") c = new string (' ', Interface.TAB_SIZE); @@ -810,7 +856,7 @@ namespace Crow { return; } - cPos += te.XAdvance; + cPos += te.XAdvance;*/ } CurrentColumn = lines[CurrentLine].Length; textCursorPos = cPos; @@ -831,9 +877,9 @@ namespace Crow { double GetXFromTextPointer(Context gr, Point pos) { try { - string l = lines [pos.Y].Substring (0, pos.X). - Replace ("\t", new String (' ', Interface.TAB_SIZE)); - return gr.TextExtents (l).XAdvance; + /*string l = lines [pos.Y].Substring (0, pos.X). + Replace ("\t", new String (' ', Interface.TAB_SIZE));*/ + return gr.TextExtents (_text.GetLine(lines[pos.Y], pos.X), Interface.TAB_SIZE).XAdvance; } catch{ return -1; } diff --git a/Samples/PerfTests/Properties/launchSettings.json b/Samples/PerfTests/Properties/launchSettings.json new file mode 100644 index 00000000..2ffae322 --- /dev/null +++ b/Samples/PerfTests/Properties/launchSettings.json @@ -0,0 +1,8 @@ +{ + "profiles": { + "PerfTests": { + "commandName": "Project", + "commandLineArgs": "1" + } + } +} \ No newline at end of file