public void Parse(string richText, out List <RichTextTag> tags) { tagParserStack.Clear(); tagList.Clear(); Match match = regex.Match(richText); while (match.Success) { if (match.Value == endStr) { if (tagParserStack.Count > 0) { RichTextTagParser tagParser = tagParserStack.Pop(); tagParser.end = match.Index - 1; if (tagParser.end >= tagParser.start) { RichTextTag tag = tagParser.Parse(); if (tag != null) { tagList.Add(tag); } } } } else { RichTextTagParser tagParser = new RichTextTagParser(); tagParser.content = match.Value; tagParser.start = match.Index + match.Length; tagParserStack.Push(tagParser); } match = match.NextMatch(); } tags = tagList; }
private void LogTag(RichTextTag tag) { if (tag != null) { Debug.Log("Tag: " + tag.ToString()); } }
/// <summary> /// 根据字符串创建文本标记 /// </summary> /// <param name="_text"></param> /// <returns></returns> private static void CreateSymbolsFromText(string _text) { int textSymbolIndex = 0; int parsedCharacters = 0; while (parsedCharacters < _text.Length) { TextSymbol symbol = null; // 检查富文本标签 var remainingText = _text.Substring(parsedCharacters, _text.Length - parsedCharacters); if (RichTextTag.StringStartsWithTag(remainingText)) { var tag = RichTextTag.ParseNext(remainingText); if (null != tag) { // 富文本标签的文本标记 symbol = GetTextSymbol(textSymbolIndex++); symbol.Tag = tag; } } if (null == symbol) { // 普通字符的文本标记 symbol = GetTextSymbol(textSymbolIndex++); symbol.Character = remainingText.Substring(0, 1); } parsedCharacters += symbol.Length; } m_ValidTextSymbolCount = textSymbolIndex; }
private static List <TypedTextSymbol> CreateSymbolListFromText(string text) { var symbolList = new List <TypedTextSymbol>(); int parsedCharacters = 0; while (parsedCharacters < text.Length) { TypedTextSymbol symbol = null; // Check for tags var remainingText = text.Substring(parsedCharacters, text.Length - parsedCharacters); if (RichTextTag.StringStartsWithTag(remainingText)) { var tag = RichTextTag.ParseNext(remainingText); symbol = new TypedTextSymbol(tag); } else { symbol = new TypedTextSymbol(remainingText.Substring(0, 1)); } parsedCharacters += symbol.Length; symbolList.Add(symbol); } return(symbolList); }
private void ReportInitialHighlightError(int index, string text, string foundDescriptor, string expectedDescriptor, ResearchEncyclopediaArticleConfig config) { // Set the start index either to the beginning of the string // or ten less than index, whichever is bigger int startIndex = Mathf.Max(0, index - 20); // Set length to 20 or the difference between the start index // and end of string, whichever is smaller int length = Mathf.Min(40, text.Length - startIndex); // Get the substring of the text string reportString = text.Substring(startIndex, length); // Apply the color red to the part of the tag that is invalid RichTextTag invalidBraceTag = new RichTextTag("color", "red"); reportString = invalidBraceTag.Apply(reportString, index - startIndex, 1); // Add ellipses if this is not the start or end of the whole string if (startIndex > 0) { reportString = "..." + reportString; } if (startIndex + length < text.Length) { reportString += "..."; } // Add double quotes around the report string reportString = "\"" + reportString; reportString += "\""; Debug.LogWarning("Found " + foundDescriptor + " where " + expectedDescriptor + " was expected\n" + "\tArticle: " + config.ID.ToString() + "\n" + "\tPosition: " + reportString + "\n"); }
public static IEnumerable <RichText> ParseRichText(string richText, bool insideCodeBlock = false) { #if VERBOSE_DEBUGGING Debug.Log($"Began Parsing: {richText}"); #endif List <RichText> resultantRichText = new List <RichText>(); RichTextTag currentRichTextTag = default; Stack <RichTextTag> previousRichTextTags = new Stack <RichTextTag>(); int currentIndex = 0; int length = richText.Length; int lastNewTag = 0; //Loop through the whole string while (currentIndex < length) { int currentSearchingIndex = currentIndex; //Looking to discover a valid starting delimiter //indexOfOpening is the index of the character after the opening < int indexOfOpening = currentSearchingIndex; bool discoveredValidStartingDelimiter = false; while (!discoveredValidStartingDelimiter) { switch (DiscoverValidStartingDelimiter(out indexOfOpening)) { case Discovery.Invalid: continue; case Discovery.Valid: break; case Discovery.End: Exit(); return(resultantRichText); default: throw new ArgumentOutOfRangeException(); } currentSearchingIndex = indexOfOpening + 1; } //Now we have the index of a possible starting delimiter look for the ending delimiter. int indexOfClosing = richText.IndexOf(closingTagDelimiter, currentSearchingIndex); if (indexOfClosing < 0) { Exit(); return(resultantRichText); } //Make sure that that closing delimiter doesn't have an opening delimiter behind it. while (true) { switch (DiscoverValidStartingDelimiter(out int indexOfNextOpening, indexOfClosing - currentSearchingIndex)) {
private static string RemoveCustomTags(string textWithTags) { string textWithoutTags = textWithTags; foreach (var customTag in CustomTagTypes) { textWithoutTags = RichTextTag.RemoveTagsFromString(textWithoutTags, customTag); } return(textWithoutTags); }
/// <summary> /// 从字符串中移除富文本标签 /// </summary> /// <param name="_textWithTags"></param> /// <param name="_tags"></param> /// <returns></returns> private static string RemoveTags(string _textWithTags, string[] _tags) { var textWithoutTags = _textWithTags; foreach (var tag in _tags) { textWithoutTags = RichTextTag.RemoveTagsFromString(textWithoutTags, tag); } return(textWithoutTags); }
private static string RemoveUnityTags(string textWithTags) { string textWithoutTags = textWithTags; foreach (var unityTag in UnityTagTypes) { textWithoutTags = RichTextTag.RemoveTagsFromString(textWithoutTags, unityTag); } return(textWithoutTags); }
public void Constructor_OpeningTag_Parses() { //Arrange var tag = "<b>"; //Act var richTextTag = new RichTextTag(tag); //Assert Assert.AreEqual(tag, richTextTag.TagText); Assert.AreEqual("b", richTextTag.TagType); Assert.IsFalse(richTextTag.IsClosingTag); Assert.AreEqual("</b>", richTextTag.ClosingTagText); Assert.AreEqual(string.Empty, richTextTag.Parameter); }
public void Constructor_TagAndParameterWithQuotes_Parses() { //Arrange var tag = "<color=\"red\">"; //Act var richTextTag = new RichTextTag(tag); //Assert Assert.AreEqual(tag, richTextTag.TagText); Assert.AreEqual("color", richTextTag.TagType); Assert.IsFalse(richTextTag.IsClosingTag); Assert.AreEqual("</color>", richTextTag.ClosingTagText); Assert.AreEqual("red", richTextTag.Parameter); }
public void Update() { if (Input.GetKeyDown(KeyCode.Tilde)) { var tag = RichTextTag.ParseNext("blah<color=red>boo</color"); LogTag(tag); tag = RichTextTag.ParseNext("<color=blue>blue</color"); LogTag(tag); tag = RichTextTag.ParseNext("No tag in here"); LogTag(tag); tag = RichTextTag.ParseNext("No <color=blueblue</color tag here either"); LogTag(tag); tag = RichTextTag.ParseNext("This tag is a closing tag </bold>"); LogTag(tag); } if (Input.GetKeyDown(KeyCode.Space)) { HandlePrintNextClicked(); } }
public string RichEncyclopediaArticleText() { string richText = articleConfig.Text; int indexAdjuster = 0; // Adjust the index for each highlight int indexIncrementer = 0; // Length of all the tags used in each highlight // Compute the index incrementer by incrementing tag lengths foreach (RichTextTag tag in highlightTags) { indexIncrementer += tag.Length; } // Go through all highlights foreach (TextHighlight highlight in articleData.Highlights) { richText = RichTextTag.ApplyMultiple(highlightTags, richText, highlight.Start + indexAdjuster, highlight.Length); // Increase the global index adjuster indexAdjuster += indexIncrementer; } return(richText); }
public void Update() { if (Input.GetKeyDown(KeyCode.Tilde)) { var tag = RichTextTag.ParseNext("blah<color=red>boo</color"); LogTag(tag); tag = RichTextTag.ParseNext("<color=blue>blue</color"); LogTag(tag); tag = RichTextTag.ParseNext("No tag in here"); LogTag(tag); tag = RichTextTag.ParseNext("No <color=blueblue</color tag here either"); LogTag(tag); tag = RichTextTag.ParseNext("This tag is a closing tag </bold>"); LogTag(tag); } if (Input.GetButtonUp("Shoot") || Input.GetButtonUp("Use")) { HandlePrintNextClicked(); } if (choiceButtons[0].IsActive() && initializeButtonChoice == false) { if (Input.GetButtonUp("MoveLeft")) { choiceButtons[0].FindSelectableOnLeft(); initializeButtonChoice = true; } else if (Input.GetButtonUp("MoveRight")) { choiceButtons[0].FindSelectableOnRight(); initializeButtonChoice = true; } else if (Input.GetButtonUp("Shoot") || Input.GetButtonUp("Use")) { choiceButtons[0].Select(); initializeButtonChoice = true; } } }
public RichTextTag Parse() { RichTextTag tag = null; Match match = tagRegex.Match(content); if (match.Success) { string tagName = match.Groups[1].Value;//标签类型 if (!tagName.StartsWith("#")) { var keyValueCollection = paraRegex.Matches(match.Groups[2].Value);//标签参数 switch (tagName) { case "underline": { tag = new RichTextUnderlineTag(); break; } default: break; } if (tag != null) { tag.start = start; tag.end = end; for (int i = 0; i < keyValueCollection.Count; i++) { string key = keyValueCollection[i].Groups[1].Value; string value = keyValueCollection[i].Groups[2].Value; tag.SetValue(key, value); } } } } return(tag); }
public static IEnumerable <RichText> ParseRichText(string richText, bool insideCodeBlock = false) { #if VERBOSE_DEBUGGING Debug.Log($"Began Parsing: {richText}"); #endif List <RichText> resultantRichText = new List <RichText>(); RichTextTag currentRichTextTag = default; Stack <RichTextTag> previousRichTextTags = new Stack <RichTextTag>(); int currentIndex = 0; int length = richText.Length; int lastNewTag = 0; //Loop through the whole string while (currentIndex < length) { int currentSearchingIndex = currentIndex; //Looking to discover a valid starting delimiter //indexOfOpening is the index of the character after the opening < int indexOfOpening = currentSearchingIndex; bool discoveredValidStartingDelimiter = false; while (!discoveredValidStartingDelimiter) { switch (DiscoverValidStartingDelimiter(out indexOfOpening)) { case Discovery.Invalid: continue; case Discovery.Valid: break; case Discovery.End: Exit(); return(resultantRichText); default: throw new ArgumentOutOfRangeException(); } currentSearchingIndex = indexOfOpening + 1; } //Now we have the index of a possible starting delimiter look for the ending delimiter. int indexOfClosing = richText.IndexOf(closingTagDelimiter, currentSearchingIndex); if (indexOfClosing < 0) { Exit(); return(resultantRichText); } //Make sure that that closing delimiter doesn't have an opening delimiter behind it. while (true) { var val = DiscoverValidStartingDelimiter(out int indexOfNextOpening, indexOfClosing - currentSearchingIndex); switch (val) { case Discovery.Invalid: continue; case Discovery.Valid: if (insideCodeBlock) { break; } Debug.LogError( $"Text parsed by {nameof(RichTextParser)} has two un-escaped opening delimiters: \"{openingTagDelimiter}\" at {currentSearchingIndex} & {indexOfNextOpening}. {NotParsedError()}"); Exit(); return(resultantRichText); case Discovery.End: break; default: throw new ArgumentOutOfRangeException(); } break; } //Get the location of the starting delimiter if it exists. //search index returned is the index after the start delimiter. Discovery DiscoverValidStartingDelimiter(out int searchIndex, int count = -1) { searchIndex = currentSearchingIndex; int indexOfOpeningDelimiter = count < 0 ? richText.IndexOf(openingTagDelimiter, currentSearchingIndex) : richText.IndexOf(openingTagDelimiter, currentSearchingIndex, count); if (indexOfOpeningDelimiter < 0) { return(Discovery.End); } if (indexOfOpeningDelimiter - 1 > 0) { //if the text behind the opening tag is not the escape character then the tag must be valid. if (!richText[indexOfOpeningDelimiter - 1].Equals(escapeCharacter)) { /*//Unless it's a space, * && indexOfOpeningDelimiter + 1 < richText.Length && richText[indexOfOpeningDelimiter + 1] != ' ' * //Or a numerical character. * && (richText[indexOfOpeningDelimiter + 1] < 48 || 57 < richText[indexOfOpeningDelimiter + 1])*/ discoveredValidStartingDelimiter = true; } } else { //if there is no characters behind the opening tag it must be valid. discoveredValidStartingDelimiter = true; } searchIndex = indexOfOpeningDelimiter + 1; return(discoveredValidStartingDelimiter ? Discovery.Valid : Discovery.Invalid); } string resultantTag = richText.Substring(indexOfOpening, indexOfClosing - indexOfOpening); bool successfullyParsedTag = true; if (insideCodeBlock) { if (ParseForSpan(resultantTag, out string styleClass)) { AddLastTextWithRichTextTag(currentRichTextTag); currentRichTextTag = currentRichTextTag.GetWithSpan(styleClass); } else if (resultantTag.Equals("/span")) { AddLastTextWithRichTextTag(currentRichTextTag, false); currentRichTextTag = currentRichTextTag.GetWithRemovedSpan(); RemoveTag(); } else { successfullyParsedTag = false; } } else { if (currentRichTextTag.tag == Tag.code) { //When inside the a code tag we ignore all tags except the closing of a code tag. if (resultantTag.Equals("/code")) { AddLastTextWithRichTextTag(currentRichTextTag, false); currentRichTextTag = default; ClearTags(); } else { //Continue parsing, looking for that closing code tag. currentIndex = indexOfOpening; continue; } } else { //Switch through tags that are entire strings switch (resultantTag) { case "/": //Exit Tag AddLastTextWithRichTextTag(currentRichTextTag, false); RemoveTag(true); break; case "b": //Start Bold AddLastTextWithRichTextTag(currentRichTextTag); currentRichTextTag = currentRichTextTag.GetWithAddedBold(); break; case "/b": //End Bold AddLastTextWithRichTextTag(currentRichTextTag, false); currentRichTextTag = currentRichTextTag.GetWithRemovedBold(); RemoveTag(); break; case "i": //Start Italics AddLastTextWithRichTextTag(currentRichTextTag); currentRichTextTag = currentRichTextTag.GetWithAddedItalics(); break; case "/i": //End Italics AddLastTextWithRichTextTag(currentRichTextTag, false); currentRichTextTag = currentRichTextTag.GetWithRemovedItalics(); RemoveTag(); break; case "code": //Start Code if (!currentRichTextTag.Equals(default(RichTextTag))) { Debug.LogError($"Rich Text entered a Code tag without closing prior tags. This is not allowed. {NotParsedError()}"); RichTextDebug(); Exit(); return(resultantRichText); } AddLastTextWithRichTextTag(currentRichTextTag, false); currentRichTextTag = new RichTextTag(Tag.code, FontStyle.Normal, Color.clear, 0, null); ClearTags(); break; case "/code": //End Code but if not already in a code block. if (currentRichTextTag.tag != Tag.code) { Debug.LogError($"Code tag was exited without being in a code block. {NotParsedError()}"); RichTextDebugHighlit(indexOfOpening, indexOfClosing); Exit(); return(resultantRichText); } break; case "/button": //End Button AddLastTextWithRichTextTag(currentRichTextTag, false); currentRichTextTag = currentRichTextTag.GetWithRemovedButton(); RemoveTag(); break; case "/color": //End Colour AddLastTextWithRichTextTag(currentRichTextTag, false); currentRichTextTag = currentRichTextTag.GetWithRemovedColor(); RemoveTag(); break; case "/span": //End Span AddLastTextWithRichTextTag(currentRichTextTag, false); currentRichTextTag = currentRichTextTag.GetWithRemovedSpan(); RemoveTag(); break; default: successfullyParsedTag = false; break; } //StartsWith cannot be in the above switch. if (!successfullyParsedTag) { successfullyParsedTag = true; if (resultantTag.StartsWith("size")) //START SIZE { if (!GetStringVariables("size", out string stringVariables)) { continue; } if (!int.TryParse(stringVariables, out int size)) { Debug.Log($"Size tag \"{resultantTag}\" does not contain a parseable integer. \"{stringVariables}\""); RichTextDebugHighlit(indexOfOpening, indexOfClosing); } else { AddLastTextWithRichTextTag(currentRichTextTag); currentRichTextTag = currentRichTextTag.GetWithNewSize(size); } } else if (resultantTag.StartsWith("button")) //START BUTTON { if (!GetStringVariables("button", out string stringVariables)) { continue; } if (string.IsNullOrEmpty(stringVariables)) { Debug.Log($"Button tag \"{resultantTag}\" does not contain a key. \"{stringVariables}\""); RichTextDebugHighlit(indexOfOpening, indexOfClosing); } else { AddLastTextWithRichTextTag(currentRichTextTag); currentRichTextTag = currentRichTextTag.GetWithButton(stringVariables); } } else if (resultantTag.StartsWith("color")) //START COLOR { if (!GetStringVariables("color", out string stringVariables)) { continue; } if (!ColorUtility.TryParseHtmlString(stringVariables, out Color colour)) { Debug.Log($"Color tag \"{resultantTag}\" does not contain a HTML colour. \"{stringVariables}\""); RichTextDebugHighlit(indexOfOpening, indexOfClosing); } else { AddLastTextWithRichTextTag(currentRichTextTag); currentRichTextTag = currentRichTextTag.GetWithNewColor(colour); } } else if (ParseForSpan(resultantTag, out string styleClass)) { AddLastTextWithRichTextTag(currentRichTextTag); currentRichTextTag = currentRichTextTag.GetWithSpan(styleClass); } else { successfullyParsedTag = false; } } } //Gets the plain string variables following a tag. bool GetStringVariables(string tag, out string stringVariables) { stringVariables = null; int indexOfEquals = resultantTag.IndexOf('=', tag.Length); if (indexOfEquals < 0) { Debug.Log($"{tag} tag \"{resultantTag}\" does not contain an = and variables."); RichTextDebugHighlit(indexOfOpening, indexOfClosing); return(false); } stringVariables = resultantTag.Substring(indexOfEquals + 1).Replace(" ", string.Empty); return(true); } } void RemoveTag(bool assignTag = false) { if (previousRichTextTags.Count == 0) { Debug.LogError( $"No Tags to Pop! Last added text: {(resultantRichText.Count > 0 ? $"{resultantRichText[resultantRichText.Count - 1].richTextTag.ToString()} | {resultantRichText[resultantRichText.Count - 1].associatedText}" : "none")}"); return; } if (assignTag) { currentRichTextTag = previousRichTextTags.Pop(); } else { previousRichTextTags.Pop(); } } void AddTag() => previousRichTextTags.Push(currentRichTextTag); void ClearTags() => previousRichTextTags.Clear(); void AddLastTextWithRichTextTag(RichTextTag tag, bool addTag = true) { if (addTag) { AddTag(); } //Don't add if no text content to add. if (lastNewTag == indexOfOpening - 1) { return; } string text = richText.Substring(lastNewTag, (indexOfOpening - 1) - lastNewTag); #if VERBOSE_DEBUGGING Debug.Log($"Added Text: \"{text}\"."); #endif resultantRichText.Add(new RichText(tag, text)); } if (successfullyParsedTag) { #if VERBOSE_DEBUGGING Debug.Log($"<color=green>{GetRichTextCapableText($"<{resultantTag}>")}</color>"); #endif currentIndex = indexOfClosing + 1; lastNewTag = currentIndex; } else { #if VERBOSE_DEBUGGING Debug.Log($"<color=red>{GetRichTextCapableText($"<{resultantTag}>")}</color>"); #endif currentIndex = indexOfOpening; } string NotParsedError() => "This text has not been parsed beyond this point."; void RichTextDebug() => Debug.Log(richText); void RichTextDebugHighlit(int highlightStart, int highlightEnd) => Debug.LogWarning( $"{GetRichTextCapableText(richText.Substring(0, highlightStart))}<color=red>{GetRichTextCapableText(richText.Substring(highlightStart, highlightEnd - highlightStart))}</color>{GetRichTextCapableText(richText.Substring(highlightEnd))}") ; string GetRichTextCapableText(string text) => text.Replace("<", "<<b></b>"); } Exit(); return(resultantRichText); void Exit() => resultantRichText.Add(new RichText(currentRichTextTag, richText.Substring(lastNewTag))); }
public TypedTextSymbol(RichTextTag tag) { this.Tag = tag; }
/// <summary> /// Gets the typed text at the specified visibleCharacterIndex. This is the text that should be written /// to the Text component. /// </summary> /// <returns>The <see cref="TypedText"/> generated at the specified visible character index.</returns> /// <param name="text">Text to parse.</param> /// <param name="visibleCharacterIndex">Visible character index (ignores tags).</param> public string GetTypedTextAt(string text, int visibleCharacterIndex) { var textAsSymbolList = CreateSymbolListFromText(text); // Split the text into shown and hide strings based on the actual visible characters int printedCharCount = 0; var shownText = string.Empty; var hiddenText = string.Empty; var lastVisibleCharacter = char.MinValue; foreach (var symbol in textAsSymbolList) { if (printedCharCount <= visibleCharacterIndex) { shownText += symbol.Text; // Keep track of the visible characters that have been printed if (!symbol.IsTag) { lastVisibleCharacter = symbol.Character.ToCharArray()[0]; } } else { hiddenText += symbol.Text; } if (!symbol.IsTag) { printedCharCount++; } } var activeTags = GetActiveTagsInSymbolList(textAsSymbolList, visibleCharacterIndex); // Remove closing tags for active tags from hidden text (move to before color hide tag) foreach (var activeTag in activeTags) { hiddenText = RemoveFirstOccurance(hiddenText, activeTag.ClosingTagText); } // Remove all color tags from hidden text so that they don't cause it to be shown // ex: <color=clear>This should <color=red>be clear</color></color> will show 'be clear" in red hiddenText = RichTextTag.RemoveTagsFromString(hiddenText, "color"); // Add the hidden text, provided there is text to hide if (!string.IsNullOrEmpty(hiddenText)) { var hiddenTag = RichTextTag.ClearColorTag; hiddenText = hiddenText.Insert(0, hiddenTag.TagText); hiddenText = hiddenText.Insert(hiddenText.Length, hiddenTag.ClosingTagText); } // Add back in closing tags in reverse order for (int i = 0; i < activeTags.Count; ++i) { hiddenText = hiddenText.Insert(0, activeTags[i].ClosingTagText); } // Remove all custom tags since Unity will display them when printed (it doesn't recognize them as rich text tags) var printText = shownText + hiddenText; foreach (var customTag in CustomTagTypes) { printText = RichTextTag.RemoveTagsFromString(printText, customTag); } // Calculate Delay, if active var delay = 0.0f; foreach (var activeTag in activeTags) { if (activeTag.TagType == "delay") { try { delay = activeTag.IsOpeningTag ? float.Parse(activeTag.Parameter) : 0.0f; } catch (System.FormatException e) { var warning = string.Format( "TypedTextGenerator found Invalid paramter format in tag [{0}]. " + "Parameter [{1}] does not parse to a float. Exception: {2}", activeTag, activeTag.Parameter, e); Debug.Log(warning); delay = 0.0f; } } } return(printText); }
public static List <VisualElement> AddRichText(string text, IButtonRegistry buttonRegistry, VisualElement root, bool isInsideCodeBlock) { List <VisualElement> results = new List <VisualElement>(); IEnumerable <RichText> richTexts = ParseRichText(text, isInsideCodeBlock); //Parse rich texts to create paragraphs. List <List <RichText> > paragraphs = new List <List <RichText> > { new List <RichText>() }; foreach (RichText richText in richTexts) { if (richText.richTextTag.tag == RichTextTag.Tag.button || richText.richTextTag.tag == RichTextTag.Tag.code) { paragraphs[paragraphs.Count - 1].Add(richText); continue; } string[] strings = richText.associatedText.Split('\n'); for (int i = 0; i < strings.Length; i++) { if (i != 0) { paragraphs.Add(new List <RichText>()); } //Split paragraph content (already split by tag) into individual words string[] wordSplit = Regex.Split(strings[i], @"(?<=[ -])"); //Split but keep delimiters attached. foreach (var word in wordSplit) { if (!string.IsNullOrEmpty(word)) { paragraphs[paragraphs.Count - 1].Add(new RichText(richText.richTextTag, word)); } } } } foreach (List <RichText> paragraph in paragraphs) { //Add all the paragraphs VisualElement rootTemp = root; root = AddParagraphContainer(root); for (int i = 0; i < paragraph.Count; i++) { RichText word = paragraph[i]; if (i < paragraph.Count - 1) { //If there are more words RichText nextWord = paragraph[i + 1]; string nextText = nextWord.associatedText; if (Regex.IsMatch(nextText, "^[^a-zA-Z] ?")) { VisualElement inlineGroup = new VisualElement(); root.Add(inlineGroup); inlineGroup.AddToClassList("inline-text-group"); AddRichTextInternal(word, inlineGroup); AddRichTextInternal(nextWord, inlineGroup); ++i; continue; } } AddRichTextInternal(word, root); //Add all the words and style them. void AddRichTextInternal(RichText richText, VisualElement rootToAddTo) { RichTextTag tag = richText.richTextTag; TextElement inlineText = null; switch (tag.tag) { case RichTextTag.Tag.none: inlineText = AddInlineText(richText.associatedText, rootToAddTo); break; case RichTextTag.Tag.button: if (buttonRegistry == null) { Debug.LogWarning("There was no ButtonRegistry provided to AddRichText. Button tags will not function."); inlineText = AddInlineButton(() => Debug.LogWarning("There was no ButtonRegistry provided to AddRichText. Button tags will not function."), richText.associatedText, rootToAddTo); break; } if (!buttonRegistry.GetRegisteredButtonAction(tag.stringVariables, out Action action)) { return; } inlineText = AddInlineButton(action, richText.associatedText, rootToAddTo); break; case RichTextTag.Tag.code: //Scroll ScrollView codeScroll = new ScrollView(ScrollViewMode.Horizontal); VisualElement contentContainer = codeScroll.contentContainer; codeScroll.contentViewport.style.flexDirection = FlexDirection.Column; codeScroll.contentViewport.style.alignItems = Align.Stretch; codeScroll.AddToClassList("code-scroll"); root.Add(codeScroll); contentContainer.ClearClassList(); contentContainer.AddToClassList("code-container"); VisualElement codeContainer = contentContainer; CSharpHighlighter highlighter = new CSharpHighlighter { AddStyleDefinition = false }; // To add code, we first use the CSharpHighlighter to construct rich text for us. string highlit = highlighter.Highlight(richText.associatedText); // After constructing new rich text we pass the text back recursively through this function with the new parent. AddRichText(highlit, buttonRegistry, codeContainer, true); // only parse spans because this is all the CSharpHighlighter parses. //Finalise content container foreach (VisualElement child in codeContainer.Children()) { if (child.ClassListContains(paragraphContainerClass)) { child.AddToClassList("code"); if (child.childCount == 1) { AddInlineText("", child); //This seems to be required to get layout to function properly. } } } //Begin Hack FieldInfo m_inheritedStyle = typeof(VisualElement).GetField("inheritedStyle", BindingFlags.NonPublic | BindingFlags.Instance); if (m_inheritedStyle == null) { m_inheritedStyle = typeof(VisualElement).GetField("m_InheritedStylesData", BindingFlags.NonPublic | BindingFlags.Instance); } Type inheritedStylesData = Type.GetType("UnityEngine.UIElements.StyleSheets.InheritedStylesData,UnityEngine"); FieldInfo font = inheritedStylesData.GetField("font", BindingFlags.Public | BindingFlags.Instance); FieldInfo fontSize = inheritedStylesData.GetField("fontSize", BindingFlags.Public | BindingFlags.Instance); Font consola = (Font)EditorGUIUtility.Load("consola"); contentContainer.Query <Label>().ForEach(l => { l.AddToClassList("code"); //Hack to regenerate the font size as Rich Text tags are removed from the original calculation. object value = m_inheritedStyle.GetValue(l); StyleFont fontVar = (StyleFont)font.GetValue(value); fontVar.value = consola; font.SetValue(value, fontVar); StyleLength fontSizeVar = 12; // = (StyleLength) fontSize.GetValue(value); //This doesn't seem to work properly, hard coded for now. fontSize.SetValue(value, fontSizeVar); m_inheritedStyle.SetValue(l, value); Vector2 measuredTextSize = l.MeasureTextSize(l.text.Replace('>', ' '), 0, VisualElement.MeasureMode.Undefined, 0, VisualElement.MeasureMode.Undefined); l.style.width = measuredTextSize.x; l.style.height = measuredTextSize.y; }); //Button Button codeCopyButtonButtonContainer = new Button(() => { EditorGUIUtility.systemCopyBuffer = richText.associatedText; Debug.Log("Copied Code to Clipboard"); }); codeCopyButtonButtonContainer.ClearClassList(); codeCopyButtonButtonContainer.AddToClassList("code-button"); codeCopyButtonButtonContainer.StretchToParentSize(); codeContainer.Add(codeCopyButtonButtonContainer); break; case RichTextTag.Tag.span: Label spanLabel = new Label { text = richText.associatedText }; spanLabel.AddToClassList(tag.stringVariables); rootToAddTo.Add(spanLabel); break; case RichTextTag.Tag.image: throw new NotImplementedException(); default: throw new ArgumentOutOfRangeException(); } if (inlineText != null) { inlineText.style.unityFontStyleAndWeight = tag.fontStyle; if (tag.size > 0) { inlineText.style.fontSize = tag.size; } if (tag.color != Color.clear) { inlineText.style.color = tag.color; } results.Add(inlineText); } } } root = rootTemp; } return(results); /*void RichTextDebug(string richText) => Debug.Log(GetRichTextCapableText(richText)); * string GetRichTextCapableText(string richText) => text.Replace("<", "<<b></b>");*/ }
protected override void OnPopulateMesh(VertexHelper toFill) { if (font == null) { return; } // We don't care if we the font Texture changes while we are doing our Update. // The end result of cachedTextGenerator will be valid for this instance. // Otherwise we can get issues like Case 619238. m_DisableFontTextureRebuiltCallback = true; //处理事件 eventList.Clear(); //处理图片标签 string richText = text; IList <UIVertex> verts = null; richText = CalculateLayoutWithImage(richText, out verts); //处理文字标签 List <RichTextTag> tagList = null; richTextParser.Parse(richText, out tagList); for (int i = 0; i < tagList.Count; i++) { RichTextTag tag = tagList[i]; switch (tag.tagType) { case RichTextTagType.None: break; case RichTextTagType.Underline: ApplyUnderlineEffect(tag as RichTextUnderlineTag, verts); break; default: break; } } Rect inputRect = rectTransform.rect; // get the text alignment anchor point for the text in local space Vector2 textAnchorPivot = GetTextAnchorPivot(fontData.alignment); Vector2 refPoint = Vector2.zero; refPoint.x = Mathf.Lerp(inputRect.xMin, inputRect.xMax, textAnchorPivot.x); refPoint.y = Mathf.Lerp(inputRect.yMin, inputRect.yMax, textAnchorPivot.y); // Determine fraction of pixel to offset text mesh. Vector2 roundingOffset = PixelAdjustPoint(refPoint) - refPoint; // Apply the offset to the vertices //IList<UIVertex> verts = cachedTextGenerator.verts; float unitsPerPixel = 1 / pixelsPerUnit; //Last 4 verts are always a new line... int vertCount = verts.Count - 4; toFill.Clear(); if (roundingOffset != Vector2.zero) { for (int i = 0; i < vertCount; ++i) { int tempVertsIndex = i & 3; m_TempVerts[tempVertsIndex] = verts[i]; m_TempVerts[tempVertsIndex].position *= unitsPerPixel; m_TempVerts[tempVertsIndex].position.x += roundingOffset.x; m_TempVerts[tempVertsIndex].position.y += roundingOffset.y; if (tempVertsIndex == 3) { toFill.AddUIVertexQuad(m_TempVerts); } } } else { //Debug.Log(unitsPerPixel); for (int i = 0; i < vertCount; ++i) { int tempVertsIndex = i & 3; m_TempVerts[tempVertsIndex] = verts[i]; m_TempVerts[tempVertsIndex].position *= unitsPerPixel; if (tempVertsIndex == 3) { toFill.AddUIVertexQuad(m_TempVerts); } //Debug.LogWarning(i + "_" + tempVertsIndex + "_" + m_TempVerts[tempVertsIndex].position); } } m_DisableFontTextureRebuiltCallback = false; }
public RichText(RichTextTag richTextTag, string associatedText) { this.richTextTag = richTextTag; this.associatedText = associatedText; }