]> O.S.I.I.S - jp/crow.git/commitdiff
custom utf8 encoding with tabulation handling, linebreak detection
authorJean-Philippe Bruyère <jp_bruyere@hotmail.com>
Sat, 30 Jan 2021 06:13:38 +0000 (07:13 +0100)
committerj-p <jp_bruyere@hotmail.com>
Sat, 6 Feb 2021 19:28:02 +0000 (20:28 +0100)
Crow/src/ExtensionsMethods.cs
Crow/src/Mono.Cairo/Context.cs
Crow/src/Text/Encoding.cs [new file with mode: 0644]
Crow/src/Text/Extensions.cs [new file with mode: 0644]
Crow/src/Text/TextLine.cs
Crow/src/Text/TextLineCollection.cs
Crow/src/Widgets/Label.cs
Crow/src/Widgets/TextBox.cs

index 4961ea907feafcd31d8a2f7e2d9d7a31831880b9..f8aefaf1d026b0b637ac290072c6b58a7fa96aad 100644 (file)
@@ -197,29 +197,6 @@ namespace Crow
 
                internal static bool IsAnyLineBreakCharacter (this char c) 
                        => c == '\n' || c == '\r' || c == '\u0085' || c == '\u2028' || c == '\u2029';
-               internal static ReadOnlySpan<char> GetLine (this string str, TextLine ls) {
-                       if (ls.Start >= str.Length)
-                               return "".AsSpan ();
-                       return str.AsSpan ().Slice (ls.Start, ls.Length);                               
-               }
-               internal static ReadOnlySpan<char> GetLine (this string str, TextLine ls, int offset) {
-                       int start = ls.Start + offset;
-                       if (start >= str.Length)
-                               return "".AsSpan ();
-                       return str.AsSpan ().Slice (start, ls.Length);
-                               
-               }
-               internal static ReadOnlySpan<char> GetLineIncludingLineBreak (this string str, TextLine ls) {
-                       if (ls.Start >= str.Length)
-                               return "".AsSpan ();
-                       return str.AsSpan ().Slice (ls.Start, ls.LengthIncludingLineBreak);
-               }
-               internal static ReadOnlySpan<char> GetLineIncludingLineBreak (this string str, TextLine ls, int offset) {
-                       int start = ls.Start + offset;
-                       if (start >= str.Length)
-                               return "".AsSpan ();
-                       return str.AsSpan ().Slice (start, ls.LengthIncludingLineBreak);
-               }
        }
 }
 
index a8dfac3438905807341081913b85998fdc8109ad..456009299d7a9d9dae715b671f87bdbdd7fbb426 100644 (file)
@@ -882,7 +882,7 @@ namespace Crow.Cairo {
                public void ShowText (ReadOnlySpan<char> s, int tabSize) {
                        int size = s.Length * 4 + 1;
                        Span<byte> bytes = size > 512 ? new byte[size] : stackalloc byte[size];
-                       int encodedBytes = Encoding.UTF8.GetBytes(s, bytes);
+                       int encodedBytes = Crow.Text.Encoding.ToUtf8 (s, bytes, tabSize);
                        bytes[encodedBytes] = 0;
 
                        NativeMethods.cairo_show_text (handle, ref bytes.Slice (0, encodedBytes + 1).GetPinnableReference ());
@@ -891,7 +891,7 @@ namespace Crow.Cairo {
                        TextExtents extents;
                        int size = s.Length * 4 + 1;
                        Span<byte> bytes = size > 512 ? new byte[size] : stackalloc byte[size];
-                       int encodedBytes = Encoding.UTF8.GetBytes (s, bytes);
+                       int encodedBytes = Crow.Text.Encoding.ToUtf8 (s, bytes, tabSize);
                        bytes[encodedBytes] = 0;
 
                        NativeMethods.cairo_text_extents (handle, ref bytes.Slice (0, encodedBytes + 1).GetPinnableReference (), out extents);
@@ -900,7 +900,7 @@ namespace Crow.Cairo {
                public void TextExtents (ReadOnlySpan<char> s, int tabSize, out TextExtents extents) {
                        int size = s.Length * 4 + 1;
                        Span<byte> bytes = size > 512 ? new byte[size] : stackalloc byte[size];
-                       int encodedBytes = Encoding.UTF8.GetBytes (s, bytes);
+                       int encodedBytes = Crow.Text.Encoding.ToUtf8 (s, bytes, tabSize);
                        bytes[encodedBytes] = 0;
 
                        NativeMethods.cairo_text_extents (handle, ref bytes.Slice (0, encodedBytes + 1).GetPinnableReference (), out extents);
@@ -908,7 +908,7 @@ namespace Crow.Cairo {
                public void TextExtents (ReadOnlySpan<char> s, out TextExtents extents) {
                        int size = s.Length * 4 + 1;
                        Span<byte> bytes = size > 512 ? new byte[size] : stackalloc byte[size];
-                       int encodedBytes = Encoding.UTF8.GetBytes (s, bytes);
+                       int encodedBytes = Crow.Text.Encoding.ToUtf8 (s, bytes);
                        bytes[encodedBytes] = 0;
 
                        NativeMethods.cairo_text_extents (handle, ref bytes.Slice (0, encodedBytes + 1).GetPinnableReference (), out extents);
diff --git a/Crow/src/Text/Encoding.cs b/Crow/src/Text/Encoding.cs
new file mode 100644 (file)
index 0000000..f89ae0e
--- /dev/null
@@ -0,0 +1,61 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace Crow.Text
+{
+    public class Encoding
+    {
+        public static int ToUtf8 (ReadOnlySpan<char> source, Span<byte> buff, int tabWidth = 4) {
+            int c = 0;
+            int encodedBytes = 0;
+            int encodedChar = 0;
+            while (c < source.Length) {
+                if (source[c] < 0xD800) {
+                    if (source[c] == '\t') {
+                        int encTabWidth = tabWidth - encodedChar % tabWidth;
+                        for (int i = 0; i < encTabWidth; i++) {
+                            buff[encodedBytes++] = 0x20;
+                            encodedChar++;
+                        }
+                        c++;
+                        continue;
+                    }
+                    /*unsafe {
+                        fixed (void* bp = &source.GetPinnableReference())
+                        fixed (byte* buffer = &buff.GetPinnableReference())
+                        {
+                            return Utf16toUtf8 ((byte*)bp, buffer, tabWidth);
+                            
+                        }
+                    }*/
+
+
+                    if (source[c] < 0x80) { //1 byte
+                        buff[encodedBytes++] = (byte)source[c++];
+                    }else if (source[c] < 0x7FF) { //2 bytes
+                        buff[encodedBytes++] = (byte)((source[c] >> 6) + 0xC0);
+                        buff[encodedBytes++] = (byte)((source[c] & 0x3F) + 0x80);
+                        c++;
+                    } else { //3 bytes
+                        //ushort ch = (ushort)(source[c] - 0x10000u);
+                        buff[encodedBytes++] = (byte)((source[c] >> 12) + 0xE0);
+                        buff[encodedBytes++] = (byte)((source[c] >> 6) & 0x00BF);
+                        buff[encodedBytes++] = (byte)((source[c] & 0x3F) + 0x80);
+                        c++;
+                    }
+
+                    encodedChar++;
+                    continue;
+                }
+                throw new NotImplementedException();
+            }
+            return encodedBytes;
+        }
+
+
+        /*unsafe static int Utf16toUtf8 (byte* cptr, byte* buff, int tabWidth) {
+
+        }*/
+    }
+}
diff --git a/Crow/src/Text/Extensions.cs b/Crow/src/Text/Extensions.cs
new file mode 100644 (file)
index 0000000..7f5a53b
--- /dev/null
@@ -0,0 +1,50 @@
+using System;
+
+namespace Crow.Text
+{
+    public static class Extensions
+    {
+               public static ReadOnlySpan<char> GetLine (this string str, TextLine ls) {
+                       if (ls.Start >= str.Length)
+                               return "".AsSpan ();
+                       return str.AsSpan ().Slice (ls.Start, ls.Length);
+               }
+               public static ReadOnlySpan<char> GetLine (this string str, TextLine ls, int offset) {
+                       int start = ls.Start + offset;
+                       if (start >= str.Length)
+                               return "".AsSpan ();
+                       return str.AsSpan ().Slice (start, ls.Length);
+
+               }
+               public static ReadOnlySpan<char> GetLineIncludingLineBreak (this string str, TextLine ls) {
+                       if (ls.Start >= str.Length)
+                               return "".AsSpan ();
+                       return str.AsSpan ().Slice (ls.Start, ls.LengthIncludingLineBreak);
+               }
+               public static ReadOnlySpan<char> GetLineBreak (this string str, TextLine ls) {
+                       if (ls.LineBreakLength == 0)
+                               return "".AsSpan ();
+                       return str.AsSpan ().Slice (ls.End, ls.LineBreakLength);
+               }
+               public static ReadOnlySpan<char> GetLineIncludingLineBreak (this string str, TextLine ls, int offset) {
+                       int start = ls.Start + offset;
+                       if (start >= str.Length)
+                               return "".AsSpan ();
+                       return str.AsSpan ().Slice (start, ls.LengthIncludingLineBreak);
+               }
+
+               public static ReadOnlySpan<char> ToCharSpan (this LineBreakKind lineBreak) {
+                       switch (lineBreak) {
+                       case LineBreakKind.Unix:
+                               return "\n".AsSpan ();
+                       case LineBreakKind.Windows:
+                               return "\r\n".AsSpan ();
+                       case LineBreakKind.Other:
+                               return "\r".AsSpan ();
+                       default:
+                               return "\r\n".AsSpan ();
+                       }
+               }
+
+       }
+}
index 5319074868a9d9efeeee77d794c221a6eddeb65e..f5798a3186cfbb513ca54c0cd8c0fe0794c3798a 100644 (file)
@@ -5,6 +5,13 @@ using System.Text;
 
 namespace Crow.Text
 {
+       public enum LineBreakKind
+    {
+               Undefined,
+               Unix,
+               Windows,
+               Other
+    }
        [DebuggerDisplay ("{Start}, {Length}, {LengthInPixel}")]
        public struct TextLine : IComparable<TextLine>
        {
@@ -30,20 +37,8 @@ namespace Crow.Text
                }
                public TextLine WithStartOffset (int start) => new TextLine (Start + start, End, EndIncludingLineBreak);
 
+               
+
                public int CompareTo (TextLine other) => Start - other.Start;
-        /*public ReadOnlySpan<char> 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<char> GetSubStringIncludingLineBreak (string str) {
-    if (Start >= str.Length)
-        return "".AsSpan ();
-    return (str.Length - Start < LengthIncludingLineBreak) ?
-        str.AsSpan ().Slice (Start, LengthIncludingLineBreak) :
-        str.AsSpan ().Slice (Start);
-}*/
     }
 }
index a505409fce942eb69e4f83ed3a9437b655999dfb..586c0d8c79db8c767db63fd133b6ce70863bea89 100644 (file)
@@ -26,7 +26,7 @@ namespace Crow.Text
         }
         #endregion
 
-        public int GetAbsolutePosition (CharLocation loc) => lines[loc.Line].Start + loc.Column;
+        public int GetAbsolutePosition (CharLocation loc) => lines[loc.Line].Start + loc.Column;        
         public CharLocation GetLocation (int absolutePosition) {
             TextLine tl = new TextLine (absolutePosition);
             int result = lines.AsSpan (0, length).BinarySearch (tl);            
@@ -43,12 +43,13 @@ namespace Crow.Text
         }
         public int Count => length;
         public bool IsReadOnly => false;
+        public bool IsEmpty => length == 0;
 
         public TextLine this[int index] { get => lines[index]; set => lines[index] = value; }
 
         public void Add (TextLine item) {
             if (lines.Length < length + 1) {
-                TextLine[] tmp = new TextLine[length + 4];
+                TextLine[] tmp = new TextLine[length * 2];
                 lines.AsSpan ().CopyTo (tmp);
                 lines = tmp;
             }
index 81632de7d380c60b4862ac9cf00ccb9492cd7b87..45ea16bf2d1551ec9ecbfa58c53a3d901ed97671 100644 (file)
@@ -139,6 +139,7 @@ namespace Crow {
                                if (value == _multiline)
                                        return;
                                _multiline = value;
+                               lines = new LineCollection (_multiline ? 4 : 1);
                                NotifyValueChangedAuto (_multiline);
                                RegisterForGraphicUpdate();
                        }
@@ -446,52 +447,78 @@ namespace Crow {
                */
                protected bool textMeasureIsUpToDate = false;
                Size cachedTextSize = default(Size);
-               protected LineCollection lines;         
+               protected LineCollection lines;
+               protected string LineBreak = null;
+               bool mixedLineBreak = false;
+
+               protected void detectLineBreak () {
+                       if (!_multiline)
+                               return;                 
+                       mixedLineBreak = false;
+
+                       if (lines.Count == 0 || lines[0].LineBreakLength == 0) {
+                               LineBreak = Environment.NewLine;
+                               return;
+            }
+                       LineBreak = _text.GetLineBreak (lines[0]).ToString ();
+
+                       for (int i = 1; i < lines.Count; i++) {
+                               ReadOnlySpan<char> lb = _text.GetLineBreak (lines[i]);
+                               if (!lb.SequenceEqual (LineBreak)) {
+                                       mixedLineBreak = true;
+                                       break;
+                }
+                       }
+        }
+
                protected void getLines () {
+
+                       if (lines == null)
+                               lines = new LineCollection (_multiline ? 4 : 1);
+                       else
+                               lines.Clear ();
+
                        if (string.IsNullOrEmpty (_text)) {
-                               lines = new LineCollection(new TextLine[] { new TextLine (0, 0, 0) });
+                               lines.Add (new TextLine (0, 0, 0));
                                return;
                        }
                        if (!_multiline) {
-                               lines = new LineCollection (new TextLine[] { new TextLine (0, _text.Length, _text.Length) });
+                               lines.Add (new TextLine (0, _text.Length, _text.Length));
                                return;
                        }
-
-                       List<TextLine> _lines = new List<TextLine> ();                  
+                                       
                        int start = 0, i = 0;
                        while (i < _text.Length) {
                                char c = _text[i];
                                if (c == '\r') {
                                        if (++i < _text.Length) {
                                                if (_text[i] == '\n')
-                                                       _lines.Add (new TextLine (start, i - 1, ++i));
+                                                       lines.Add (new TextLine (start, i - 1, ++i));
                                                else
-                                                       _lines.Add (new TextLine (start, i - 1, i));
+                                                       lines.Add (new TextLine (start, i - 1, i));
                                        } else
-                                               _lines.Add (new TextLine (start, i - 1, i));
+                                               lines.Add (new TextLine (start, i - 1, i));
                                        start = i;
                                } else if (c == '\n') {
                                        if (++i < _text.Length) {
                                                if (_text[i] == '\r')
-                                                       _lines.Add (new TextLine (start, i - 1, ++i));
+                                                       lines.Add (new TextLine (start, i - 1, ++i));
                                                else
-                                                       _lines.Add (new TextLine (start, i - 1, i));
+                                                       lines.Add (new TextLine (start, i - 1, i));
                                        } else
-                                               _lines.Add (new TextLine (start, i - 1, i));
+                                               lines.Add (new TextLine (start, i - 1, i));
                                        start = i;
 
                                } else if (c == '\u0085' || c == '\u2028' || c == '\u2029')
-                                       _lines.Add (new TextLine (start, i - 1, i));
+                                       lines.Add (new TextLine (start, i - 1, i));
                                else
                                        i++;
                        }
 
                        if (start < i)
-                               _lines.Add (new TextLine (start, _text.Length, _text.Length));
+                               lines.Add (new TextLine (start, _text.Length, _text.Length));
                        else
-                               _lines.Add (new TextLine (_text.Length, _text.Length, _text.Length));
-
-                       lines = new LineCollection (_lines.ToArray ());
+                               lines.Add (new TextLine (_text.Length, _text.Length, _text.Length));                    
                }
                void resetLocationXs () {
                        if (currentLoc.HasValue) {
@@ -540,7 +567,7 @@ namespace Crow {
                #region GraphicObject overrides
                public override int measureRawSize(LayoutingType lt)
                {
-                       if (lines == null)
+                       if ((bool)lines?.IsEmpty)
                                getLines ();
 
                        if (!textMeasureIsUpToDate) {
@@ -646,7 +673,7 @@ namespace Crow {
                                        if (bytes.Length < size)
                                                bytes = size > 512 ? new byte[size] : stackalloc byte[size];
 
-                                       encodedBytes = Encoding.UTF8.GetBytes (_text.GetLine (lines[i]), bytes);
+                                       encodedBytes = Crow.Text.Encoding.ToUtf8 (_text.GetLine (lines[i]), bytes);
                                        bytes[encodedBytes++] = 0;
 
                                        if (lines[i].LengthInPixel < 0) {
@@ -868,6 +895,7 @@ namespace Crow {
                        double cPos = Width.IsFit && !Multiline ? 0 : getX (clientWidth, ls);
                        
                        if (loc.Column >= 0) {
+                               //int encodedBytes = Crow.Text.Encoding2.ToUtf8 (curLine.Slice (0, loc.Column), bytes);
                                loc.VisualCharXPosition = gr.TextExtents (curLine.Slice (0, loc.Column), Interface.TAB_SIZE).XAdvance + cPos;
                                location = loc;
                                return;
@@ -878,7 +906,7 @@ namespace Crow {
 
                        for (int i = 0; i < ls.Length; i++)
                        {
-                               int encodedBytes = Encoding.UTF8.GetBytes (curLine.Slice (i, 1), bytes);
+                               int encodedBytes = Crow.Text.Encoding.ToUtf8 (curLine.Slice (i, 1), bytes);
                                bytes[encodedBytes] = 0;
 
                                gr.TextExtents (bytes, out te);
index 61af412701d63aa0d0b6fa5ab078dfb33f073fa8..282e01c0a019b00580868447cd9a1555c392e9ae 100644 (file)
@@ -32,18 +32,25 @@ namespace Crow
             TextSpan selection = Selection;
             switch (key) {
             case Key.Backspace:
-                if (selection.Length == 0) {
+                if (selection.IsEmpty) {
                     if (selection.Start == 0)
                         return;
-                    update (new TextChange (selection.Start - 1, 1, ""));
+                    if (currentLoc.Value.Column == 0) {
+                        int lbLength = lines[currentLoc.Value.Line - 1].LineBreakLength;
+                        update (new TextChange (selection.Start - lbLength, lbLength, ""));
+                    }else
+                        update (new TextChange (selection.Start - 1, 1, ""));
                 } else
                     update (new TextChange (selection.Start, selection.Length, ""));
                 break;
             case Key.Delete:
-                if (selection.Length == 0) {
+                if (selection.IsEmpty) {
                     if (selection.Start == Text.Length)
                         return;
-                    update (new TextChange (selection.Start, 1, ""));
+                    if (currentLoc.Value.Column >= lines[currentLoc.Value.Line].Length) 
+                        update (new TextChange (selection.Start, lines[currentLoc.Value.Line].LineBreakLength, ""));                        
+                    else
+                        update (new TextChange (selection.Start, 1, ""));
                 } else {
                     if (IFace.Shift)
                         IFace.Clipboard = Text.AsSpan (selection.Start, selection.End).ToString ();
@@ -58,10 +65,12 @@ namespace Crow
                 break;
             case Key.KeypadEnter:
             case Key.Enter:
-                if (Multiline)
-                    update (new TextChange (selection.Start, selection.Length, "\n"));
-                else
-                    OnValidate (this, new ValidateEventArgs(_text));
+                if (Multiline) {
+                    if (string.IsNullOrEmpty (LineBreak))
+                        detectLineBreak ();
+                    update (new TextChange (selection.Start, selection.Length, LineBreak));
+                } else
+                    OnValidate (this, new ValidateEventArgs (_text));
                 break;
             case Key.Escape:
                 selectionStart = null;
@@ -102,14 +111,13 @@ namespace Crow
             _text = tmp.ToString ();
 
             getLines ();
+            selectionStart = null;
+            currentLoc = lines.GetLocation (change.Start + change.ChangedText.Length);
 
             textMeasureIsUpToDate = false;
             NotifyValueChanged ("Text", Text);
             OnTextChanged (this, new TextChangeEventArgs (change));
 
-            selectionStart = null;
-            currentLoc = lines.GetLocation (change.Start + change.ChangedText.Length);
-
             RegisterForGraphicUpdate ();
         }
     }