/// <summary> /// Function used to evaluate the length of a text string. /// </summary> /// <param name="text"></param> /// <returns></returns> public TextInfo GetTextInfo(string text) { TextInfo temp_textInfo = new TextInfo(); // Early exit if no font asset was assigned. This should not be needed since Arial SDF will be assigned by default. if (m_fontAsset.characterDictionary == null) { Debug.Log("Can't Generate Mesh! No Font Asset has been assigned to Object ID: " + this.GetInstanceID()); return null; } // Early exit if string is empty. if (text == null || text.Length == 0) { return null; } // Convert String to Char[] StringToCharArray(text, ref m_text_buffer); int size = GetArraySizes(m_text_buffer); temp_textInfo.characterInfo = new TMPro_CharacterInfo[size]; m_fontIndex = 0; m_fontAssetArray[m_fontIndex] = m_fontAsset; // Scale the font to approximately match the point size m_fontScale = (m_fontSize / m_fontAssetArray[m_fontIndex].fontInfo.PointSize * (m_isOrthographic ? 1 : 0.1f)); float baseScale = m_fontScale; // BaseScale keeps the character aligned vertically since <size=+000> results in font of different scale. int charCode = 0; // Holds the character code of the currently being processed character. int prev_charCode = 0; //bool isMissingCharacter; // Used to handle missing characters in the Font Atlas / Definition. m_style = FontStyles.Normal; // Set defaul style as normal. // GetPadding to adjust the size of the mesh due to border thickness, softness, glow, etc... if (checkPaddingRequired) { checkPaddingRequired = false; m_padding = ShaderUtilities.GetPadding(m_renderer.sharedMaterials, m_enableExtraPadding, m_isUsingBold); m_alignmentPadding = ShaderUtilities.GetFontExtent(m_sharedMaterial); } float style_padding = 0; // Extra padding required to accomodate Bold style. float xadvance_multiplier = 1; // Used to increase spacing between character when style is bold. m_baselineOffset = 0; // Used by subscript characters. float lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). m_xAdvance = 0; // Used to track the position of each character. int lineNumber = 0; int wordCount = 0; int character_Count = 0; // Total characters in the char[] int visibleCharacter_Count = 0; // # of visible characters. // Limit Line Length to whatever size fits all characters on a single line. m_lineLength = m_lineLength > max_LineWrapLength ? max_LineWrapLength : m_lineLength; // Initialize struct to track states of word wrapping m_SaveWordWrapState = new WordWrapState(); int wrappingIndex = 0; if (temp_textInfo.lineInfo == null) temp_textInfo.lineInfo = new LineInfo[8]; for (int i = 0; i < temp_textInfo.lineInfo.Length; i++) { temp_textInfo.lineInfo[i] = new LineInfo(); //m_textInfo.lineInfo[i].lineExtents = new Extents(k_InfinityVector, -k_InfinityVector); } // Tracking of the highest Ascender float maxAscender = 0; float maxDescender = 0; int lastLineNumber = 0; int endTagIndex = 0; // Parse through Character buffer to read html tags and begin creating mesh. for (int i = 0; m_text_buffer[i] != 0; i++) { m_tabSpacing = -999; m_spacing = -999; charCode = m_text_buffer[i]; if (m_isRichText && charCode == 60) // '<' { // Check if Tag is valid. If valid, skip to the end of the validated tag. if (ValidateHtmlTag(m_text_buffer, i + 1, out endTagIndex)) { i = endTagIndex; if (m_tabSpacing != -999) { // Move character to a fix position. Position expresses in characters (approximation). m_xAdvance = m_tabSpacing * m_cached_Underline_GlyphInfo.width * m_fontScale; } if (m_spacing != -999) { m_xAdvance += m_spacing * m_fontScale * m_cached_Underline_GlyphInfo.width; } continue; } } //isMissingCharacter = false; // Look up Character Data from Dictionary and cache it. m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(charCode, out m_cached_GlyphInfo); if (m_cached_GlyphInfo == null) { // Character wasn't found in the Dictionary. m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(88, out m_cached_GlyphInfo); if (m_cached_GlyphInfo != null) { Debug.LogWarning("Character with ASCII value of " + charCode + " was not found in the Font Asset Glyph Table."); // Replace the missing character by X (if it is found) charCode = 88; //isMissingCharacter = true; } else { // At this point the character isn't in the Dictionary, the replacement X isn't either so ... //continue; } } // Store some of the text object's information temp_textInfo.characterInfo[character_Count].character = (char)charCode; //temp_textInfo.characterInfo[character_Count].color = m_htmlColor; //temp_textInfo.characterInfo[character_Count].style = m_style; temp_textInfo.characterInfo[character_Count].index = (short)i; // Handle Kerning if Enabled. if (m_enableKerning && character_Count >= 1) { KerningPairKey keyValue = new KerningPairKey(prev_charCode, charCode); KerningPair pair; m_fontAsset.kerningDictionary.TryGetValue(keyValue.key, out pair); if (pair != null) { m_xAdvance += pair.XadvanceOffset * m_fontScale; } } // Set Padding based on selected font style if ((m_style & FontStyles.Bold) == FontStyles.Bold) // Checks for any combination of Bold Style. { style_padding = m_fontAsset.BoldStyle * 2; xadvance_multiplier = 1.07f; // Increase xAdvance for bold characters. } else { style_padding = m_fontAsset.NormalStyle * 2; xadvance_multiplier = 1.0f; } // Setup Vertices for each character. Vector3 top_left = new Vector3(0 + m_xAdvance + ((m_cached_GlyphInfo.xOffset - m_padding - style_padding) * m_fontScale), (m_cached_GlyphInfo.yOffset + m_baselineOffset + m_padding) * m_fontScale - lineOffset * baseScale, 0); Vector3 bottom_left = new Vector3(top_left.x, top_left.y - ((m_cached_GlyphInfo.height + m_padding * 2) * m_fontScale), 0); Vector3 top_right = new Vector3(bottom_left.x + ((m_cached_GlyphInfo.width + m_padding * 2 + style_padding * 2) * m_fontScale), top_left.y, 0); Vector3 bottom_right = new Vector3(top_right.x, bottom_left.y, 0); // Check if we need to Shear the rectangles for Italic styles if ((m_style & FontStyles.Italic) == FontStyles.Italic) { // Shift Top vertices forward by half (Shear Value * height of character) and Bottom vertices back by same amount. float shear_value = m_fontAsset.ItalicStyle * 0.01f; Vector3 topShear = new Vector3(shear_value * ((m_cached_GlyphInfo.yOffset + m_padding + style_padding) * m_fontScale), 0, 0); Vector3 bottomShear = new Vector3(shear_value * (((m_cached_GlyphInfo.yOffset - m_cached_GlyphInfo.height - m_padding - style_padding)) * m_fontScale), 0, 0); top_left = top_left + topShear; bottom_left = bottom_left + bottomShear; top_right = top_right + topShear; bottom_right = bottom_right + bottomShear; } // Track Word Count per line and for the object if (character_Count > 0 && (char.IsWhiteSpace((char)charCode) || char.IsPunctuation((char)charCode))) { if (char.IsLetterOrDigit(temp_textInfo.characterInfo[character_Count - 1].character)) { wordCount += 1; temp_textInfo.lineInfo[lineNumber].wordCount += 1; } } // Setup Mesh for visible characters. ie. not a SPACE / LINEFEED / CARRIAGE RETURN. if (charCode != 32 && charCode != 9 && charCode != 10 && charCode != 13) { // Determine the bounds of the Mesh. //meshExtents.min = new Vector2(Mathf.Min(meshExtents.min.x, bottom_left.x), Mathf.Min(meshExtents.min.y, bottom_left.y)); //meshExtents.max = new Vector2(Mathf.Max(meshExtents.max.x, top_right.x), Mathf.Max(meshExtents.max.y, top_left.y)); // Determine the extend of each line LineInfo lineInfo = temp_textInfo.lineInfo[lineNumber]; Extents lineExtents = lineInfo.lineExtents; temp_textInfo.lineInfo[lineNumber].lineExtents.min = new Vector2(Mathf.Min(lineExtents.min.x, bottom_left.x), Mathf.Min(lineExtents.min.y, bottom_left.y)); temp_textInfo.lineInfo[lineNumber].lineExtents.max = new Vector2(Mathf.Max(lineExtents.max.x, top_right.x), Mathf.Max(lineExtents.max.y, top_left.y)); if (m_enableWordWrapping && top_right.x > m_lineLength) { // Check if further wrapping is possible or if we need to increase the line length if (wrappingIndex == m_SaveWordWrapState.previous_WordBreak) { if (isAffectingWordWrapping) { m_lineLength = Mathf.Round(top_right.x * 100 + 0.5f) / 100f;//m_lineLength = top_right.x; GenerateTextMesh(); isAffectingWordWrapping = false; } Debug.Log("Line " + lineNumber + " Cannot wrap lines anymore."); return null; } // Restore to previously stored state character_Count = m_SaveWordWrapState.total_CharacterCount + 1; visibleCharacter_Count = m_SaveWordWrapState.visible_CharacterCount; m_textInfo.lineInfo[lineNumber] = m_SaveWordWrapState.lineInfo; m_htmlColor = m_SaveWordWrapState.vertexColor; m_style = m_SaveWordWrapState.fontStyle; m_baselineOffset = m_SaveWordWrapState.baselineOffset; m_fontScale = m_SaveWordWrapState.fontScale; i = m_SaveWordWrapState.previous_WordBreak; wrappingIndex = i; // Used to dectect when line length can no longer be reduced. lineNumber += 1; // Check to make sure Array is large enough to hold a new line. if (lineNumber >= temp_textInfo.lineInfo.Length) Array.Resize(ref temp_textInfo.lineInfo, Mathf.NextPowerOfTwo(lineNumber)); lineOffset += (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing); m_xAdvance = 0; continue; } //visibleCharacter_Count += 1; } else { // This is a Space, Tab, LineFeed or Carriage Return // Track # of spaces per line which is used for line justification. if (charCode == 9 || charCode == 32) { //m_lineExtents[lineNumber].spaceCount += 1; temp_textInfo.lineInfo[lineNumber].spaceCount += 1; temp_textInfo.spaceCount += 1; } // We store the state of numerous variables for the most recent Space, LineFeed or Carriage Return to enable them to be restored // for Word Wrapping. m_SaveWordWrapState.previous_WordBreak = i; m_SaveWordWrapState.total_CharacterCount = character_Count; m_SaveWordWrapState.visible_CharacterCount = visibleCharacter_Count; m_SaveWordWrapState.maxLineLength = m_xAdvance; m_SaveWordWrapState.fontScale = m_fontScale; m_SaveWordWrapState.baselineOffset = m_baselineOffset; m_SaveWordWrapState.fontStyle = m_style; m_SaveWordWrapState.vertexColor = m_htmlColor; m_SaveWordWrapState.lineInfo = temp_textInfo.lineInfo[lineNumber]; } // Store Rectangle positions for each Character. temp_textInfo.characterInfo[character_Count].bottomLeft = bottom_left; temp_textInfo.characterInfo[character_Count].topRight = top_right; temp_textInfo.characterInfo[character_Count].lineNumber = (short)lineNumber; //temp_textInfo.characterInfo[character_Count].baseLine = top_right.y - (m_cached_GlyphInfo.yOffset + m_padding) * m_fontScale; //temp_textInfo.characterInfo[character_Count].topLine = temp_textInfo.characterInfo[character_Count].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Ascender + m_alignmentPadding.y) * m_fontScale; // Ascender //temp_textInfo.characterInfo[character_Count].bottomLine = temp_textInfo.characterInfo[character_Count].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Descender - m_alignmentPadding.w) * m_fontScale; // Descender maxAscender = temp_textInfo.characterInfo[character_Count].topLine > maxAscender ? temp_textInfo.characterInfo[character_Count].topLine : maxAscender; maxDescender = temp_textInfo.characterInfo[character_Count].bottomLine < maxDescender ? temp_textInfo.characterInfo[character_Count].bottomLine : maxDescender; //temp_textInfo.characterInfo[character_Count].aspectRatio = m_cached_GlyphInfo.width / m_cached_GlyphInfo.height; //temp_textInfo.characterInfo[character_Count].scale = m_fontScale; temp_textInfo.lineInfo[lineNumber].characterCount += 1; //Debug.Log("Character #" + i + " is [" + (char)charCode + "] ASCII (" + charCode + ")"); // Store LineInfo if (lineNumber != lastLineNumber) { temp_textInfo.lineInfo[lineNumber].firstCharacterIndex = character_Count; temp_textInfo.lineInfo[lineNumber - 1].lastCharacterIndex = character_Count - 1; temp_textInfo.lineInfo[lineNumber - 1].characterCount = temp_textInfo.lineInfo[lineNumber - 1].lastCharacterIndex - temp_textInfo.lineInfo[lineNumber - 1].firstCharacterIndex + 1; temp_textInfo.lineInfo[lineNumber - 1].lineLength = temp_textInfo.characterInfo[character_Count - 1].topRight.x - m_padding * m_fontScale; } lastLineNumber = lineNumber; // Handle Tabulation Stops. Tab stops at every 25% of Font Size. if (charCode == 9) { m_xAdvance = (int)(m_xAdvance / (m_fontSize * 0.25f) + 1) * (m_fontSize * 0.25f); } else m_xAdvance += (m_cached_GlyphInfo.xAdvance * xadvance_multiplier * m_fontScale) + m_characterSpacing; // Handle Line Feed as well as Word Wrapping if (charCode == 10 || charCode == 13) { lineNumber += 1; // Check to make sure Array is large enough to hold a new line. if (lineNumber >= temp_textInfo.lineInfo.Length) Array.Resize(ref temp_textInfo.lineInfo, Mathf.NextPowerOfTwo(lineNumber)); lineOffset += (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing); m_xAdvance = 0; } character_Count += 1; prev_charCode = charCode; } temp_textInfo.lineInfo[lineNumber].lastCharacterIndex = character_Count - 1; temp_textInfo.lineInfo[lineNumber].characterCount = temp_textInfo.lineInfo[lineNumber].lastCharacterIndex - temp_textInfo.lineInfo[lineNumber].firstCharacterIndex + 1; temp_textInfo.lineInfo[lineNumber].lineLength = temp_textInfo.characterInfo[character_Count - 1].topRight.x - m_padding * m_fontScale; //m_textMetrics = new TMPro_TextMetrics(); temp_textInfo.characterCount = character_Count; temp_textInfo.lineCount = lineNumber + 1; temp_textInfo.wordCount = wordCount; //for (int i = 0; i < lineNumber + 1; i++) //{ // Debug.Log("Line: " + (i + 1) + " # Char: " + temp_textInfo.lineInfo[i].characterCount // + " Word Count: " + temp_textInfo.lineInfo[i].wordCount // + " Space: " + temp_textInfo.lineInfo[i].spaceCount // + " First:" + temp_textInfo.lineInfo[i].firstCharacterIndex + " Last [" + temp_textInfo.characterInfo[temp_textInfo.lineInfo[i].lastCharacterIndex].character // + "] at Index: " + temp_textInfo.lineInfo[i].lastCharacterIndex + " Length: " + temp_textInfo.lineInfo[i].lineLength // + " Line Extents: " + temp_textInfo.lineInfo[i].lineExtents); // //Debug.Log("line: " + (i + 1) + " m_lineExtents Count: " + m_lineExtents[i].characterCount + " lineInfo: " + m_textInfo.lineInfo[i].characterCount); // //Debug.DrawLine(new Vector3(m_textInfo.lineInfo[i].lineLength, 2, 0), new Vector3(m_textInfo.lineInfo[i].lineLength, -2, 0), Color.red, 30f); //} return temp_textInfo; }
// ** Still needs to be implemented ** //private Camera managerCamera; //private TMPro_UpdateManager m_updateManager; //private bool isAlreadyScheduled; // DEBUG Variables //private System.Diagnostics.Stopwatch m_StopWatch; //private int frame = 0; //private int loopCountA = 0; //private int loopCountB = 0; //private int loopCountC = 0; //private int loopCountD = 0; //private int loopCountE = 0; protected override void Awake() { //base.Awake(); //Debug.Log("***** Awake() *****"); // on Object ID:" + GetInstanceID()); m_isAwake = true; // Cache Reference to the Canvas //m_canvas = GetComponentInParent<Canvas>(); //if (m_canvas == null) // m_canvas = gameObject.AddComponent<Canvas>(); // Cache Reference to RectTransform. m_rectTransform = gameObject.GetComponent<RectTransform>(); if (m_rectTransform == null) m_rectTransform = gameObject.AddComponent<RectTransform>(); // Cache a reference to the UIRenderer. m_uiRenderer = GetComponent<CanvasRenderer>(); if (m_uiRenderer == null) m_uiRenderer = gameObject.AddComponent<CanvasRenderer> (); m_uiRenderer.hideFlags = HideFlags.HideInInspector; // Determine if the RectTransform is Driven m_layoutController = GetComponent(typeof(ILayoutController)) as ILayoutController ?? (transform.parent != null ? transform.parent.GetComponent(typeof(ILayoutController)) as ILayoutController : null); if (m_layoutController != null) IsRectTransformDriven = true; // Cache reference to Mask Component if one is present m_mask = GetComponentInParent<Mask>(); // Load the font asset and assign material to renderer. LoadFontAsset(); // Allocate our initial buffers. m_char_buffer = new int[m_max_characters]; m_cached_GlyphInfo = new GlyphInfo(); m_uiVertices = new UIVertex[0]; // m_isFirstAllocation = true; m_textInfo = new TextInfo(); m_textInfo.wordInfo = new List<WordInfo>(); m_textInfo.lineInfo = new LineInfo[m_max_numberOfLines]; m_textInfo.meshInfo = new TMPro_MeshInfo(); m_fontAssetArray = new TextMeshProFont[16]; // Check if we have a font asset assigned. Return if we don't because no one likes to see purple squares on screen. if (m_fontAsset == null) { Debug.LogWarning("Please assign a Font Asset to this " + transform.name + " gameobject."); return; } // Set Defaults for Font Auto-sizing if (m_fontSizeMin == 0) m_fontSizeMin = m_fontSize / 2; if (m_fontSizeMax == 0) m_fontSizeMax = m_fontSize * 2; //// Set flags to cause ensure our text is parsed and text redrawn. isInputParsingRequired = true; havePropertiesChanged = true; m_rectTransformDimensionsChanged = true; m_isCalculateSizeRequired = true; //m_isNewTextObject = true; //LayoutRebuilder.MarkLayoutForRebuild(m_rectTransform); ForceMeshUpdate(); // Added to force OnWillRenderObject() to be called in case object is not visible so we get initial bounds for the mesh. ///* ScheduleUpdate(); }
// Restore the State of various variables used in the mesh creation loop. int RestoreWordWrappingState(ref WordWrapState state) { m_textInfo.lineInfo[m_lineNumber] = state.lineInfo; m_textInfo = state.textInfo; m_currentFontSize = state.currentFontSize; m_fontScale = state.fontScale; m_baselineOffset = state.baselineOffset; m_style = state.fontStyle; m_htmlColor = state.vertexColor; m_characterCount = state.total_CharacterCount + 1; m_visibleCharacterCount = state.visible_CharacterCount; m_meshExtents = state.meshExtents; m_xAdvance = state.xAdvance; m_maxAscender = state.maxAscender; m_maxDescender = state.maxDescender; m_lineOffset = state.lineOffset; m_maxFontScale = state.maxFontScale; int index = state.previous_WordBreak; return index; }
/// <summary> /// Function used to evaluate the length of a text string. /// </summary> /// <param name="text"></param> /// <returns></returns> public TextInfo GetTextInfo(string text) { TextInfo temp_textInfo = new TextInfo(); // Early exit if no font asset was assigned. This should not be needed since Arial SDF will be assigned by default. if (m_fontAsset.characterDictionary == null) { Debug.Log("Can't Generate Mesh! No Font Asset has been assigned to Object ID: " + this.GetInstanceID()); return(null); } // Early exit if string is empty. if (text == null || text.Length == 0) { return(null); } // Convert String to Char[] StringToCharArray(text, ref m_text_buffer); int size = GetArraySizes(m_text_buffer); temp_textInfo.characterInfo = new TMPro_CharacterInfo[size]; m_fontIndex = 0; m_fontAssetArray[m_fontIndex] = m_fontAsset; // Scale the font to approximately match the point size m_fontScale = (m_fontSize / m_fontAssetArray[m_fontIndex].fontInfo.PointSize * (m_isOrthographic ? 1 : 0.1f)); float baseScale = m_fontScale; // BaseScale keeps the character aligned vertically since <size=+000> results in font of different scale. int charCode = 0; // Holds the character code of the currently being processed character. int prev_charCode = 0; //bool isMissingCharacter; // Used to handle missing characters in the Font Atlas / Definition. m_style = FontStyles.Normal; // Set defaul style as normal. // GetPadding to adjust the size of the mesh due to border thickness, softness, glow, etc... if (checkPaddingRequired) { checkPaddingRequired = false; m_padding = ShaderUtilities.GetPadding(m_renderer.sharedMaterials, m_enableExtraPadding, m_isUsingBold); m_alignmentPadding = ShaderUtilities.GetFontExtent(m_sharedMaterial); } float style_padding = 0; // Extra padding required to accomodate Bold style. float xadvance_multiplier = 1; // Used to increase spacing between character when style is bold. m_baselineOffset = 0; // Used by subscript characters. float lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). m_xAdvance = 0; // Used to track the position of each character. int lineNumber = 0; int wordCount = 0; int character_Count = 0; // Total characters in the char[] int visibleCharacter_Count = 0; // # of visible characters. // Limit Line Length to whatever size fits all characters on a single line. m_lineLength = m_lineLength > max_LineWrapLength ? max_LineWrapLength : m_lineLength; // Initialize struct to track states of word wrapping m_SaveWordWrapState = new WordWrapState(); int wrappingIndex = 0; if (temp_textInfo.lineInfo == null) { temp_textInfo.lineInfo = new LineInfo[8]; } for (int i = 0; i < temp_textInfo.lineInfo.Length; i++) { temp_textInfo.lineInfo[i] = new LineInfo(); //m_textInfo.lineInfo[i].lineExtents = new Extents(k_InfinityVector, -k_InfinityVector); } // Tracking of the highest Ascender float maxAscender = 0; float maxDescender = 0; int lastLineNumber = 0; int endTagIndex = 0; // Parse through Character buffer to read html tags and begin creating mesh. for (int i = 0; m_text_buffer[i] != 0; i++) { m_tabSpacing = -999; m_spacing = -999; charCode = m_text_buffer[i]; if (m_isRichText && charCode == 60) // '<' { // Check if Tag is valid. If valid, skip to the end of the validated tag. if (ValidateHtmlTag(m_text_buffer, i + 1, out endTagIndex)) { i = endTagIndex; if (m_tabSpacing != -999) { // Move character to a fix position. Position expresses in characters (approximation). m_xAdvance = m_tabSpacing * m_cached_Underline_GlyphInfo.width * m_fontScale; } if (m_spacing != -999) { m_xAdvance += m_spacing * m_fontScale * m_cached_Underline_GlyphInfo.width; } continue; } } //isMissingCharacter = false; // Look up Character Data from Dictionary and cache it. m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(charCode, out m_cached_GlyphInfo); if (m_cached_GlyphInfo == null) { // Character wasn't found in the Dictionary. m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(88, out m_cached_GlyphInfo); if (m_cached_GlyphInfo != null) { Debug.LogWarning("Character with ASCII value of " + charCode + " was not found in the Font Asset Glyph Table."); // Replace the missing character by X (if it is found) charCode = 88; //isMissingCharacter = true; } else { // At this point the character isn't in the Dictionary, the replacement X isn't either so ... //continue; } } // Store some of the text object's information temp_textInfo.characterInfo[character_Count].character = (char)charCode; //temp_textInfo.characterInfo[character_Count].color = m_htmlColor; //temp_textInfo.characterInfo[character_Count].style = m_style; temp_textInfo.characterInfo[character_Count].index = (short)i; // Handle Kerning if Enabled. if (m_enableKerning && character_Count >= 1) { KerningPairKey keyValue = new KerningPairKey(prev_charCode, charCode); KerningPair pair; m_fontAsset.kerningDictionary.TryGetValue(keyValue.key, out pair); if (pair != null) { m_xAdvance += pair.XadvanceOffset * m_fontScale; } } // Set Padding based on selected font style if ((m_style & FontStyles.Bold) == FontStyles.Bold) // Checks for any combination of Bold Style. { style_padding = m_fontAsset.BoldStyle * 2; xadvance_multiplier = 1.07f; // Increase xAdvance for bold characters. } else { style_padding = m_fontAsset.NormalStyle * 2; xadvance_multiplier = 1.0f; } // Setup Vertices for each character. Vector3 top_left = new Vector3(0 + m_xAdvance + ((m_cached_GlyphInfo.xOffset - m_padding - style_padding) * m_fontScale), (m_cached_GlyphInfo.yOffset + m_baselineOffset + m_padding) * m_fontScale - lineOffset * baseScale, 0); Vector3 bottom_left = new Vector3(top_left.x, top_left.y - ((m_cached_GlyphInfo.height + m_padding * 2) * m_fontScale), 0); Vector3 top_right = new Vector3(bottom_left.x + ((m_cached_GlyphInfo.width + m_padding * 2 + style_padding * 2) * m_fontScale), top_left.y, 0); Vector3 bottom_right = new Vector3(top_right.x, bottom_left.y, 0); // Check if we need to Shear the rectangles for Italic styles if ((m_style & FontStyles.Italic) == FontStyles.Italic) { // Shift Top vertices forward by half (Shear Value * height of character) and Bottom vertices back by same amount. float shear_value = m_fontAsset.ItalicStyle * 0.01f; Vector3 topShear = new Vector3(shear_value * ((m_cached_GlyphInfo.yOffset + m_padding + style_padding) * m_fontScale), 0, 0); Vector3 bottomShear = new Vector3(shear_value * (((m_cached_GlyphInfo.yOffset - m_cached_GlyphInfo.height - m_padding - style_padding)) * m_fontScale), 0, 0); top_left = top_left + topShear; bottom_left = bottom_left + bottomShear; top_right = top_right + topShear; bottom_right = bottom_right + bottomShear; } // Track Word Count per line and for the object if (character_Count > 0 && (char.IsWhiteSpace((char)charCode) || char.IsPunctuation((char)charCode))) { if (char.IsLetterOrDigit(temp_textInfo.characterInfo[character_Count - 1].character)) { wordCount += 1; temp_textInfo.lineInfo[lineNumber].wordCount += 1; } } // Setup Mesh for visible characters. ie. not a SPACE / LINEFEED / CARRIAGE RETURN. if (charCode != 32 && charCode != 9 && charCode != 10 && charCode != 13) { // Determine the bounds of the Mesh. //meshExtents.min = new Vector2(Mathf.Min(meshExtents.min.x, bottom_left.x), Mathf.Min(meshExtents.min.y, bottom_left.y)); //meshExtents.max = new Vector2(Mathf.Max(meshExtents.max.x, top_right.x), Mathf.Max(meshExtents.max.y, top_left.y)); // Determine the extend of each line LineInfo lineInfo = temp_textInfo.lineInfo[lineNumber]; Extents lineExtents = lineInfo.lineExtents; temp_textInfo.lineInfo[lineNumber].lineExtents.min = new Vector2(Mathf.Min(lineExtents.min.x, bottom_left.x), Mathf.Min(lineExtents.min.y, bottom_left.y)); temp_textInfo.lineInfo[lineNumber].lineExtents.max = new Vector2(Mathf.Max(lineExtents.max.x, top_right.x), Mathf.Max(lineExtents.max.y, top_left.y)); if (m_enableWordWrapping && top_right.x > m_lineLength) { // Check if further wrapping is possible or if we need to increase the line length if (wrappingIndex == m_SaveWordWrapState.previous_WordBreak) { if (isAffectingWordWrapping) { m_lineLength = Mathf.Round(top_right.x * 100 + 0.5f) / 100f;//m_lineLength = top_right.x; GenerateTextMesh(); isAffectingWordWrapping = false; } Debug.Log("Line " + lineNumber + " Cannot wrap lines anymore."); return(null); } // Restore to previously stored state character_Count = m_SaveWordWrapState.total_CharacterCount + 1; visibleCharacter_Count = m_SaveWordWrapState.visible_CharacterCount; m_textInfo.lineInfo[lineNumber] = m_SaveWordWrapState.lineInfo; m_htmlColor = m_SaveWordWrapState.vertexColor; m_style = m_SaveWordWrapState.fontStyle; m_baselineOffset = m_SaveWordWrapState.baselineOffset; m_fontScale = m_SaveWordWrapState.fontScale; i = m_SaveWordWrapState.previous_WordBreak; wrappingIndex = i; // Used to dectect when line length can no longer be reduced. lineNumber += 1; // Check to make sure Array is large enough to hold a new line. if (lineNumber >= temp_textInfo.lineInfo.Length) { Array.Resize(ref temp_textInfo.lineInfo, Mathf.NextPowerOfTwo(lineNumber)); } lineOffset += (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing); m_xAdvance = 0; continue; } //visibleCharacter_Count += 1; } else { // This is a Space, Tab, LineFeed or Carriage Return // Track # of spaces per line which is used for line justification. if (charCode == 9 || charCode == 32) { //m_lineExtents[lineNumber].spaceCount += 1; temp_textInfo.lineInfo[lineNumber].spaceCount += 1; temp_textInfo.spaceCount += 1; } // We store the state of numerous variables for the most recent Space, LineFeed or Carriage Return to enable them to be restored // for Word Wrapping. m_SaveWordWrapState.previous_WordBreak = i; m_SaveWordWrapState.total_CharacterCount = character_Count; m_SaveWordWrapState.visible_CharacterCount = visibleCharacter_Count; m_SaveWordWrapState.maxLineLength = m_xAdvance; m_SaveWordWrapState.fontScale = m_fontScale; m_SaveWordWrapState.baselineOffset = m_baselineOffset; m_SaveWordWrapState.fontStyle = m_style; m_SaveWordWrapState.vertexColor = m_htmlColor; m_SaveWordWrapState.lineInfo = temp_textInfo.lineInfo[lineNumber]; } // Store Rectangle positions for each Character. temp_textInfo.characterInfo[character_Count].bottomLeft = bottom_left; temp_textInfo.characterInfo[character_Count].topRight = top_right; temp_textInfo.characterInfo[character_Count].lineNumber = (short)lineNumber; //temp_textInfo.characterInfo[character_Count].baseLine = top_right.y - (m_cached_GlyphInfo.yOffset + m_padding) * m_fontScale; //temp_textInfo.characterInfo[character_Count].topLine = temp_textInfo.characterInfo[character_Count].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Ascender + m_alignmentPadding.y) * m_fontScale; // Ascender //temp_textInfo.characterInfo[character_Count].bottomLine = temp_textInfo.characterInfo[character_Count].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Descender - m_alignmentPadding.w) * m_fontScale; // Descender maxAscender = temp_textInfo.characterInfo[character_Count].topLine > maxAscender ? temp_textInfo.characterInfo[character_Count].topLine : maxAscender; maxDescender = temp_textInfo.characterInfo[character_Count].bottomLine < maxDescender ? temp_textInfo.characterInfo[character_Count].bottomLine : maxDescender; //temp_textInfo.characterInfo[character_Count].aspectRatio = m_cached_GlyphInfo.width / m_cached_GlyphInfo.height; //temp_textInfo.characterInfo[character_Count].scale = m_fontScale; temp_textInfo.lineInfo[lineNumber].characterCount += 1; //Debug.Log("Character #" + i + " is [" + (char)charCode + "] ASCII (" + charCode + ")"); // Store LineInfo if (lineNumber != lastLineNumber) { temp_textInfo.lineInfo[lineNumber].firstCharacterIndex = character_Count; temp_textInfo.lineInfo[lineNumber - 1].lastCharacterIndex = character_Count - 1; temp_textInfo.lineInfo[lineNumber - 1].characterCount = temp_textInfo.lineInfo[lineNumber - 1].lastCharacterIndex - temp_textInfo.lineInfo[lineNumber - 1].firstCharacterIndex + 1; temp_textInfo.lineInfo[lineNumber - 1].lineLength = temp_textInfo.characterInfo[character_Count - 1].topRight.x - m_padding * m_fontScale; } lastLineNumber = lineNumber; // Handle Tabulation Stops. Tab stops at every 25% of Font Size. if (charCode == 9) { m_xAdvance = (int)(m_xAdvance / (m_fontSize * 0.25f) + 1) * (m_fontSize * 0.25f); } else { m_xAdvance += (m_cached_GlyphInfo.xAdvance * xadvance_multiplier * m_fontScale) + m_characterSpacing; } // Handle Line Feed as well as Word Wrapping if (charCode == 10 || charCode == 13) { lineNumber += 1; // Check to make sure Array is large enough to hold a new line. if (lineNumber >= temp_textInfo.lineInfo.Length) { Array.Resize(ref temp_textInfo.lineInfo, Mathf.NextPowerOfTwo(lineNumber)); } lineOffset += (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing); m_xAdvance = 0; } character_Count += 1; prev_charCode = charCode; } temp_textInfo.lineInfo[lineNumber].lastCharacterIndex = character_Count - 1; temp_textInfo.lineInfo[lineNumber].characterCount = temp_textInfo.lineInfo[lineNumber].lastCharacterIndex - temp_textInfo.lineInfo[lineNumber].firstCharacterIndex + 1; temp_textInfo.lineInfo[lineNumber].lineLength = temp_textInfo.characterInfo[character_Count - 1].topRight.x - m_padding * m_fontScale; //m_textMetrics = new TMPro_TextMetrics(); temp_textInfo.characterCount = character_Count; temp_textInfo.lineCount = lineNumber + 1; temp_textInfo.wordCount = wordCount; //for (int i = 0; i < lineNumber + 1; i++) //{ // Debug.Log("Line: " + (i + 1) + " # Char: " + temp_textInfo.lineInfo[i].characterCount // + " Word Count: " + temp_textInfo.lineInfo[i].wordCount // + " Space: " + temp_textInfo.lineInfo[i].spaceCount // + " First:" + temp_textInfo.lineInfo[i].firstCharacterIndex + " Last [" + temp_textInfo.characterInfo[temp_textInfo.lineInfo[i].lastCharacterIndex].character // + "] at Index: " + temp_textInfo.lineInfo[i].lastCharacterIndex + " Length: " + temp_textInfo.lineInfo[i].lineLength // + " Line Extents: " + temp_textInfo.lineInfo[i].lineExtents); // //Debug.Log("line: " + (i + 1) + " m_lineExtents Count: " + m_lineExtents[i].characterCount + " lineInfo: " + m_textInfo.lineInfo[i].characterCount); // //Debug.DrawLine(new Vector3(m_textInfo.lineInfo[i].lineLength, 2, 0), new Vector3(m_textInfo.lineInfo[i].lineLength, -2, 0), Color.red, 30f); //} return(temp_textInfo); }
//public void RedrawMesh() //{ // isRedrawRequired = true; //} public void DrawMesh() { Matrix4x4 matrix; TMPro_MeshInfo meshInfos = m_textMeshProComponent.meshInfo; TextInfo textInfos = m_textMeshProComponent.textInfo; TMPro_CharacterInfo[] characters_Info = textInfos.characterInfo; int characterCount = textInfos.characterCount; Vector3[] vertices = meshInfos.vertices; Vector2[] uv0s = meshInfos.uv0s; Vector2[] uv2s = meshInfos.uv2s; Color32[] vertexColors = meshInfos.vertexColors; Vector3[] normals = meshInfos.normals; Vector4[] tangents = meshInfos.tangents; float curveScale = 1; char charCode; float xScale = 0; float xAdvance = 0; GlyphInfo glyph; // Draw Scale Changes First for (int i = 0; i < characterCount; i++) { charCode = characters_Info[i].character; glyph = m_textMeshProComponent.font.characterDictionary[charCode]; int vertIndex = characters_Info[i].vertexIndex; if (characters_Info[i].isVisible) { float charMidLine = (characters_Info[i].bottomLeft.x + characters_Info[i].topRight.x) / 2; // Middle X-Axis of character. // Need to translate all 4 vertices of each quad to aligned with middle of character / baseline. Vector3 offset = new Vector3(charMidLine, 0, 0); vertices[vertIndex + 0] += -offset; vertices[vertIndex + 1] += -offset; vertices[vertIndex + 2] += -offset; vertices[vertIndex + 3] += -offset; // Apply Scale Matrix first float x = characterCount > 1 ? (float)i / (characterCount - 1) : 0; xScale = ScaleCurve.Evaluate(x); matrix = Matrix4x4.TRS(new Vector3(0, 0, 0), Quaternion.Euler(0, 0, 0), new Vector3(xScale, xScale, 1)); vertices[vertIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertIndex + 0]); vertices[vertIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertIndex + 1]); vertices[vertIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertIndex + 2]); vertices[vertIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertIndex + 3]); // Redo the spacing for each character. float adjusted_pos = glyph.xOffset * characters_Info[i].scale * xScale + (characters_Info[i].topRight.x - characters_Info[i].bottomLeft.x) / 2 * xScale; offset = new Vector3(adjusted_pos + xAdvance, 0, 0); vertices[vertIndex + 0] += offset; vertices[vertIndex + 1] += offset; vertices[vertIndex + 2] += offset; vertices[vertIndex + 3] += offset; // Update Scale packed in UV2 for SDF Shader. // Skip this for surface shader. uv2s[vertIndex + 0].y *= xScale; uv2s[vertIndex + 1].y *= xScale; uv2s[vertIndex + 2].y *= xScale; uv2s[vertIndex + 3].y *= xScale; } xAdvance += glyph.xAdvance * characters_Info[i].scale * xScale + m_textMeshProComponent.characterSpacing; } int lastIndex = characters_Info[characterCount - 1].vertexIndex; float startPos = vertices[0].x; float lastCharPos = vertices[lastIndex + 2].x; // Position of middle of last character. for (int i = 0; i < characterCount; i++) { int vertIndex = characters_Info[i].vertexIndex; if (characters_Info[i].isVisible) { float charMidLine = (vertices[vertIndex + 0].x + vertices[vertIndex + 2].x) / 2; // Middle X-Axis of character. // Need to translate all 4 vertices of each quad to aligned with middle of character / baseline. Vector3 offset = new Vector3(charMidLine, 0, 0); vertices[vertIndex + 0] += -offset; vertices[vertIndex + 1] += -offset; vertices[vertIndex + 2] += -offset; vertices[vertIndex + 3] += -offset; float x0 = (charMidLine - startPos) / (lastCharPos - startPos); // Character position relative to X-Axis normalized (0 - 1) with curve. float x1 = x0 + 0.0001f; float y0 = TranslationCurve.Evaluate(x0) * curveScale; // Y position for middle of character. float y1 = TranslationCurve.Evaluate(x1) * curveScale; Vector3 horizontal = new Vector3(1, 0, 0); Vector3 normal = new Vector3(-(y1 - y0), (x1 * (lastCharPos - startPos) + startPos) - charMidLine, 0); //Vector3 dir = new Vector3(x1 * (lastCharPos - startPos) + startPos, y1) - new Vector3(charMidLine, y0); //Debug.DrawLine(new Vector3(charMidLine, y0, 0), new Vector3(charMidLine, y0, 0) + dir * 1000, Color.green, 60); Debug.DrawLine(new Vector3(charMidLine, y0, 0), normal, Color.green, 60); Vector3 tangent = new Vector3(x1 * (lastCharPos - startPos) + startPos, y1) - new Vector3(charMidLine, y0); //Debug.Log(((x1 * (lastCharPos - startPos) + startPos) - charMidLine)); float dot = Mathf.Acos(Vector3.Dot(horizontal, tangent.normalized)) * 57.2957795f; Vector3 cross = Vector3.Cross(horizontal, tangent); float angle = cross.z > 0 ? dot : 360 - dot; matrix = Matrix4x4.TRS(new Vector3(0, y0, 0), Quaternion.Euler(0, 0, angle), new Vector3(1, 1, 1)); vertices[vertIndex + 0] = matrix.MultiplyPoint3x4(vertices[vertIndex + 0]); vertices[vertIndex + 1] = matrix.MultiplyPoint3x4(vertices[vertIndex + 1]); vertices[vertIndex + 2] = matrix.MultiplyPoint3x4(vertices[vertIndex + 2]); vertices[vertIndex + 3] = matrix.MultiplyPoint3x4(vertices[vertIndex + 3]); //offset = new Vector3(adjusted_pos + xAdvance, 0, 0); vertices[vertIndex + 0] += offset; vertices[vertIndex + 1] += offset; vertices[vertIndex + 2] += offset; vertices[vertIndex + 3] += offset; } } // Upload Mesh Data Mesh mesh = m_textMeshProComponent.mesh; mesh.vertices = vertices; mesh.uv = uv0s; mesh.uv2 = uv2s; mesh.colors32 = vertexColors; // If Surface Shader, upload Normals & Tangents to the mesh. if (m_sharedMaterial.HasProperty(ShaderUtilities.ID_Shininess)) { mesh.normals = normals; mesh.tangents = tangents; } mesh.RecalculateBounds(); // Setting Mesh Bounds manually is more efficient. //m_mesh.bounds = new Bounds(new Vector3((meshExtents.Max.x + meshExtents.Min.x) / 2, (meshExtents.Max.y + meshExtents.Min.y) / 2, 0) + anchorOffset, new Vector3(meshExtents.Max.x - meshExtents.Min.x, meshExtents.Max.y - meshExtents.Min.y, 0)); }
void Awake() { //Debug.Log("TextMeshPro OnEnable() has been called. HavePropertiesChanged = " + havePropertiesChanged); // has been called on Object ID:" + gameObject.GetInstanceID()); // Get Reference to AdvancedLayoutComponent if one is attached. m_advancedLayoutComponent = GetComponent<TMPro_AdvancedLayout>(); //isAdvancedLayoutComponentPresent = m_advancedLayoutComponent == null ? false : true; // Reset max line lenght max_LineWrapLength = 999; // Cache Reference to the Mesh Renderer. m_renderer = renderer; m_renderer.sortingLayerID = m_sortingLayerID; m_renderer.sortingOrder = m_sortingOrder; // Cache Reference to the transform; m_transform = transform; // Cache a reference to the Mesh Filter. m_meshFilter = GetComponent<MeshFilter>(); if (m_meshFilter == null) m_meshFilter = gameObject.AddComponent<MeshFilter>(); // Cache a reference to our mesh. if (m_mesh == null) { //Debug.Log("Creating new mesh."); m_mesh = new Mesh(); m_mesh.hideFlags = HideFlags.HideAndDontSave; m_meshFilter.mesh = m_mesh; //m_mesh.bounds = new Bounds(transform.position, new Vector3(1000, 1000, 0)); } m_meshFilter.hideFlags = HideFlags.HideInInspector; // Load the font asset and assign material to renderer. LoadFontAsset(); // Allocate our initial buffers. m_char_buffer = new int[m_max_characters]; //m_lineExtents = new Mesh_Extents[m_max_numberOfLines]; m_cached_GlyphInfo = new GlyphInfo(); m_vertices = new Vector3[0]; // m_isFirstAllocation = true; m_textInfo = new TextInfo(); m_fontAssetArray = new TextMeshProFont[16]; m_textInfo.lineInfo = new LineInfo[m_max_numberOfLines]; m_textInfo.meshInfo = new TMPro_MeshInfo(); // Check if we have a font asset assigned. Return if we don't because no one likes to see purple squares on screen. if (m_fontAsset == null) { Debug.LogWarning("Please assign a Font Asset to this " + transform.name + " gameobject."); return; } //// Set flags to cause ensure our text is parsed and text redrawn. isInputParsingRequired = true; havePropertiesChanged = true; ForceMeshUpdate(); // Added to force OnWillRenderObject() to be called in case object is not visible so we get initial bounds for the mesh. ///* ScheduleUpdate(); }