public static VtmlToken[] Tokenize(ILogger errorLogger, string vtml) { List <VtmlToken> tokenized = new List <VtmlToken>(); Stack <VtmlTagToken> tokenStack = new Stack <VtmlTagToken>(); string text = ""; string tag = ""; bool insideTag = false; for (int pos = 0; pos < vtml.Length; pos++) { if (vtml[pos] == '<') { insideTag = true; if (text.Length > 0) { text = text .Replace(">", ">") .Replace("<", "<") .Replace(" ", " ") ; if (tokenStack.Count > 0) { tokenStack.Peek().ChildElements.Add(new VtmlTextToken() { Text = text }); } else { tokenized.Add(new VtmlTextToken() { Text = text }); } } text = ""; continue; } if (vtml[pos] == '>') { if (!insideTag) { errorLogger.Error("Found closing tag char > but no tag was opened at " + pos + ". Use >/< if you want to display them as plain characters. See debug log for full text."); errorLogger.VerboseDebug(vtml); } insideTag = false; // </div> if (tag.Length > 0 && tag[0] == '/') { if (tokenStack.Count == 0 || tokenStack.Peek().Name != tag.Substring(1)) { if (tokenStack.Count == 0) { errorLogger.Error("Found closing tag <" + tag.Substring(1) + "> at position " + pos + " but it was never opened. See debug log for full text."); } else { errorLogger.Error("Found closing tag <" + tag.Substring(1) + "> at position " + pos + " but <" + tokenStack.Peek().Name + "> should be closed first. See debug log for full text."); } errorLogger.VerboseDebug(vtml); } if (tokenStack.Count > 0) { tokenStack.Pop(); } tag = ""; continue; } VtmlTagToken tagToken; // <br> if (tag == "br") { tagToken = new VtmlTagToken() { Name = "br" }; if (tokenStack.Count > 0) { tokenStack.Peek().ChildElements.Add(tagToken); } else { tokenized.Add(tagToken); } tag = ""; continue; } // <div a=b /> if (vtml[pos - 1] == '/') { tagToken = parseTagAttributes(tag.Substring(0, tag.Length - 1)); if (tokenStack.Count > 0) { tokenStack.Peek().ChildElements.Add(tagToken); } else { tokenized.Add(tagToken); } tag = ""; continue; } // <div a=b> { tagToken = parseTagAttributes(tag); if (tokenStack.Count > 0) { tokenStack.Peek().ChildElements.Add(tagToken); } else { tokenized.Add(tagToken); } tokenStack.Push(tagToken); tag = ""; } continue; } if (insideTag) { tag += vtml[pos]; } else { text += vtml[pos]; } } if (text.Length > 0) { text = text .Replace(">", ">") .Replace("<", "<") //.Replace(" ", "") //.Replace(" ", "") .Replace(" ", " ") ; tokenized.Add(new VtmlTextToken() { Text = text }); } return(tokenized.ToArray()); }
static void Richtextify(ICoreClientAPI capi, VtmlToken token, ref List <RichTextComponentBase> elems, Stack <CairoFont> fontStack, Action <LinkTextComponent> didClickLink) { if (token is VtmlTagToken) { VtmlTagToken tagToken = token as VtmlTagToken; switch (tagToken.Name) { case "br": elems.Add(new RichTextComponent(capi, "\r\n", fontStack.Peek())); break; case "i": CairoFont font = fontStack.Peek().Clone(); font.Slant = FontSlant.Italic; fontStack.Push(font); foreach (var val in tagToken.ChildElements) { Richtextify(capi, val, ref elems, fontStack, didClickLink); } fontStack.Pop(); break; case "a": LinkTextComponent cmp = new LinkTextComponent(capi, tagToken.ContentText, fontStack.Peek(), didClickLink); tagToken.Attributes.TryGetValue("href", out cmp.Href); elems.Add(cmp); break; case "icon": string iconName; tagToken.Attributes.TryGetValue("name", out iconName); IconComponent iconcmp = new IconComponent(capi, iconName, fontStack.Peek()); elems.Add(iconcmp); break; case "itemstack": string code; string type; float size = (float)fontStack.Peek().GetFontExtents().Height; EnumFloat floatType = EnumFloat.Inline; string floattypestr; if (tagToken.Attributes.TryGetValue("floattype", out floattypestr)) { if (!Enum.TryParse(floattypestr, out floatType)) { floatType = EnumFloat.Inline; } } tagToken.Attributes.TryGetValue("code", out code); if (!tagToken.Attributes.TryGetValue("type", out type)) { type = "block"; } ItemStack stack; if (type == "item") { stack = new ItemStack(capi.World.GetItem(new AssetLocation(code))); } else { stack = new ItemStack(capi.World.GetBlock(new AssetLocation(code))); } float sizemul = 1f; if (tagToken.Attributes.TryGetValue("rsize", out var sizemulstr)) { sizemul = sizemulstr.ToFloat(); } SlideshowItemstackTextComponent stckcmp = new SlideshowItemstackTextComponent(capi, new ItemStack[] { stack }, size / RuntimeEnv.GUIScale, floatType); stckcmp.renderSize *= sizemul; stckcmp.VerticalAlign = EnumVerticalAlign.Middle; if (tagToken.Attributes.TryGetValue("offx", out var offxstr)) { stckcmp.offX = GuiElement.scaled(offxstr.ToFloat(0)); } if (tagToken.Attributes.TryGetValue("offy", out var offystr)) { stckcmp.offY = GuiElement.scaled(offystr.ToFloat(0)); } elems.Add(stckcmp); break; case "font": fontStack.Push(getFont(tagToken, fontStack)); foreach (var val in tagToken.ChildElements) { Richtextify(capi, val, ref elems, fontStack, didClickLink); } fontStack.Pop(); break; case "clear": elems.Add(new ClearFloatTextComponent(capi)); break; case "strong": fontStack.Push(fontStack.Peek().Clone().WithWeight(Cairo.FontWeight.Bold)); foreach (var val in tagToken.ChildElements) { Richtextify(capi, val, ref elems, fontStack, didClickLink); } fontStack.Pop(); break; } if (tagToken.Name != null && TagConverters.ContainsKey(tagToken.Name)) { RichTextComponentBase elem = TagConverters[tagToken.Name](capi, tagToken, fontStack, didClickLink); if (elem != null) { elems.Add(elem); } } } else { VtmlTextToken textToken = token as VtmlTextToken; elems.Add(new RichTextComponent(capi, textToken.Text, fontStack.Peek())); } }
static CairoFont getFont(VtmlTagToken tag, Stack <CairoFont> fontStack) { double size = 0; double lineHeight = 1; string fontName = ""; EnumTextOrientation orient = EnumTextOrientation.Left; double[] color = ColorUtil.WhiteArgbDouble; FontWeight weight = FontWeight.Normal; CairoFont prevFont = fontStack.Pop(); if (!tag.Attributes.ContainsKey("size") || !double.TryParse(tag.Attributes["size"], NumberStyles.Any, GlobalConstants.DefaultCultureInfo, out size)) { size = prevFont.UnscaledFontsize; } if (!tag.Attributes.ContainsKey("family")) { fontName = prevFont.Fontname; } else { fontName = tag.Attributes["family"]; } if (!tag.Attributes.ContainsKey("color") || !parseHexColor(tag.Attributes["color"], out color)) { color = prevFont.Color; } double opacity; if (tag.Attributes.ContainsKey("opacity") && double.TryParse(tag.Attributes["opacity"], NumberStyles.Any, GlobalConstants.DefaultCultureInfo, out opacity)) { color = (double[])color.Clone(); color[3] *= opacity; } if (tag.Attributes.ContainsKey("weight")) { weight = tag.Attributes["weight"] == "bold" ? FontWeight.Bold : FontWeight.Normal; } else { weight = prevFont.FontWeight; } if (!tag.Attributes.ContainsKey("lineheight") || !double.TryParse(tag.Attributes["lineheight"], NumberStyles.Any, GlobalConstants.DefaultCultureInfo, out lineHeight)) { lineHeight = prevFont.LineHeightMultiplier; } if (tag.Attributes.ContainsKey("align")) { switch (tag.Attributes["align"]) { case "left": orient = EnumTextOrientation.Left; break; case "right": orient = EnumTextOrientation.Right; break; case "center": orient = EnumTextOrientation.Center; break; case "justify": orient = EnumTextOrientation.Justify; break; } } else { orient = prevFont.Orientation; } fontStack.Push(prevFont); return(new CairoFont(size, fontName, color).WithWeight(weight).WithLineHeightMultiplier(lineHeight).WithOrientation(orient)); }