// ClassicalSharp copyright 2014-2016 UnknownShadow200 | Licensed under MIT using System; using System.Drawing; #if ANDROID using Android.Graphics; #endif namespace ClassicalSharp.Gui.Widgets { public sealed partial class TextGroupWidget : Widget { public void SetText(int index, string text) { gfx.DeleteTexture(ref Textures[index]); DrawTextArgs args = new DrawTextArgs(text, font, true); linkData[index] = default(LinkData); LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0; if (!String.IsNullOrEmpty(text)) { Texture tex = NextToken(text, 0, ref prevFlags) == -1 ? DrawSimple(ref args) : DrawAdvanced(ref args, index, text); game.Drawer2D.ReducePadding(ref tex, Utils.Floor(args.Font.Size), 3); tex.X1 = CalcPos(HorizontalAnchor, XOffset, tex.Width, game.Width); tex.Y1 = CalcY(index, tex.Height); Textures[index] = tex; lines[index] = text; } else { int height = PlaceholderHeight[index] ? defaultHeight : 0; int y = CalcY(index, height); Textures[index] = new Texture(-1, 0, y, 0, height, 0, 0); lines[index] = null; } UpdateDimensions(); } Texture DrawSimple(ref DrawTextArgs args) { return game.Drawer2D.MakeChatTextTexture(ref args, 0, 0); } unsafe Texture DrawAdvanced(ref DrawTextArgs args, int index, string text) { LinkData data = Split(index, text); Size total = Size.Empty; Size* partSizes = stackalloc Size[data.parts.Length]; linkData[index] = data; for (int i = 0; i < data.parts.Length; i++) { args.Text = data.parts[i]; args.Font = (i & 1) == 0 ? font : underlineFont; partSizes[i] = game.Drawer2D.MeasureChatSize(ref args); total.Height = Math.Max(partSizes[i].Height, total.Height); total.Width += partSizes[i].Width; } using (IDrawer2D drawer = game.Drawer2D) using (Bitmap bmp = IDrawer2D.CreatePow2Bitmap(total)) { drawer.SetBitmap(bmp); int x = 0; for (int i = 0; i < data.parts.Length; i++) { args.Text = data.parts[i]; args.Font = (i & 1) == 0 ? font : underlineFont; Size size = partSizes[i]; drawer.DrawChatText(ref args, x, 0); data.bounds[i].X = x; data.bounds[i].Width = size.Width; x += size.Width; } return drawer.Make2DTexture(bmp, total, 0, 0); } } LinkData Split(int index, string line) { int start = 0, lastEnd = 0, count = 0; LinkData data = default(LinkData); data.parts = new string[GetTokensCount(index, line)]; data.urls = new string[data.parts.Length]; data.bounds = new Rectangle[data.parts.Length]; LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0; while ((start = NextToken(line, start, ref prevFlags)) >= 0) { int nextEnd = line.IndexOf(' ', start); if (nextEnd == -1) { nextEnd = line.Length; data.flags |= LinkFlags.Continue; } data.AddPart(count, GetPart(line, lastEnd, start)); // word bit data.AddPart(count + 1, GetPart(line, start, nextEnd)); // url bit count += 2; if ((prevFlags & LinkFlags.Append) != 0) { string url = linkData[index - 1].LastUrl + data.urls[count - 1]; data.urls[count - 1] = url; data.parts[count - 2] = ""; UpdatePreviousUrls(index - 1, url); } if ((prevFlags & LinkFlags.NewLink) != 0) data.flags |= LinkFlags.NewLink; start = nextEnd; lastEnd = nextEnd; } if (lastEnd < line.Length) data.AddPart(count, GetPart(line, lastEnd, line.Length)); // word bit return data; } void UpdatePreviousUrls(int i, string url) { while (i >= 0 && linkData[i].urls != null && (linkData[i].flags & LinkFlags.Continue) != 0) { linkData[i].LastUrl = url; if (linkData[i].urls.Length > 2 || (linkData[i].flags & LinkFlags.NewLink) != 0) break; i--; } } string GetPart(string line, int start, int end) { string part = line.Substring(start, end - start); int colIndex = line.LastIndexOf('&', start, start); // We may split up a line into say %e // url and word both need to have %e at the start. if (colIndex >= 0 && colIndex < line.Length - 1) { if (game.Drawer2D.ValidColour(line[colIndex + 1])) part = "&" + line[colIndex + 1] + part; } return part; } int NextToken(string line, int start, ref LinkFlags prevFlags) { bool isWrapped = start == 0 && line.StartsWith("> "); if ((prevFlags & LinkFlags.Continue) != 0 && isWrapped) { prevFlags = 0; if (!Utils.IsUrlPrefix(Utils.StripColours(line), 2)) prevFlags |= LinkFlags.Append; else prevFlags |= LinkFlags.NewLink; return 2; } prevFlags = LinkFlags.NewLink; int nextHttp = line.IndexOf("http://", start); int nextHttps = line.IndexOf("https://", start); return nextHttp == -1 ? nextHttps : nextHttp; } int GetTokensCount(int index, string line) { int start = 0, lastEnd = 0, count = 0; LinkFlags prevFlags = index > 0 ? linkData[index - 1].flags : 0; while ((start = NextToken(line, start, ref prevFlags)) >= 0) { int nextEnd = line.IndexOf(' ', start); if (nextEnd == -1) nextEnd = line.Length; start = nextEnd; lastEnd = nextEnd; count += 2; } if (lastEnd < line.Length) count++; return count; } struct LinkData { public Rectangle[] bounds; public string[] parts, urls; public LinkFlags flags; public void AddPart(int index, string part) { parts[index] = part; urls[index] = part; } public string LastUrl { get { return urls[parts.Length - 1]; } set { urls[parts.Length - 1] = value; } } } [Flags] enum LinkFlags : byte { Continue = 2, // "part1" "> part2" type urls Append = 4, // used for internally combining "part2" and "part2" NewLink = 8, // used to signify that part2 is a separate url from part1 } } }