From 1b5276bae527fc3154a2196a1c071fb753359de8 Mon Sep 17 00:00:00 2001 From: UnknownShadow200 Date: Mon, 23 Apr 2018 14:53:14 +1000 Subject: [PATCH] Vastly simplify line wrapping code, and fix it in C port too --- .../2D/Widgets/Chat/ChatInputWidget.cs | 8 +- ClassicalSharp/2D/Widgets/InputWidget.cs | 4 +- ClassicalSharp/ClassicalSharp.csproj | 1 - ClassicalSharp/Entities/Model/CreeperModel.cs | 11 +- ClassicalSharp/Entities/Model/PigModel.cs | 1 - ClassicalSharp/Entities/Model/SheepModel.cs | 7 +- ClassicalSharp/Entities/Model/SpiderModel.cs | 6 +- ClassicalSharp/Map/World.cs | 13 +- ClassicalSharp/Utils/StringBuffer.cs | 134 ++++++++++- ClassicalSharp/Utils/WrappableStringBuffer.cs | 212 ------------------ src/Client/Client.vcxproj | 2 - src/Client/Client.vcxproj.filters | 6 - src/Client/Program.c | 40 ++++ src/Client/String.c | 95 ++++++++ src/Client/String.h | 6 + src/Client/Widgets.c | 2 - src/Client/WordWrap.c | 141 ------------ src/Client/WordWrap.h | 13 -- 18 files changed, 288 insertions(+), 414 deletions(-) delete mode 100644 ClassicalSharp/Utils/WrappableStringBuffer.cs delete mode 100644 src/Client/WordWrap.c delete mode 100644 src/Client/WordWrap.h diff --git a/ClassicalSharp/2D/Widgets/Chat/ChatInputWidget.cs b/ClassicalSharp/2D/Widgets/Chat/ChatInputWidget.cs index b5a3ccae8..34679e295 100644 --- a/ClassicalSharp/2D/Widgets/Chat/ChatInputWidget.cs +++ b/ClassicalSharp/2D/Widgets/Chat/ChatInputWidget.cs @@ -48,9 +48,11 @@ namespace ClassicalSharp.Gui.Widgets { public override void EnterInput() { - if (!Text.Empty) { - // Don't want trailing spaces in output message - string text = new String(Text.value, 0, Text.TextLength); + // Don't want trailing spaces in output message + int length = Text.Length; + while (length > 0 && Text.value[length - 1] == ' ') { length--; } + if (length > 0) { + string text = new String(Text.value, 0, length); game.Chat.Send(text); } diff --git a/ClassicalSharp/2D/Widgets/InputWidget.cs b/ClassicalSharp/2D/Widgets/InputWidget.cs index 361eca24b..90225318b 100644 --- a/ClassicalSharp/2D/Widgets/InputWidget.cs +++ b/ClassicalSharp/2D/Widgets/InputWidget.cs @@ -10,7 +10,7 @@ namespace ClassicalSharp.Gui.Widgets { public abstract class InputWidget : Widget { public InputWidget(Game game, Font font, string prefix, int maxLines) : base(game) { - Text = new WrappableStringBuffer(Utils.StringLength * maxLines); + Text = new StringBuffer(Utils.StringLength * maxLines); lines = new string[maxLines]; lineSizes = new Size[maxLines]; this.font = font; @@ -43,7 +43,7 @@ namespace ClassicalSharp.Gui.Widgets { /// The raw text entered. /// You should Append() to add more text, as that also updates the caret position and texture. - public WrappableStringBuffer Text; + public StringBuffer Text; /// The maximum number of lines that may be entered. public abstract int UsedLines { get; } diff --git a/ClassicalSharp/ClassicalSharp.csproj b/ClassicalSharp/ClassicalSharp.csproj index fe92a3174..6dc9cf13c 100644 --- a/ClassicalSharp/ClassicalSharp.csproj +++ b/ClassicalSharp/ClassicalSharp.csproj @@ -295,7 +295,6 @@ - diff --git a/ClassicalSharp/Entities/Model/CreeperModel.cs b/ClassicalSharp/Entities/Model/CreeperModel.cs index 835e67a64..28ee5f7ce 100644 --- a/ClassicalSharp/Entities/Model/CreeperModel.cs +++ b/ClassicalSharp/Entities/Model/CreeperModel.cs @@ -32,24 +32,19 @@ namespace ClassicalSharp.Model { .TexOrigin(0, 16) .RotOrigin(0, 6, 2)); } - - /// + public override float NameYOffset { get { return 1.7f; } } - - /// + public override float GetEyeY(Entity entity) { return 22/16f; } - - /// + public override Vector3 CollisionSize { get { return new Vector3(8/16f, 26/16f, 8/16f); } } - /// public override AABB PickingBounds { get { return new AABB(-4/16f, 0, -6/16f, 4/16f, 26/16f, 6/16f); } } - /// public override void DrawModel(Entity p) { game.Graphics.BindTexture(GetTexture(p)); DrawRotate(-p.HeadXRadians, 0, 0, Head, true); diff --git a/ClassicalSharp/Entities/Model/PigModel.cs b/ClassicalSharp/Entities/Model/PigModel.cs index 4ace6a512..10de8afad 100644 --- a/ClassicalSharp/Entities/Model/PigModel.cs +++ b/ClassicalSharp/Entities/Model/PigModel.cs @@ -10,7 +10,6 @@ namespace ClassicalSharp.Model { public PigModel(Game window) : base(window) { SurivalScore = 10; } - /// public override void CreateParts() { vertices = new ModelVertex[boxVertices * 6]; Head = BuildBox(MakeBoxBounds(-4, 8, -14, 4, 16, -6) diff --git a/ClassicalSharp/Entities/Model/SheepModel.cs b/ClassicalSharp/Entities/Model/SheepModel.cs index 20681f0a4..5522a69c5 100644 --- a/ClassicalSharp/Entities/Model/SheepModel.cs +++ b/ClassicalSharp/Entities/Model/SheepModel.cs @@ -65,24 +65,19 @@ namespace ClassicalSharp.Model { .RotOrigin(0, 12, 7)); } - - /// + public override float NameYOffset { get { return Fur ? 1.48125f: 1.075f; } } - /// public override float GetEyeY(Entity entity) { return 20/16f; } - /// public override Vector3 CollisionSize { get { return new Vector3(10/16f, 20/16f, 10/16f); } } - /// public override AABB PickingBounds { get { return new AABB(-6/16f, 0, -13/16f, 6/16f, 23/16f, 10/16f); } } - /// public override void DrawModel(Entity p) { IGraphicsApi gfx = game.Graphics; gfx.BindTexture(GetTexture(p)); diff --git a/ClassicalSharp/Entities/Model/SpiderModel.cs b/ClassicalSharp/Entities/Model/SpiderModel.cs index 8507bbeed..9a17f539f 100644 --- a/ClassicalSharp/Entities/Model/SpiderModel.cs +++ b/ClassicalSharp/Entities/Model/SpiderModel.cs @@ -28,18 +28,14 @@ namespace ClassicalSharp.Model { .RotOrigin(3, 8, 0)); } - /// public override float NameYOffset { get { return 1.0125f; } } - /// public override float GetEyeY(Entity entity) { return 8/16f; } - - /// + public override Vector3 CollisionSize { get { return new Vector3(15/16f, 12/16f, 15/16f); } } - /// public override AABB PickingBounds { get { return new AABB(-5/16f, 0, -11/16f, 5/16f, 12/16f, 15/16f); } } diff --git a/ClassicalSharp/Map/World.cs b/ClassicalSharp/Map/World.cs index 56f7855ef..396354ffa 100644 --- a/ClassicalSharp/Map/World.cs +++ b/ClassicalSharp/Map/World.cs @@ -62,7 +62,6 @@ namespace ClassicalSharp.Map { if (Env.CloudHeight == -1) Env.CloudHeight = height + 2; } - /// Sets the block at the given world coordinates without bounds checking. public void SetBlock(int x, int y, int z, BlockID blockId) { int i = (y * Length + z) * Width + x; blocks[i] = (BlockRaw)blockId; @@ -76,7 +75,6 @@ namespace ClassicalSharp.Map { blocks2[i] = (BlockRaw)(blockId >> 8); } - /// Returns the block at the given world coordinates without bounds checking. public BlockID GetBlock(int x, int y, int z) { int i = (y * Length + z) * Width + x; #if !ONLY_8BIT @@ -86,7 +84,6 @@ namespace ClassicalSharp.Map { #endif } - /// Returns the block at the given world coordinates without bounds checking. public BlockID GetBlock(Vector3I p) { int i = (p.Y * Length + p.Z) * Width + p.X; #if !ONLY_8BIT @@ -96,21 +93,15 @@ namespace ClassicalSharp.Map { #endif } - /// Returns the block at the given world coordinates with bounds checking, - /// returning 0 is the coordinates were outside the map. public BlockID SafeGetBlock(Vector3I p) { return IsValidPos(p.X, p.Y, p.Z) ? GetBlock(p) : Block.Air; } - - /// Returns whether the given world coordinates are contained - /// within the dimensions of the map. + public bool IsValidPos(int x, int y, int z) { return x >= 0 && y >= 0 && z >= 0 && x < Width && y < Height && z < Length; } - - /// Returns whether the given world coordinates are contained - /// within the dimensions of the map. + public bool IsValidPos(Vector3I p) { return p.X >= 0 && p.Y >= 0 && p.Z >= 0 && p.X < Width && p.Y < Height && p.Z < Length; diff --git a/ClassicalSharp/Utils/StringBuffer.cs b/ClassicalSharp/Utils/StringBuffer.cs index 7368256b6..6af2ee943 100644 --- a/ClassicalSharp/Utils/StringBuffer.cs +++ b/ClassicalSharp/Utils/StringBuffer.cs @@ -7,6 +7,7 @@ namespace ClassicalSharp { public char[] value; public int Capacity, Length; + public bool Empty { get { return Length == 0; } } public StringBuffer(int capacity) { this.Capacity = capacity; @@ -39,7 +40,7 @@ namespace ClassicalSharp { internal static int MakeNum(int num) { int len = 0; do { - numBuffer[len] = (char)('0' + (num % 10)); + numBuffer[len] = (char)('0' + (num % 10)); num /= 10; len++; } while (num > 0); return len; @@ -61,6 +62,31 @@ namespace ClassicalSharp { return this; } + public StringBuffer DeleteAt(int index) { + for (int i = index; i < Length - 1; i++) { + value[i] = value[i + 1]; + } + + value[Length - 1] = '\0'; + Length--; + return this; + } + + public StringBuffer InsertAt(int index, char c) { + for (int i = Length - 1; i > index; i--) { + value[i] = value[i - 1]; + } + + value[index] = c; + Length++; + return this; + } + + public StringBuffer Set(string s) { + for (int i = 0; i < s.Length; i++) value[i] = s[i]; + return this; + } + public StringBuffer Clear() { Length = 0; return this; @@ -69,5 +95,111 @@ namespace ClassicalSharp { public override string ToString() { return new String(value, 0, Length); } + + bool IsWrapper(char c) { + return c == '\0' || c == ' ' || c == '-' || c == '>' + || c == '<' || c == '/' || c == '\\'; + } + + unsafe string Substring(int offset, int len) { + if (len == 0) return ""; + char* tmp = stackalloc char[len]; + + // convert %0-f to &0-f for colour preview. + for (int i = 0; i < len; i++) { + tmp[i] = value[offset + i]; + if (tmp[i] != '%' || (i + 1) >= len) continue; + if (IDrawer2D.ValidColCode(tmp[i + 1])) tmp[i] = '&'; + } + return new String(tmp, 0, len); + } + + public void WordWrap(IDrawer2D drawer, string[] lines, int numLines, int lineLen) { + for (int i = 0; i < numLines; i++) { lines[i] = null; } + + int lineStart = 0, lineEnd; + for (int i = 0; i < numLines; i++) { + int nextLineStart = lineStart + lineLen; + // No more text to wrap + if (nextLineStart >= Length) { + lines[i] = Substring(lineStart, Length - lineStart); return; + } + + // Find beginning of last word on current line + for (lineEnd = nextLineStart; lineEnd >= lineStart; lineEnd--) { + if (IsWrapper(value[lineEnd])) break; + } + lineEnd++; // move after wrapper char (i.e. beginning of last word) + + if (lineEnd <= lineStart || lineEnd >= nextLineStart) { + // Three special cases handled by this: + // - Entire line is filled with a single word + // - Last character(s) on current line are wrapper characters + // - First character on next line is a wrapper character (last word ends at current line end) + lines[i] = Substring(lineStart, lineLen); + lineStart += lineLen; + } else { + // Last word in current line does not end in current line (extends onto next line) + // Trim current line to end at beginning of last word + // Set next line to start at beginning of last word + lines[i] = Substring(lineStart, lineEnd - lineStart); + lineStart = lineEnd; + } + } + } + + /// Calculates where the given raw index is located in the wrapped lines. + public void GetCoords(int index, string[] lines, out int coordX, out int coordY) { + if (index == -1) index = Int32.MaxValue; + int total = 0; coordX = -1; coordY = 0; + + for (int y = 0; y < lines.Length; y++) { + int lineLength = LineLength(lines[y]); + if (lineLength == 0) break; + + coordY = y; + if (index < total + lineLength) { + coordX = index - total; break; + } + total += lineLength; + } + if (coordX == -1) coordX = LineLength(lines[coordY]); + } + + static int LineLength(string line) { return line == null ? 0 : line.Length; } + + public int GetBackLength(int index) { + if (index <= 0) return 0; + int start = index; + + bool lookingSpace = value[index] == ' '; + // go back to the end of the previous word + if (lookingSpace) { + while (index > 0 && value[index] == ' ') + index--; + } + + // go back to the start of the current word + while (index > 0 && value[index] != ' ') + index--; + return (start - index); + } + + public int GetForwardLength(int index) { + if (index == -1) return 0; + int start = index; + + bool lookingLetter = value[index] != ' '; + // go forward to the end of the current word + if (lookingLetter) { + while (index < Length && value[index] != ' ') + index++; + } + + // go forward to the start of the next word + while (index < Length && value[index] == ' ') + index++; + return index - start; + } } } diff --git a/ClassicalSharp/Utils/WrappableStringBuffer.cs b/ClassicalSharp/Utils/WrappableStringBuffer.cs deleted file mode 100644 index fce54e433..000000000 --- a/ClassicalSharp/Utils/WrappableStringBuffer.cs +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -using System; - -namespace ClassicalSharp { - - public unsafe sealed class WrappableStringBuffer { - - public int Capacity; - public char[] value; - char[] wrap; - - public WrappableStringBuffer(int capacity) { - this.Capacity = capacity; - value = new char[capacity]; - wrap = new char[capacity]; - } - - public void DeleteAt(int index) { - for (int i = index; i < Capacity - 1; i++) - value[i] = value[i + 1]; - value[Capacity - 1] = '\0'; - } - - public void InsertAt(int index, char c) { - for (int i = Capacity - 1; i > index; i--) - value[i] = value[i - 1]; - value[index] = c; - } - - public void Set(string s) { - for (int i = 0; i < s.Length; i++) value[i] = s[i]; - } - - public void Clear() { - for (int i = 0; i < Capacity; i++) value[i] = '\0'; - } - - public bool Empty { - get { - for (int i = 0; i < Capacity; i++) { - if (value[i] != '\0') return false; - } - return true; - } - } - - public int Length { - get { - int len = Capacity; - for (int i = Capacity - 1; i >= 0; i--) { - if (value[i] != '\0') break; - len--; - } - return len; - } - } - - public int TextLength { - get { - int len = Capacity; - for (int i = Capacity - 1; i >= 0; i--) { - if (value[i] != '\0' && value[i] != ' ') break; - len--; - } - return len; - } - } - - public override string ToString() { - return new String(value, 0, Length); - } - - - public void WordWrap(IDrawer2D drawer, string[] lines, int maxLines, int maxPerLine) { - int len = Length; - int* lineLens = stackalloc int[lines.Length]; - for (int i = 0; i < lines.Length; i++) { - lines[i] = null; - lineLens[i] = 0; - } - - // Need to make a copy because we mutate the characters. - char[] realText = value; - MakeWrapCopy(); - - int usedLines = 0, totalChars = maxPerLine * maxLines; - for (int index = 0; index < totalChars; index += maxPerLine) { - if (value[index] == '\0') break; - - int lineEnd = index + (maxPerLine - 1), nextStart = lineEnd + 1; - usedLines++; - - // Do we need word wrapping? - bool needWrap = !IsWrapper(value[lineEnd]) - && nextStart < totalChars && !IsWrapper(value[nextStart]); - int wrappedLen = needWrap ? WrapLine(index, maxPerLine) : maxPerLine; - - // Calculate the maximum size of this line - int lineLen = maxPerLine; - for (int i = lineEnd; i >= index; i--) { - if (value[i] != '\0') break; - lineLen--; - } - lineLens[index / maxPerLine] = Math.Min(lineLen, wrappedLen); - } - - // Output the used lines - OutputLines(drawer, lines, lineLens, usedLines, maxLines, maxPerLine); - value = realText; - } - - void MakeWrapCopy() { - int len = Length; - for (int i = 0; i < len; i++) - wrap[i] = value[i]; - - for (int i = len; i < Capacity; i++) - wrap[i] = '\0'; - value = wrap; - } - - void OutputLines(IDrawer2D drawer, string[] lines, int* lineLens, int usedLines, int maxLines, int charsPerLine) { - int totalChars = charsPerLine * maxLines; - for (int i = 0; i < totalChars; i++) { - if (value[i] == '\0') value[i] = ' '; - } - // convert %0-f to &0-f for colour preview. - for (int i = 0; i < totalChars - 1; i++) { - if (value[i] == '%' && IDrawer2D.ValidColCode(value[i + 1])) - value[i] = '&'; - } - - usedLines = Math.Max(1, usedLines); - for (int i = 0; i < usedLines; i++) - lines[i] = new String(value, i * charsPerLine, lineLens[i]); - } - - int WrapLine(int index, int lineSize) { - int lineEnd = index + (lineSize - 1); - // wrap - but we don't want to wrap if the entire line is filled. - for (int i = lineEnd; i >= index + 1; i--) { - if (IsWrapper(value[i])) { - for (int j = lineEnd; j >= i + 1; j--) { - InsertAt(index + lineSize, value[j]); - value[j] = ' '; - } - return (i + 1) - index; - } - } - return lineSize; - } - - bool IsWrapper(char c) { - return c == '\0' || c == ' ' || c == '-' || c == '>' - || c == '<' || c == '/' || c == '\\'; - } - - /// Calculates where the given raw index is located in the wrapped lines. - public void GetCoords(int index, string[] lines, out int coordX, out int coordY) { - if (index == -1) index = Int32.MaxValue; - int total = 0; coordX = -1; coordY = 0; - - for (int y = 0; y < lines.Length; y++) { - int lineLength = LineLength(lines[y]); - if (lineLength == 0) break; - - coordY = y; - if (index < total + lineLength) { - coordX = index - total; break; - } - total += lineLength; - } - if (coordX == -1) coordX = LineLength(lines[coordY]); - } - - static int LineLength(string line) { return line == null ? 0 : line.Length; } - - public int GetBackLength(int index) { - if (index <= 0) return 0; - int start = index; - - bool lookingSpace = value[index] == ' '; - // go back to the end of the previous word - if (lookingSpace) { - while (index > 0 && value[index] == ' ') - index--; - } - - // go back to the start of the current word - while (index > 0 && value[index] != ' ') - index--; - return (start - index); - } - - public int GetForwardLength(int index) { - if (index == -1) return 0; - int start = index; - - bool lookingLetter = value[index] != ' '; - // go forward to the end of the current word - if (lookingLetter) { - while (index < Length && value[index] != ' ') - index++; - } - - // go forward to the start of the next word - while (index < Length && value[index] == ' ') - index++; - return index - start; - } - } -} diff --git a/src/Client/Client.vcxproj b/src/Client/Client.vcxproj index 63307e211..95e1c0b7a 100644 --- a/src/Client/Client.vcxproj +++ b/src/Client/Client.vcxproj @@ -250,7 +250,6 @@ - @@ -319,7 +318,6 @@ - diff --git a/src/Client/Client.vcxproj.filters b/src/Client/Client.vcxproj.filters index 94993dbbf..aacd2d432 100644 --- a/src/Client/Client.vcxproj.filters +++ b/src/Client/Client.vcxproj.filters @@ -315,9 +315,6 @@ Header Files\2D - - Header Files\Utils - Header Files\Game @@ -521,9 +518,6 @@ Source Files\2D - - Source Files\Utils - Source Files\Game diff --git a/src/Client/Program.c b/src/Client/Program.c index 405c2c082..158a161d8 100644 --- a/src/Client/Program.c +++ b/src/Client/Program.c @@ -11,6 +11,46 @@ #include "Game.h" int main(int argc, char* argv[]) { + String text1 = String_FromConst("abcd"); + String lines1[3] = { 0 }; + WordWrap_Do(&text1, lines1, 3, 4); + + String text2 = String_FromConst("abcde/fgh"); + String lines2[3] = { 0 }; + WordWrap_Do(&text2, lines2, 3, 4); + + String text3 = String_FromConst("abc/defg"); + String lines3[3] = { 0 }; + WordWrap_Do(&text3, lines3, 3, 4); + + String text4 = String_FromConst("ab/cdef"); + String lines4[3] = { 0 }; + WordWrap_Do(&text4, lines4, 3, 4); + + String text5 = String_FromConst("abcd/efg"); + String lines5[3] = { 0 }; + WordWrap_Do(&text5, lines5, 3, 4); + + String text6 = String_FromConst("abc/efg/hij/"); + String lines6[3] = { 0 }; + WordWrap_Do(&text6, lines6, 3, 4); + + String text7 = String_FromConst("ab cde fgh"); + String lines7[3] = { 0 }; + WordWrap_Do(&text7, lines7, 3, 4); + + String text8 = String_FromConst("ab//cd"); + String lines8[3] = { 0 }; + WordWrap_Do(&text8, lines8, 3, 4); + + String text9 = String_FromConst("a///b"); + String lines9[3] = { 0 }; + WordWrap_Do(&text9, lines9, 3, 4); + + String text10 = String_FromConst("/aaab"); + String lines10[3] = { 0 }; + WordWrap_Do(&text10, lines10, 3, 4); + ErrorHandler_Init("client.log"); Platform_Init(); diff --git a/src/Client/String.c b/src/Client/String.c index d9fd5c3b2..e2e6b0848 100644 --- a/src/Client/String.c +++ b/src/Client/String.c @@ -684,4 +684,99 @@ Int32 StringsBuffer_Compare(StringsBuffer* buffer, UInt32 idxA, UInt32 idxB) { String strA = StringsBuffer_UNSAFE_Get(buffer, idxA); String strB = StringsBuffer_UNSAFE_Get(buffer, idxB); return String_Compare(&strA, &strB); +} + + +bool WordWrap_IsWrapper(UInt8 c) { + return c == NULL || c == ' ' || c == '-' || c == '>' || c == '<' || c == '/' || c == '\\'; +} + +void WordWrap_Do(STRING_REF String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 lineLen) { + Int32 i; + for (i = 0; i < numLines; i++) { lines[i] = String_MakeNull(); } + + Int32 lineStart = 0, lineEnd; + for (i = 0; i < numLines; i++) { + Int32 nextLineStart = lineStart + lineLen; + /* No more text to wrap */ + if (nextLineStart >= text->length) { + lines[i] = String_UNSAFE_SubstringAt(text, lineStart); return; + } + + /* Find beginning of last word on current line */ + for (lineEnd = nextLineStart; lineEnd >= lineStart; lineEnd--) { + if (WordWrap_IsWrapper(text->buffer[lineEnd])) break; + } + lineEnd++; /* move after wrapper char (i.e. beginning of last word)*/ + + if (lineEnd <= lineStart || lineEnd >= nextLineStart) { + /* Three special cases handled by this: */ + /* - Entire line is filled with a single word */ + /* - Last character(s) on current line are wrapper characters */ + /* - First character on next line is a wrapper character (last word ends at current line end) */ + lines[i] = String_UNSAFE_Substring(text, lineStart, lineLen); + lineStart += lineLen; + } else { + /* Last word in current line does not end in current line (extends onto next line) */ + /* Trim current line to end at beginning of last word */ + /* Set next line to start at beginning of last word */ + lines[i] = String_UNSAFE_Substring(text, lineStart, lineEnd - lineStart); + lineStart = lineEnd; + } + } +} + +/* Calculates where the given raw index is located in the wrapped lines. */ +void WordWrap_GetCoords(Int32 index, STRING_PURE String* lines, Int32 numLines, Int32* coordX, Int32* coordY) { + if (index == -1) index = Int32_MaxValue; + Int32 offset = 0; *coordX = -1; *coordY = 0; + + Int32 y; + for (y = 0; y < numLines; y++) { + Int32 lineLength = lines[y].length; + if (lineLength == 0) break; + + *coordY = y; + if (index < offset + lineLength) { + *coordX = index - offset; break; + } + offset += lineLength; + } + if (*coordX == -1) *coordX = lines[*coordY].length; +} + +Int32 WordWrap_GetBackLength(STRING_PURE String* text, Int32 index) { + if (index <= 0) return 0; + if (index >= text->length) { + ErrorHandler_Fail("WordWrap_GetBackLength - index past end of string"); + } + + Int32 start = index; + bool lookingSpace = text->buffer[index] == ' '; + /* go back to the end of the previous word */ + if (lookingSpace) { + while (index > 0 && text->buffer[index] == ' ') { index--; } + } + + /* go back to the start of the current word */ + while (index > 0 && text->buffer[index] != ' ') { index--; } + return start - index; +} + +Int32 WordWrap_GetForwardLength(STRING_PURE String* text, Int32 index) { + if (index == -1) return 0; + if (index >= text->length) { + ErrorHandler_Fail("WordWrap_GetForwardLength - index past end of string"); + } + + Int32 start = index, length = text->length; + bool lookingLetter = text->buffer[index] != ' '; + /* go forward to the end of the current word */ + if (lookingLetter) { + while (index < length && text->buffer[index] != ' ') { index++; } + } + + /* go forward to the start of the next word */ + while (index < length && text->buffer[index] == ' ') { index++; } + return index - start; } \ No newline at end of file diff --git a/src/Client/String.h b/src/Client/String.h index 4f6d986d5..734e9323a 100644 --- a/src/Client/String.h +++ b/src/Client/String.h @@ -4,6 +4,7 @@ /* Implements operations for a string. Also implements conversions betweens strings and numbers. Also implements converting code page 437 indices to/from unicode. + Also implements wrapping a single line of text into multiple lines. Copyright 2017 ClassicalSharp | Licensed under BSD-3 */ @@ -120,4 +121,9 @@ void StringsBuffer_Resize(void** buffer, UInt32* elems, UInt32 elemSize, UInt32 void StringsBuffer_Add(StringsBuffer* buffer, STRING_PURE String* text); void StringsBuffer_Remove(StringsBuffer* buffer, UInt32 index); Int32 StringsBuffer_Compare(StringsBuffer* buffer, UInt32 idxA, UInt32 idxB); + +void WordWrap_Do(STRING_REF String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 lineLen); +void WordWrap_GetCoords(Int32 index, STRING_PURE String* lines, Int32 numLines, Int32* coordX, Int32* coordY); +Int32 WordWrap_GetBackLength(STRING_PURE String* text, Int32 index); +Int32 WordWrap_GetForwardLength(STRING_PURE String* text, Int32 index); #endif \ No newline at end of file diff --git a/src/Client/Widgets.c b/src/Client/Widgets.c index 45cdb72cf..351e42411 100644 --- a/src/Client/Widgets.c +++ b/src/Client/Widgets.c @@ -11,7 +11,6 @@ #include "ModelCache.h" #include "Screens.h" #include "Platform.h" -#include "WordWrap.h" #include "ServerConnection.h" #include "Event.h" #include "Chat.h" @@ -1211,7 +1210,6 @@ void InputWidget_Init(GuiElement* elem) { InputWidget* widget = (InputWidget*)elem; Int32 lines = widget->GetMaxLines(); if (lines > 1) { - /* TODO: Actually make this work */ WordWrap_Do(&widget->Text, widget->Lines, lines, INPUTWIDGET_LEN); } else { widget->Lines[0] = widget->Text; diff --git a/src/Client/WordWrap.c b/src/Client/WordWrap.c deleted file mode 100644 index 85bca0709..000000000 --- a/src/Client/WordWrap.c +++ /dev/null @@ -1,141 +0,0 @@ -#include "WordWrap.h" -#include "Drawer2D.h" -#include "Funcs.h" -#include "Platform.h" -#include "ErrorHandler.h" - -#define WORDWRAP_MAX_LINES_TO_WRAP 128 -#define WORDWRAP_MAX_BUFFER_SIZE 2048 - -void WordWrap_OutputLines(String* text, String* lines, Int32* lineLens, Int32 numLines, Int32 usedLines, Int32 charsPerLine) { - Int32 totalChars = charsPerLine * numLines, i, j; - for (i = 0; i < totalChars; i++) { - if (text->buffer[i] == NULL) text->buffer[i] = ' '; - } - - /* convert %0-f to &0-f for colour preview. */ - for (i = 0; i < totalChars - 1; i++) { - if (text->buffer[i] == '%' && Drawer2D_ValidColCode(text->buffer[i + 1])) { - text->buffer[i] = '&'; - } - } - - usedLines = max(1, usedLines); - for (i = 0; i < usedLines; i++) { - String* dst = &lines[i]; - UInt8* src = &text->buffer[i * charsPerLine]; - for (j = 0; j < lineLens[i]; j++) { String_Append(dst, src[j]); } - } -} - -bool WordWrap_IsWrapper(UInt8 c) { - return c == NULL || c == ' ' || c == '-' || c == '>' - || c == '<' || c == '/' || c == '\\'; -} - -Int32 WordWrap_WrapLine(String* text, Int32 index, Int32 lineSize) { - Int32 lineEnd = index + (lineSize - 1), i, j; - /* wrap - but we don't want to wrap if the entire line is filled. */ - for (i = lineEnd; i >= index + 1; i--) { - if (!WordWrap_IsWrapper(text->buffer[i])) continue; - - for (j = lineEnd; j >= i + 1; j--) { - UInt8 c = text->buffer[j]; - String_InsertAt(text, index + lineSize, c); - text->buffer[j] = ' '; - } - return (i + 1) - index; - } - return lineSize; -} - -void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 maxPerLine) { - Int32 len = text->length, i; - Int32 lineLens[WORDWRAP_MAX_LINES_TO_WRAP]; - for (i = 0; i < numLines; i++) { - String_Clear(&lines[i]); - lineLens[i] = 0; - } - - /* Need to make a copy because we mutate the characters. */ - UInt8 copyBuffer[String_BufferSize(WORDWRAP_MAX_BUFFER_SIZE)]; - String copy = String_InitAndClearArray(copyBuffer); - String_AppendString(©, text); - - Int32 usedLines = 0, index, totalChars = maxPerLine * numLines; - for (index = 0; index < totalChars; index += maxPerLine) { - if (copy.buffer[index] == NULL) break; - usedLines++; - Int32 lineEnd = index + (maxPerLine - 1), nextStart = lineEnd + 1; - - /* Do we need word wrapping? */ - bool needWrap = !WordWrap_IsWrapper(copy.buffer[lineEnd]) - && nextStart < totalChars && !WordWrap_IsWrapper(copy.buffer[nextStart]); - Int32 wrappedLen = needWrap ? WordWrap_WrapLine(©, index, maxPerLine) : maxPerLine; - - /* Calculate the maximum size of this line */ - Int32 lineLen = maxPerLine, i; - for (i = lineEnd; i >= index; i--) { - if (copy.buffer[i] != NULL) break; - lineLen--; - } - lineLens[index / maxPerLine] = min(lineLen, wrappedLen); - } - - /* Output the used lines */ - WordWrap_OutputLines(©, lines, lineLens, numLines, usedLines, maxPerLine); -} - -/* Calculates where the given raw index is located in the wrapped lines. */ -void WordWrap_GetCoords(Int32 index, STRING_PURE String* lines, Int32 numLines, Int32* coordX, Int32* coordY) { - if (index == -1) index = Int32_MaxValue; - Int32 offset = 0; *coordX = -1; *coordY = 0; - - for (Int32 y = 0; y < numLines; y++) { - Int32 lineLength = lines[y].length; - if (lineLength == 0) break; - - *coordY = y; - if (index < offset + lineLength) { - *coordX = index - offset; break; - } - offset += lineLength; - } - if (*coordX == -1) *coordX = lines[*coordY].length; -} - -Int32 WordWrap_GetBackLength(STRING_PURE String* text, Int32 index) { - if (index <= 0) return 0; - if (index >= text->length) { - ErrorHandler_Fail("WordWrap_GetBackLength - index past end of string"); - } - - Int32 start = index; - bool lookingSpace = text->buffer[index] == ' '; - /* go back to the end of the previous word */ - if (lookingSpace) { - while (index > 0 && text->buffer[index] == ' ') { index--; } - } - - /* go back to the start of the current word */ - while (index > 0 && text->buffer[index] != ' ') { index--; } - return start - index; -} - -Int32 WordWrap_GetForwardLength(STRING_PURE String* text, Int32 index) { - if (index == -1) return 0; - if (index >= text->length) { - ErrorHandler_Fail("WordWrap_GetForwardLength - index past end of string"); - } - - Int32 start = index, length = text->length; - bool lookingLetter = text->buffer[index] != ' '; - /* go forward to the end of the current word */ - if (lookingLetter) { - while (index < length && text->buffer[index] != ' ') { index++; } - } - - /* go forward to the start of the next word */ - while (index < length && text->buffer[index] == ' ') { index++; } - return index - start; -} diff --git a/src/Client/WordWrap.h b/src/Client/WordWrap.h deleted file mode 100644 index e3637ab37..000000000 --- a/src/Client/WordWrap.h +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef CC_WORDWRAP_H -#define CC_WORDWRAP_H -#include "String.h" -/* Allows wrapping a single line of text into multiple lines. - Copyright 2014-2017 ClassicalSharp | Licensed under BSD-3 -*/ - -void WordWrap_Do(STRING_TRANSIENT String* text, STRING_TRANSIENT String* lines, Int32 numLines, Int32 maxPerLine); -/* Calculates where the given raw index is located in the wrapped lines. */ -void WordWrap_GetCoords(Int32 index, STRING_PURE String* lines, Int32 numLines, Int32* coordX, Int32* coordY); -Int32 WordWrap_GetBackLength(STRING_PURE String* text, Int32 index); -Int32 WordWrap_GetForwardLength(STRING_PURE String* text, Int32 index); -#endif \ No newline at end of file