// Save the State of various variables used in the mesh creation loop in conjunction with Word Wrapping void SaveWordWrappingState(ref WordWrapState state, int index, int count) { state.previous_WordBreak = index; state.total_CharacterCount = count; state.visible_CharacterCount = m_visibleCharacterCount; state.firstCharacterIndex = m_firstCharacterOfLine; state.visible_SpriteCount = m_visibleSpriteCount; state.visible_LinkCount = m_textInfo.linkCount; state.firstVisibleCharacterIndex = m_firstVisibleCharacterOfLine; state.lastVisibleCharIndex = m_lastVisibleCharacterOfLine; state.xAdvance = m_xAdvance; state.maxAscender = m_maxAscender; state.maxDescender = m_maxDescender; state.preferredWidth = m_preferredWidth; state.preferredHeight = m_preferredHeight; state.fontScale = m_fontScale; state.maxFontScale = m_maxFontScale; //state.previousLineScale = m_previousFontScale; state.currentFontSize = m_currentFontSize; state.lineNumber = m_lineNumber; state.lineOffset = m_lineOffset; state.baselineOffset = m_baselineOffset; state.fontStyle = m_style; //state.alignment = m_lineJustification; state.vertexColor = m_htmlColor; state.colorStackIndex = m_colorStackIndex; state.meshExtents = m_meshExtents; state.lineInfo = m_textInfo.lineInfo[m_lineNumber]; //state.textInfo = m_textInfo; }
// 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 != null ? state.textInfo : m_textInfo; m_currentFontSize = state.currentFontSize; m_fontScale = state.fontScale; m_baselineOffset = state.baselineOffset; m_style = state.fontStyle; //m_lineJustification = state.alignment; m_htmlColor = state.vertexColor; m_colorStackIndex = state.colorStackIndex; m_characterCount = state.total_CharacterCount + 1; m_visibleCharacterCount = state.visible_CharacterCount; m_visibleSpriteCount = state.visible_SpriteCount; m_textInfo.linkCount = state.visible_LinkCount; m_firstCharacterOfLine = state.firstCharacterIndex; m_firstVisibleCharacterOfLine = state.firstVisibleCharacterIndex; m_lastVisibleCharacterOfLine = state.lastVisibleCharIndex; m_meshExtents = state.meshExtents; m_xAdvance = state.xAdvance; m_maxAscender = state.maxAscender; m_maxDescender = state.maxDescender; m_preferredWidth = state.preferredWidth; m_preferredHeight = state.preferredHeight; m_lineNumber = state.lineNumber; m_lineOffset = state.lineOffset; //m_previousFontScale = state.previousLineScale; 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; }
/// <summary> /// This is the main function that is responsible for creating / displaying the text. /// </summary> void GenerateTextMesh() { //Debug.Log("***** GenerateTextMesh() ***** Frame: " + Time.frameCount); // + ". Point Size: " + m_fontSize + ". Margins are (W) " + m_marginWidth + " (H) " + m_marginHeight); // ". Iteration Count: " + loopCountA + ". Min: " + m_minFontSize + " Max: " + m_maxFontSize + " Delta: " + (m_maxFontSize - m_minFontSize) + " Font size is " + m_fontSize); //called for Object with ID " + GetInstanceID()); // Assigned Material is " + m_uiRenderer.GetMaterial().name); // IncludeForMasking " + this.m_IncludeForMasking); // and text is " + m_text); //Debug.Log(this.defaultMaterial.GetInstanceID() + " " + m_sharedMaterial.GetInstanceID() + " " + m_uiRenderer.GetMaterial().GetInstanceID()); // 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; } // Reset TextInfo if (m_textInfo != null) m_textInfo.Clear(); // Early exit if we don't have any Text to generate. if (m_char_buffer == null || m_char_buffer.Length == 0 || m_char_buffer[0] == (char)0) { //Debug.Log("Early Out! No Text has been set."); //Vector3[] vertices = m_textInfo.meshInfo.vertices; m_uiRenderer.SetMesh(null); if (m_inlineGraphics != null) m_inlineGraphics.ClearUIVertex(); /* if (vertices != null) { Array.Clear(vertices, 0, vertices.Length); m_textInfo.meshInfo.mesh.vertices = vertices; } */ m_preferredWidth = 0; m_preferredHeight = 0; m_renderedWidth = 0; m_renderedHeight = 0; // This should only be called if there is a layout component attached LayoutRebuilder.MarkLayoutForRebuild(m_rectTransform); return; } m_currentFontAsset = m_fontAsset; m_currentMaterial = m_sharedMaterial; // Determine how many characters will be visible and make the necessary allocations (if needed). int totalCharacterCount = SetArraySizes(m_char_buffer); // Scale the font to approximately match the point size m_fontScale = (m_fontSize / m_currentFontAsset.fontInfo.PointSize); float baseScale = m_fontScale; // BaseScale keeps the character aligned vertically since <size=+000> results in font of different scale. m_maxFontScale = baseScale; float previousLineMaxScale = baseScale; float firstVisibleCharacterScale = 0; float spriteScale = 1; m_currentFontSize = m_fontSize; float fontSizeDelta = 0; 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 = m_fontStyle; // Set the default style. m_lineJustification = m_textAlignment; // Sets the line justification mode to match editor alignment. // GetPadding to adjust the size of the mesh due to border thickness, softness, glow, etc... if (checkPaddingRequired) { checkPaddingRequired = false; m_padding = ShaderUtilities.GetPadding(m_uiRenderer.GetMaterial(), m_enableExtraPadding, m_isUsingBold); //m_alignmentPadding = ShaderUtilities.GetFontExtent(m_sharedMaterial); m_isMaskingEnabled = ShaderUtilities.IsMaskingEnabled(m_sharedMaterial); } float style_padding = 0; // Extra padding required to accommodate Bold style. float xadvance_multiplier = 1; // Used to increase spacing between character when style is bold. m_baselineOffset = 0; // Used by subscript characters. // Underline bool beginUnderline = false; Vector3 underline_start = Vector3.zero; // Used to track where underline starts & ends. Vector3 underline_end = Vector3.zero; // Strike-through bool beginStrikethrough = false; Vector3 strikethrough_start = Vector3.zero; Vector3 strikethrough_end = Vector3.zero; m_fontColor32 = m_fontColor; Color32 vertexColor; m_htmlColor = m_fontColor32; m_colorStackIndex = 0; Array.Clear(m_colorStack, 0, m_colorStack.Length); m_styleStackIndex = 0; Array.Clear(m_styleStack, 0, m_styleStack.Length); m_lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). m_lineHeight = 0; m_cSpacing = 0; // Amount of space added between characters as a result of the use of the <cspace> tag. m_monoSpacing = 0; float lineOffsetDelta = 0; m_xAdvance = 0; // Used to track the position of each character. m_maxXAdvance = 0; tag_LineIndent = 0; // Used for indentation of text. tag_Indent = 0; tag_NoParsing = false; m_isIgnoringAlignment = false; m_characterCount = 0; // Total characters in the char[] m_visibleCharacterCount = 0; // # of visible characters. m_visibleSpriteCount = 0; // Tracking of line information m_firstCharacterOfLine = 0; m_lastCharacterOfLine = 0; m_firstVisibleCharacterOfLine = 0; m_lastVisibleCharacterOfLine = 0; m_lineNumber = 0; bool isStartOfNewLine = true; m_pageNumber = 0; int pageToDisplay = Mathf.Clamp(m_pageToDisplay - 1, 0, m_textInfo.pageInfo.Length - 1); int ellipsisIndex = 0; m_rectTransform.GetLocalCorners(m_rectCorners); Vector4 margins = m_margin; float marginWidth = m_marginWidth; float marginHeight = m_marginHeight; m_marginLeft = 0; m_marginRight = 0; m_width = -1; // Used by Unity's Auto Layout system. m_renderedWidth = 0; m_renderedHeight = 0; // Initialize struct to track states of word wrapping bool isFirstWord = true; bool isLastBreakingChar = false; //bool isEastAsianLanguage = false; m_SavedLineState = new WordWrapState(); m_SavedWordWrapState = new WordWrapState(); int wrappingIndex = 0; // Need to initialize these Extents structures m_meshExtents = new Extents(k_InfinityVector, -k_InfinityVector); // Initialize lineInfo if (m_textInfo.lineInfo == null) m_textInfo.lineInfo = new TMP_LineInfo[2]; for (int i = 0; i < m_textInfo.lineInfo.Length; i++) { m_textInfo.lineInfo[i] = new TMP_LineInfo(); m_textInfo.lineInfo[i].lineExtents = new Extents(k_InfinityVector, -k_InfinityVector); m_textInfo.lineInfo[i].ascender = -k_InfinityVector.x; m_textInfo.lineInfo[i].descender = k_InfinityVector.x; } // Tracking of the highest Ascender m_maxAscender = 0; m_maxDescender = 0; float pageAscender = 0; float maxVisibleDescender = 0; bool isMaxVisibleDescenderSet = false; m_isNewPage = false; loopCountA += 1; int endTagIndex = 0; // Parse through Character buffer to read HTML tags and begin creating mesh. for (int i = 0; m_char_buffer[i] != 0; i++) { charCode = m_char_buffer[i]; m_isSprite = false; spriteScale = 1; //Debug.Log("i:" + i + " Character [" + (char)charCode + "] with ASCII of " + charCode); //if (m_characterCount >= m_maxVisibleCharacters || m_lineNumber >= m_maxVisibleLines) // break; // Parse Rich Text Tag #region Parse Rich Text Tag if (m_isRichText && charCode == 60) // '<' { m_isParsingText = true; // Check if Tag is valid. If valid, skip to the end of the validated tag. if (ValidateHtmlTag(m_char_buffer, i + 1, out endTagIndex)) { i = endTagIndex; if (m_isRecalculateScaleRequired) { m_fontScale = m_currentFontSize / m_currentFontAsset.fontInfo.PointSize; m_isRecalculateScaleRequired = false; } if (!m_isSprite) continue; } } #endregion End Parse Rich Text Tag m_isParsingText = false; isMissingCharacter = false; // Check if we should be using a different font asset //if (m_fontIndex != 0) //{ // // Check if we need to load the new font asset // if (m_currentFontAsset == null) // { // Debug.Log("Loading secondary font asset."); // m_currentFontAsset = Resources.Load("Fonts & Materials/Bangers SDF", typeof(TextMeshProFont)) as TextMeshProFont; // //m_sharedMaterials.Add(m_currentFontAsset.material); // //m_renderer.sharedMaterials = new Material[] { m_sharedMaterial, m_currentFontAsset.material }; // m_sharedMaterials.ToArray(); // } //} //Debug.Log("Char [" + (char)charCode + "] is using FontIndex: " + m_fontIndex); // Handle Font Styles like LowerCase, UpperCase and SmallCaps. #region Handling of LowerCase, UpperCase and SmallCaps Font Styles if ((m_style & FontStyles.UpperCase) == FontStyles.UpperCase) { // If this character is lowercase, switch to uppercase. if (char.IsLower((char)charCode)) charCode = char.ToUpper((char)charCode); } else if ((m_style & FontStyles.LowerCase) == FontStyles.LowerCase) { // If this character is uppercase, switch to lowercase. if (char.IsUpper((char)charCode)) charCode = char.ToLower((char)charCode); } else if ((m_fontStyle & FontStyles.SmallCaps) == FontStyles.SmallCaps || (m_style & FontStyles.SmallCaps) == FontStyles.SmallCaps) { if (char.IsLower((char)charCode)) { m_fontScale = m_currentFontSize * 0.8f / m_currentFontAsset.fontInfo.PointSize; charCode = char.ToUpper((char)charCode); } else m_fontScale = m_currentFontSize / m_currentFontAsset.fontInfo.PointSize; } #endregion // Look up Character Data from Dictionary and cache it. #region Look up Character Data if (m_isSprite) { SpriteInfo spriteInfo = m_inlineGraphics.GetSprite(m_spriteIndex); if (spriteInfo == null) continue; // Sprites are assigned in the E000 Private Area + sprite Index charCode = 57344 + m_spriteIndex; m_cached_GlyphInfo = new GlyphInfo(); // Generates 40 bytes m_cached_GlyphInfo.x = spriteInfo.x; m_cached_GlyphInfo.y = spriteInfo.y; m_cached_GlyphInfo.width = spriteInfo.width; m_cached_GlyphInfo.height = spriteInfo.height; m_cached_GlyphInfo.xOffset = spriteInfo.pivot.x + spriteInfo.xOffset; m_cached_GlyphInfo.yOffset = spriteInfo.pivot.y + spriteInfo.yOffset; spriteScale = m_fontAsset.fontInfo.Ascender / spriteInfo.height * spriteInfo.scale; m_cached_GlyphInfo.xAdvance = spriteInfo.xAdvance * spriteScale; m_textInfo.characterInfo[m_characterCount].type = TMP_CharacterType.Sprite; } else { m_currentFontAsset.characterDictionary.TryGetValue(charCode, out m_cached_GlyphInfo); if (m_cached_GlyphInfo == null) { // Character wasn't found in the Dictionary. if (char.IsLower((char)charCode)) { if (m_currentFontAsset.characterDictionary.TryGetValue(char.ToUpper((char)charCode), out m_cached_GlyphInfo)) charCode = char.ToUpper((char)charCode); } else if (char.IsUpper((char)charCode)) { if (m_currentFontAsset.characterDictionary.TryGetValue(char.ToLower((char)charCode), out m_cached_GlyphInfo)) charCode = char.ToLower((char)charCode); } // Still don't have a replacement? if (m_cached_GlyphInfo == null) { m_currentFontAsset.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.", this); // 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 ... Debug.LogWarning("Character with ASCII value of " + charCode + " was not found in the Font Asset Glyph Table.", this); continue; } } } m_textInfo.characterInfo[m_characterCount].type = TMP_CharacterType.Character; } #endregion // Store some of the text object's information m_textInfo.characterInfo[m_characterCount].character = (char)charCode; m_textInfo.characterInfo[m_characterCount].pointSize = m_currentFontSize; m_textInfo.characterInfo[m_characterCount].color = m_htmlColor; m_textInfo.characterInfo[m_characterCount].style = m_style; m_textInfo.characterInfo[m_characterCount].index = (short)i; //m_textInfo.characterInfo[m_characterCount].isIgnoringAlignment = m_isIgnoringAlignment; // Handle Kerning if Enabled. #region Handle Kerning if (m_enableKerning && m_characterCount >= 1) { int prev_charCode = m_textInfo.characterInfo[m_characterCount - 1].character; KerningPairKey keyValue = new KerningPairKey(prev_charCode, charCode); KerningPair pair; m_currentFontAsset.kerningDictionary.TryGetValue(keyValue.key, out pair); if (pair != null) { m_xAdvance += pair.XadvanceOffset * m_fontScale; } } #endregion // Handle Mono Spacing #region Handle Mono Spacing float monoAdvance = 0; if (m_monoSpacing != 0) { monoAdvance = (m_monoSpacing / 2 - (m_cached_GlyphInfo.width / 2 + m_cached_GlyphInfo.xOffset) * m_fontScale) * (1 - m_charWidthAdjDelta); m_xAdvance += monoAdvance; } #endregion // Set Padding based on selected font style #region Handle Style Padding if ((m_style & FontStyles.Bold) == FontStyles.Bold || (m_fontStyle & FontStyles.Bold) == FontStyles.Bold) // Checks for any combination of Bold Style. { style_padding = m_currentFontAsset.BoldStyle * 2; xadvance_multiplier = 1 + m_currentFontAsset.boldSpacing * 0.01f; } else { style_padding = m_currentFontAsset.NormalStyle * 2; xadvance_multiplier = 1.0f; } #endregion Handle Style Padding // Set padding value if Character or Sprite float padding = m_isSprite ? 0 : m_padding; // Determine the position of the vertices of the Character or Sprite. Vector3 top_left = new Vector3(0 + m_xAdvance + ((m_cached_GlyphInfo.xOffset - padding - style_padding) * m_fontScale * spriteScale * (1 - m_charWidthAdjDelta)), (m_cached_GlyphInfo.yOffset + padding) * m_fontScale * spriteScale - m_lineOffset + m_baselineOffset, 0); Vector3 bottom_left = new Vector3(top_left.x, top_left.y - ((m_cached_GlyphInfo.height + padding * 2) * m_fontScale * spriteScale), 0); Vector3 top_right = new Vector3(bottom_left.x + ((m_cached_GlyphInfo.width + padding * 2 + style_padding * 2) * m_fontScale * spriteScale * (1 - m_charWidthAdjDelta)), 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 #region Handle Italic & Shearing if ((m_style & FontStyles.Italic) == FontStyles.Italic || (m_fontStyle & 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_currentFontAsset.ItalicStyle * 0.01f; Vector3 topShear = new Vector3(shear_value * ((m_cached_GlyphInfo.yOffset + padding + style_padding) * m_fontScale * spriteScale), 0, 0); Vector3 bottomShear = new Vector3(shear_value * (((m_cached_GlyphInfo.yOffset - m_cached_GlyphInfo.height - padding - style_padding)) * m_fontScale * spriteScale), 0, 0); top_left = top_left + topShear; bottom_left = bottom_left + bottomShear; top_right = top_right + topShear; bottom_right = bottom_right + bottomShear; } #endregion Handle Italics & Shearing // Store position of the vertices for the Character or Sprite. m_textInfo.characterInfo[m_characterCount].bottomLeft = bottom_left; m_textInfo.characterInfo[m_characterCount].topLeft = top_left; m_textInfo.characterInfo[m_characterCount].topRight = top_right; m_textInfo.characterInfo[m_characterCount].bottomRight = bottom_right; m_textInfo.characterInfo[m_characterCount].baseLine = 0 - m_lineOffset + m_baselineOffset; m_textInfo.characterInfo[m_characterCount].scale = m_fontScale; // Compute MaxAscender & MaxDescender which is used for AutoScaling & other type layout options float ascender = m_fontAsset.fontInfo.Ascender * m_fontScale + m_baselineOffset; if ((charCode == 10 || charCode == 13) && m_characterCount > m_firstVisibleCharacterOfLine) ascender = m_baselineOffset; float descender = m_fontAsset.fontInfo.Descender * m_fontScale - m_lineOffset + m_baselineOffset; // Check if Sprite exceeds the Ascender and Descender of the font and if so make the adjustment. if (m_isSprite) { ascender = Mathf.Max(ascender, top_left.y - padding * m_fontScale * spriteScale); descender = Mathf.Min(descender, bottom_right.y - padding * m_fontScale * spriteScale); } if (m_lineNumber == 0) m_maxAscender = m_maxAscender > ascender ? m_maxAscender : ascender; if (m_lineOffset == 0) pageAscender = pageAscender > ascender ? pageAscender : ascender; // Track Line Height //maxLineHeight = Mathf.Max(m_lineHeight, maxLineHeight); // Used to adjust line spacing when larger fonts or the size tag is used. if (m_baselineOffset == 0) m_maxFontScale = Mathf.Max(m_maxFontScale, m_fontScale); // Set Characters to not visible by default. m_textInfo.characterInfo[m_characterCount].isVisible = false; // Setup Mesh for visible characters or sprites. ie. not a SPACE / LINEFEED / CARRIAGE RETURN. #region Handle Visible Characters if (charCode != 10 && charCode != 13 && charCode != 32 && charCode != 160 || m_isSprite) { m_textInfo.characterInfo[m_characterCount].isVisible = true; //if (isStartOfNewLine) { isStartOfNewLine = false; m_firstVisibleCharacterOfLine = m_characterCount; } // Check if Character exceeds the width of the Text Container #region Check for Characters Exceeding Width of Text Container float width = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - m_marginLeft - m_marginRight, m_width) : marginWidth + 0.0001f - m_marginLeft - m_marginRight; m_textInfo.lineInfo[m_lineNumber].width = width; m_textInfo.lineInfo[m_lineNumber].marginLeft = m_marginLeft; if (m_xAdvance + m_cached_GlyphInfo.xAdvance * (1 - m_charWidthAdjDelta) * m_fontScale > width) { ellipsisIndex = m_characterCount - 1; // Last safely rendered character // Word Wrapping #region Handle Word Wrapping if (enableWordWrapping && m_characterCount != m_firstCharacterOfLine) { if (wrappingIndex == m_SavedWordWrapState.previous_WordBreak || isFirstWord) { // Word wrapping is no longer possible. Shrink size of text if auto-sizing is enabled. if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { // Handle Character Width Adjustments #region Character Width Adjustments if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100) { loopCountA = 0; m_charWidthAdjDelta += 0.01f; GenerateTextMesh(); return; } #endregion // Adjust Point Size m_maxFontSize = m_fontSize; m_fontSize -= Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize = (int)(Mathf.Max(m_fontSize, m_fontSizeMin) * 20 + 0.5f) / 20f; if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } // Word wrapping is no longer possible, now breaking up individual words. if (m_isCharacterWrappingEnabled == false) { m_isCharacterWrappingEnabled = true; } else isLastBreakingChar = true; //Debug.Log("Wrapping Index " + wrappingIndex + ". Recursive Count: " + m_recursiveCount); m_recursiveCount += 1; if (m_recursiveCount > 20) { //Debug.Log("Recursive count exceeded!"); continue; } //Debug.Log("Line #" + m_lineNumber + " Character [" + (char)charCode + "] cannot be wrapped. WrappingIndex: " + wrappingIndex + " Saved Index: " + m_SavedWordWrapState.previous_WordBreak + ". Character Count is " + m_characterCount); } // Restore to previously stored state of last valid (space character or linefeed) i = RestoreWordWrappingState(ref m_SavedWordWrapState); wrappingIndex = i; // Used to detect when line length can no longer be reduced. //Debug.Log("Last Visible Character of line # " + m_lineNumber + " is [" + m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].character + " Character Count: " + m_characterCount + " Last visible: " + m_lastVisibleCharacterOfLine); // Check if Line Spacing of previous line needs to be adjusted. FaceInfo face = m_currentFontAsset.fontInfo; float gap = m_lineHeight == 0 ? face.LineHeight - (face.Ascender - face.Descender) : m_lineHeight - (face.Ascender - face.Descender); if (m_lineNumber > 0 && m_maxFontScale != 0 && m_lineHeight == 0 && firstVisibleCharacterScale != m_maxFontScale && !m_isNewPage) { float offsetDelta = 0 - face.Descender * previousLineMaxScale + (face.Ascender + gap + m_lineSpacing + m_paragraphSpacing + m_lineSpacingDelta) * m_maxFontScale; m_lineOffset += offsetDelta - lineOffsetDelta; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount - 1, offsetDelta - lineOffsetDelta); m_SavedWordWrapState.lineOffset = m_lineOffset; } m_isNewPage = false; // Calculate lineAscender & make sure if last character is superscript or subscript that we check that as well. float lineAscender = m_fontAsset.fontInfo.Ascender * m_maxFontScale - m_lineOffset; float lineAscender2 = m_fontAsset.fontInfo.Ascender * m_fontScale - m_lineOffset + m_baselineOffset; lineAscender = lineAscender > lineAscender2 ? lineAscender : lineAscender2; // Calculate lineDescender & make sure if last character is superscript or subscript that we check that as well. float lineDescender = m_fontAsset.fontInfo.Descender * m_maxFontScale - m_lineOffset; float lineDescender2 = m_fontAsset.fontInfo.Descender * m_fontScale - m_lineOffset + m_baselineOffset; lineDescender = lineDescender < lineDescender2 ? lineDescender : lineDescender2; // Update maxDescender and maxVisibleDescender m_maxDescender = m_maxDescender < lineDescender ? m_maxDescender : lineDescender; if (!isMaxVisibleDescenderSet) maxVisibleDescender = m_maxDescender; if (m_characterCount >= m_maxVisibleCharacters || m_lineNumber >= m_maxVisibleLines) isMaxVisibleDescenderSet = true; // Track & Store lineInfo for the new line m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex = m_firstCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].firstVisibleCharacterIndex = m_firstVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex = m_characterCount - 1 > 0 ? m_characterCount - 1 : 0; m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex = m_lastVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].characterCount = m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex - m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + 1; m_textInfo.lineInfo[m_lineNumber].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_firstVisibleCharacterOfLine].bottomLeft.x, lineDescender); m_textInfo.lineInfo[m_lineNumber].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].topRight.x, lineAscender); m_textInfo.lineInfo[m_lineNumber].lineLength = m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x - padding * m_maxFontScale; m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance - m_characterSpacing * m_fontScale; m_textInfo.lineInfo[m_lineNumber].maxScale = m_maxFontScale; m_firstCharacterOfLine = m_characterCount; // Store first character of the next line. // Compute Preferred Width & Height m_renderedWidth += m_xAdvance; if (m_enableWordWrapping) m_renderedHeight = m_maxAscender - m_maxDescender; else m_renderedHeight = Mathf.Max(m_renderedHeight, lineAscender - lineDescender); //Debug.Log("Line # " + m_lineNumber + " Max Font Scale: " + m_maxFontScale + " Current Font Scale: " + currentFontScale); //Debug.Log("LineInfo for line # " + (m_lineNumber) + " First character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + // " First visible character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstVisibleCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[m_lineNumber].firstVisibleCharacterIndex + // " Last character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex + // " Last Visible character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex + // " Character Count of " + m_textInfo.lineInfo[m_lineNumber].characterCount /* + " Line Length of " + m_textInfo.lineInfo[m_lineNumber].lineLength + // " MinX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.x + " MinY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.y + // " MaxX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x + " MaxY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.y + // " Line Ascender: " + lineAscender + " Line Descender: " + lineDescender */ ); // Store the state of the line before starting on the new line. SaveWordWrappingState(ref m_SavedLineState, i, m_characterCount - 1); m_lineNumber += 1; isStartOfNewLine = true; // Check to make sure Array is large enough to hold a new line. if (m_lineNumber >= m_textInfo.lineInfo.Length) ResizeLineExtents(m_lineNumber); // Apply Line Spacing based on scale of the last character of the line. FontStyles style = m_textInfo.characterInfo[m_characterCount].style; float scale = (style & FontStyles.Subscript) == FontStyles.Subscript || (style & FontStyles.Superscript) == FontStyles.Superscript ? m_maxFontScale : m_textInfo.characterInfo[m_characterCount].scale; lineOffsetDelta = 0 - face.Descender * m_maxFontScale + (face.Ascender + gap + m_lineSpacing + m_lineSpacingDelta) * scale; m_lineOffset += lineOffsetDelta; previousLineMaxScale = m_maxFontScale; firstVisibleCharacterScale = scale; m_maxFontScale = 0; spriteScale = 1; m_xAdvance = 0 + tag_Indent; continue; } #endregion End Word Wrapping // Text Auto-Sizing (text exceeding Width of container. #region Handle Text Auto-Sizing if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { // Handle Character Width Adjustments #region Character Width Adjustments if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100) { loopCountA = 0; m_charWidthAdjDelta += 0.01f; GenerateTextMesh(); return; } #endregion // Adjust Point Size m_maxFontSize = m_fontSize; m_fontSize -= Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize = (int)(Mathf.Max(m_fontSize, m_fontSizeMin) * 20 + 0.5f) / 20f; m_recursiveCount = 0; if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } #endregion End Text Auto-Sizing // Handle Text Overflow #region Handle Text Overflow switch (m_overflowMode) { case TextOverflowModes.Overflow: if (m_isMaskingEnabled) DisableMasking(); break; case TextOverflowModes.Ellipsis: if (m_isMaskingEnabled) DisableMasking(); m_isTextTruncated = true; if (m_characterCount < 1) { m_textInfo.characterInfo[m_characterCount].isVisible = false; m_visibleCharacterCount -= 1; break; } m_char_buffer[i - 1] = 8230; m_char_buffer[i] = (char)0; GenerateTextMesh(); return; case TextOverflowModes.Masking: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.ScrollRect: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.Truncate: if (m_isMaskingEnabled) DisableMasking(); m_textInfo.characterInfo[m_characterCount].isVisible = false; break; } #endregion End Text Overflow } #endregion End Check for Characters Exceeding Width of Text Container if (charCode != 9) { // Determine Vertex Color if (isMissingCharacter) vertexColor = Color.red; else if (m_overrideHtmlColors) vertexColor = m_fontColor32; else vertexColor = m_htmlColor; // Store Character & Sprite Vertex Information if (!m_isSprite) SaveGlyphVertexInfo(style_padding, vertexColor); else SaveSpriteVertexInfo(vertexColor); } else // If character is Tab { m_textInfo.characterInfo[m_characterCount].isVisible = false; m_lastVisibleCharacterOfLine = m_characterCount; m_textInfo.lineInfo[m_lineNumber].spaceCount += 1; m_textInfo.spaceCount += 1; } // Increase visible count for Characters. if (m_textInfo.characterInfo[m_characterCount].isVisible) { if (m_isSprite) m_visibleSpriteCount += 1; else m_visibleCharacterCount += 1; if (isStartOfNewLine) { isStartOfNewLine = false; m_firstVisibleCharacterOfLine = m_characterCount; } m_lastVisibleCharacterOfLine = m_characterCount; } } 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 || charCode == 160) { m_textInfo.lineInfo[m_lineNumber].spaceCount += 1; m_textInfo.spaceCount += 1; } } #endregion Handle Visible Characters // Store Rectangle positions for each Character. #region Store Character Data m_textInfo.characterInfo[m_characterCount].lineNumber = (short)m_lineNumber; m_textInfo.characterInfo[m_characterCount].pageNumber = (short)m_pageNumber; if (charCode != 10 && charCode != 13 && charCode != 8230 || m_textInfo.lineInfo[m_lineNumber].characterCount == 1) m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; #endregion Store Character Data // Check if text Exceeds the vertical bounds of the margin area. #region Check Vertical Bounds & Auto-Sizing if (m_maxAscender - descender > marginHeight + 0.0001f) { //Debug.Log((m_maxAscender - descender + (m_alignmentPadding.w * 2 * m_fontScale)).ToString("f6") + " " + marginHeight.ToString("f6")); //Debug.Log("Character [" + (char)charCode + "] at Index: " + m_characterCount + " has exceeded the Height of the text container. Max Ascender: " + m_maxAscender + " Max Descender: " + m_maxDescender + " Margin Height: " + marginHeight + " Bottom Left: " + bottom_left.y); // Handle Line spacing adjustments #region Line Spacing Adjustments if (m_enableAutoSizing && m_lineSpacingDelta > m_lineSpacingMax && m_lineNumber > 0) { m_lineSpacingDelta -= 1; GenerateTextMesh(); return; } #endregion // Handle Text Auto-sizing resulting from text exceeding vertical bounds. #region Text Auto-Sizing (Text greater than vertical bounds) if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { m_maxFontSize = m_fontSize; m_fontSize -= Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize = (int)(Mathf.Max(m_fontSize, m_fontSizeMin) * 20 + 0.5f) / 20f; m_recursiveCount = 0; if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } #endregion Text Auto-Sizing // Handle Text Overflow #region Text Overflow switch (m_overflowMode) { case TextOverflowModes.Overflow: if (m_isMaskingEnabled) DisableMasking(); break; case TextOverflowModes.Ellipsis: if (m_isMaskingEnabled) DisableMasking(); if (m_lineNumber > 0) { m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index] = 8230; m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index + 1] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } else { m_char_buffer[0] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } case TextOverflowModes.Masking: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.ScrollRect: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.Truncate: if (m_isMaskingEnabled) DisableMasking(); // Alternative Implementation //if (m_lineNumber > 0) //{ // if (!m_isTextTruncated && m_textInfo.characterInfo[ellipsisIndex + 1].character != 10) // { // Debug.Log("Char [" + (char)charCode + "] on line " + m_lineNumber + " exceeds the vertical bounds. Last safe character was " + (int)m_textInfo.characterInfo[ellipsisIndex + 1].character); // i = RestoreWordWrappingState(ref m_SavedWordWrapState); // m_lineNumber -= 1; // m_isTextTruncated = true; // m_isCharacterWrappingEnabled = true; // continue; // } // else // { // //Debug.Log("Char [" + (char)charCode + "] on line " + m_lineNumber + " set to invisible."); // m_textInfo.characterInfo[m_characterCount].isVisible = false; // } //// m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index + 1] = (char)0; //// m_isTextTruncated = true; //// i = RestoreWordWrappingState(ref m_SavedLineState); //// m_lineNumber -= 1; //// continue; //} //break; // TODO : Optimize if (m_lineNumber > 0) { m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index + 1] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } else { m_char_buffer[0] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } case TextOverflowModes.Page: if (m_isMaskingEnabled) DisableMasking(); // Ignore Page Break, Linefeed or carriage return if (charCode == 13 || charCode == 10) break; //Debug.Log("Character is [" + (char)charCode + "] with ASCII (" + charCode + ") on Page " + m_pageNumber + ". Ascender: " + m_textInfo.pageInfo[m_pageNumber].ascender + " BaseLine: " + m_textInfo.pageInfo[m_pageNumber].baseLine + " Descender: " + m_textInfo.pageInfo[m_pageNumber].descender); // Go back to previous line and re-layout i = RestoreWordWrappingState(ref m_SavedLineState); if (i == 0) { m_char_buffer[0] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } m_isNewPage = true; m_xAdvance = 0 + tag_Indent; m_lineOffset = 0; m_lineNumber += 1; m_pageNumber += 1; continue; } #endregion End Text Overflow } #endregion Check Vertical Bounds // Handle xAdvance & Tabulation Stops. Tab stops at every 25% of Font Size. #region XAdvance, Tabulation & Stops if (charCode == 9) m_xAdvance += m_fontAsset.fontInfo.TabWidth * m_fontScale; else if (m_monoSpacing != 0) m_xAdvance += (m_monoSpacing - monoAdvance + (m_characterSpacing * m_fontScale) + m_cSpacing) * (1 - m_charWidthAdjDelta); else m_xAdvance += ((m_cached_GlyphInfo.xAdvance * xadvance_multiplier + m_characterSpacing) * m_fontScale + m_cSpacing) * (1 - m_charWidthAdjDelta); // Store xAdvance information m_textInfo.characterInfo[m_characterCount].xAdvance = m_xAdvance; #endregion Tabulation & Stops // Handle Carriage Return #region Carriage Return if (charCode == 13) { m_maxXAdvance = Mathf.Max(m_maxXAdvance, m_renderedWidth + m_xAdvance); m_renderedWidth = 0; m_xAdvance = 0 + tag_Indent; } #endregion Carriage Return // Handle Line Spacing Adjustments + Word Wrapping & special case for last line. #region Check for Line Feed and Last Character if (charCode == 10 || m_characterCount == totalCharacterCount - 1) { // Check if Line Spacing of previous line needs to be adjusted. FaceInfo face = m_currentFontAsset.fontInfo; float gap = m_lineHeight == 0 ? face.LineHeight - (face.Ascender - face.Descender) : m_lineHeight - (face.Ascender - face.Descender); if (m_lineNumber > 0 && m_maxFontScale != 0 && m_lineHeight == 0 && firstVisibleCharacterScale != m_maxFontScale && !m_isNewPage) { float offsetDelta = 0 - face.Descender * previousLineMaxScale + (face.Ascender + gap + m_lineSpacing + m_paragraphSpacing + m_lineSpacingDelta) * m_maxFontScale; m_lineOffset += offsetDelta - lineOffsetDelta; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, offsetDelta - lineOffsetDelta); } m_isNewPage = false; // Calculate lineAscender & make sure if last character is superscript or subscript that we check that as well. float lineAscender = m_fontAsset.fontInfo.Ascender * m_maxFontScale - m_lineOffset; float lineAscender2 = m_fontAsset.fontInfo.Ascender * m_fontScale - m_lineOffset + m_baselineOffset; lineAscender = lineAscender > lineAscender2 ? lineAscender : lineAscender2; // Calculate lineDescender & make sure if last character is superscript or subscript that we check that as well. float lineDescender = m_fontAsset.fontInfo.Descender * m_maxFontScale - m_lineOffset; float lineDescender2 = m_fontAsset.fontInfo.Descender * m_fontScale - m_lineOffset + m_baselineOffset; lineDescender = lineDescender < lineDescender2 ? lineDescender : lineDescender2; // Update maxDescender and maxVisibleDescender m_maxDescender = m_maxDescender < lineDescender ? m_maxDescender : lineDescender; if (!isMaxVisibleDescenderSet) maxVisibleDescender = m_maxDescender; if (m_characterCount >= m_maxVisibleCharacters || m_lineNumber >= m_maxVisibleLines) isMaxVisibleDescenderSet = true; // Save Line Information m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex = m_firstCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].firstVisibleCharacterIndex = m_firstVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex = m_characterCount; m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex = m_lastVisibleCharacterOfLine >= m_firstVisibleCharacterOfLine ? m_lastVisibleCharacterOfLine : m_firstVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].characterCount = m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex - m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + 1; m_textInfo.lineInfo[m_lineNumber].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_firstVisibleCharacterOfLine].bottomLeft.x, lineDescender); m_textInfo.lineInfo[m_lineNumber].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].topRight.x, lineAscender); m_textInfo.lineInfo[m_lineNumber].lineLength = m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x - (padding * m_maxFontScale); m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance - m_characterSpacing * m_fontScale; m_textInfo.lineInfo[m_lineNumber].maxScale = m_maxFontScale; m_firstCharacterOfLine = m_characterCount + 1; // Store PreferredWidth paying attention to linefeed and last character of text. if (charCode == 10 && m_characterCount != totalCharacterCount - 1) { m_maxXAdvance = Mathf.Max(m_maxXAdvance, m_renderedWidth + m_xAdvance); m_renderedWidth = 0; } else m_renderedWidth = Mathf.Max(m_maxXAdvance, m_renderedWidth + m_xAdvance); m_renderedHeight = m_maxAscender - m_maxDescender; //Debug.Log("Line # " + m_lineNumber + " Max Font Scale: " + m_maxFontScale + " Next line Scale: " + m_fontScale); //Debug.Log("LineInfo for line # " + (m_lineNumber) + " First character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + // " First visible character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstVisibleCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[m_lineNumber].firstVisibleCharacterIndex + // " Last character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex + // " Last Visible character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex + // " Character Count of " + m_textInfo.lineInfo[m_lineNumber].characterCount /* + " Line Length of " + m_textInfo.lineInfo[m_lineNumber].lineLength + // " MinX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.x + " MinY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.y + // " MaxX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x + " MaxY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.y + // " Line Ascender: " + lineAscender + " Line Descender: " + lineDescender */ ); // Add new line if not last lines or character. if (charCode == 10) { // Store the state of the line before starting on the new line. SaveWordWrappingState(ref m_SavedLineState, i, m_characterCount); // Store the state of the last Character before the new line. SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); m_lineNumber += 1; isStartOfNewLine = true; // Check to make sure Array is large enough to hold a new line. if (m_lineNumber >= m_textInfo.lineInfo.Length) ResizeLineExtents(m_lineNumber); // Apply Line Spacing float scale = (m_style & FontStyles.Subscript) == FontStyles.Subscript || (m_style & FontStyles.Superscript) == FontStyles.Superscript ? m_maxFontScale : m_fontScale; lineOffsetDelta = 0 - face.Descender * m_maxFontScale + (face.Ascender + gap + m_lineSpacing + m_paragraphSpacing + m_lineSpacingDelta) * scale; m_lineOffset += lineOffsetDelta; previousLineMaxScale = m_maxFontScale; firstVisibleCharacterScale = scale; m_maxFontScale = 0; spriteScale = 1; m_xAdvance = 0 + tag_LineIndent + tag_Indent; ellipsisIndex = m_characterCount - 1; } } #endregion Check for Linefeed or Last Character // Store Rectangle positions for each Character and Mesh Extents. #region Save CharacterInfo for the current character. m_textInfo.characterInfo[m_characterCount].topLine = m_textInfo.characterInfo[m_characterCount].baseLine + m_currentFontAsset.fontInfo.Ascender * m_fontScale; // Ascender m_textInfo.characterInfo[m_characterCount].bottomLine = m_textInfo.characterInfo[m_characterCount].baseLine + m_currentFontAsset.fontInfo.Descender * m_fontScale; // Descender m_textInfo.characterInfo[m_characterCount].padding = padding * m_fontScale; m_textInfo.characterInfo[m_characterCount].aspectRatio = m_cached_GlyphInfo.width / m_cached_GlyphInfo.height; //m_textInfo.characterInfo[m_characterCount].scale = m_fontScale; // Determine the bounds of the Mesh. if (m_textInfo.characterInfo[m_characterCount].isVisible) { m_meshExtents.min = new Vector2(Mathf.Min(m_meshExtents.min.x, m_textInfo.characterInfo[m_characterCount].vertex_BL.position.x), Mathf.Min(m_meshExtents.min.y, m_textInfo.characterInfo[m_characterCount].vertex_BL.position.y)); m_meshExtents.max = new Vector2(Mathf.Max(m_meshExtents.max.x, m_textInfo.characterInfo[m_characterCount].vertex_TR.position.x), Mathf.Max(m_meshExtents.max.y, m_textInfo.characterInfo[m_characterCount].vertex_TL.position.y)); } // Save pageInfo Data if (charCode != 13 && charCode != 10 && m_pageNumber < 16) { m_textInfo.pageInfo[m_pageNumber].ascender = pageAscender; m_textInfo.pageInfo[m_pageNumber].descender = descender < m_textInfo.pageInfo[m_pageNumber].descender ? descender : m_textInfo.pageInfo[m_pageNumber].descender; //Debug.Log("Char [" + (char)charCode + "] with ASCII (" + charCode + ") on Page # " + m_pageNumber + " with Ascender: " + m_textInfo.pageInfo[m_pageNumber].ascender + ". Descender: " + m_textInfo.pageInfo[m_pageNumber].descender); if (m_pageNumber == 0 && m_characterCount == 0) m_textInfo.pageInfo[m_pageNumber].firstCharacterIndex = m_characterCount; else if (m_characterCount > 0 && m_pageNumber != m_textInfo.characterInfo[m_characterCount - 1].pageNumber) { m_textInfo.pageInfo[m_pageNumber - 1].lastCharacterIndex = m_characterCount - 1; m_textInfo.pageInfo[m_pageNumber].firstCharacterIndex = m_characterCount; } else if (m_characterCount == totalCharacterCount - 1) m_textInfo.pageInfo[m_pageNumber].lastCharacterIndex = m_characterCount; } #endregion Saving CharacterInfo // Save State of Mesh Creation for handling of Word Wrapping #region Save Word Wrapping State if (m_enableWordWrapping || m_overflowMode == TextOverflowModes.Truncate || m_overflowMode == TextOverflowModes.Ellipsis) { if ((charCode == 9 || charCode == 32) && !m_isNonBreakingSpace) { // 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. SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); m_isCharacterWrappingEnabled = false; isFirstWord = false; } else if (charCode > 0x2e80 && charCode < 0x9fff || m_fontAsset.lineBreakingInfo.leadingCharacters.ContainsKey(charCode) || m_fontAsset.lineBreakingInfo.followingCharacters.ContainsKey(charCode)) { if (m_characterCount < totalCharacterCount - 1 && m_fontAsset.lineBreakingInfo.leadingCharacters.ContainsKey(charCode) == false && m_fontAsset.lineBreakingInfo.followingCharacters.ContainsKey(m_VisibleCharacters[m_characterCount + 1]) == false) { SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); m_isCharacterWrappingEnabled = false; isFirstWord = false; } } else if ((isFirstWord || m_isCharacterWrappingEnabled == true || isLastBreakingChar)) SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); } #endregion Save Word Wrapping State m_characterCount += 1; } // Check Auto Sizing and increase font size to fill text container. #region Check Auto-Sizing (Upper Font Size Bounds) fontSizeDelta = m_maxFontSize - m_minFontSize; if (!m_isCharacterWrappingEnabled && m_enableAutoSizing && fontSizeDelta > 0.051f && m_fontSize < m_fontSizeMax) { m_minFontSize = m_fontSize; m_fontSize += Mathf.Max((m_maxFontSize - m_fontSize) / 2, 0.05f); m_fontSize = (int)(Mathf.Min(m_fontSize, m_fontSizeMax) * 20 + 0.5f) / 20f; if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } #endregion End Auto-sizing Check m_isCharacterWrappingEnabled = false; // Adjust Preferred Height to account for Margins. m_renderedHeight += m_margin.y > 0 ? m_margin.y : 0; if (m_renderMode == TextRenderFlags.GetPreferredSizes) return; if (!IsRectTransformDriven) { m_preferredWidth = m_renderedWidth; m_preferredHeight = m_renderedHeight; } // DEBUG & PERFORMANCE CHECKS (0.006ms) //Debug.Log("Iteration Count: " + loopCountA + ". Final Point Size: " + m_fontSize); // + " B: " + loopCountB + " C: " + loopCountC + " D: " + loopCountD); // If there are no visible characters... no need to continue if (m_visibleCharacterCount == 0 && m_visibleSpriteCount == 0) { m_uiRenderer.SetMesh(null); //Vector3[] vertices = m_textInfo.meshInfo.vertices; //if (vertices != null) //{ // Array.Clear(vertices, 0, vertices.Length); // m_mesh.vertices = vertices; //} return; } int last_vert_index = m_visibleCharacterCount * 4; // Partial clear of the vertices array to mark unused vertices as degenerate. Array.Clear(m_textInfo.meshInfo.vertices, last_vert_index, m_textInfo.meshInfo.vertices.Length - last_vert_index); // Do we want to clear the sprite array? // Handle Text Alignment #region Text Alignment switch (m_textAlignment) { // Top Vertically case TextAlignmentOptions.Top: case TextAlignmentOptions.TopLeft: case TextAlignmentOptions.TopJustified: case TextAlignmentOptions.TopRight: if (m_overflowMode != TextOverflowModes.Page) m_anchorOffset = m_rectCorners[1] + new Vector3(0 + margins.x, 0 - m_maxAscender - margins.y, 0); else m_anchorOffset = m_rectCorners[1] + new Vector3(0 + margins.x, 0 - m_textInfo.pageInfo[pageToDisplay].ascender - margins.y, 0); break; // Middle Vertically case TextAlignmentOptions.Left: case TextAlignmentOptions.Right: case TextAlignmentOptions.Center: case TextAlignmentOptions.Justified: if (m_overflowMode != TextOverflowModes.Page) m_anchorOffset = (m_rectCorners[0] + m_rectCorners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_maxAscender + margins.y + maxVisibleDescender - margins.w) / 2, 0); else m_anchorOffset = (m_rectCorners[0] + m_rectCorners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_textInfo.pageInfo[pageToDisplay].ascender + margins.y + m_textInfo.pageInfo[pageToDisplay].descender - margins.w) / 2, 0); break; // Bottom Vertically case TextAlignmentOptions.Bottom: case TextAlignmentOptions.BottomLeft: case TextAlignmentOptions.BottomRight: case TextAlignmentOptions.BottomJustified: if (m_overflowMode != TextOverflowModes.Page) m_anchorOffset = m_rectCorners[0] + new Vector3(0 + margins.x, 0 - maxVisibleDescender + margins.w, 0); else m_anchorOffset = m_rectCorners[0] + new Vector3(0 + margins.x, 0 - m_textInfo.pageInfo[pageToDisplay].descender + margins.w, 0); break; // Baseline Vertically case TextAlignmentOptions.Baseline: case TextAlignmentOptions.BaselineLeft: case TextAlignmentOptions.BaselineRight: case TextAlignmentOptions.BaselineJustified: m_anchorOffset = (m_rectCorners[0] + m_rectCorners[1]) / 2 + new Vector3(0 + margins.x, 0, 0); break; // Midline Vertically case TextAlignmentOptions.MidlineLeft: case TextAlignmentOptions.Midline: case TextAlignmentOptions.MidlineRight: case TextAlignmentOptions.MidlineJustified: m_anchorOffset = (m_rectCorners[0] + m_rectCorners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_meshExtents.max.y + margins.y + m_meshExtents.min.y - margins.w) / 2, 0); break; } #endregion Text Alignment // Initialization for Second Pass Vector3 justificationOffset = Vector3.zero; Vector3 offset = Vector3.zero; int vert_index_X4 = 0; int sprite_index_X4 = 0; //Array.Clear(m_meshAllocCount, 0, 17); int wordCount = 0; int lineCount = 0; int lastLine = 0; bool isStartOfWord = false; int wordFirstChar = 0; int wordLastChar = 0; // Second Pass : Line Justification, UV Mapping, Character & Line Visibility & more. #region Handle Line Justification & UV Mapping & Character Visibility & More // Variables used to handle Canvas Render Modes and SDF Scaling bool isCameraAssigned = m_canvas.worldCamera == null ? false : true; float lossyScale = m_rectTransform.lossyScale.z; RenderMode canvasRenderMode = m_canvas.renderMode; float canvasScaleFactor = m_canvas.scaleFactor; int underlineSegmentCount = 0; Color32 underlineColor = Color.white; Color32 strikethroughColor = Color.white; float underlineStartScale = 0; float underlineEndScale = 0; float underlineMaxScale = 0; float underlineBaseLine = Mathf.Infinity; int lastPage = 0; float strikethroughPointSize = 0; float strikethroughScale = 0; float strikethroughBaseline = 0; TMP_CharacterInfo[] characterInfos = m_textInfo.characterInfo; for (int i = 0; i < m_characterCount; i++) { int currentLine = characterInfos[i].lineNumber; char currentCharacter = characterInfos[i].character; TMP_LineInfo lineInfo = m_textInfo.lineInfo[currentLine]; TextAlignmentOptions lineAlignment = lineInfo.alignment; lineCount = currentLine + 1; // Process Line Justification #region Handle Line Justification //if (!characterInfos[i].isIgnoringAlignment) //{ switch (lineAlignment) { case TextAlignmentOptions.TopLeft: case TextAlignmentOptions.Left: case TextAlignmentOptions.BottomLeft: case TextAlignmentOptions.BaselineLeft: case TextAlignmentOptions.MidlineLeft: justificationOffset = new Vector3 (0 + lineInfo.marginLeft, 0, 0); break; case TextAlignmentOptions.Top: case TextAlignmentOptions.Center: case TextAlignmentOptions.Bottom: case TextAlignmentOptions.Baseline: case TextAlignmentOptions.Midline: justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width / 2 - lineInfo.maxAdvance / 2, 0, 0); break; case TextAlignmentOptions.TopRight: case TextAlignmentOptions.Right: case TextAlignmentOptions.BottomRight: case TextAlignmentOptions.BaselineRight: case TextAlignmentOptions.MidlineRight: justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width - lineInfo.maxAdvance, 0, 0); break; case TextAlignmentOptions.TopJustified: case TextAlignmentOptions.Justified: case TextAlignmentOptions.BottomJustified: case TextAlignmentOptions.BaselineJustified: case TextAlignmentOptions.MidlineJustified: charCode = m_textInfo.characterInfo[i].character; char lastCharOfCurrentLine = m_textInfo.characterInfo[lineInfo.lastCharacterIndex].character; if (/*char.IsWhiteSpace(lastCharOfCurrentLine) &&*/ !char.IsControl(lastCharOfCurrentLine) && currentLine < m_lineNumber) { // All lines are justified accept the last one. float gap = lineInfo.width - lineInfo.maxAdvance; if (currentLine != lastLine || i == 0) justificationOffset = new Vector3(lineInfo.marginLeft, 0, 0); else { if (charCode == 9 || charCode == 32 || charCode == 160) { int spaces = lineInfo.spaceCount - 1 > 0 ? lineInfo.spaceCount - 1 : 1; justificationOffset += new Vector3(gap * (1 - m_wordWrappingRatios) / (spaces), 0, 0); } else { justificationOffset += new Vector3(gap * m_wordWrappingRatios / (lineInfo.characterCount - lineInfo.spaceCount - 1), 0, 0); } } } else justificationOffset = new Vector3(lineInfo.marginLeft, 0, 0); // Keep last line left justified. //Debug.Log("Char [" + (char)charCode + "] Code:" + charCode + " Line # " + currentLine + " Offset:" + justificationOffset + " # Spaces:" + lineInfo.spaceCount + " # Characters:" + lineInfo.characterCount); break; } //} #endregion End Text Justification offset = m_anchorOffset + justificationOffset; // Handle Visible Characters #region Handle Visible Characters if (characterInfos[i].isVisible) { TMP_CharacterType type = characterInfos[i].type; switch (type) { // CHARACTERS case TMP_CharacterType.Character: Extents lineExtents = lineInfo.lineExtents; float uvOffset = (m_uvLineOffset * currentLine) % 1 + m_uvOffset.x; // Setup UV2 based on Character Mapping Options Selected #region Handle UV Mapping Options switch (m_horizontalMapping) { case TextureMappingOptions.Character: characterInfos[i].vertex_BL.uv2.x = 0 + m_uvOffset.x; characterInfos[i].vertex_TL.uv2.x = 0 + m_uvOffset.x; characterInfos[i].vertex_TR.uv2.x = 1 + m_uvOffset.x; characterInfos[i].vertex_BR.uv2.x = 1 + m_uvOffset.x; break; case TextureMappingOptions.Line: if (m_textAlignment != TextAlignmentOptions.Justified) { characterInfos[i].vertex_BL.uv2.x = (characterInfos[i].vertex_BL.position.x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; characterInfos[i].vertex_TL.uv2.x = (characterInfos[i].vertex_TL.position.x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; characterInfos[i].vertex_TR.uv2.x = (characterInfos[i].vertex_TR.position.x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; characterInfos[i].vertex_BR.uv2.x = (characterInfos[i].vertex_BR.position.x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; break; } else // Special Case if Justified is used in Line Mode. { characterInfos[i].vertex_BL.uv2.x = (characterInfos[i].vertex_BL.position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; characterInfos[i].vertex_TL.uv2.x = (characterInfos[i].vertex_TL.position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; characterInfos[i].vertex_TR.uv2.x = (characterInfos[i].vertex_TR.position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; characterInfos[i].vertex_BR.uv2.x = (characterInfos[i].vertex_BR.position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; break; } case TextureMappingOptions.Paragraph: characterInfos[i].vertex_BL.uv2.x = (characterInfos[i].vertex_BL.position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; characterInfos[i].vertex_TL.uv2.x = (characterInfos[i].vertex_TL.position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; characterInfos[i].vertex_TR.uv2.x = (characterInfos[i].vertex_TR.position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; characterInfos[i].vertex_BR.uv2.x = (characterInfos[i].vertex_BR.position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; break; case TextureMappingOptions.MatchAspect: switch (m_verticalMapping) { case TextureMappingOptions.Character: characterInfos[i].vertex_BL.uv2.y = 0 + m_uvOffset.y; characterInfos[i].vertex_TL.uv2.y = 1 + m_uvOffset.y; characterInfos[i].vertex_TR.uv2.y = 0 + m_uvOffset.y; characterInfos[i].vertex_BR.uv2.y = 1 + m_uvOffset.y; break; case TextureMappingOptions.Line: characterInfos[i].vertex_BL.uv2.y = (characterInfos[i].vertex_BL.position.y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + uvOffset; characterInfos[i].vertex_TL.uv2.y = (characterInfos[i].vertex_TL.position.y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + uvOffset; characterInfos[i].vertex_TR.uv2.y = characterInfos[i].vertex_BL.uv2.y; characterInfos[i].vertex_BR.uv2.y = characterInfos[i].vertex_TL.uv2.y; break; case TextureMappingOptions.Paragraph: characterInfos[i].vertex_BL.uv2.y = (characterInfos[i].vertex_BL.position.y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + uvOffset; characterInfos[i].vertex_TL.uv2.y = (characterInfos[i].vertex_TL.position.y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + uvOffset; characterInfos[i].vertex_TR.uv2.y = characterInfos[i].vertex_BL.uv2.y; characterInfos[i].vertex_BR.uv2.y = characterInfos[i].vertex_TL.uv2.y; break; case TextureMappingOptions.MatchAspect: Debug.Log("ERROR: Cannot Match both Vertical & Horizontal."); break; } //float xDelta = 1 - (_uv2s[vert_index + 0].y * textMeshCharacterInfo[i].AspectRatio); // Left aligned float xDelta = (1 - ((characterInfos[i].vertex_BL.uv2.y + characterInfos[i].vertex_TL.uv2.y) * characterInfos[i].aspectRatio)) / 2; // Center of Rectangle characterInfos[i].vertex_BL.uv2.x = (characterInfos[i].vertex_BL.uv2.y * characterInfos[i].aspectRatio) + xDelta + uvOffset; characterInfos[i].vertex_TL.uv2.x = characterInfos[i].vertex_BL.uv2.x; characterInfos[i].vertex_TR.uv2.x = (characterInfos[i].vertex_TL.uv2.y * characterInfos[i].aspectRatio) + xDelta + uvOffset; characterInfos[i].vertex_BR.uv2.x = characterInfos[i].vertex_TR.uv2.x; break; } switch (m_verticalMapping) { case TextureMappingOptions.Character: characterInfos[i].vertex_BL.uv2.y = 0 + m_uvOffset.y; characterInfos[i].vertex_TL.uv2.y = 1 + m_uvOffset.y; characterInfos[i].vertex_TR.uv2.y = 1 + m_uvOffset.y; characterInfos[i].vertex_BR.uv2.y = 0 + m_uvOffset.y; break; case TextureMappingOptions.Line: characterInfos[i].vertex_BL.uv2.y = (characterInfos[i].vertex_BL.position.y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + m_uvOffset.y; characterInfos[i].vertex_TL.uv2.y = (characterInfos[i].vertex_TL.position.y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + m_uvOffset.y; characterInfos[i].vertex_BR.uv2.y = characterInfos[i].vertex_BL.uv2.y; characterInfos[i].vertex_TR.uv2.y = characterInfos[i].vertex_TL.uv2.y; break; case TextureMappingOptions.Paragraph: characterInfos[i].vertex_BL.uv2.y = (characterInfos[i].vertex_BL.position.y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + m_uvOffset.y; characterInfos[i].vertex_TL.uv2.y = (characterInfos[i].vertex_TL.position.y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + m_uvOffset.y; characterInfos[i].vertex_BR.uv2.y = characterInfos[i].vertex_BL.uv2.y; characterInfos[i].vertex_TR.uv2.y = characterInfos[i].vertex_TL.uv2.y; break; case TextureMappingOptions.MatchAspect: //float yDelta = 1 - (_uv2s[vert_index + 2].x / textMeshCharacterInfo[i].AspectRatio); // Top Corner float yDelta = (1 - ((characterInfos[i].vertex_BL.uv2.x + characterInfos[i].vertex_TR.uv2.x) / characterInfos[i].aspectRatio)) / 2; // Center of Rectangle //float yDelta = 0; characterInfos[i].vertex_BL.uv2.y = yDelta + (characterInfos[i].vertex_BL.uv2.x / characterInfos[i].aspectRatio) + m_uvOffset.y; characterInfos[i].vertex_TL.uv2.y = yDelta + (characterInfos[i].vertex_TR.uv2.x / characterInfos[i].aspectRatio) + m_uvOffset.y; characterInfos[i].vertex_BR.uv2.y = characterInfos[i].vertex_BL.uv2.y; characterInfos[i].vertex_TR.uv2.y = characterInfos[i].vertex_TL.uv2.y; break; } #endregion End UV Mapping Options // Pack UV's so that we can pass Xscale needed for Shader to maintain 1:1 ratio. #region Pack Scale into UV2 float xScale = characterInfos[i].scale * (1 - m_charWidthAdjDelta); if ((characterInfos[i].style & FontStyles.Bold) == FontStyles.Bold) xScale *= -1; switch (canvasRenderMode) { case RenderMode.ScreenSpaceOverlay: xScale *= lossyScale / canvasScaleFactor; break; case RenderMode.ScreenSpaceCamera: xScale *= isCameraAssigned ? lossyScale : 1; break; case RenderMode.WorldSpace: xScale *= lossyScale; break; } float x0 = characterInfos[i].vertex_BL.uv2.x; float y0 = characterInfos[i].vertex_BL.uv2.y; float x1 = characterInfos[i].vertex_TR.uv2.x; float y1 = characterInfos[i].vertex_TR.uv2.y; float dx = Mathf.Floor(x0); float dy = Mathf.Floor(y0); x0 = x0 - dx; x1 = x1 - dx; y0 = y0 - dy; y1 = y1 - dy; characterInfos[i].vertex_BL.uv2 = PackUV(x0, y0, xScale); characterInfos[i].vertex_TL.uv2 = PackUV(x0, y1, xScale); characterInfos[i].vertex_TR.uv2 = PackUV(x1, y1, xScale); characterInfos[i].vertex_BR.uv2 = PackUV(x1, y0, xScale); #endregion break; // SPRITES case TMP_CharacterType.Sprite: // Nothing right now break; } // Handle maxVisibleCharacters, maxVisibleLines and Overflow Page Mode. #region Handle maxVisibleCharacters / maxVisibleLines / Page Mode if (i < m_maxVisibleCharacters && currentLine < m_maxVisibleLines && m_overflowMode != TextOverflowModes.Page) { characterInfos[i].vertex_BL.position += offset; characterInfos[i].vertex_TL.position += offset; characterInfos[i].vertex_TR.position += offset; characterInfos[i].vertex_BR.position += offset; } else if (i < m_maxVisibleCharacters && currentLine < m_maxVisibleLines && m_overflowMode == TextOverflowModes.Page && characterInfos[i].pageNumber == pageToDisplay) { characterInfos[i].vertex_BL.position += offset; characterInfos[i].vertex_TL.position += offset; characterInfos[i].vertex_TR.position += offset; characterInfos[i].vertex_BR.position += offset; } else { characterInfos[i].vertex_BL.position *= 0; characterInfos[i].vertex_TL.position *= 0; characterInfos[i].vertex_TR.position *= 0; characterInfos[i].vertex_BR.position *= 0; } #endregion // Fill Vertex Buffers for the various types of element if (type == TMP_CharacterType.Character) { FillCharacterVertexBuffers(i, vert_index_X4); vert_index_X4 += 4; } else if (type == TMP_CharacterType.Sprite) { FillSpriteVertexBuffers(i, sprite_index_X4); sprite_index_X4 += 4; } } #endregion // Apply Alignment and Justification Offset m_textInfo.characterInfo[i].bottomLeft += offset; m_textInfo.characterInfo[i].topLeft += offset; m_textInfo.characterInfo[i].topRight += offset; m_textInfo.characterInfo[i].bottomRight += offset; // Need to add top left and bottom right. m_textInfo.characterInfo[i].topLine += offset.y; m_textInfo.characterInfo[i].bottomLine += offset.y; m_textInfo.characterInfo[i].baseLine += offset.y; // Store Max Ascender & Descender if (currentCharacter != 10 && currentCharacter != 13) { m_textInfo.lineInfo[currentLine].ascender = m_textInfo.characterInfo[i].topLine > m_textInfo.lineInfo[currentLine].ascender ? m_textInfo.characterInfo[i].topLine : m_textInfo.lineInfo[currentLine].ascender; m_textInfo.lineInfo[currentLine].descender = m_textInfo.characterInfo[i].bottomLine < m_textInfo.lineInfo[currentLine].descender ? m_textInfo.characterInfo[i].bottomLine : m_textInfo.lineInfo[currentLine].descender; } // Need to recompute lineExtent to account for the offset from justification. #region Adjust lineExtents resulting from alignment offset if (currentLine != lastLine || i == m_characterCount - 1) { // Update the previous line's extents if (currentLine != lastLine) { m_textInfo.lineInfo[lastLine].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[lastLine].firstCharacterIndex].bottomLeft.x, m_textInfo.lineInfo[lastLine].descender); m_textInfo.lineInfo[lastLine].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[lastLine].lastVisibleCharacterIndex].topRight.x, m_textInfo.lineInfo[lastLine].ascender); } // Update the current line's extents if (i == m_characterCount - 1) { m_textInfo.lineInfo[currentLine].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[currentLine].firstCharacterIndex].bottomLeft.x, m_textInfo.lineInfo[currentLine].descender); m_textInfo.lineInfo[currentLine].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[currentLine].lastVisibleCharacterIndex].topRight.x, m_textInfo.lineInfo[currentLine].ascender); } } #endregion // Track Word Count per line and for the object #region Track Word Count if (char.IsLetterOrDigit(currentCharacter) || currentCharacter == 39 || currentCharacter == 8217) { if (isStartOfWord == false) { isStartOfWord = true; wordFirstChar = i; } // If last character is a word if (isStartOfWord && i == m_characterCount - 1) { wordLastChar = i; wordCount += 1; m_textInfo.lineInfo[currentLine].wordCount += 1; TMP_WordInfo wordInfo = new TMP_WordInfo(); wordInfo.firstCharacterIndex = wordFirstChar; wordInfo.lastCharacterIndex = wordLastChar; wordInfo.characterCount = wordLastChar - wordFirstChar + 1; m_textInfo.wordInfo.Add(wordInfo); } } else if (isStartOfWord || i == 0 && (char.IsPunctuation(currentCharacter) || char.IsWhiteSpace(currentCharacter) || i == m_characterCount - 1)) { wordLastChar = i == m_characterCount - 1 && char.IsLetterOrDigit(currentCharacter) ? i : i - 1; isStartOfWord = false; wordCount += 1; m_textInfo.lineInfo[currentLine].wordCount += 1; TMP_WordInfo wordInfo = new TMP_WordInfo(); wordInfo.firstCharacterIndex = wordFirstChar; wordInfo.lastCharacterIndex = wordLastChar; wordInfo.characterCount = wordLastChar - wordFirstChar + 1; m_textInfo.wordInfo.Add(wordInfo); } #endregion // Setup & Handle Underline #region Underline // NOTE: Need to figure out how underline will be handled with multiple fonts and which font will be used for the underline. bool isUnderline = (m_textInfo.characterInfo[i].style & FontStyles.Underline) == FontStyles.Underline; if (isUnderline) { bool isUnderlineVisible = true; int currentPage = m_textInfo.characterInfo[i].pageNumber; if (i > m_maxVisibleCharacters || currentLine > m_maxVisibleLines || (m_overflowMode == TextOverflowModes.Page && currentPage + 1 != m_pageToDisplay)) isUnderlineVisible = false; // We only use the scale of visible characters. if (currentCharacter != 10 && currentCharacter != 13 && currentCharacter != 32 && currentCharacter != 160) { underlineMaxScale = Mathf.Max(underlineMaxScale, m_textInfo.characterInfo[i].scale); underlineBaseLine = Mathf.Min(currentPage == lastPage ? underlineBaseLine : Mathf.Infinity, m_textInfo.characterInfo[i].baseLine + font.fontInfo.Underline * underlineMaxScale); lastPage = currentPage; // Need to track pages to ensure we reset baseline for the new pages. } if (beginUnderline == false && isUnderlineVisible == true && i <= lineInfo.lastVisibleCharacterIndex && currentCharacter != 10 && currentCharacter != 13) { if (i == lineInfo.lastVisibleCharacterIndex && (currentCharacter == 32 || currentCharacter == 160)) { } else { beginUnderline = true; underlineStartScale = m_textInfo.characterInfo[i].scale; if (underlineMaxScale == 0) underlineMaxScale = underlineStartScale; underline_start = new Vector3(m_textInfo.characterInfo[i].bottomLeft.x, underlineBaseLine, 0); underlineColor = m_textInfo.characterInfo[i].color; } } // End Underline if text only contains one character. if (beginUnderline && m_characterCount == 1) { beginUnderline = false; underline_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[i].scale; DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index, underlineStartScale, underlineEndScale, underlineMaxScale, underlineColor); underlineSegmentCount += 1; underlineMaxScale = 0; underlineBaseLine = Mathf.Infinity; } else if (beginUnderline && (i == lineInfo.lastCharacterIndex || i >= lineInfo.lastVisibleCharacterIndex)) { // Terminate underline at previous visible character if space or carriage return. if (currentCharacter == 10 || currentCharacter == 13 || currentCharacter == 32 || currentCharacter == 160) { int lastVisibleCharacterIndex = lineInfo.lastVisibleCharacterIndex; underline_end = new Vector3(m_textInfo.characterInfo[lastVisibleCharacterIndex].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[lastVisibleCharacterIndex].scale; } else { // End underline if last character of the line. underline_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[i].scale; } beginUnderline = false; DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index, underlineStartScale, underlineEndScale, underlineMaxScale, underlineColor); underlineSegmentCount += 1; underlineMaxScale = 0; underlineBaseLine = Mathf.Infinity; } else if (beginUnderline && !isUnderlineVisible) { beginUnderline = false; underline_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[i - 1].scale; DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index, underlineStartScale, underlineEndScale, underlineMaxScale, underlineColor); underlineSegmentCount += 1; underlineMaxScale = 0; underlineBaseLine = Mathf.Infinity; } } else { // End Underline if (beginUnderline == true) { beginUnderline = false; underline_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[i - 1].scale; DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index, underlineStartScale, underlineEndScale, underlineMaxScale, underlineColor); underlineSegmentCount += 1; underlineMaxScale = 0; underlineBaseLine = Mathf.Infinity; } } #endregion // Setup & Handle Strikethrough #region Strikethrough // NOTE: Need to figure out how underline will be handled with multiple fonts and which font will be used for the underline. bool isStrikethrough = (m_textInfo.characterInfo[i].style & FontStyles.Strikethrough) == FontStyles.Strikethrough; if (isStrikethrough) { bool isStrikeThroughVisible = true; if (i > m_maxVisibleCharacters || currentLine > m_maxVisibleLines || (m_overflowMode == TextOverflowModes.Page && m_textInfo.characterInfo[i].pageNumber + 1 != m_pageToDisplay)) isStrikeThroughVisible = false; if (beginStrikethrough == false && isStrikeThroughVisible && i <= lineInfo.lastVisibleCharacterIndex && currentCharacter != 10 && currentCharacter != 13) { if (i == lineInfo.lastVisibleCharacterIndex && (currentCharacter == 32 || currentCharacter == 160)) { } else { beginStrikethrough = true; strikethroughPointSize = m_textInfo.characterInfo[i].pointSize; strikethroughScale = m_textInfo.characterInfo[i].scale; strikethrough_start = new Vector3(m_textInfo.characterInfo[i].bottomLeft.x, m_textInfo.characterInfo[i].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2.75f * strikethroughScale, 0); strikethroughColor = m_textInfo.characterInfo[i].color; strikethroughBaseline = m_textInfo.characterInfo[i].baseLine; //Debug.Log("Char [" + currentCharacter + "] Start Strikethrough POS: " + strikethrough_start); } } // End Strikethrough if text only contains one character. if (beginStrikethrough && m_characterCount == 1) { beginStrikethrough = false; strikethrough_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); underlineSegmentCount += 1; } else if (beginStrikethrough && i == lineInfo.lastCharacterIndex) { // Terminate Strikethrough at previous visible character if space or carriage return. if (currentCharacter == 10 || currentCharacter == 13 || currentCharacter == 32 || currentCharacter == 160) { int lastVisibleCharacterIndex = lineInfo.lastVisibleCharacterIndex; strikethrough_end = new Vector3(m_textInfo.characterInfo[lastVisibleCharacterIndex].topRight.x, m_textInfo.characterInfo[lastVisibleCharacterIndex].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); } else { // Terminate Strikethrough at last character of line. strikethrough_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); } beginStrikethrough = false; DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); underlineSegmentCount += 1; } else if (beginStrikethrough && i < m_characterCount && (m_textInfo.characterInfo[i + 1].pointSize != strikethroughPointSize || !TMP_Math.Equals(m_textInfo.characterInfo[i + 1].baseLine + offset.y, strikethroughBaseline))) { // Terminate Strikethrough if scale changes. beginStrikethrough = false; int lastVisibleCharacterIndex = lineInfo.lastVisibleCharacterIndex; if (i > lastVisibleCharacterIndex) strikethrough_end = new Vector3(m_textInfo.characterInfo[lastVisibleCharacterIndex].topRight.x, m_textInfo.characterInfo[lastVisibleCharacterIndex].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); else strikethrough_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); underlineSegmentCount += 1; //Debug.Log("Char [" + currentCharacter + "] at Index: " + i + " End Strikethrough POS: " + strikethrough_end + " Baseline: " + m_textInfo.characterInfo[i].baseLine.ToString("f3")); } else if (beginStrikethrough && !isStrikeThroughVisible) { // Terminate Strikethrough if character is not visible. beginStrikethrough = false; strikethrough_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, m_textInfo.characterInfo[i - 1].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); underlineSegmentCount += 1; } } else { // End Underline if (beginStrikethrough == true) { beginStrikethrough = false; strikethrough_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, m_textInfo.characterInfo[i - 1].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * m_fontScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); underlineSegmentCount += 1; } } #endregion lastLine = currentLine; } #endregion // METRICS ABOUT THE TEXT OBJECT m_textInfo.characterCount = (short)m_characterCount; m_textInfo.spriteCount = m_spriteCount; m_textInfo.lineCount = (short)lineCount; m_textInfo.wordCount = wordCount != 0 && m_characterCount > 0 ? (short)wordCount : (short)1; m_textInfo.pageCount = m_pageNumber + 1; // If Advanced Layout Component is present, don't upload the mesh. if (m_renderMode == TextRenderFlags.Render) // m_isAdvanceLayoutComponentPresent == false || m_advancedLayoutComponent.isEnabled == false) { //Debug.Log("Uploading Mesh normally."); // Upload Mesh Data m_mesh.vertices = m_textInfo.meshInfo.vertices; m_mesh.uv = m_textInfo.meshInfo.uvs0; m_mesh.uv2 = m_textInfo.meshInfo.uvs2; m_mesh.colors32 = m_textInfo.meshInfo.colors32; m_mesh.RecalculateBounds(); // Replace by manual bound calculation to improve performance. m_uiRenderer.SetMesh(m_mesh); if (m_inlineGraphics != null) m_inlineGraphics.DrawSprite(m_inlineGraphics.uiVertex, m_visibleSpriteCount); } // Compute Bounds for the mesh. Manual computation is more efficient then using Mesh.recalcualteBounds. m_bounds = new Bounds(new Vector3((m_meshExtents.max.x + m_meshExtents.min.x) / 2, (m_meshExtents.max.y + m_meshExtents.min.y) / 2, 0) + offset, new Vector3(m_meshExtents.max.x - m_meshExtents.min.x, m_meshExtents.max.y - m_meshExtents.min.y, 0)); // Has Text Container's Width or Height been specified by the user? /* if (m_rectTransform.sizeDelta.x == 0 || m_rectTransform.sizeDelta.y == 0) { //Debug.Log("Auto-fitting Text. Default Width:" + m_textContainer.isDefaultWidth + " Default Height:" + m_textContainer.isDefaultHeight); if (marginWidth == 0) m_rectTransform.sizeDelta = new Vector2(m_preferredWidth + margins.x + margins.z, m_rectTransform.sizeDelta.y); if (marginHeight == 0) m_rectTransform.sizeDelta = new Vector2(m_rectTransform.sizeDelta.x, m_preferredHeight + margins.y + margins.w); Debug.Log("Auto-fitting Text. Default Width:" + m_preferredWidth + " Default Height:" + m_preferredHeight); GenerateTextMesh(); return; } */ //for (int i = 0; i < m_lineNumber + 1; i++) //{ // Debug.Log("Line: " + (i + 1) + " Character Count: " + m_textInfo.lineInfo[i].characterCount // + " Word Count: " + m_textInfo.lineInfo[i].wordCount // + " Space Count: " + m_textInfo.lineInfo[i].spaceCount // + " First: [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].firstCharacterIndex // + " First Visible: [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].firstVisibleCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].firstVisibleCharacterIndex // + " Last [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].lastCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].lastCharacterIndex // + " Last visible [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].lastVisibleCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].lastVisibleCharacterIndex // + " Length: " + m_textInfo.lineInfo[i].lineLength // + " Line Extents: " + m_textInfo.lineInfo[i].lineExtents); //} //Debug.Log("Done rendering text. Character Count is " + m_textInfo.characterCount); //Debug.Log("Done rendering text. Preferred Width:" + m_preferredWidth + " Preferred Height:" + m_preferredHeight); // Event indicating the text has been regenerated. TMPro_EventManager.ON_TEXT_CHANGED(this); //Debug.Log(m_minWidth); //Profiler.EndSample(); //m_StopWatch.Stop(); //Debug.Log("Done Rendering Text."); //Debug.Log("TimeElapsed is:" + (m_StopWatch.ElapsedTicks / 10000f).ToString("f4")); //m_StopWatch.Reset(); }
/// <summary> /// Restore the State of various variables used in the mesh creation loop. /// </summary> /// <param name="state"></param> /// <returns></returns> protected int RestoreWordWrappingState(ref WordWrapState state) { int index = state.previous_WordBreak; // Multi Font & Material support related m_currentFontAsset = state.currentFontAsset; m_currentSpriteAsset = state.currentSpriteAsset; m_currentMaterial = state.currentMaterial; m_currentMaterialIndex = state.currentMaterialIndex; m_characterCount = state.total_CharacterCount + 1; m_visibleCharacterCount = state.visible_CharacterCount; m_visibleSpriteCount = state.visible_SpriteCount; m_textInfo.linkCount = state.visible_LinkCount; m_firstCharacterOfLine = state.firstCharacterIndex; m_firstVisibleCharacterOfLine = state.firstVisibleCharacterIndex; m_lastVisibleCharacterOfLine = state.lastVisibleCharIndex; m_style = state.fontStyle; m_fontScale = state.fontScale; m_fontScaleMultiplier = state.fontScaleMultiplier; //m_maxFontScale = state.maxFontScale; m_currentFontSize = state.currentFontSize; m_xAdvance = state.xAdvance; m_maxAscender = state.maxAscender; m_maxDescender = state.maxDescender; m_maxLineAscender = state.maxLineAscender; m_maxLineDescender = state.maxLineDescender; m_startOfLineAscender = state.previousLineAscender; m_preferredWidth = state.preferredWidth; m_preferredHeight = state.preferredHeight; m_meshExtents = state.meshExtents; m_lineNumber = state.lineNumber; m_lineOffset = state.lineOffset; m_baselineOffset = state.baselineOffset; //m_lineJustification = state.alignment; m_htmlColor = state.vertexColor; tag_NoParsing = state.tagNoParsing; // XML Tag Stack m_colorStack = state.colorStack; m_sizeStack = state.sizeStack; m_fontWeightStack = state.fontWeightStack; m_styleStack = state.styleStack; m_materialReferenceStack = state.materialReferenceStack; if (m_lineNumber < m_textInfo.lineInfo.Length) m_textInfo.lineInfo[m_lineNumber] = state.lineInfo; return index; }
/// <summary> /// Save the State of various variables used in the mesh creation loop in conjunction with Word Wrapping /// </summary> /// <param name="state"></param> /// <param name="index"></param> /// <param name="count"></param> protected void SaveWordWrappingState(ref WordWrapState state, int index, int count) { // Multi Font & Material support related state.currentFontAsset = m_currentFontAsset; state.currentSpriteAsset = m_currentSpriteAsset; state.currentMaterial = m_currentMaterial; state.currentMaterialIndex = m_currentMaterialIndex; state.previous_WordBreak = index; state.total_CharacterCount = count; state.visible_CharacterCount = m_visibleCharacterCount; state.visible_SpriteCount = m_visibleSpriteCount; state.visible_LinkCount = m_textInfo.linkCount; state.firstCharacterIndex = m_firstCharacterOfLine; state.firstVisibleCharacterIndex = m_firstVisibleCharacterOfLine; state.lastVisibleCharIndex = m_lastVisibleCharacterOfLine; state.fontStyle = m_style; state.fontScale = m_fontScale; //state.maxFontScale = m_maxFontScale; state.fontScaleMultiplier = m_fontScaleMultiplier; state.currentFontSize = m_currentFontSize; state.xAdvance = m_xAdvance; state.maxAscender = m_maxAscender; state.maxDescender = m_maxDescender; state.maxLineAscender = m_maxLineAscender; state.maxLineDescender = m_maxLineDescender; state.previousLineAscender = m_startOfLineAscender; state.preferredWidth = m_preferredWidth; state.preferredHeight = m_preferredHeight; state.meshExtents = m_meshExtents; state.lineNumber = m_lineNumber; state.lineOffset = m_lineOffset; state.baselineOffset = m_baselineOffset; //state.alignment = m_lineJustification; state.vertexColor = m_htmlColor; state.tagNoParsing = tag_NoParsing; // XML Tag Stack state.colorStack = m_colorStack; state.sizeStack = m_sizeStack; state.fontWeightStack = m_fontWeightStack; state.styleStack = m_styleStack; state.materialReferenceStack = m_materialReferenceStack; if (m_lineNumber < m_textInfo.lineInfo.Length) state.lineInfo = m_textInfo.lineInfo[m_lineNumber]; }
/// <summary> /// This is the main function that is responsible for creating / displaying the text. /// </summary> void GenerateTextMesh() { //Debug.Log("GenerateTextMesh() called."); // Assigned Material is " + m_uiRenderer.GetMaterial().name); // IncludeForMasking " + this.m_IncludeForMasking); // and text is " + m_text); //Debug.Log(this.defaultMaterial.GetInstanceID() + " " + m_sharedMaterial.GetInstanceID() + " " + m_uiRenderer.GetMaterial().GetInstanceID()); // 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; } // Reset TextInfo if (m_textInfo != null) { m_textInfo.characterCount = 0; m_textInfo.lineCount = 0; m_textInfo.spaceCount = 0; m_textInfo.wordCount = 0; m_textInfo.minWidth = 0; } // Early exit if we don't have any Text to generate. if (m_char_buffer == null || m_char_buffer.Length == 0 || m_char_buffer[0] == (char)0) { //Debug.Log("Early Out! No Text has been set."); if (m_uiVertices != null) { m_uiRenderer.SetVertices(m_uiVertices, 0); } m_preferredWidth = 0; m_preferredHeight = 0; // This should only be called if there is a layout component attached if (IsRectTransformDriven) LayoutRebuilder.MarkLayoutForRebuild(m_rectTransform); return; } // Determine how many characters will be visible and make the necessary allocations (if needed). int totalCharacterCount = SetArraySizes(m_char_buffer); m_fontIndex = 0; // Will be used when support for using different font assets or sprites withint the same object will be added. 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); //float baseScale = m_fontScale; // BaseScale keeps the character aligned vertically since <size=+000> results in font of different scale. m_maxFontScale = 0; float previousFontScale = 0; m_currentFontSize = m_fontSize; float fontSizeDelta = 0; 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. //bool isLineTruncated = false; m_style = m_fontStyle; // Set the default style. m_lineJustification = m_textAlignment; // Sets the line justification mode to match editor alignment. // GetPadding to adjust the size of the mesh due to border thickness, softness, glow, etc... if (checkPaddingRequired) { checkPaddingRequired = false; m_padding = ShaderUtilities.GetPadding(m_uiRenderer.GetMaterial(), m_enableExtraPadding, m_isUsingBold); m_alignmentPadding = ShaderUtilities.GetFontExtent(m_sharedMaterial); m_isMaskingEnabled = ShaderUtilities.IsMaskingEnabled(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. bool beginUnderline = false; Vector3 underline_start = Vector3.zero; // Used to track where underline starts & ends. Vector3 underline_end = Vector3.zero; m_fontColor32 = m_fontColor; Color32 vertexColor; m_htmlColor = m_fontColor32; m_lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). m_cSpacing = 0; // Amount of space added between characters as a result of the use of the <cspace> tag. float lineOffsetDelta = 0; m_xAdvance = 0; // Used to track the position of each character. m_maxXAdvance = 0; m_lineNumber = 0; m_characterCount = 0; // Total characters in the char[] m_visibleCharacterCount = 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; int firstCharacterOfLine = 0; int lastCharacterOfLine = 0; int ellipsisIndex = 0; //int truncateIndex = -1; //bool isLineTruncated = false; //int truncatedLine = 0; //m_isTextTruncated = false; m_rectTransform.GetLocalCorners(m_rectCorners); // m_textContainer.corners; //Debug.Log (corners [0] + " " + corners [2]); Vector4 margins = m_margin; // _textContainer.margins; float marginWidth = m_marginWidth; // m_rectTransform.rect.width - margins.z - margins.x; float marginHeight = m_marginHeight; // m_rectTransform.rect.height - margins.y - margins.w; m_preferredWidth = 0; m_preferredHeight = 0; //m_layoutWidth = 0; //m_layoutHeight = 0; // Initialize struct to track states of word wrapping m_SavedWordWrapState = new WordWrapState(); m_SavedLineState = new WordWrapState(); int wrappingIndex = 0; // Need to initialize these Extents Structs m_meshExtents = new Extents(k_InfinityVector, -k_InfinityVector); if (m_textInfo.wordInfo == null) m_textInfo.wordInfo = new List<WordInfo>(); else m_textInfo.wordInfo.Clear(); if (m_textInfo.lineInfo == null) m_textInfo.lineInfo = new LineInfo[2]; for (int i = 0; i < m_textInfo.lineInfo.Length; i++) { m_textInfo.lineInfo[i] = new LineInfo(); m_textInfo.lineInfo[i].lineExtents = new Extents(k_InfinityVector, -k_InfinityVector); m_textInfo.lineInfo[i].ascender = -k_InfinityVector.x; m_textInfo.lineInfo[i].descender = k_InfinityVector.x; } // Tracking of the highest Ascender m_maxAscender = 0; m_maxDescender = 0; bool isLineOffsetAdjusted = false; int endTagIndex = 0; // Parse through Character buffer to read html tags and begin creating mesh. for (int i = 0; m_char_buffer[i] != 0; i++) { m_tabSpacing = -999; m_spacing = -999; charCode = m_char_buffer[i]; //Debug.Log("i:" + i + " Character [" + (char)charCode + "]"); //loopCountE += 1; if (m_isRichText && charCode == 60) // '<' { // Check if Tag is valid. If valid, skip to the end of the validated tag. if (ValidateHtmlTag(m_char_buffer, i + 1, out endTagIndex)) { i = endTagIndex; if (m_isRecalculateScaleRequired) { m_fontScale = m_currentFontSize / m_fontAssetArray[m_fontIndex].fontInfo.PointSize; //isAffectingWordWrapping = true; m_isRecalculateScaleRequired = false; } 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; // Check if we should be using a different font asset //if (m_fontIndex != 0) //{ // // Check if we need to load the new font asset // if (m_fontAssetArray[m_fontIndex] == null) // { // Debug.Log("Loading secondary font asset."); // m_fontAssetArray[m_fontIndex] = Resources.Load("Fonts & Materials/Bangers SDF", typeof(TextMeshProFont)) as TextMeshProFont; // //m_sharedMaterials.Add(m_fontAssetArray[m_fontIndex].material); // //m_renderer.sharedMaterials = new Material[] { m_sharedMaterial, m_fontAssetArray[m_fontIndex].material }; // m_sharedMaterials.ToArray(); // } //} //Debug.Log("Char [" + (char)charCode + "] is using FontIndex: " + m_fontIndex); // Handle Font Styles like LowerCase, UpperCase and SmallCaps. #region Handling of LowerCase, UpperCase and SmallCaps Font Styles if ((m_style & FontStyles.UpperCase) == FontStyles.UpperCase) { // If this character is lowercase, switch to uppercase. if (char.IsLower((char)charCode)) charCode -= 32; } else if ((m_style & FontStyles.LowerCase) == FontStyles.LowerCase) { // If this character is uppercase, switch to lowercase. if (char.IsUpper((char)charCode)) charCode += 32; } else if ((m_fontStyle & FontStyles.SmallCaps) == FontStyles.SmallCaps || (m_style & FontStyles.SmallCaps) == FontStyles.SmallCaps) { if (char.IsLower((char)charCode)) { m_fontScale = m_currentFontSize * 0.8f / m_fontAssetArray[m_fontIndex].fontInfo.PointSize; charCode -= 32; } else m_fontScale = m_currentFontSize / m_fontAssetArray[m_fontIndex].fontInfo.PointSize; } #endregion // Look up Character Data from Dictionary and cache it. #region Look up Character Data m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(charCode, out m_cached_GlyphInfo); if (m_cached_GlyphInfo == null) { // Character wasn't found in the Dictionary. // Check if Lowercase & Replace by Uppercase if possible if (char.IsLower((char)charCode)) { if (m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(charCode - 32, out m_cached_GlyphInfo)) charCode -= 32; } else if (char.IsUpper((char)charCode)) { if (m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(charCode + 32, out m_cached_GlyphInfo)) charCode += 32; } // Still don't have a replacement? if (m_cached_GlyphInfo == null) { 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 ... Debug.LogWarning("Character with ASCII value of " + charCode + " was not found in the Font Asset Glyph Table."); continue; } } } #endregion // Store some of the text object's information m_textInfo.characterInfo[m_characterCount].character = (char)charCode; m_textInfo.characterInfo[m_characterCount].color = m_htmlColor; m_textInfo.characterInfo[m_characterCount].style = m_style; m_textInfo.characterInfo[m_characterCount].index = (short)i; // Handle Kerning if Enabled. #region Handle Kerning if (m_enableKerning && m_characterCount >= 1) { int prev_charCode = m_textInfo.characterInfo[m_characterCount - 1].character; KerningPairKey keyValue = new KerningPairKey(prev_charCode, charCode); KerningPair pair; m_fontAssetArray[m_fontIndex].kerningDictionary.TryGetValue(keyValue.key, out pair); if (pair != null) { m_xAdvance += pair.XadvanceOffset * m_fontScale; } } #endregion // Set Padding based on selected font style #region Handle Style Padding if ((m_style & FontStyles.Bold) == FontStyles.Bold || (m_fontStyle & FontStyles.Bold) == FontStyles.Bold) // Checks for any combination of Bold Style. { style_padding = m_fontAssetArray[m_fontIndex].BoldStyle * 2; xadvance_multiplier = 1.07f; // Increase xAdvance for bold characters. } else { style_padding = m_fontAssetArray[m_fontIndex].NormalStyle * 2; xadvance_multiplier = 1.0f; } #endregion Handle Style Padding // 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 - m_lineOffset, 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 #region Handle Italic & Shearing if ((m_style & FontStyles.Italic) == FontStyles.Italic || (m_fontStyle & 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_fontAssetArray[m_fontIndex].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; } #endregion Handle Italics & Shearing // Store postion of vertices for each character m_textInfo.characterInfo[m_characterCount].topLeft = top_left; m_textInfo.characterInfo[m_characterCount].bottomLeft = bottom_left; m_textInfo.characterInfo[m_characterCount].topRight = top_right; m_textInfo.characterInfo[m_characterCount].bottomRight = bottom_right; // Compute MaxAscender & MaxDescender which is used for AutoScaling & other type layout options float ascender = (m_fontAsset.fontInfo.Ascender + m_baselineOffset + m_alignmentPadding.y) * m_fontScale; if (m_lineNumber == 0) m_maxAscender = m_maxAscender > ascender ? m_maxAscender : ascender; //float descender = (m_fontAsset.fontInfo.Descender + m_baselineOffset + m_alignmentPadding.w) * m_fontScale - m_lineOffset; //m_maxDescender = m_maxDescender < descender ? m_maxDescender : descender; // Set Characters to not visible by default. m_textInfo.characterInfo[m_characterCount].isVisible = false; // Setup Mesh for visible characters. ie. not a SPACE / LINEFEED / CARRIAGE RETURN. #region Handle Visible Characters if (charCode != 32 && charCode != 9 && charCode != 10 && charCode != 13) { int index_X4 = m_visibleCharacterCount * 4; m_textInfo.characterInfo[m_characterCount].isVisible = true; m_textInfo.characterInfo[m_characterCount].vertexIndex = (short)(0 + index_X4); // Vertices m_uiVertices[0 + index_X4].position = m_textInfo.characterInfo[m_characterCount].bottomLeft; m_uiVertices[1 + index_X4].position = m_textInfo.characterInfo[m_characterCount].topLeft; m_uiVertices[2 + index_X4].position = m_textInfo.characterInfo[m_characterCount].topRight; m_uiVertices[3 + index_X4].position = m_textInfo.characterInfo[m_characterCount].bottomRight; //m_textInfo.characterInfo[character_Count].charNumber = (short)m_lineExtents[lineNumber].characterCount; // Used to adjust line spacing when larger fonts or the size tag is used. if (m_baselineOffset == 0) m_maxFontScale = Mathf.Max(m_maxFontScale, m_fontScale); //Debug.Log("Char [" + (char)charCode + "] Width is " + (m_textInfo.characterInfo[m_characterCount].topRight.x - (m_padding - m_alignmentPadding.z) * m_fontScale) + " Margin Width is " + marginWidth + // " Vertex Padding: " + (m_padding * m_fontScale) + " Alignment Padding: " + (m_alignmentPadding.z * m_fontScale) + " Alignment Padding: " + (m_alignmentPadding.x * m_fontScale)); // Check if Character exceeds the width of the Text Container #region Check for Characters Exceeding Width of Text Container if (m_textInfo.characterInfo[m_characterCount].topRight.x - (m_padding - m_alignmentPadding.z) * m_fontScale > marginWidth) { // Word Wrapping #region Handle Word Wrapping if (enableWordWrapping) { ellipsisIndex = m_characterCount - 1; if (wrappingIndex == m_SavedWordWrapState.previous_WordBreak) // && m_isCharacterWrappingEnabled == false) { // Word wrapping is no longer possible. Shrink size of text if auto-sizing is enabled. if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { //Debug.Log("Reducing Font Size."); m_maxFontSize = m_fontSize; float delta = Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.01f); m_fontSize -= delta; //Debug.Log("Delta = " + delta); m_fontSize = Mathf.Max(m_fontSize, m_fontSizeMin); //loopCountC += 1; //Debug.Log("Count C: " + loopCountC + " Min: " + m_minFontSize + " Max: " + m_maxFontSize + " Size: " + m_fontSize + " Delta: " + fontSizeDelta); //m_fontSize -= 1f; // 0.25f; //m_maxFontSize = m_fontSize; //Debug.Log("Decreasing Width to " + m_fontSize); GenerateTextMesh(); return; } // Word wrapping is no longer possible, now breaking up individual words. if (m_isCharacterWrappingEnabled == false) { m_isCharacterWrappingEnabled = true; // Should add a check to make sure this mode is available. //Debug.Log("Enabling Character Wrapping."); GenerateTextMesh(); return; } /* if (isAffectingWordWrapping) { m_textContainer.size = new Vector2(Mathf.Round((top_right.x + margins.x + margins.z) * 100 + 0.5f) / 100f, m_textContainer.rect.height); GenerateTextMesh(); isAffectingWordWrapping = false; } else { //Debug.Log("Text can no longer be reduced in size. Min font size reached."); //m_textContainer.size = new Vector2(Mathf.Round((top_right.x + margins.x + margins.z) * 100 + 0.5f) / 100f, m_textContainer.rect.height); //GenerateTextMesh(); } */ //m_isCharacterWrappingEnabled = true; //Debug.Log("Line #" + lineNumber + " Character [" + (char)charCode + "] cannot be wrapped."); // WrappingIndex: " + wrappingIndex + " Saved Index: " + m_SaveWordWrapState.previous_WordBreak); return; } // Restore to previously stored state of last valid (space character or linefeed) i = RestoreWordWrappingState(ref m_SavedWordWrapState); wrappingIndex = i; // Used to dectect when line length can no longer be reduced. // Check if we need to Adjust LineOffset & Restore State to the start of the line. if (m_lineNumber > 0 && m_maxFontScale != 0 && m_maxFontScale != previousFontScale && !isLineOffsetAdjusted) { // Compute Offset float gap = m_fontAssetArray[m_fontIndex].fontInfo.LineHeight - (m_fontAssetArray[m_fontIndex].fontInfo.Ascender - m_fontAssetArray[m_fontIndex].fontInfo.Descender); float offsetDelta = (m_fontAssetArray[m_fontIndex].fontInfo.Ascender + m_lineSpacing + m_paragraphSpacing + gap + m_lineSpacingDelta) * m_maxFontScale - (m_fontAssetArray[m_fontIndex].fontInfo.Descender - gap) * previousFontScale; m_lineOffset += offsetDelta - lineOffsetDelta; AdjustLineOffset(firstCharacterOfLine, lastCharacterOfLine, offsetDelta - lineOffsetDelta); } // Calculate lineAscender & make sure if last character is superscript or subscript that we check that as well. float lineAscender = (m_fontAsset.fontInfo.Ascender + m_alignmentPadding.y) * m_maxFontScale - m_lineOffset; float lineAscender2 = (m_fontAsset.fontInfo.Ascender + m_baselineOffset + m_alignmentPadding.y) * m_fontScale - m_lineOffset; lineAscender = lineAscender > lineAscender2 ? lineAscender : lineAscender2; // Calculate lineDescender & make sure if last character is superscript or subscript that we check that as well. float lineDescender = (m_fontAsset.fontInfo.Descender + m_alignmentPadding.w) * m_maxFontScale - m_lineOffset; float lineDescender2 = (m_fontAsset.fontInfo.Descender + m_baselineOffset + m_alignmentPadding.w) * m_fontScale - m_lineOffset; lineDescender = lineDescender < lineDescender2 ? lineDescender : lineDescender2; m_maxDescender = m_maxDescender < lineDescender ? m_maxDescender : lineDescender; // Track & Store lineInfo for the new line m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex = firstCharacterOfLine; // Need new variable to track this m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex = m_characterCount - 1 > 0 ? m_characterCount - 1 : 1; firstCharacterOfLine = m_characterCount; // Store first character for the next line. m_textInfo.lineInfo[m_lineNumber].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].bottomLeft.x, lineDescender); m_textInfo.lineInfo[m_lineNumber].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex - 1].topRight.x, lineAscender); m_textInfo.lineInfo[m_lineNumber].lineLength = m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x - m_padding * m_maxFontScale; // Compute Preferred Width & Height m_preferredWidth += m_xAdvance; // m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].topRight.x - m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].bottomLeft.x; if (m_enableWordWrapping) m_preferredHeight = m_maxAscender - m_maxDescender; else m_preferredHeight = Mathf.Max(m_preferredHeight, lineAscender - lineDescender); //Debug.Log("LineInfo for line # " + (m_lineNumber) + " First character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + // " Last character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex + // " Character Count of " + m_textInfo.lineInfo[m_lineNumber].characterCount + " Line Lenght of " + m_textInfo.lineInfo[m_lineNumber].lineLength + // " MinX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.x + " MinY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.y + // " MaxX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x + " MaxY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.y + // " Line Ascender: " + lineAscender + " Line Descender: " + lineDescender); // Store the state of the line before starting on the new line. SaveWordWrappingState(ref m_SavedLineState, i); m_lineNumber += 1; // Check to make sure Array is large enough to hold a new line. if (m_lineNumber >= m_textInfo.lineInfo.Length) ResizeLineExtents(m_lineNumber); // Apply Line Spacing based on scale of the last character of the line. lineOffsetDelta = (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing + m_lineSpacingDelta) * m_fontScale; m_lineOffset += lineOffsetDelta; previousFontScale = m_fontScale; m_xAdvance = 0; m_maxFontScale = 0; // Handle Page # //if (m_lineNumber % 5 == 0) // m_lineOffset = 0; continue; } #endregion End Word Wrapping // Text Auto-Sizing (text exceeding Width of container. #region Handle Text Auto-Sizing if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { m_maxFontSize = m_fontSize; float delta = Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.01f); m_fontSize -= delta; m_fontSize = Mathf.Max(m_fontSize, m_fontSizeMin); //Debug.Log("Decreasing Width to " + m_fontSize); //loopCountA += 1; //Debug.Log("Count A: " + loopCountA + " Min: " + m_minFontSize + " Max: " + m_maxFontSize + " Size: " + m_fontSize + " Delta: " + fontSizeDelta); GenerateTextMesh(); return; } #endregion End Text Auto-Sizing // Handle Text Overflow #region Handle Text Overflow switch (m_overflowMode) { case TextOverflowModes.Overflow: if (m_isMaskingEnabled) DisableMasking(); break; case TextOverflowModes.Ellipsis: if (m_isMaskingEnabled) DisableMasking(); m_isTextTruncated = true; if (i < 1) break; m_char_buffer[i - 1] = 8230; m_char_buffer[i] = (char)0; GenerateTextMesh(); return; case TextOverflowModes.Masking: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.ScrollRect: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.Truncate: if (m_isMaskingEnabled) DisableMasking(); m_textInfo.characterInfo[m_characterCount].isVisible = false; m_visibleCharacterCount -= 1; break; } #endregion End Text Overflow } #endregion End Check for Characters Exceeding Width of Text Container // Determine what color gets assigned to vertex. #region Handle Vertex Colors if (isMissingCharacter) vertexColor = Color.red; else if (m_overrideHtmlColors) vertexColor = m_fontColor32; else vertexColor = m_htmlColor; // Set Alpha for Shader to render font as normal or bold. (Alpha channel is being used to let the shader know if the character is bold or normal). if ((m_style & FontStyles.Bold) == FontStyles.Bold || (m_fontStyle & FontStyles.Bold) == FontStyles.Bold) { vertexColor.a = m_fontColor32.a < vertexColor.a ? (byte)(m_fontColor32.a >> 1) : (byte)(vertexColor.a >> 1); vertexColor.a += 128; } else { vertexColor.a = m_fontColor32.a < vertexColor.a ? (byte)(m_fontColor32.a >> 1) : (byte)(vertexColor.a >> 1); } // Vertex Colors if (!m_enableVertexGradient) { m_uiVertices[0 + index_X4].color = vertexColor; m_uiVertices[1 + index_X4].color = vertexColor; m_uiVertices[2 + index_X4].color = vertexColor; m_uiVertices[3 + index_X4].color = vertexColor; } else { if (!m_overrideHtmlColors && !m_htmlColor.CompareRGB(m_fontColor32)) { m_uiVertices[0 + index_X4].color = vertexColor; m_uiVertices[1 + index_X4].color = vertexColor; m_uiVertices[2 + index_X4].color = vertexColor; m_uiVertices[3 + index_X4].color = vertexColor; } else { m_uiVertices[0 + index_X4].color = m_fontColorGradient.bottomLeft; m_uiVertices[1 + index_X4].color = m_fontColorGradient.topLeft; m_uiVertices[2 + index_X4].color = m_fontColorGradient.topRight; m_uiVertices[3 + index_X4].color = m_fontColorGradient.bottomRight; } m_uiVertices[0 + index_X4].color.a = vertexColor.a; m_uiVertices[1 + index_X4].color.a = vertexColor.a; m_uiVertices[2 + index_X4].color.a = vertexColor.a; m_uiVertices[3 + index_X4].color.a = vertexColor.a; } #endregion Handle Vertex Colors // Apply style_padding only if this is a SDF Shader. if (!m_sharedMaterial.HasProperty(ShaderUtilities.ID_WeightNormal)) style_padding = 0; // Setup UVs for the Mesh #region Setup UVs Vector2 uv0 = new Vector2((m_cached_GlyphInfo.x - m_padding - style_padding) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasWidth, 1 - (m_cached_GlyphInfo.y + m_padding + style_padding + m_cached_GlyphInfo.height) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasHeight); // bottom left Vector2 uv1 = new Vector2(uv0.x, 1 - (m_cached_GlyphInfo.y - m_padding - style_padding) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasHeight); // top left Vector2 uv2 = new Vector2((m_cached_GlyphInfo.x + m_padding + style_padding + m_cached_GlyphInfo.width) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasWidth, uv0.y); // bottom right Vector2 uv3 = new Vector2(uv2.x, uv1.y); // top right // UV m_uiVertices[0 + index_X4].uv0 = uv0; // BL m_uiVertices[1 + index_X4].uv0 = uv1; // TL m_uiVertices[2 + index_X4].uv0 = uv3; // TR m_uiVertices[3 + index_X4].uv0 = uv2; // BR #endregion Setup UVs // Normal #region Setup Normals & Tangents Vector3 normal = new Vector3(0, 0, -1); m_uiVertices[0 + index_X4].normal = normal; m_uiVertices[1 + index_X4].normal = normal; m_uiVertices[2 + index_X4].normal = normal; m_uiVertices[3 + index_X4].normal = normal; // Tangents Vector4 tangent = new Vector4(-1, 0, 0, 1); m_uiVertices[0 + index_X4].tangent = tangent; m_uiVertices[1 + index_X4].tangent = tangent; m_uiVertices[2 + index_X4].tangent = tangent; m_uiVertices[3 + index_X4].tangent = tangent; #endregion end Normals & Tangents // Determine the bounds of the Mesh. m_meshExtents.min = new Vector2(Mathf.Min(m_meshExtents.min.x, m_textInfo.characterInfo[m_characterCount].bottomLeft.x), Mathf.Min(m_meshExtents.min.y, m_textInfo.characterInfo[m_characterCount].bottomLeft.y)); m_meshExtents.max = new Vector2(Mathf.Max(m_meshExtents.max.x, m_textInfo.characterInfo[m_characterCount].topRight.x), Mathf.Max(m_meshExtents.max.y, m_textInfo.characterInfo[m_characterCount].topLeft.y)); m_visibleCharacterCount += 1; lastCharacterOfLine = m_characterCount; } 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_textInfo.lineInfo[m_lineNumber].spaceCount += 1; m_textInfo.spaceCount += 1; } } #endregion Handle Visible Characters // Store Rectangle positions for each Character. #region Store Character Data m_textInfo.characterInfo[m_characterCount].lineNumber = (short)m_lineNumber; m_textInfo.lineInfo[m_lineNumber].characterCount += 1; if (charCode != 10 && charCode != 13) m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; #endregion Store Character Data // Handle Tabulation Stops. Tab stops at every 25% of Font Size. #region xAdvance & Tabulation 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 + m_cSpacing; #endregion Tabulation & Stops // Handle Carriage Return #region Carriage Return if (charCode == 13) { m_maxXAdvance = Mathf.Max(m_maxXAdvance, m_preferredWidth + m_xAdvance + (m_alignmentPadding.z * m_fontScale)); m_preferredWidth = 0; m_xAdvance = 0; } #endregion Carriage Return //Debug.Log("Char [" + (char)charCode + "] with ASCII (" + charCode + ") cummulative xAdvance: " + m_xAdvance); // Handle Line Spacing Adjustments + Word Wrapping & special case for last line. #region Check for Line Feed and Last Character if (charCode == 10 || m_characterCount == totalCharacterCount - 1) { //Debug.Log("Line # " + m_lineNumber + " Current Character is [" + (char)charCode + "] with ASC value of " + charCode); // Handle Line Spacing Changes if (m_lineNumber > 0 && m_maxFontScale != 0 && m_maxFontScale != previousFontScale && !isLineOffsetAdjusted) { float gap = m_fontAssetArray[m_fontIndex].fontInfo.LineHeight - (m_fontAssetArray[m_fontIndex].fontInfo.Ascender - m_fontAssetArray[m_fontIndex].fontInfo.Descender); float offsetDelta = (m_fontAssetArray[m_fontIndex].fontInfo.Ascender + m_lineSpacing + m_paragraphSpacing + gap + m_lineSpacingDelta) * m_maxFontScale - (m_fontAssetArray[m_fontIndex].fontInfo.Descender - gap) * previousFontScale; m_lineOffset += offsetDelta - lineOffsetDelta; AdjustLineOffset(firstCharacterOfLine, lastCharacterOfLine, offsetDelta - lineOffsetDelta); } // Calculate lineAscender & make sure if last character is superscript or subscript that we check that as well. float lineAscender = (m_fontAsset.fontInfo.Ascender + m_alignmentPadding.y) * m_maxFontScale - m_lineOffset; float lineAscender2 = (m_fontAsset.fontInfo.Ascender + m_baselineOffset + m_alignmentPadding.y) * m_fontScale - m_lineOffset; lineAscender = lineAscender > lineAscender2 ? lineAscender : lineAscender2; // Calculate lineDescender & make sure if last character is superscript or subscript that we check that as well. float lineDescender = (m_fontAsset.fontInfo.Descender + m_alignmentPadding.w) * m_maxFontScale - m_lineOffset; float lineDescender2 = (m_fontAsset.fontInfo.Descender + m_baselineOffset + m_alignmentPadding.w) * m_fontScale - m_lineOffset; lineDescender = lineDescender < lineDescender2 ? lineDescender : lineDescender2; m_maxDescender = m_maxDescender < lineDescender ? m_maxDescender : lineDescender; // Save Line Information m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex = firstCharacterOfLine; // Need new variable to track this m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex = charCode == 10 ? lastCharacterOfLine + 1 : lastCharacterOfLine; firstCharacterOfLine = m_characterCount + 1; m_textInfo.lineInfo[m_lineNumber].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].bottomLeft.x, lineDescender); m_textInfo.lineInfo[m_lineNumber].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex - (charCode == 10 ? 1 : 0)].topRight.x, lineAscender); m_textInfo.lineInfo[m_lineNumber].lineLength = m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x - (m_padding * m_maxFontScale); // Store PreferredWidth paying attention to linefeed and last character of text. if (charCode == 10 && m_characterCount != totalCharacterCount - 1) { m_maxXAdvance = Mathf.Max(m_maxXAdvance, m_preferredWidth + m_xAdvance + (m_alignmentPadding.z * m_fontScale)); m_preferredWidth = 0; } else m_preferredWidth = Mathf.Max(m_maxXAdvance, m_preferredWidth + m_xAdvance + (m_alignmentPadding.z * m_fontScale)); //Debug.Log("Line # " + m_lineNumber + " XAdance is " + (m_preferredWidth + m_xAdvance + (m_alignmentPadding.z * m_fontScale)) + " Max XAdvance: " + m_maxXAdvance); //m_preferredWidth += m_xAdvance + (m_alignmentPadding.z * m_fontScale); if (m_enableWordWrapping) m_preferredHeight = m_maxAscender - m_maxDescender; else m_preferredHeight = Mathf.Max(m_preferredHeight, lineAscender - lineDescender); //Debug.Log("LineInfo for line # " + (m_lineNumber) + " First character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + // " Last character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex + // " Character Count of " + m_textInfo.lineInfo[m_lineNumber].characterCount + " Line Lenght of " + m_textInfo.lineInfo[m_lineNumber].lineLength + // " MinX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.x + " MinY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.y + // " MaxX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x + " MaxY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.y + // " Line Ascender: " + lineAscender + " Line Descender: " + lineDescender); // Add new line if not last lines or character. if (charCode == 10) { // Store the state of the line before starting on the new line. SaveWordWrappingState(ref m_SavedLineState, i); m_lineNumber += 1; // Check to make sure Array is large enough to hold a new line. if (m_lineNumber >= m_textInfo.lineInfo.Length) ResizeLineExtents(m_lineNumber); // Apply Line Spacing based on scale of the last character of the line. lineOffsetDelta = (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_paragraphSpacing + m_lineSpacing + m_lineSpacingDelta) * m_fontScale; m_lineOffset += lineOffsetDelta; previousFontScale = m_fontScale; m_maxFontScale = 0; m_xAdvance = 0; // Check for page # //if (m_lineNumber % 5 == 0) // m_lineOffset = 0; } } #endregion Check for Linefeed or Last Character // Check if text Exceeds the vertical bounds of the margin area. #region Check Vertical Bounds & Auto-Sizing if (m_maxAscender - m_maxDescender + (m_alignmentPadding.w * 2 * m_fontScale) > marginHeight) { //Debug.Log((m_maxAscender - m_maxDescender) + " " + marginHeight); //Debug.Log("Character [" + (char)charCode + "] at Index: " + m_characterCount + " has exceeded the Height of the text container. Max Ascender: " + m_maxAscender + " Max Descender: " + m_maxDescender + " Margin Height: " + marginHeight + " Bottom Left: " + bottom_left.y); // Handle Linespacing adjustments #region Line Spacing Adjustments if (m_enableAutoSizing && m_lineSpacingDelta > m_lineSpacingMax) { m_lineSpacingDelta -= 1; GenerateTextMesh(); return; } #endregion // Handle Text Auto-sizing resulting from text exceeding vertical bounds. #region Text Auto-Sizing (Text greater than verical bounds) if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { m_maxFontSize = m_fontSize; float delta = Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.01f); //if (delta == 0.01f) return; m_fontSize -= delta; m_fontSize = Mathf.Max(m_fontSize, m_fontSizeMin); //Debug.Log("Decreasing Height to " + m_fontSize); //loopCountB += 1; //Debug.Log("Count B: " + loopCountB + " Min: " + m_minFontSize + " Max: " + m_maxFontSize + " Size: " + m_fontSize + " Delta: " + (m_maxFontSize - m_minFontSize).ToString("f4")); GenerateTextMesh(); return; } #endregion Text Auto-Sizing // Handle Text Overflow #region Text Overflow switch (m_overflowMode) { case TextOverflowModes.Overflow: if (m_isMaskingEnabled) DisableMasking(); break; case TextOverflowModes.Ellipsis: if (m_isMaskingEnabled) DisableMasking(); if (m_lineNumber > 0) { m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index] = 8230; m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index + 1] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } else { m_char_buffer[0] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } case TextOverflowModes.Masking: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.ScrollRect: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.Truncate: if (m_isMaskingEnabled) DisableMasking(); if (m_lineNumber > 0) { m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index + 1] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } else { m_char_buffer[0] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } //case TextOverflowModes.Pages: // if (m_isMaskingEnabled) // DisableMasking(); // // Go back to previous line and re-layout // i = RestoreWordWrappingState(ref m_SavedLineState); // m_xAdvance = 0; // m_lineOffset = 0; // continue; // //break; } #endregion End Text Overflow } #endregion Check Vertical Bounds //// Store Rectangle positions for each Character. #region Save CharacterInfo for the current character. m_textInfo.characterInfo[m_characterCount].baseLine = m_textInfo.characterInfo[m_characterCount].topRight.y - (m_cached_GlyphInfo.yOffset + m_padding) * m_fontScale; m_textInfo.characterInfo[m_characterCount].topLine = m_textInfo.characterInfo[m_characterCount].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Ascender + m_alignmentPadding.y) * m_fontScale; // Ascender m_textInfo.characterInfo[m_characterCount].bottomLine = m_textInfo.characterInfo[m_characterCount].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Descender - m_alignmentPadding.w) * m_fontScale; // Descender m_textInfo.characterInfo[m_characterCount].padding = m_padding * m_fontScale; m_textInfo.characterInfo[m_characterCount].aspectRatio = m_cached_GlyphInfo.width / m_cached_GlyphInfo.height; m_textInfo.characterInfo[m_characterCount].scale = m_fontScale; #endregion Saving CharacterInfo // Save State of Mesh Creation forhandling of Word Wrapping #region Save Word Wrapping State if (m_enableWordWrapping) { //char c; //m_fontAsset.lineBreakingInfo.leadingCharacters.ContainsKey(charCode); //Debug.Log((char)charCode + " " + m_fontAsset.lineBreakingInfo.leadingCharacters.ContainsKey(charCode)); if (m_isCharacterWrappingEnabled == false && (charCode == 9 || charCode == 32)) { // 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. SaveWordWrappingState(ref m_SavedWordWrapState, i); //Debug.Log("Storing Character [" + (char)charCode + "] at Index: " + i); } else if (m_isCharacterWrappingEnabled == true && m_characterCount < totalCharacterCount - 1 && m_fontAsset.lineBreakingInfo.leadingCharacters.ContainsKey(charCode) == false && m_fontAsset.lineBreakingInfo.followingCharacters.ContainsKey(m_VisibleCharacters[m_characterCount + 1]) == false && (charCode < 48 || charCode > 57) && charCode != 44) { //Debug.Log("Storing Character [" + (char)charCode + "] at Index: " + i); SaveWordWrappingState(ref m_SavedWordWrapState, i); } } #endregion Save Word Wrapping State m_characterCount += 1; } // Check Auto Sizing and increase font size to fill text container. #region Check Auto-Sizing (Upper Font Size Bounds) fontSizeDelta = m_maxFontSize - m_minFontSize; if (m_enableAutoSizing && fontSizeDelta > 0.25f && m_fontSize < m_fontSizeMax) { m_minFontSize = m_fontSize; m_fontSize = m_fontSize + (m_maxFontSize - m_fontSize) / 2; //loopCountD += 1; //Debug.Log("Count D: " + loopCountD + " Min: " + m_minFontSize + " Max: " + m_maxFontSize + " Size: " + m_fontSize + " Delta: " + fontSizeDelta); m_fontSize = Mathf.Min(m_fontSize, m_fontSizeMax); GenerateTextMesh(); return; } #endregion End Auto-sizing Check // Add Termination Character to textMeshCharacterInfo which is used by the Advanced Layout Component. if (m_characterCount < m_textInfo.characterInfo.Length) m_textInfo.characterInfo[m_characterCount].character = (char)0; if (m_renderMode == TextRenderFlags.GetPreferredSizes) return; // DEBUG & PERFORMANCE CHECKS (0.006ms) //m_StopWatch.Stop(); // If there are no visible characters... no need to continue if (m_visibleCharacterCount == 0) { if (m_uiVertices != null) { m_uiRenderer.SetVertices(m_uiVertices, 0); } return; } int last_vert_index = m_visibleCharacterCount * 4; // Partial clear of the vertices array to mark unused vertices as degenerate. Array.Clear(m_uiVertices, last_vert_index, m_uiVertices.Length - last_vert_index); // Handle Text Alignment #region Text Alignment switch (m_textAlignment) { // Top Vertically case TextAlignmentOptions.Top: case TextAlignmentOptions.TopLeft: case TextAlignmentOptions.TopJustified: case TextAlignmentOptions.TopRight: m_anchorOffset = m_rectCorners[1] + new Vector3(0 + margins.x, 0 - m_maxAscender - margins.y, 0); break; // Middle Vertically case TextAlignmentOptions.Left: case TextAlignmentOptions.Right: case TextAlignmentOptions.Center: case TextAlignmentOptions.Justified: m_anchorOffset = (m_rectCorners[0] + m_rectCorners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_maxAscender + margins.y + m_maxDescender - margins.w) / 2, 0); break; // Bottom Vertically case TextAlignmentOptions.Bottom: case TextAlignmentOptions.BottomLeft: case TextAlignmentOptions.BottomRight: case TextAlignmentOptions.BottomJustified: m_anchorOffset = m_rectCorners[0] + new Vector3(0 + margins.x, 0 - m_maxDescender + margins.w, 0); break; // Baseline Vertically case TextAlignmentOptions.BaselineLeft: case TextAlignmentOptions.BaselineRight: case TextAlignmentOptions.Baseline: m_anchorOffset = (m_rectCorners[0] + m_rectCorners[1]) / 2 + new Vector3(0 + margins.x, 0, 0); break; } #endregion Text Alignment // Handling of Anchor Dampening. If mesh width changes by more than 1/3 of the underline character's wdith then adjust it. float currentMeshWidth = m_meshExtents.max.x - m_meshExtents.min.x; if (m_anchorDampening) { float delta = currentMeshWidth - m_baseDampeningWidth; if (m_baseDampeningWidth != 0 && Mathf.Abs(delta) < m_cached_Underline_GlyphInfo.width * m_fontScale * 0.6f) m_anchorOffset.x += delta / 2; else m_baseDampeningWidth = currentMeshWidth; } // Initialization for Second Pass Vector3 justificationOffset = Vector3.zero; Vector3 offset = Vector3.zero; int vert_index_X4 = 0; int underlineSegmentCount = 0; int wordCount = 0; int lineCount = 0; int lastLine = 0; bool isStartOfWord = false; int wordFirstChar = 0; int wordLastChar = 0; // Second Pass : Line Justification, UV Mapping, Character & Line Visibility & more. #region Handle Line Justification & UV Mapping & Character Visibility & More for (int i = 0; i < m_characterCount; i++) { int currentLine = m_textInfo.characterInfo[i].lineNumber; char currentCharacter = m_textInfo.characterInfo[i].character; LineInfo lineInfo = m_textInfo.lineInfo[currentLine]; TextAlignmentOptions lineAlignment = lineInfo.alignment; lineCount = currentLine + 1; // Process Line Justification #region Handle Line Justification switch (lineAlignment) { case TextAlignmentOptions.TopLeft: case TextAlignmentOptions.Left: case TextAlignmentOptions.BottomLeft: case TextAlignmentOptions.BaselineLeft: justificationOffset = Vector3.zero; break; case TextAlignmentOptions.Top: case TextAlignmentOptions.Center: case TextAlignmentOptions.Bottom: case TextAlignmentOptions.Baseline: justificationOffset = new Vector3(marginWidth / 2 - (lineInfo.lineExtents.min.x + lineInfo.lineExtents.max.x) / 2, 0, 0); break; case TextAlignmentOptions.TopRight: case TextAlignmentOptions.Right: case TextAlignmentOptions.BottomRight: case TextAlignmentOptions.BaselineRight: justificationOffset = new Vector3(marginWidth - lineInfo.lineExtents.max.x, 0, 0); break; case TextAlignmentOptions.TopJustified: case TextAlignmentOptions.Justified: case TextAlignmentOptions.BottomJustified: charCode = m_textInfo.characterInfo[i].character; char lastCharOfCurrentLine = m_textInfo.characterInfo[lineInfo.lastCharacterIndex].character; if (char.IsWhiteSpace(lastCharOfCurrentLine) && !char.IsControl(lastCharOfCurrentLine) && currentLine < m_lineNumber) { // All lines are justified accept the last one. float gap = (m_rectCorners[3].x - margins.z) - (m_rectCorners[0].x + margins.x) - (lineInfo.lineExtents.max.x); if (currentLine != lastLine || i == 0) justificationOffset = Vector3.zero; else { if (charCode == 9 || charCode == 32) { justificationOffset += new Vector3(gap * (1 - m_wordWrappingRatios) / (lineInfo.spaceCount - 1), 0, 0); } else { //Debug.Log("LineInfo Character Count: " + lineInfo.characterCount); justificationOffset += new Vector3(gap * m_wordWrappingRatios / (lineInfo.characterCount - lineInfo.spaceCount - 1), 0, 0); } } } else justificationOffset = Vector3.zero; // Keep last line left justified. //Debug.Log("Char [" + (char)charCode + "] Code:" + charCode + " Offset:" + justificationOffset + " # Spaces:" + m_lineExtents[currentLine].NumberOfSpaces + " # Characters:" + m_lineExtents[currentLine].NumberOfChars); break; } #endregion End Text Justification offset = m_anchorOffset + justificationOffset; if (m_textInfo.characterInfo[i].isVisible) { Extents lineExtents = lineInfo.lineExtents; float uvOffset = (m_uvLineOffset * currentLine) % 1 + m_uvOffset.x; // Setup UV2 based on Character Mapping Options Selected #region Handle UV Mapping Options switch (m_horizontalMapping) { case TextureMappingOptions.Character: m_uiVertices[vert_index_X4 + 0].uv1.x = 0 + m_uvOffset.x; m_uiVertices[vert_index_X4 + 1].uv1.x = 0 + m_uvOffset.x; m_uiVertices[vert_index_X4 + 2].uv1.x = 1 + m_uvOffset.x; m_uiVertices[vert_index_X4 + 3].uv1.x = 1 + m_uvOffset.x; break; case TextureMappingOptions.Line: if (m_textAlignment != TextAlignmentOptions.Justified) { m_uiVertices[vert_index_X4 + 0].uv1.x = (m_uiVertices[vert_index_X4 + 0].position.x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 1].uv1.x = (m_uiVertices[vert_index_X4 + 1].position.x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 2].uv1.x = (m_uiVertices[vert_index_X4 + 2].position.x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 3].uv1.x = (m_uiVertices[vert_index_X4 + 3].position.x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; break; } else // Special Case if Justified is used in Line Mode. { m_uiVertices[vert_index_X4 + 0].uv1.x = (m_uiVertices[vert_index_X4 + 0].position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 1].uv1.x = (m_uiVertices[vert_index_X4 + 1].position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 2].uv1.x = (m_uiVertices[vert_index_X4 + 2].position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 3].uv1.x = (m_uiVertices[vert_index_X4 + 3].position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; break; } case TextureMappingOptions.Paragraph: m_uiVertices[vert_index_X4 + 0].uv1.x = (m_uiVertices[vert_index_X4 + 0].position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 1].uv1.x = (m_uiVertices[vert_index_X4 + 1].position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 2].uv1.x = (m_uiVertices[vert_index_X4 + 2].position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uiVertices[vert_index_X4 + 3].uv1.x = (m_uiVertices[vert_index_X4 + 3].position.x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; break; case TextureMappingOptions.MatchAspect: switch (m_verticalMapping) { case TextureMappingOptions.Character: m_uiVertices[vert_index_X4 + 0].uv1.y = 0 + m_uvOffset.y; m_uiVertices[vert_index_X4 + 1].uv1.y = 1 + m_uvOffset.y; m_uiVertices[vert_index_X4 + 2].uv1.y = 0 + m_uvOffset.y; m_uiVertices[vert_index_X4 + 3].uv1.y = 1 + m_uvOffset.y; break; case TextureMappingOptions.Line: m_uiVertices[vert_index_X4 + 0].uv1.y = (m_uiVertices[vert_index_X4].position.y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + uvOffset; m_uiVertices[vert_index_X4 + 1].uv1.y = (m_uiVertices[vert_index_X4 + 1].position.y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + uvOffset; m_uiVertices[vert_index_X4 + 2].uv1.y = m_uiVertices[vert_index_X4].uv1.y; m_uiVertices[vert_index_X4 + 3].uv1.y = m_uiVertices[vert_index_X4 + 1].uv1.y; break; case TextureMappingOptions.Paragraph: m_uiVertices[vert_index_X4 + 0].uv1.y = (m_uiVertices[vert_index_X4].position.y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + uvOffset; m_uiVertices[vert_index_X4 + 1].uv1.y = (m_uiVertices[vert_index_X4 + 1].position.y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + uvOffset; m_uiVertices[vert_index_X4 + 2].uv1.y = m_uiVertices[vert_index_X4].uv1.y; m_uiVertices[vert_index_X4 + 3].uv1.y = m_uiVertices[vert_index_X4 + 1].uv1.y; break; case TextureMappingOptions.MatchAspect: Debug.Log("ERROR: Cannot Match both Vertical & Horizontal."); break; } //float xDelta = 1 - (_uv2s[vert_index + 0].y * textMeshCharacterInfo[i].AspectRatio); // Left aligned float xDelta = (1 - ((m_uiVertices[vert_index_X4 + 0].uv1.y + m_uiVertices[vert_index_X4 + 1].uv1.y) * m_textInfo.characterInfo[i].aspectRatio)) / 2; // Center of Rectangle //float xDelta = 0; m_uiVertices[vert_index_X4 + 0].uv1.x = (m_uiVertices[vert_index_X4 + 0].uv1.y * m_textInfo.characterInfo[i].aspectRatio) + xDelta + uvOffset; m_uiVertices[vert_index_X4 + 1].uv1.x = m_uiVertices[vert_index_X4 + 0].uv1.x; m_uiVertices[vert_index_X4 + 2].uv1.x = (m_uiVertices[vert_index_X4 + 1].uv1.y * m_textInfo.characterInfo[i].aspectRatio) + xDelta + uvOffset; m_uiVertices[vert_index_X4 + 3].uv1.x = m_uiVertices[vert_index_X4 + 2].uv1.x; break; } switch (m_verticalMapping) { case TextureMappingOptions.Character: m_uiVertices[vert_index_X4 + 0].uv1.y = 0 + m_uvOffset.y; m_uiVertices[vert_index_X4 + 1].uv1.y = 1 + m_uvOffset.y; m_uiVertices[vert_index_X4 + 2].uv1.y = 1 + m_uvOffset.y; m_uiVertices[vert_index_X4 + 3].uv1.y = 0 + m_uvOffset.y; break; case TextureMappingOptions.Line: m_uiVertices[vert_index_X4 + 0].uv1.y = (m_uiVertices[vert_index_X4].position.y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + m_uvOffset.y; m_uiVertices[vert_index_X4 + 1].uv1.y = (m_uiVertices[vert_index_X4 + 1].position.y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + m_uvOffset.y; m_uiVertices[vert_index_X4 + 3].uv1.y = m_uiVertices[vert_index_X4].uv1.y; m_uiVertices[vert_index_X4 + 2].uv1.y = m_uiVertices[vert_index_X4 + 1].uv1.y; break; case TextureMappingOptions.Paragraph: m_uiVertices[vert_index_X4 + 0].uv1.y = (m_uiVertices[vert_index_X4].position.y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + m_uvOffset.y; m_uiVertices[vert_index_X4 + 1].uv1.y = (m_uiVertices[vert_index_X4 + 1].position.y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + m_uvOffset.y; m_uiVertices[vert_index_X4 + 3].uv1.y = m_uiVertices[vert_index_X4].uv1.y; m_uiVertices[vert_index_X4 + 2].uv1.y = m_uiVertices[vert_index_X4 + 1].uv1.y; break; case TextureMappingOptions.MatchAspect: //float yDelta = 1 - (_uv2s[vert_index + 2].x / textMeshCharacterInfo[i].AspectRatio); // Top Corner float yDelta = (1 - ((m_uiVertices[vert_index_X4 + 0].uv1.x + m_uiVertices[vert_index_X4 + 2].uv1.x) / m_textInfo.characterInfo[i].aspectRatio)) / 2; // Center of Rectangle //float yDelta = 0; m_uiVertices[vert_index_X4 + 0].uv1.y = yDelta + (m_uiVertices[vert_index_X4 + 0].uv1.x / m_textInfo.characterInfo[i].aspectRatio) + m_uvOffset.y; m_uiVertices[vert_index_X4 + 1].uv1.y = yDelta + (m_uiVertices[vert_index_X4 + 2].uv1.x / m_textInfo.characterInfo[i].aspectRatio) + m_uvOffset.y; m_uiVertices[vert_index_X4 + 2].uv1.y = m_uiVertices[vert_index_X4 + 0].uv1.y; m_uiVertices[vert_index_X4 + 3].uv1.y = m_uiVertices[vert_index_X4 + 1].uv1.y; break; } #endregion End UV Mapping Options // Pack UV's so that we can pass Xscale needed for Shader to maintain 1:1 ratio. float xScale = m_textInfo.characterInfo[i].scale * m_rectTransform.lossyScale.z; float x0 = m_uiVertices[vert_index_X4 + 0].uv1.x; float y0 = m_uiVertices[vert_index_X4 + 0].uv1.y; float x1 = m_uiVertices[vert_index_X4 + 2].uv1.x; float y1 = m_uiVertices[vert_index_X4 + 2].uv1.y; float dx = Mathf.Floor(x0); float dy = Mathf.Floor(y0); x0 = x0 - dx; x1 = x1 - dx; y0 = y0 - dy; y1 = y1 - dy; m_uiVertices[vert_index_X4 + 0].uv1 = PackUV(x0, y0, xScale); m_uiVertices[vert_index_X4 + 1].uv1 = PackUV(x0, y1, xScale); m_uiVertices[vert_index_X4 + 2].uv1 = PackUV(x1, y1, xScale); m_uiVertices[vert_index_X4 + 3].uv1 = PackUV(x1, y0, xScale); // Enables control of the visibility of characters && lines. if (m_maxVisibleCharacters != -1 && i >= m_maxVisibleCharacters || m_maxVisibleLines != -1 && currentLine >= m_maxVisibleLines) // || pages != AllPages && { m_uiVertices[vert_index_X4 + 0].position *= 0; m_uiVertices[vert_index_X4 + 1].position *= 0; m_uiVertices[vert_index_X4 + 2].position *= 0; m_uiVertices[vert_index_X4 + 3].position *= 0; } else { m_uiVertices[vert_index_X4 + 0].position += offset; m_uiVertices[vert_index_X4 + 1].position += offset; m_uiVertices[vert_index_X4 + 2].position += offset; m_uiVertices[vert_index_X4 + 3].position += offset; } vert_index_X4 += 4; } m_textInfo.characterInfo[i].bottomLeft += offset; m_textInfo.characterInfo[i].topRight += offset; m_textInfo.characterInfo[i].topLine += offset.y; m_textInfo.characterInfo[i].bottomLine += offset.y; m_textInfo.characterInfo[i].baseLine += offset.y; // Store Max Ascender & Descender m_textInfo.lineInfo[currentLine].ascender = m_textInfo.characterInfo[i].topLine > m_textInfo.lineInfo[currentLine].ascender ? m_textInfo.characterInfo[i].topLine : m_textInfo.lineInfo[currentLine].ascender; m_textInfo.lineInfo[currentLine].descender = m_textInfo.characterInfo[i].bottomLine < m_textInfo.lineInfo[currentLine].descender ? m_textInfo.characterInfo[i].bottomLine : m_textInfo.lineInfo[currentLine].descender; // Need to recompute lineExtent to account for the offset from justification. if (currentLine != lastLine || i == m_characterCount - 1) { m_textInfo.lineInfo[lastLine].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[lastLine].firstCharacterIndex].bottomLeft.x, m_textInfo.lineInfo[lastLine].descender); m_textInfo.lineInfo[lastLine].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[lastLine].lastCharacterIndex].topRight.x, m_textInfo.lineInfo[lastLine].ascender); } // Track Word Count per line and for the object if (char.IsLetterOrDigit(currentCharacter) && i < m_characterCount - 1) { if (isStartOfWord == false) { isStartOfWord = true; wordFirstChar = i; } } else if ((char.IsPunctuation(currentCharacter) || char.IsWhiteSpace(currentCharacter) || i == m_characterCount - 1) && isStartOfWord || i == 0) { wordLastChar = i == m_characterCount - 1 && char.IsLetterOrDigit(currentCharacter) ? i : i - 1; isStartOfWord = false; wordCount += 1; m_textInfo.lineInfo[currentLine].wordCount += 1; WordInfo wordInfo = new WordInfo(); wordInfo.firstCharacterIndex = wordFirstChar; wordInfo.lastCharacterIndex = wordLastChar; wordInfo.characterCount = wordLastChar - wordFirstChar + 1; m_textInfo.wordInfo.Add(wordInfo); //Debug.Log("Word #" + wordCount + " is [" + wordInfo.word + "] Start Index: " + wordInfo.firstCharacterIndex + " End Index: " + wordInfo.lastCharacterIndex); } // Handle Underline #region Underline Tracking // TODO : Address underline and which font to use in the list if ((m_textInfo.characterInfo[i].style & FontStyles.Underline) == FontStyles.Underline && i != m_textInfo.lineInfo[currentLine].lastCharacterIndex) // m_textInfo.characterInfo[i].character != 10 && m_textInfo.characterInfo[i].character != 13) { if (beginUnderline == false) { beginUnderline = true; underline_start = new Vector3(m_textInfo.characterInfo[i].bottomLeft.x, m_textInfo.characterInfo[i].baseLine + font.fontInfo.Underline * m_fontScale, 0); //Debug.Log("Underline Start Char [" + m_textInfo.characterInfo[i].character + "]."); } } else { if (beginUnderline == true) { beginUnderline = false; if (i != m_characterCount - 1 && (m_textInfo.characterInfo[i].character == 32 || m_textInfo.characterInfo[i].character == 10)) { underline_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, m_textInfo.characterInfo[i - 1].baseLine + font.fontInfo.Underline * m_fontScale, 0); //Debug.Log("Underline End Char [" + m_textInfo.characterInfo[i - 1].character + "]."); } else { underline_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + font.fontInfo.Underline * m_fontScale, 0); //Debug.Log("Underline End Char [" + m_textInfo.characterInfo[i].character + "]."); } DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index); underlineSegmentCount += 1; } } #endregion lastLine = currentLine; } #endregion // METRICS ABOUT THE TEXT OBJECT m_textInfo.characterCount = (short)m_characterCount; m_textInfo.lineCount = (short)lineCount; m_textInfo.wordCount = wordCount != 0 && m_characterCount > 0 ? (short)wordCount : (short)1; // Need to store UI Vertex m_textInfo.meshInfo.uiVertices = m_uiVertices; // If Advanced Layout Component is present, don't upload the mesh. if (m_renderMode == TextRenderFlags.Render) // m_isAdvanceLayoutComponentPresent == false || m_advancedLayoutComponent.isEnabled == false) { //Debug.Log("Uploading Mesh normally."); // Upload Mesh Data m_uiRenderer.SetVertices(m_uiVertices, vert_index_X4 + underlineSegmentCount * 12); // Setting Mesh Bounds manually is more efficient. m_bounds = new Bounds(new Vector3((m_meshExtents.max.x + m_meshExtents.min.x) / 2, (m_meshExtents.max.y + m_meshExtents.min.y) / 2, 0) + m_anchorOffset, new Vector3(m_meshExtents.max.x - m_meshExtents.min.x, m_meshExtents.max.y - m_meshExtents.min.y, 0)); //m_maskOffset = new Vector4(m_mesh.bounds.center.x, m_mesh.bounds.center.y, m_mesh.bounds.size.x, m_mesh.bounds.size.y); } m_bounds = new Bounds(new Vector3((m_meshExtents.max.x + m_meshExtents.min.x) / 2, (m_meshExtents.max.y + m_meshExtents.min.y) / 2, 0) + m_anchorOffset, new Vector3(m_meshExtents.max.x - m_meshExtents.min.x, m_meshExtents.max.y - m_meshExtents.min.y, 0)); m_isCharacterWrappingEnabled = false; // Has Text Container's Width or Height been specified by the user? /* if (m_rectTransform.sizeDelta.x == 0 || m_rectTransform.sizeDelta.y == 0) { //Debug.Log("Auto-fitting Text. Default Width:" + m_textContainer.isDefaultWidth + " Default Height:" + m_textContainer.isDefaultHeight); if (marginWidth == 0) m_rectTransform.sizeDelta = new Vector2(m_preferredWidth + margins.x + margins.z, m_rectTransform.sizeDelta.y); if (marginHeight == 0) m_rectTransform.sizeDelta = new Vector2(m_rectTransform.sizeDelta.x, m_preferredHeight + margins.y + margins.w); Debug.Log("Auto-fitting Text. Default Width:" + m_preferredWidth + " Default Height:" + m_preferredHeight); GenerateTextMesh(); return; } */ //Debug.Log("Done rendering text. Margin Width was " + marginWidth + " and Margin Height was " + marginHeight + ". Preferred Width: " + m_preferredWidth + " and Height: " + m_preferredHeight); //Debug.Log(m_minWidth); //Profiler.EndSample(); //m_StopWatch.Stop(); //Debug.Log("Done Rendering Text."); //Debug.Log("TimeElapsed is:" + (m_StopWatch.ElapsedTicks / 10000f).ToString("f4")); //m_StopWatch.Reset(); }
/// <summary> /// Method to calculate the preferred width and height of the text object. /// </summary> /// <returns></returns> protected virtual Vector2 CalculatePreferredValues(float defaultFontSize, Vector2 marginSize) { //Debug.Log("*** CalculatePreferredValues() ***"); // ***** Frame: " + Time.frameCount); ////Profiler.BeginSample("TMP Generate Text - Phase I"); // Early exit if no font asset was assigned. This should not be needed since Arial SDF will be assigned by default. if (m_fontAsset == null || m_fontAsset.characterDictionary == null) { Debug.LogWarning("Can't Generate Mesh! No Font Asset has been assigned to Object ID: " + this.GetInstanceID()); return Vector2.zero; } // Early exit if we don't have any Text to generate. if (m_char_buffer == null || m_char_buffer.Length == 0 || m_char_buffer[0] == (char)0) { return Vector2.zero; } m_currentFontAsset = m_fontAsset; m_currentMaterial = m_sharedMaterial; m_currentMaterialIndex = 0; m_materialReferenceStack.SetDefault(new MaterialReference(0, m_currentFontAsset, null, m_currentMaterial, m_padding)); // Total character count is computed when the text is parsed. int totalCharacterCount = m_totalCharacterCount; // m_VisibleCharacters.Count; if (m_internalCharacterInfo == null || totalCharacterCount > m_internalCharacterInfo.Length) { m_internalCharacterInfo = new TMP_CharacterInfo[totalCharacterCount > 1024 ? totalCharacterCount + 256 : Mathf.NextPowerOfTwo(totalCharacterCount)]; } // Calculate the scale of the font based on selected font size and sampling point size. m_fontScale = (defaultFontSize / m_currentFontAsset.fontInfo.PointSize * (m_isOrthographic ? 1 : 0.1f)); m_fontScaleMultiplier = 1; // baseScale is calculated based on the font asset assigned to the text object. float baseScale = (defaultFontSize / m_fontAsset.fontInfo.PointSize * m_fontAsset.fontInfo.Scale * (m_isOrthographic ? 1 : 0.1f)); float currentElementScale = m_fontScale; m_currentFontSize = defaultFontSize; m_sizeStack.SetDefault(m_currentFontSize); int charCode = 0; // Holds the character code of the currently being processed character. m_style = m_fontStyle; // Set the default style. float bold_xAdvance_multiplier = 1; // Used to increase spacing between character when style is bold. m_baselineOffset = 0; // Used by subscript characters. m_styleStack.Clear(); m_lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). m_lineHeight = 0; float lineGap = m_currentFontAsset.fontInfo.LineHeight - (m_currentFontAsset.fontInfo.Ascender - m_currentFontAsset.fontInfo.Descender); m_cSpacing = 0; // Amount of space added between characters as a result of the use of the <cspace> tag. m_monoSpacing = 0; float lineOffsetDelta = 0; m_xAdvance = 0; // Used to track the position of each character. float maxXAdvance = 0; // Used to determine Preferred Width. tag_LineIndent = 0; // Used for indentation of text. tag_Indent = 0; m_indentStack.SetDefault(0); tag_NoParsing = false; //m_isIgnoringAlignment = false; m_characterCount = 0; // Total characters in the char[] // Tracking of line information m_firstCharacterOfLine = 0; m_maxLineAscender = -Mathf.Infinity; m_maxLineDescender = Mathf.Infinity; m_lineNumber = 0; float marginWidth = marginSize.x; //float marginHeight = marginSize.y; m_marginLeft = 0; m_marginRight = 0; m_width = -1; // Used by Unity's Auto Layout system. float renderedWidth = 0; float renderedHeight = 0; // Tracking of the highest Ascender m_maxAscender = 0; m_maxDescender = 0; // Initialize struct to track states of word wrapping bool isFirstWord = true; bool isLastBreakingChar = false; WordWrapState savedLineState = new WordWrapState(); SaveWordWrappingState(ref savedLineState, 0, 0); WordWrapState savedWordWrapState = new WordWrapState(); int wrappingIndex = 0; //int loopCountA = 0; int endTagIndex = 0; // Parse through Character buffer to read HTML tags and begin creating mesh. for (int i = 0; m_char_buffer[i] != 0; i++) { charCode = m_char_buffer[i]; m_textElementType = TMP_TextElementType.Character; m_currentMaterialIndex = m_textInfo.characterInfo[m_characterCount].materialReferenceIndex; m_currentFontAsset = m_materialReferences[m_currentMaterialIndex].fontAsset; int prev_MaterialIndex = m_currentMaterialIndex; // Parse Rich Text Tag #region Parse Rich Text Tag if (m_isRichText && charCode == 60) // '<' { m_isParsingText = true; // Check if Tag is valid. If valid, skip to the end of the validated tag. if (ValidateHtmlTag(m_char_buffer, i + 1, out endTagIndex)) { i = endTagIndex; // Continue to next character or handle the sprite element if (m_textElementType == TMP_TextElementType.Character) continue; } } #endregion End Parse Rich Text Tag m_isParsingText = false; // Handle Font Styles like LowerCase, UpperCase and SmallCaps. #region Handling of LowerCase, UpperCase and SmallCaps Font Styles float smallCapsMultiplier = 1.0f; if (m_textElementType == TMP_TextElementType.Character) { if ((m_style & FontStyles.UpperCase) == FontStyles.UpperCase) { // If this character is lowercase, switch to uppercase. if (char.IsLower((char)charCode)) charCode = char.ToUpper((char)charCode); } else if ((m_style & FontStyles.LowerCase) == FontStyles.LowerCase) { // If this character is uppercase, switch to lowercase. if (char.IsUpper((char)charCode)) charCode = char.ToLower((char)charCode); } else if ((m_fontStyle & FontStyles.SmallCaps) == FontStyles.SmallCaps || (m_style & FontStyles.SmallCaps) == FontStyles.SmallCaps) { if (char.IsLower((char)charCode)) { smallCapsMultiplier = 0.8f; charCode = char.ToUpper((char)charCode); } } } #endregion // Look up Character Data from Dictionary and cache it. #region Look up Character Data if (m_textElementType == TMP_TextElementType.Sprite) { TMP_Sprite sprite = m_currentSpriteAsset.spriteInfoList[m_spriteIndex]; if (sprite == null) continue; // Sprites are assigned in the E000 Private Area + sprite Index charCode = 57344 + m_spriteIndex; m_cached_TextElement = sprite; // Adjust the offset relative to the pivot point. //sprite.xOffset += sprite.pivot.x; //sprite.yOffset += sprite.pivot.y; currentElementScale = m_fontAsset.fontInfo.Ascender / sprite.height * sprite.scale * baseScale; m_internalCharacterInfo[m_characterCount].elementType = TMP_TextElementType.Sprite; m_currentMaterialIndex = prev_MaterialIndex; } else if (m_textElementType == TMP_TextElementType.Character) { m_cached_TextElement = m_textInfo.characterInfo[m_characterCount].textElement; m_currentFontAsset = m_textInfo.characterInfo[m_characterCount].fontAsset; m_currentMaterialIndex = m_textInfo.characterInfo[m_characterCount].materialReferenceIndex; // Re-calculate font scale as the font asset may have changed. m_fontScale = m_currentFontSize * smallCapsMultiplier / m_currentFontAsset.fontInfo.PointSize * m_currentFontAsset.fontInfo.Scale * (m_isOrthographic ? 1 : 0.1f); currentElementScale = m_fontScale * m_fontScaleMultiplier; m_internalCharacterInfo[m_characterCount].elementType = TMP_TextElementType.Character; //padding = m_currentMaterialIndex == 0 ? m_padding : m_subTextObjects[m_currentMaterialIndex].padding; } #endregion // Store some of the text object's information m_internalCharacterInfo[m_characterCount].character = (char)charCode; // Handle Kerning if Enabled. #region Handle Kerning if (m_enableKerning && m_characterCount >= 1) { int prev_charCode = m_internalCharacterInfo[m_characterCount - 1].character; KerningPairKey keyValue = new KerningPairKey(prev_charCode, charCode); KerningPair pair; m_currentFontAsset.kerningDictionary.TryGetValue(keyValue.key, out pair); if (pair != null) { m_xAdvance += pair.XadvanceOffset * currentElementScale; } } #endregion // Handle Mono Spacing #region Handle Mono Spacing float monoAdvance = 0; if (m_monoSpacing != 0) { monoAdvance = (m_monoSpacing / 2 - (m_cached_TextElement.width / 2 + m_cached_TextElement.xOffset) * currentElementScale); m_xAdvance += monoAdvance; } #endregion // Set Padding based on selected font style #region Handle Style Padding if ((m_style & FontStyles.Bold) == FontStyles.Bold || (m_fontStyle & FontStyles.Bold) == FontStyles.Bold) // Checks for any combination of Bold Style. { //style_padding = m_currentFontAsset.boldStyle * 2; bold_xAdvance_multiplier = 1 + m_currentFontAsset.boldSpacing * 0.01f; } else { //style_padding = m_currentFontAsset.normalStyle * 2; bold_xAdvance_multiplier = 1.0f; } #endregion Handle Style Padding //m_internalTextInfo.characterInfo[m_characterCount].scale = currentElementScale; //m_internalTextInfo.characterInfo[m_characterCount].origin = m_xAdvance; m_internalCharacterInfo[m_characterCount].baseLine = 0 - m_lineOffset + m_baselineOffset; // Compute and save text element Ascender and maximum line Ascender. float elementAscender = m_currentFontAsset.fontInfo.Ascender * (m_textElementType == TMP_TextElementType.Character ? currentElementScale : baseScale) + m_baselineOffset; /* float elementAscenderII = */ m_internalCharacterInfo[m_characterCount].ascender = elementAscender - m_lineOffset; m_maxLineAscender = elementAscender > m_maxLineAscender ? elementAscender : m_maxLineAscender; // Compute and save text element Descender and maximum line Descender. float elementDescender = m_currentFontAsset.fontInfo.Descender * (m_textElementType == TMP_TextElementType.Character ? currentElementScale : baseScale) + m_baselineOffset; float elementDescenderII = m_internalCharacterInfo[m_characterCount].descender = elementDescender - m_lineOffset; m_maxLineDescender = elementDescender < m_maxLineDescender ? elementDescender : m_maxLineDescender; // Adjust maxLineAscender and maxLineDescender if style is superscript or subscript if ((m_style & FontStyles.Subscript) == FontStyles.Subscript || (m_style & FontStyles.Superscript) == FontStyles.Superscript) { float baseAscender = (elementAscender - m_baselineOffset) / m_currentFontAsset.fontInfo.SubSize; elementAscender = m_maxLineAscender; m_maxLineAscender = baseAscender > m_maxLineAscender ? baseAscender : m_maxLineAscender; float baseDescender = (elementDescender - m_baselineOffset) / m_currentFontAsset.fontInfo.SubSize; elementDescender = m_maxLineDescender; m_maxLineDescender = baseDescender < m_maxLineDescender ? baseDescender : m_maxLineDescender; } if (m_lineNumber == 0) m_maxAscender = m_maxAscender > elementAscender ? m_maxAscender : elementAscender; //if (m_lineOffset == 0) pageAscender = pageAscender > elementAscender ? pageAscender : elementAscender; // Setup Mesh for visible text elements. ie. not a SPACE / LINEFEED / CARRIAGE RETURN. #region Handle Visible Characters if (charCode == 9 || !char.IsWhiteSpace((char)charCode) || m_textElementType == TMP_TextElementType.Sprite) { //m_internalTextInfo.characterInfo[m_characterCount].isVisible = true; // Check if Character exceeds the width of the Text Container #region Handle Line Breaking, Text Auto-Sizing and Horizontal Overflow float width = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - m_marginLeft - m_marginRight, m_width) : marginWidth + 0.0001f - m_marginLeft - m_marginRight; //m_internalTextInfo.lineInfo[m_lineNumber].width = width; //m_internalTextInfo.lineInfo[m_lineNumber].marginLeft = m_marginLeft; if (m_xAdvance + m_cached_TextElement.xAdvance * currentElementScale > width) { // Word Wrapping #region Handle Word Wrapping if (enableWordWrapping && m_characterCount != m_firstCharacterOfLine) { // Check if word wrapping is still possible #region Line Breaking Check if (wrappingIndex == savedWordWrapState.previous_WordBreak || isFirstWord) { // Word wrapping is no longer possible, now breaking up individual words. if (m_isCharacterWrappingEnabled == false) { m_isCharacterWrappingEnabled = true; } else isLastBreakingChar = true; //m_recursiveCount += 1; //if (m_recursiveCount > 20) //{ //Debug.Log("Recursive count exceeded!"); //continue; //} } #endregion // Restore to previously stored state of last valid (space character or linefeed) i = RestoreWordWrappingState(ref savedWordWrapState); wrappingIndex = i; // Used to detect when line length can no longer be reduced. // Check if Line Spacing of previous line needs to be adjusted. if (m_lineNumber > 0 && !TMP_Math.Approximately(m_maxLineAscender, m_startOfLineAscender) && m_lineHeight == 0) { //Debug.Log("(1) Adjusting Line Spacing on line #" + m_lineNumber); float offsetDelta = m_maxLineAscender - m_startOfLineAscender; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, offsetDelta); m_lineOffset += offsetDelta; savedWordWrapState.lineOffset = m_lineOffset; savedWordWrapState.previousLineAscender = m_maxLineAscender; // TODO - Add check for character exceeding vertical bounds } //m_isNewPage = false; // Calculate lineAscender & make sure if last character is superscript or subscript that we check that as well. float lineAscender = m_maxLineAscender - m_lineOffset; float lineDescender = m_maxLineDescender - m_lineOffset; // Update maxDescender and maxVisibleDescender m_maxDescender = m_maxDescender < lineDescender ? m_maxDescender : lineDescender; m_firstCharacterOfLine = m_characterCount; // Store first character of the next line. // Compute Preferred Width & Height renderedWidth += m_xAdvance; if (m_enableWordWrapping) renderedHeight = m_maxAscender - m_maxDescender; else renderedHeight = Mathf.Max(renderedHeight, lineAscender - lineDescender); // Store the state of the line before starting on the new line. SaveWordWrappingState(ref savedLineState, i, m_characterCount - 1); m_lineNumber += 1; //isStartOfNewLine = true; // Check to make sure Array is large enough to hold a new line. //if (m_lineNumber >= m_internalTextInfo.lineInfo.Length) // ResizeLineExtents(m_lineNumber); // Apply Line Spacing based on scale of the last character of the line. if (m_lineHeight == 0) { float ascender = m_internalCharacterInfo[m_characterCount].ascender - m_internalCharacterInfo[m_characterCount].baseLine; lineOffsetDelta = 0 - m_maxLineDescender + ascender + (lineGap + m_lineSpacing + m_lineSpacingDelta) * baseScale; m_lineOffset += lineOffsetDelta; m_startOfLineAscender = ascender; } else m_lineOffset += m_lineHeight + m_lineSpacing * baseScale; m_maxLineAscender = -Mathf.Infinity; m_maxLineDescender = Mathf.Infinity; m_xAdvance = 0 + tag_Indent; continue; } #endregion End Word Wrapping } #endregion End Check for Characters Exceeding Width of Text Container } #endregion Handle Visible Characters // Check if Line Spacing of previous line needs to be adjusted. #region Adjust Line Spacing if (m_lineNumber > 0 && !TMP_Math.Approximately(m_maxLineAscender, m_startOfLineAscender) && m_lineHeight == 0 && !m_isNewPage) { //Debug.Log("Inline - Adjusting Line Spacing on line #" + m_lineNumber); //float gap = 0; // Compute gap. float offsetDelta = m_maxLineAscender - m_startOfLineAscender; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, offsetDelta); elementDescenderII -= offsetDelta; m_lineOffset += offsetDelta; m_startOfLineAscender += offsetDelta; savedWordWrapState.lineOffset = m_lineOffset; savedWordWrapState.previousLineAscender = m_startOfLineAscender; } #endregion // Handle xAdvance & Tabulation Stops. Tab stops at every 25% of Font Size. #region XAdvance, Tabulation & Stops if (charCode == 9) { m_xAdvance += m_currentFontAsset.fontInfo.TabWidth * currentElementScale; } else if (m_monoSpacing != 0) m_xAdvance += (m_monoSpacing - monoAdvance + ((m_characterSpacing + m_currentFontAsset.normalSpacingOffset) * currentElementScale) + m_cSpacing); else { m_xAdvance += ((m_cached_TextElement.xAdvance * bold_xAdvance_multiplier + m_characterSpacing + m_currentFontAsset.normalSpacingOffset) * currentElementScale + m_cSpacing); } // Store xAdvance information //m_internalTextInfo.characterInfo[m_characterCount].xAdvance = m_xAdvance; #endregion Tabulation & Stops // Handle Carriage Return #region Carriage Return if (charCode == 13) { maxXAdvance = Mathf.Max(maxXAdvance, renderedWidth + m_xAdvance); renderedWidth = 0; m_xAdvance = 0 + tag_Indent; } #endregion Carriage Return // Handle Line Spacing Adjustments + Word Wrapping & special case for last line. #region Check for Line Feed and Last Character if (charCode == 10 || m_characterCount == totalCharacterCount - 1) { // Check if Line Spacing of previous line needs to be adjusted. if (m_lineNumber > 0 && !TMP_Math.Approximately(m_maxLineAscender, m_startOfLineAscender) && m_lineHeight == 0) { //Debug.Log("(2) Adjusting Line Spacing on line #" + m_lineNumber); float offsetDelta = m_maxLineAscender - m_startOfLineAscender; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, offsetDelta); elementDescenderII -= offsetDelta; m_lineOffset += offsetDelta; } // Calculate lineAscender & make sure if last character is superscript or subscript that we check that as well. //float lineAscender = m_maxLineAscender - m_lineOffset; float lineDescender = m_maxLineDescender - m_lineOffset; // Update maxDescender and maxVisibleDescender m_maxDescender = m_maxDescender < lineDescender ? m_maxDescender : lineDescender; m_firstCharacterOfLine = m_characterCount + 1; // Store PreferredWidth paying attention to linefeed and last character of text. if (charCode == 10 && m_characterCount != totalCharacterCount - 1) { maxXAdvance = Mathf.Max(maxXAdvance, renderedWidth + m_xAdvance); renderedWidth = 0; } else renderedWidth = Mathf.Max(maxXAdvance, renderedWidth + m_xAdvance); renderedHeight = m_maxAscender - m_maxDescender; // Add new line if not last lines or character. if (charCode == 10) { // Store the state of the line before starting on the new line. SaveWordWrappingState(ref savedLineState, i, m_characterCount); // Store the state of the last Character before the new line. SaveWordWrappingState(ref savedWordWrapState, i, m_characterCount); m_lineNumber += 1; // Apply Line Spacing if (m_lineHeight == 0) { lineOffsetDelta = 0 - m_maxLineDescender + elementAscender + (lineGap + m_lineSpacing + m_paragraphSpacing + m_lineSpacingDelta) * baseScale; m_lineOffset += lineOffsetDelta; } else m_lineOffset += m_lineHeight + (m_lineSpacing + m_paragraphSpacing) * baseScale; m_maxLineAscender = -Mathf.Infinity; m_maxLineDescender = Mathf.Infinity; m_startOfLineAscender = elementAscender; m_xAdvance = 0 + tag_LineIndent + tag_Indent; } } #endregion Check for Linefeed or Last Character // Save State of Mesh Creation for handling of Word Wrapping #region Save Word Wrapping State if (m_enableWordWrapping || m_overflowMode == TextOverflowModes.Truncate || m_overflowMode == TextOverflowModes.Ellipsis) { if ((charCode == 9 || charCode == 32) && !m_isNonBreakingSpace) { // 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. SaveWordWrappingState(ref savedWordWrapState, i, m_characterCount); m_isCharacterWrappingEnabled = false; isFirstWord = false; } else if (charCode > 0x2e80 && charCode < 0x9fff) { if (m_currentFontAsset.lineBreakingInfo.leadingCharacters.ContainsKey(charCode) == false && (m_characterCount < totalCharacterCount - 1 && m_currentFontAsset.lineBreakingInfo.followingCharacters.ContainsKey(m_internalCharacterInfo[m_characterCount + 1].character) == false)) { SaveWordWrappingState(ref savedWordWrapState, i, m_characterCount); m_isCharacterWrappingEnabled = false; isFirstWord = false; } } else if ((isFirstWord || m_isCharacterWrappingEnabled == true || isLastBreakingChar)) SaveWordWrappingState(ref savedWordWrapState, i, m_characterCount); } #endregion Save Word Wrapping State m_characterCount += 1; } m_isCharacterWrappingEnabled = false; // Adjust Preferred Width and Height to account for Margins. renderedWidth += m_margin.x > 0 ? m_margin.x : 0; renderedWidth += m_margin.z > 0 ? m_margin.z : 0; renderedHeight += m_margin.y > 0 ? m_margin.y : 0; renderedHeight += m_margin.w > 0 ? m_margin.w : 0; ////Profiler.EndSample(); return new Vector2(renderedWidth, renderedHeight); }
// Save the State of various variables used in the mesh creation loop in conjunction with Word Wrapping void SaveWordWrappingState(ref WordWrapState state, int index) { state.previous_WordBreak = index; state.total_CharacterCount = m_characterCount; state.visible_CharacterCount = m_visibleCharacterCount; state.xAdvance = m_xAdvance; state.maxAscender = m_maxAscender; state.maxDescender = m_maxDescender; state.fontScale = m_fontScale; state.maxFontScale = m_maxFontScale; state.lineOffset = m_lineOffset; state.currentFontSize = m_currentFontSize; state.baselineOffset = m_baselineOffset; state.fontStyle = m_style; state.vertexColor = m_htmlColor; state.meshExtents = m_meshExtents; state.lineInfo = m_textInfo.lineInfo[m_lineNumber]; state.textInfo = m_textInfo; }
// 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); }
/// <summary> /// This is the main function that is responsible for creating / displaying the text. /// </summary> void GenerateTextMesh() { //Debug.Log("GenerateTextMesh has been called."); // 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; } // Early exit if we don't have any Text to generate. if (m_char_buffer == null || m_char_buffer[0] == (char)0) { if (m_vertices != null) { Array.Clear(m_vertices, 0, m_vertices.Length); m_mesh.vertices = m_vertices; } return; } // Determine how many characters will be visible and make the necessary allocations (if needed). SetArraySizes(m_char_buffer); m_fontIndex = 0; // Will be used when support for using different font assets or sprites withint the same object will be added. 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. m_currentFontSize = m_fontSize; 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. //bool isLineTruncated = false; 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. bool beginUnderline = false; Vector3 underline_start = Vector3.zero; // Used to track where underline starts & ends. Vector3 underline_end = Vector3.zero; Color32 vertexColor; m_htmlColor = m_fontColor; 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; bool isLineTruncated = false; // Initialize struct to track states of word wrapping m_SaveWordWrapState = new WordWrapState(); int wrappingIndex = 0; // Need to initialize these Extents Structs Extents meshExtents = new Extents(k_InfinityVector, -k_InfinityVector); if (m_textInfo.lineInfo == null) m_textInfo.lineInfo = new LineInfo[2]; for (int i = 0; i < m_textInfo.lineInfo.Length; i++) { //m_lineExtents[i] = meshExtents; m_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_char_buffer[i] != 0; i++) { m_tabSpacing = -999; m_spacing = -999; charCode = m_char_buffer[i]; //Debug.Log("i:" + i + " Character [" + (char)charCode + "]"); if (m_isRichText && charCode == 60) // '<' { // Check if Tag is valid. If valid, skip to the end of the validated tag. if (ValidateHtmlTag(m_char_buffer, i + 1, out endTagIndex)) { i = endTagIndex; if (m_isRecalculateScaleRequired) { m_fontScale = (m_currentFontSize / m_fontAssetArray[m_fontIndex].fontInfo.PointSize * (m_isOrthographic ? 1 : 0.1f)); isAffectingWordWrapping = true; m_isRecalculateScaleRequired = false; } 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; // Check if we should be using a different font asset //if (m_fontIndex != 0) //{ // // Check if we need to load the new font asset // if (m_fontAssetArray[m_fontIndex] == null) // { // Debug.Log("Loading secondary font asset."); // m_fontAssetArray[m_fontIndex] = Resources.Load("Fonts/Bangers SDF", typeof(TextMeshProFont)) as TextMeshProFont; // //m_sharedMaterials.Add(m_fontAssetArray[m_fontIndex].material); // //m_renderer.sharedMaterials = new Material[] { m_sharedMaterial, m_fontAssetArray[m_fontIndex].material }; // m_sharedMaterials.ToArray(); // } //} //Debug.Log("Char [" + (char)charCode + "] is using FontIndex: " + m_fontIndex); // 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 m_textInfo.characterInfo[character_Count].character = (char)charCode; m_textInfo.characterInfo[character_Count].color = m_htmlColor; m_textInfo.characterInfo[character_Count].style = m_style; m_textInfo.characterInfo[character_Count].index = (short)i; // Handle Kerning if Enabled. if (m_enableKerning && character_Count >= 1) { int prev_charCode = m_textInfo.characterInfo[character_Count - 1].character; KerningPairKey keyValue = new KerningPairKey(prev_charCode, charCode); KerningPair pair; m_fontAssetArray[m_fontIndex].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_fontAssetArray[m_fontIndex].BoldStyle * 2; xadvance_multiplier = 1.07f; // Increase xAdvance for bold characters. } else { style_padding = m_fontAssetArray[m_fontIndex].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_fontAssetArray[m_fontIndex].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; } // Set Characters to not visible by default. m_textInfo.characterInfo[character_Count].isVisible = false; // Track Word Count per line and for the object if (character_Count > 0 && (char.IsWhiteSpace((char)charCode) || char.IsPunctuation((char)charCode))) { if (char.IsLetterOrDigit(m_textInfo.characterInfo[character_Count - 1].character)) { wordCount += 1; m_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) { int index_X4 = visibleCharacter_Count * 4; //int index_X6 = VisibleCharacter_Count * 6; m_textInfo.characterInfo[character_Count].isVisible = true; m_textInfo.characterInfo[character_Count].vertexIndex = (short)(0 + index_X4); m_vertices[0 + index_X4] = bottom_left; m_vertices[1 + index_X4] = top_left; m_vertices[2 + index_X4] = bottom_right; m_vertices[3 + index_X4] = top_right; // 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 = m_textInfo.lineInfo[lineNumber]; Extents lineExtents = lineInfo.lineExtents; m_textInfo.lineInfo[lineNumber].lineExtents.min = new Vector2(Mathf.Min(lineExtents.min.x, bottom_left.x), Mathf.Min(lineExtents.min.y, bottom_left.y)); m_textInfo.lineInfo[lineNumber].lineExtents.max = new Vector2(Mathf.Max(lineExtents.max.x, top_right.x), Mathf.Max(lineExtents.max.y, top_left.y)); //m_textInfo.lineInfo[lineNumber].characterCount += 1; //m_textInfo.characterInfo[character_Count].charNumber = (short)m_lineExtents[lineNumber].characterCount; // Handle Word Wrapping if Enabled if (m_enableWordWrapping && top_right.x > m_lineLength) { //Debug.Log("Line " + lineNumber + " has exceeded the bounds."); // 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; } // 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; meshExtents = m_SaveWordWrapState.meshExtents; 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 >= m_textInfo.lineInfo.Length) ResizeLineExtents(lineNumber); lineOffset += (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing); m_xAdvance = 0; continue; } // Determine what color gets assigned to vertex. if (isMissingCharacter) vertexColor = Color.red; else if (m_overrideHtmlColors) vertexColor = m_fontColor; else vertexColor = m_htmlColor; if (m_maxVisibleLines > 0 && lineNumber >= m_maxVisibleLines && isLineTruncated == false) // && isLineTruncated == false) { isLineTruncated = true; m_char_buffer[m_SaveWordWrapState.previous_WordBreak] = (char)0; m_char_buffer[m_SaveWordWrapState.previous_WordBreak - 1] = '.'; m_char_buffer[m_SaveWordWrapState.previous_WordBreak - 2] = '.'; m_char_buffer[m_SaveWordWrapState.previous_WordBreak - 3] = '.'; GenerateTextMesh(); return; } // Set Alpha for Shader to render font as normal or bold. (Alpha channel is being used to let the shader know if the character is bold or normal). if ((m_style & FontStyles.Bold) == FontStyles.Bold) { vertexColor.a = m_fontColor.a < vertexColor.a ? (byte)(m_fontColor.a >> 1) : (byte)(vertexColor.a >> 1); vertexColor.a += 128; } else { vertexColor.a = m_fontColor.a < vertexColor.a ? (byte)(m_fontColor.a >> 1) : (byte)(vertexColor.a >> 1); } m_vertColors[0 + index_X4] = vertexColor; m_vertColors[1 + index_X4] = vertexColor; m_vertColors[2 + index_X4] = vertexColor; m_vertColors[3 + index_X4] = vertexColor; // Apply style_padding only if this is a SDF Shader. if (!m_sharedMaterial.HasProperty(ShaderUtilities.ID_WeightNormal)) style_padding = 0; // Setup UVs for the Mesh Vector2 uv0 = new Vector2((m_cached_GlyphInfo.x - m_padding - style_padding) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasWidth, 1 - (m_cached_GlyphInfo.y + m_padding + style_padding + m_cached_GlyphInfo.height) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasHeight); // bottom left Vector2 uv1 = new Vector2(uv0.x, 1 - (m_cached_GlyphInfo.y - m_padding - style_padding) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasHeight); // top left Vector2 uv2 = new Vector2((m_cached_GlyphInfo.x + m_padding + style_padding + m_cached_GlyphInfo.width) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasWidth, uv0.y); // bottom right Vector2 uv3 = new Vector2(uv2.x, uv1.y); // top right m_uvs[0 + index_X4] = uv0; m_uvs[1 + index_X4] = uv1; m_uvs[2 + index_X4] = uv2; m_uvs[3 + index_X4] = uv3; 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_textInfo.lineInfo[lineNumber].spaceCount += 1; m_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.meshExtents = meshExtents; m_SaveWordWrapState.lineInfo = m_textInfo.lineInfo[lineNumber]; } // Store Rectangle positions for each Character. m_textInfo.characterInfo[character_Count].bottomLeft = bottom_left; m_textInfo.characterInfo[character_Count].topRight = top_right; m_textInfo.characterInfo[character_Count].lineNumber = (short)lineNumber; m_textInfo.characterInfo[character_Count].baseLine = top_right.y - (m_cached_GlyphInfo.yOffset + m_padding) * m_fontScale; m_textInfo.characterInfo[character_Count].topLine = m_textInfo.characterInfo[character_Count].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Ascender + m_alignmentPadding.y) * m_fontScale; // Ascender m_textInfo.characterInfo[character_Count].bottomLine = m_textInfo.characterInfo[character_Count].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Descender - m_alignmentPadding.w) * m_fontScale; // Descender maxAscender = m_textInfo.characterInfo[character_Count].topLine > maxAscender ? m_textInfo.characterInfo[character_Count].topLine : maxAscender; maxDescender = m_textInfo.characterInfo[character_Count].bottomLine < maxDescender ? m_textInfo.characterInfo[character_Count].bottomLine : maxDescender; m_textInfo.characterInfo[character_Count].aspectRatio = m_cached_GlyphInfo.width / m_cached_GlyphInfo.height; m_textInfo.characterInfo[character_Count].scale = m_fontScale; m_textInfo.lineInfo[lineNumber].characterCount += 1; //Debug.Log("Character #" + i + " is [" + (char)charCode + "] ASCII (" + charCode + ")"); // Store LineInfo if (lineNumber != lastLineNumber) { m_textInfo.lineInfo[lineNumber].firstCharacterIndex = character_Count; m_textInfo.lineInfo[lineNumber - 1].lastCharacterIndex = character_Count - 1; m_textInfo.lineInfo[lineNumber - 1].characterCount = m_textInfo.lineInfo[lineNumber - 1].lastCharacterIndex - m_textInfo.lineInfo[lineNumber - 1].firstCharacterIndex + 1; m_textInfo.lineInfo[lineNumber - 1].lineLength = m_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 >= m_textInfo.lineInfo.Length) ResizeLineExtents(lineNumber); lineOffset += (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing); m_xAdvance = 0; } character_Count += 1; } // Add Termination Character to textMeshCharacterInfo which is used by the Advanced Layout Component. if (character_Count < m_textInfo.characterInfo.Length) m_textInfo.characterInfo[character_Count].character = (char)0; // DEBUG & PERFORMANCE CHECKS (0.006ms) //m_StopWatch.Stop(); //for (int i = 0; i < lineNumber + 1; i++) //{ // Debug.Log("Line: " + (i + 1) + " # Char: " + m_textInfo.lineInfo[i].characterCount // + " Word Count: " + m_textInfo.lineInfo[i].wordCount // + " Space: " + m_textInfo.lineInfo[i].spaceCount // + " First:" + m_textInfo.lineInfo[i].firstCharacterIndex + " Last [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].lastCharacterIndex].character // + "] at Index: " + m_textInfo.lineInfo[i].lastCharacterIndex + " Length: " + m_textInfo.lineInfo[i].lineLength // + " Line Extents: " + m_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); //} // If there are no visible characters... no need to continue if (visibleCharacter_Count == 0) { if (m_vertices != null) { Array.Clear(m_vertices, 0, m_vertices.Length); m_mesh.vertices = m_vertices; } return; } // Store last lineInfo m_textInfo.lineInfo[lineNumber].lastCharacterIndex = character_Count - 1; m_textInfo.lineInfo[lineNumber].characterCount = m_textInfo.lineInfo[lineNumber].lastCharacterIndex - m_textInfo.lineInfo[lineNumber].firstCharacterIndex + 1; m_textInfo.lineInfo[lineNumber].lineLength = m_textInfo.characterInfo[character_Count - 1].topRight.x - m_padding * m_fontScale; int last_vert_index = visibleCharacter_Count * 4; // Partial clear of the vertices array to mark unused vertices as degenerate. Array.Clear(m_vertices, last_vert_index, m_vertices.Length - last_vert_index); // Add offset to position Text Anchor float prev_anchor_xOffset = m_anchorOffset.x; // Cache previous Anchor position which is needed for Anchor Dampening. switch (m_anchor) { case AnchorPositions.TopLeft: m_anchorOffset = new Vector3(0, 0 - maxAscender, 0); break; case AnchorPositions.Left: m_anchorOffset = new Vector3(0, 0 - (maxAscender + maxDescender) / 2, 0); break; case AnchorPositions.BottomLeft: m_anchorOffset = new Vector3(0, 0 - maxDescender, 0); break; case AnchorPositions.Top: m_anchorOffset = new Vector3(0 - (meshExtents.min.x + meshExtents.max.x) / 2, 0 - maxAscender, 0); break; case AnchorPositions.Center: m_anchorOffset = new Vector3(0 - (meshExtents.min.x + meshExtents.max.x) / 2, 0 - (maxAscender + maxDescender) / 2, 0); break; case AnchorPositions.Bottom: m_anchorOffset = new Vector3(0 - ((meshExtents.min.x + meshExtents.max.x) / 2), 0 - maxDescender, 0); break; case AnchorPositions.TopRight: m_anchorOffset = new Vector3(0 - meshExtents.max.x + m_padding * m_fontScale, 0 - maxAscender, 0); break; case AnchorPositions.Right: m_anchorOffset = new Vector3(0 - meshExtents.max.x + m_padding * m_fontScale, 0 - (maxAscender + maxDescender) / 2, 0); break; case AnchorPositions.BottomRight: m_anchorOffset = new Vector3(0 - meshExtents.max.x + m_padding * m_fontScale, 0 - maxDescender, 0); break; case AnchorPositions.BaseLine: m_anchorOffset = new Vector3(0, 0, 0); break; } // Check how much the AnchorOffset.x has changed. If it changes by more than 1/3 of the underline character then adjust it. if (m_anchorDampening) { float offset_delta = Mathf.Abs(prev_anchor_xOffset - m_anchorOffset.x); if (prev_anchor_xOffset != 0 && offset_delta < m_cached_Underline_GlyphInfo.width * m_fontScale * 0.4f) m_anchorOffset.x = prev_anchor_xOffset; } Vector3 justificationOffset = Vector3.zero; Vector3 offset = Vector3.zero; int vert_index_X4 = 0; //int wordCount = 0; int lineCount = 0; int lastLine = 0; for (int i = 0; i < character_Count; i++) { int currentLine = m_textInfo.characterInfo[i].lineNumber; LineInfo lineInfo = m_textInfo.lineInfo[currentLine]; lineCount = currentLine + 1; // Process Line Justification switch (m_lineJustification) { case AlignmentTypes.Left: justificationOffset = Vector3.zero; break; case AlignmentTypes.Center: justificationOffset = new Vector3((meshExtents.min.x + meshExtents.max.x) / 2 - (lineInfo.lineExtents.min.x + lineInfo.lineExtents.max.x) / 2, 0, 0); break; case AlignmentTypes.Right: justificationOffset = new Vector3(meshExtents.max.x - lineInfo.lineExtents.max.x, 0, 0); break; case AlignmentTypes.Justified: charCode = m_textInfo.characterInfo[i].character; char lastCharOfCurrentLine = m_textInfo.characterInfo[lineInfo.lastCharacterIndex].character; if (char.IsWhiteSpace(lastCharOfCurrentLine) && !char.IsControl(lastCharOfCurrentLine) && currentLine < lineNumber) { // All lines are justified accept the last one. float gap = (meshExtents.max.x) - (lineInfo.lineExtents.max.x); if (currentLine != lastLine || i == 0) justificationOffset = Vector3.zero; else { if (charCode == 9 || charCode == 32) { justificationOffset += new Vector3(gap * (1 - m_wordWrappingRatios) / (lineInfo.spaceCount - 1), 0, 0); } else { //Debug.Log("LineInfo Character Count: " + lineInfo.characterCount); justificationOffset += new Vector3(gap * m_wordWrappingRatios / (lineInfo.characterCount - lineInfo.spaceCount - 1), 0, 0); } } } else justificationOffset = Vector3.zero; // Keep last line left justified. //Debug.Log("Char [" + (char)charCode + "] Code:" + charCode + " Offset:" + justificationOffset + " # Spaces:" + m_lineExtents[currentLine].NumberOfSpaces + " # Characters:" + m_lineExtents[currentLine].NumberOfChars); lastLine = currentLine; break; } offset = m_anchorOffset + justificationOffset; if (m_textInfo.characterInfo[i].isVisible) { Extents lineExtents = lineInfo.lineExtents; // Setup UV2 based on Character Mapping Options Selected switch (m_horizontalMapping) { case TextureMappingOptions.Character: m_uv2s[vert_index_X4 + 0].x = 0; m_uv2s[vert_index_X4 + 1].x = 0; m_uv2s[vert_index_X4 + 2].x = 1; m_uv2s[vert_index_X4 + 3].x = 1; break; case TextureMappingOptions.Line: if (m_lineJustification != AlignmentTypes.Justified) { m_uv2s[vert_index_X4 + 0].x = (m_vertices[vert_index_X4 + 0].x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x); m_uv2s[vert_index_X4 + 1].x = (m_vertices[vert_index_X4 + 1].x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x); m_uv2s[vert_index_X4 + 2].x = (m_vertices[vert_index_X4 + 2].x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x); m_uv2s[vert_index_X4 + 3].x = (m_vertices[vert_index_X4 + 3].x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x); break; } else // Special Case if Justified is used in Line Mode. { m_uv2s[vert_index_X4 + 0].x = (m_vertices[vert_index_X4 + 0].x + justificationOffset.x - meshExtents.min.x) / (meshExtents.max.x - meshExtents.min.x); m_uv2s[vert_index_X4 + 1].x = (m_vertices[vert_index_X4 + 1].x + justificationOffset.x - meshExtents.min.x) / (meshExtents.max.x - meshExtents.min.x); m_uv2s[vert_index_X4 + 2].x = (m_vertices[vert_index_X4 + 2].x + justificationOffset.x - meshExtents.min.x) / (meshExtents.max.x - meshExtents.min.x); m_uv2s[vert_index_X4 + 3].x = (m_vertices[vert_index_X4 + 3].x + justificationOffset.x - meshExtents.min.x) / (meshExtents.max.x - meshExtents.min.x); break; } case TextureMappingOptions.Paragraph: m_uv2s[vert_index_X4 + 0].x = (m_vertices[vert_index_X4 + 0].x + justificationOffset.x - meshExtents.min.x) / (meshExtents.max.x - meshExtents.min.x); m_uv2s[vert_index_X4 + 1].x = (m_vertices[vert_index_X4 + 1].x + justificationOffset.x - meshExtents.min.x) / (meshExtents.max.x - meshExtents.min.x); m_uv2s[vert_index_X4 + 2].x = (m_vertices[vert_index_X4 + 2].x + justificationOffset.x - meshExtents.min.x) / (meshExtents.max.x - meshExtents.min.x); m_uv2s[vert_index_X4 + 3].x = (m_vertices[vert_index_X4 + 3].x + justificationOffset.x - meshExtents.min.x) / (meshExtents.max.x - meshExtents.min.x); break; case TextureMappingOptions.MatchAspect: switch (m_verticalMapping) { case TextureMappingOptions.Character: m_uv2s[vert_index_X4 + 0].y = 0; m_uv2s[vert_index_X4 + 1].y = 1; m_uv2s[vert_index_X4 + 2].y = 0; m_uv2s[vert_index_X4 + 3].y = 1; break; case TextureMappingOptions.Line: m_uv2s[vert_index_X4 + 0].y = (m_vertices[vert_index_X4].y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y); m_uv2s[vert_index_X4 + 1].y = (m_vertices[vert_index_X4 + 1].y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y); m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; case TextureMappingOptions.Paragraph: m_uv2s[vert_index_X4 + 0].y = (m_vertices[vert_index_X4].y - meshExtents.min.y) / (meshExtents.max.y - meshExtents.min.y); m_uv2s[vert_index_X4 + 1].y = (m_vertices[vert_index_X4 + 1].y - meshExtents.min.y) / (meshExtents.max.y - meshExtents.min.y); m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; case TextureMappingOptions.MatchAspect: Debug.Log("ERROR: Cannot Match both Vertical & Horizontal."); break; } //float xDelta = 1 - (_uv2s[vert_index + 0].y * textMeshCharacterInfo[i].AspectRatio); // Left aligned float xDelta = (1 - ((m_uv2s[vert_index_X4 + 0].y + m_uv2s[vert_index_X4 + 1].y) * m_textInfo.characterInfo[i].aspectRatio)) / 2; // Center of Rectangle //float xDelta = 0; m_uv2s[vert_index_X4 + 0].x = (m_uv2s[vert_index_X4 + 0].y * m_textInfo.characterInfo[i].aspectRatio) + xDelta; m_uv2s[vert_index_X4 + 1].x = m_uv2s[vert_index_X4 + 0].x; m_uv2s[vert_index_X4 + 2].x = (m_uv2s[vert_index_X4 + 1].y * m_textInfo.characterInfo[i].aspectRatio) + xDelta; m_uv2s[vert_index_X4 + 3].x = m_uv2s[vert_index_X4 + 2].x; break; } switch (m_verticalMapping) { case TextureMappingOptions.Character: m_uv2s[vert_index_X4 + 0].y = 0; m_uv2s[vert_index_X4 + 1].y = 1; m_uv2s[vert_index_X4 + 2].y = 0; m_uv2s[vert_index_X4 + 3].y = 1; break; case TextureMappingOptions.Line: m_uv2s[vert_index_X4 + 0].y = (m_vertices[vert_index_X4].y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y); m_uv2s[vert_index_X4 + 1].y = (m_vertices[vert_index_X4 + 1].y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y); m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; case TextureMappingOptions.Paragraph: m_uv2s[vert_index_X4 + 0].y = (m_vertices[vert_index_X4].y - meshExtents.min.y) / (meshExtents.max.y - meshExtents.min.y); m_uv2s[vert_index_X4 + 1].y = (m_vertices[vert_index_X4 + 1].y - meshExtents.min.y) / (meshExtents.max.y - meshExtents.min.y); m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; case TextureMappingOptions.MatchAspect: //float yDelta = 1 - (_uv2s[vert_index + 2].x / textMeshCharacterInfo[i].AspectRatio); // Top Corner float yDelta = (1 - ((m_uv2s[vert_index_X4 + 0].x + m_uv2s[vert_index_X4 + 2].x) / m_textInfo.characterInfo[i].aspectRatio)) / 2; // Center of Rectangle //float yDelta = 0; m_uv2s[vert_index_X4 + 0].y = yDelta + (m_uv2s[vert_index_X4 + 0].x / m_textInfo.characterInfo[i].aspectRatio); m_uv2s[vert_index_X4 + 1].y = yDelta + (m_uv2s[vert_index_X4 + 2].x / m_textInfo.characterInfo[i].aspectRatio); m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4 + 0].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; } // Pack UV's so that we can pass Xscale needed for Shader to maintain 1:1 ratio. float xScale = m_textInfo.characterInfo[i].scale * m_transform.lossyScale.z; float x0 = m_uv2s[vert_index_X4 + 0].x; float y0 = m_uv2s[vert_index_X4 + 0].y; float x1 = m_uv2s[vert_index_X4 + 3].x; float y1 = m_uv2s[vert_index_X4 + 3].y; float dx = Mathf.Floor(x0); float dy = Mathf.Floor(y0); x0 = x0 - dx; x1 = x1 - dx; y0 = y0 - dy; y1 = y1 - dy; m_uv2s[vert_index_X4 + 0] = PackUV(x0, y0, xScale); m_uv2s[vert_index_X4 + 1] = PackUV(x0, y1, xScale); m_uv2s[vert_index_X4 + 2] = PackUV(x1, y0, xScale); m_uv2s[vert_index_X4 + 3] = PackUV(x1, y1, xScale); // Enables control of the visibility of characters && lines. if (m_maxVisibleCharacters != -1 && i >= m_maxVisibleCharacters || m_maxVisibleLines != -1 && currentLine >= m_maxVisibleLines) { m_vertices[vert_index_X4 + 0] *= 0; m_vertices[vert_index_X4 + 1] *= 0; m_vertices[vert_index_X4 + 2] *= 0; m_vertices[vert_index_X4 + 3] *= 0; } else { m_vertices[vert_index_X4 + 0] += offset; m_vertices[vert_index_X4 + 1] += offset; m_vertices[vert_index_X4 + 2] += offset; m_vertices[vert_index_X4 + 3] += offset; } vert_index_X4 += 4; } m_textInfo.characterInfo[i].bottomLeft += offset; m_textInfo.characterInfo[i].topRight += offset; m_textInfo.characterInfo[i].topLine += offset.y; m_textInfo.characterInfo[i].bottomLine += offset.y; m_textInfo.characterInfo[i].baseLine += offset.y; // Need to address underline and which font to use in the list // Create Underline Mesh if ((m_textInfo.characterInfo[i].style & FontStyles.Underline) == FontStyles.Underline && m_textInfo.characterInfo[i].character != 10 && m_textInfo.characterInfo[i].character != 13) { if (beginUnderline == false) { beginUnderline = true; underline_start = new Vector3(m_textInfo.characterInfo[i].bottomLeft.x, m_textInfo.characterInfo[i].baseLine + font.fontInfo.Underline * m_fontScale, 0); } if (i == character_Count - 1) // End Underline if we are at the last character. { underline_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + font.fontInfo.Underline * m_fontScale, 0); DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index); } } else { if (beginUnderline == true) { beginUnderline = false; underline_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, m_textInfo.characterInfo[i - 1].baseLine + font.fontInfo.Underline * m_fontScale, 0); DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index); } } } // METRICS ABOUT THE TEXT OBJECT m_textInfo.characterCount = (short)character_Count; m_textInfo.lineCount = (short)lineCount; m_textInfo.wordCount = (short)wordCount; //for (int i = 0; i < character_Count; i++) // Debug.Log("Char [" + m_characters_Info[i].character + "] or ASCII (" + (int)m_characters_Info[i].character + ")"); // If Advanced Layout Component is present, don't upload the mesh. if (m_isAdvanceLayoutComponentPresent == false || m_advancedLayoutComponent.isEnabled == false) { //Debug.Log("Uploading Mesh normally."); // Upload Mesh Data m_mesh.MarkDynamic(); m_mesh.vertices = m_vertices; m_mesh.uv = m_uvs; m_mesh.uv2 = m_uv2s; m_mesh.colors32 = m_vertColors; // Store Mesh information in MeshInfo m_textInfo.meshInfo.vertices = m_vertices; m_textInfo.meshInfo.uv0s = m_uvs; m_textInfo.meshInfo.uv2s = m_uv2s; m_textInfo.meshInfo.vertexColors = m_vertColors; // 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) + m_anchorOffset, new Vector3(meshExtents.max.x - meshExtents.min.x, meshExtents.max.y - meshExtents.min.y, 0)); //m_maskOffset = new Vector4(m_mesh.bounds.center.x, m_mesh.bounds.center.y, m_mesh.bounds.size.x, m_mesh.bounds.size.y); } else { UpdateMeshData(m_textInfo.characterInfo, character_Count, m_mesh, m_vertices, m_uvs, m_uv2s, m_vertColors, m_normals, m_tangents); m_advancedLayoutComponent.DrawMesh(); //Debug.Log("Advanced Layout Component present & enabled. Not uploading mesh."); } // Reset Check Line Flag which is used to determine if a property that affects linelenght has been modified. isAffectingWordWrapping = false; //m_safetyCount = 0; //Debug.Log("TextMesh Pro Object updated. Line Count:" + m_textInfo.lineInfo.Length); //Profiler.EndSample(); //m_StopWatch.Stop(); //Debug.Log(m_mesh.bounds); //Debug.Log("Done Rendering Text."); //Debug.Log("TimeElapsed is:" + (m_StopWatch.ElapsedTicks / 10000f).ToString("f4")); //m_StopWatch.Reset(); }
/// <summary> /// This is the main function that is responsible for creating / displaying the text. /// </summary> void GenerateTextMesh() { //Debug.Log("***** GenerateTextMesh() ***** Iteration Count: " + loopCountA + ". Min: " + m_minFontSize + " Max: " + m_maxFontSize + " Font size is " + m_fontSize); // 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; } // Reset TextInfo if (m_textInfo != null) m_textInfo.Clear(); // Early exit if we don't have any Text to generate. if (m_char_buffer == null || m_char_buffer.Length == 0 || m_char_buffer[0] == (char)0) { //Debug.Log("Early Out!"); if (m_vertices != null) { Array.Clear(m_vertices, 0, m_vertices.Length); m_mesh.vertices = m_vertices; } m_preferredWidth = 0; m_preferredHeight = 0; return; } // Determine how many characters will be visible and make the necessary allocations (if needed). int totalCharacterCount = SetArraySizes(m_char_buffer); m_fontIndex = 0; // Will be used when support for using different font assets or sprites within the same object will be added. 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. m_maxFontScale = 0; float previousFontScale = 0; //float lastVisibleCharacterScale = 0; m_currentFontSize = m_fontSize; float fontSizeDelta = 0; 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 = m_fontStyle; // Set the default style. m_lineJustification = m_textAlignment; // Sets the line justification mode to match editor alignment. // 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); m_isMaskingEnabled = ShaderUtilities.IsMaskingEnabled(m_sharedMaterial); } float style_padding = 0; // Extra padding required to accommodate Bold style. float xadvance_multiplier = 1; // Used to increase spacing between character when style is bold. m_baselineOffset = 0; // Used by subscript characters. // Underline bool beginUnderline = false; Vector3 underline_start = Vector3.zero; // Used to track where underline starts & ends. Vector3 underline_end = Vector3.zero; // Strike-through bool beginStrikethrough = false; Vector3 strikethrough_start = Vector3.zero; Vector3 strikethrough_end = Vector3.zero; m_fontColor32 = m_fontColor; Color32 vertexColor; m_htmlColor = m_fontColor32; m_colorStackIndex = 0; Array.Clear(m_colorStack, 0, m_colorStack.Length); m_styleStackIndex = 0; Array.Clear(m_styleStack, 0, m_styleStack.Length); m_lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). m_lineHeight = 0; float maxLineHeight = 0; m_cSpacing = 0; m_monoSpacing = 0; float lineOffsetDelta = 0; m_xAdvance = 0; // Used to track the position of each character. m_maxXAdvance = 0; // Used to determine Preferred Width. tag_LineIndent = 0; // Used for indentation of text. tag_Indent = 0; m_characterCount = 0; // Total characters in the char[] m_visibleCharacterCount = 0; // # of visible characters. // Tracking of line information m_firstCharacterOfLine = 0; m_lastCharacterOfLine = 0; m_firstVisibleCharacterOfLine = 0; m_lastVisibleCharacterOfLine = 0; m_lineNumber = 0; bool isStartOfNewLine = true; m_pageNumber = 0; int pageToDisplay = Mathf.Clamp(m_pageToDisplay - 1, 0, m_textInfo.pageInfo.Length - 1); int ellipsisIndex = 0; Vector3[] corners = m_textContainer.corners; Vector4 margins = m_textContainer.margins; m_marginWidth = m_textContainer.rect.width - margins.z - margins.x; float marginWidth = m_marginWidth; float marginHeight = m_textContainer.rect.height - margins.y - margins.w; m_marginLeft = 0; m_marginRight = 0; m_width = -1; float lossyScale = m_transform.lossyScale.z; // Used by Unity's Auto Layout system. m_preferredWidth = 0; m_preferredHeight = 0; // Initialize struct to track states of word wrapping bool isFirstWord = true; bool isLastBreakingChar = false; m_SavedLineState = new WordWrapState(); m_SavedWordWrapState = new WordWrapState(); int wrappingIndex = 0; // Need to initialize these Extents structures m_meshExtents = new Extents(k_InfinityVector, -k_InfinityVector); // Initialize lineInfo if (m_textInfo.lineInfo == null) m_textInfo.lineInfo = new TMP_LineInfo[2]; for (int i = 0; i < m_textInfo.lineInfo.Length; i++) { m_textInfo.lineInfo[i] = new TMP_LineInfo(); m_textInfo.lineInfo[i].lineExtents = new Extents(k_InfinityVector, -k_InfinityVector); m_textInfo.lineInfo[i].ascender = -k_InfinityVector.x; m_textInfo.lineInfo[i].descender = k_InfinityVector.x; } // Tracking of the highest Ascender m_maxAscender = 0; m_maxDescender = 0; float pageAscender = 0; float maxVisibleDescender = 0; bool isMaxVisibleDescenderSet = false; m_isNewPage = false; //bool isLineOffsetAdjusted = false; loopCountA += 1; int endTagIndex = 0; // Parse through Character buffer to read HTML tags and begin creating mesh. for (int i = 0; m_char_buffer[i] != 0; i++) { charCode = m_char_buffer[i]; //loopCountE += 1; // Parse Rich Text Tag #region Parse Rich Text Tag if (m_isRichText && charCode == 60) // '<' { m_isParsingText = true; // Check if Tag is valid. If valid, skip to the end of the validated tag. if (ValidateHtmlTag(m_char_buffer, i + 1, out endTagIndex)) { i = endTagIndex; if (m_isRecalculateScaleRequired) { m_fontScale = (m_currentFontSize / m_fontAssetArray[m_fontIndex].fontInfo.PointSize * (m_isOrthographic ? 1 : 0.1f)); m_isRecalculateScaleRequired = false; } continue; } } #endregion End Parse Rich Text Tag m_isParsingText = false; isMissingCharacter = false; // Check if we should be using a different font asset //if (m_fontIndex != 0) //{ // // Check if we need to load the new font asset // if (m_fontAssetArray[m_fontIndex] == null) // { // Debug.Log("Loading secondary font asset."); // m_fontAssetArray[m_fontIndex] = Resources.Load("Fonts & Materials/Bangers SDF", typeof(TextMeshProFont)) as TextMeshProFont; // //m_sharedMaterials.Add(m_fontAssetArray[m_fontIndex].material); // //m_renderer.sharedMaterials = new Material[] { m_sharedMaterial, m_fontAssetArray[m_fontIndex].material }; // m_sharedMaterials.ToArray(); // } //} //Debug.Log("Char [" + (char)charCode + "] ASCII " + charCode); //is using FontIndex: " + m_fontIndex); // Handle Font Styles like LowerCase, UpperCase and SmallCaps. #region Handling of LowerCase, UpperCase and SmallCaps Font Styles if ((m_style & FontStyles.UpperCase) == FontStyles.UpperCase) { // If this character is lowercase, switch to uppercase. if (char.IsLower((char)charCode)) charCode -= 32; } else if ((m_style & FontStyles.LowerCase) == FontStyles.LowerCase) { // If this character is uppercase, switch to lowercase. if (char.IsUpper((char)charCode)) charCode += 32; } else if ((m_fontStyle & FontStyles.SmallCaps) == FontStyles.SmallCaps || (m_style & FontStyles.SmallCaps) == FontStyles.SmallCaps) { if (char.IsLower((char)charCode)) { m_fontScale = m_currentFontSize * 0.8f / m_fontAssetArray[m_fontIndex].fontInfo.PointSize * (m_isOrthographic ? 1 : 0.1f); charCode -= 32; } else m_fontScale = m_currentFontSize / m_fontAssetArray[m_fontIndex].fontInfo.PointSize * (m_isOrthographic ? 1 : 0.1f); } #endregion // Look up Character Data from Dictionary and cache it. #region Look up Character Data m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(charCode, out m_cached_GlyphInfo); if (m_cached_GlyphInfo == null) { // Character wasn't found in the Dictionary. // Check if Lowercase & Replace by Uppercase if possible if (char.IsLower((char)charCode)) { if (m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(charCode - 32, out m_cached_GlyphInfo)) charCode -= 32; } else if (char.IsUpper((char)charCode)) { if (m_fontAssetArray[m_fontIndex].characterDictionary.TryGetValue(charCode + 32, out m_cached_GlyphInfo)) charCode += 32; } // Still don't have a replacement? if (m_cached_GlyphInfo == null) { 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 ... Debug.LogWarning("Character with ASCII value of " + charCode + " was not found in the Font Asset Glyph Table."); continue; } } } #endregion // Store some of the text object's information m_textInfo.characterInfo[m_characterCount].character = (char)charCode; m_textInfo.characterInfo[m_characterCount].color = m_htmlColor; m_textInfo.characterInfo[m_characterCount].style = m_style; m_textInfo.characterInfo[m_characterCount].index = (short)i; // Handle Kerning if Enabled. #region Handle Kerning if (m_enableKerning && m_characterCount >= 1) { int prev_charCode = m_textInfo.characterInfo[m_characterCount - 1].character; KerningPairKey keyValue = new KerningPairKey(prev_charCode, charCode); KerningPair pair; m_fontAssetArray[m_fontIndex].kerningDictionary.TryGetValue(keyValue.key, out pair); if (pair != null) { m_xAdvance += pair.XadvanceOffset * m_fontScale; } } #endregion // Handle Mono Spacing #region Handle Mono Spacing if (m_monoSpacing != 0 && m_xAdvance != 0) m_xAdvance -= (m_cached_GlyphInfo.width / 2 + m_cached_GlyphInfo.xOffset) * m_fontScale * (1 - m_charWidthAdjDelta); #endregion // Set Padding based on selected font style #region Handle Style Padding if ((m_style & FontStyles.Bold) == FontStyles.Bold || (m_fontStyle & FontStyles.Bold) == FontStyles.Bold) // Checks for any combination of Bold Style. { style_padding = m_fontAssetArray[m_fontIndex].BoldStyle * 2; xadvance_multiplier = 1.07f; // Increase xAdvance for bold characters. } else { style_padding = m_fontAssetArray[m_fontIndex].NormalStyle * 2; xadvance_multiplier = 1.0f; } #endregion Handle Style Padding // Setup Vertices for each character. Vector3 top_left = new Vector3(0 + m_xAdvance + ((m_cached_GlyphInfo.xOffset - m_padding - style_padding) * m_fontScale * (1 - m_charWidthAdjDelta)), (m_cached_GlyphInfo.yOffset + m_padding) * m_fontScale - m_lineOffset + m_baselineOffset, 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 * (1 - m_charWidthAdjDelta)), 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 #region Handle Italic & Shearing if ((m_style & FontStyles.Italic) == FontStyles.Italic || (m_fontStyle & 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_fontAssetArray[m_fontIndex].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; } #endregion Handle Italics & Shearing // Store position of vertices for each character m_textInfo.characterInfo[m_characterCount].topLeft = top_left; m_textInfo.characterInfo[m_characterCount].bottomLeft = bottom_left; m_textInfo.characterInfo[m_characterCount].topRight = top_right; m_textInfo.characterInfo[m_characterCount].bottomRight = bottom_right; m_textInfo.characterInfo[m_characterCount].baseLine = 0 - m_lineOffset + m_baselineOffset; // Compute MaxAscender & MaxDescender which is used for AutoScaling & other type layout options float ascender = (m_fontAsset.fontInfo.Ascender + m_alignmentPadding.y) * m_fontScale + m_baselineOffset; if ((charCode == 10 || charCode == 13) && m_characterCount > m_firstVisibleCharacterOfLine) ascender = m_alignmentPadding.y * m_fontScale + m_baselineOffset; float descender = (m_fontAsset.fontInfo.Descender + m_alignmentPadding.w) * m_fontScale - m_lineOffset + m_baselineOffset; if (m_lineNumber == 0) m_maxAscender = m_maxAscender > ascender ? m_maxAscender : ascender; if (m_lineOffset == 0) pageAscender = pageAscender > ascender ? pageAscender : ascender; // Track Line Height maxLineHeight = Mathf.Max(m_lineHeight, maxLineHeight); // Used to adjust line spacing when larger fonts or the size tag is used. if (m_baselineOffset == 0) m_maxFontScale = Mathf.Max(m_maxFontScale, m_fontScale); // Set Characters to not visible by default. m_textInfo.characterInfo[m_characterCount].isVisible = false; // Setup Mesh for visible characters. ie. not a SPACE / LINEFEED / CARRIAGE RETURN. #region Handle Visible Characters if (charCode != 10 && charCode != 13 && charCode != 32) { m_textInfo.characterInfo[m_characterCount].isVisible = true; // Check if Character exceeds the width of the Text Container #region Check for Characters Exceeding Width of Text Container float width = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - m_marginLeft - m_marginRight, m_width) : marginWidth + 0.0001f - m_marginLeft - m_marginRight; m_textInfo.lineInfo[m_lineNumber].width = width; m_textInfo.lineInfo[m_lineNumber].marginLeft = m_marginLeft; if (m_xAdvance + m_cached_GlyphInfo.xAdvance * (1 - m_charWidthAdjDelta) * m_fontScale > width && !m_textContainer.isDefaultWidth) { ellipsisIndex = m_characterCount - 1; // Last safely rendered character // Word Wrapping #region Handle Word Wrapping if (enableWordWrapping && m_characterCount != m_firstCharacterOfLine) { if (wrappingIndex == m_SavedWordWrapState.previous_WordBreak || isFirstWord) { // Word wrapping is no longer possible. Shrink size of text if auto-sizing is enabled. if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { // Handle Character Width Adjustments #region Character Width Adjustments if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100) { loopCountA = 0; m_charWidthAdjDelta += 0.01f; GenerateTextMesh(); return; } #endregion // Adjust Point Size m_maxFontSize = m_fontSize; m_fontSize -= Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize = (int)(Mathf.Max(m_fontSize, m_fontSizeMin) * 20 + 0.5f) / 20f; if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } // Word wrapping is no longer possible, now breaking up individual words. if (m_isCharacterWrappingEnabled == false) { m_isCharacterWrappingEnabled = true; // Should add a check to make sure this mode is available. } else isLastBreakingChar = true; m_recursiveCount += 1; if (m_recursiveCount > 20) { //Debug.Log("Recursive count exceeded!"); continue; } } // Restore to previously stored state of last valid (space character or linefeed) i = RestoreWordWrappingState(ref m_SavedWordWrapState); wrappingIndex = i; // Used to detect when line length can no longer be reduced. //Debug.Log("Line # " + m_lineNumber + " Last Character of Line is [" + m_textInfo.characterInfo[m_characterCount - 1].character + "]. Last Visible Character is [" + m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].character + "]."); // Check if we need to Adjust LineOffset & Restore State to the start of the line. if (m_lineNumber > 0 && m_maxFontScale != 0 && m_lineHeight == 0 && m_maxFontScale != previousFontScale && !m_isNewPage) { // Compute Offset float offsetDelta = (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing + m_paragraphSpacing + m_lineSpacingDelta) * m_maxFontScale; m_lineOffset += offsetDelta - lineOffsetDelta; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount - 1, offsetDelta - lineOffsetDelta); m_SavedWordWrapState.lineOffset = m_lineOffset; } m_isNewPage = false; // Calculate lineAscender & make sure if last character is superscript or subscript that we check that as well. float lineAscender = (m_fontAsset.fontInfo.Ascender + m_alignmentPadding.y) * m_maxFontScale - m_lineOffset; float lineAscender2 = (m_fontAsset.fontInfo.Ascender + m_alignmentPadding.y) * m_fontScale - m_lineOffset + m_baselineOffset; lineAscender = lineAscender > lineAscender2 ? lineAscender : lineAscender2; // Calculate lineDescender & make sure if last character is superscript or subscript that we check that as well. float lineDescender = (m_fontAsset.fontInfo.Descender + m_alignmentPadding.w) * m_maxFontScale - m_lineOffset; float lineDescender2 = (m_fontAsset.fontInfo.Descender + m_alignmentPadding.w) * m_fontScale - m_lineOffset + m_baselineOffset; lineDescender = lineDescender < lineDescender2 ? lineDescender : lineDescender2; // Update maxDescender and maxVisibleDescender m_maxDescender = m_maxDescender < lineDescender ? m_maxDescender : lineDescender; if (!isMaxVisibleDescenderSet) maxVisibleDescender = m_maxDescender; if (m_characterCount >= m_maxVisibleCharacters || m_lineNumber >= m_maxVisibleLines) isMaxVisibleDescenderSet = true; // Track & Store lineInfo for the new line m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex = m_firstCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].firstVisibleCharacterIndex = m_firstVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex = m_characterCount - 1 > 0 ? m_characterCount - 1 : 0; m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex = m_lastVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].characterCount = m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex - m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + 1; m_textInfo.lineInfo[m_lineNumber].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_firstVisibleCharacterOfLine].bottomLeft.x, lineDescender); m_textInfo.lineInfo[m_lineNumber].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].topRight.x, lineAscender); m_textInfo.lineInfo[m_lineNumber].lineLength = m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x - m_padding * m_maxFontScale; m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance - m_characterSpacing * m_fontScale; m_firstCharacterOfLine = m_characterCount; // Store first character of the next line. // Compute Preferred Width & Height m_preferredWidth += m_xAdvance; // m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].topRight.x - m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].bottomLeft.x; if (m_enableWordWrapping) m_preferredHeight = m_maxAscender - m_maxDescender; else m_preferredHeight = Mathf.Max(m_preferredHeight, lineAscender - lineDescender); //Debug.Log("LineInfo for line # " + (m_lineNumber) + " First character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + // " Last character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex + // " Last Visible character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex + // " Character Count: " + m_textInfo.lineInfo[m_lineNumber].characterCount + " Line Length: " + m_textInfo.lineInfo[m_lineNumber].lineLength /* + // " MinX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.x + " MinY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.y + // " MaxX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x + " MaxY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.y + // " Line Ascender: " + lineAscender + " Line Descender: " + lineDescender + " FontScale: " + m_fontScale + " MaxFontScale: " + m_maxFontScale + // " Line MaxX: " + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].topRight.x */); // Store the state of the line before starting on the new line. SaveWordWrappingState(ref m_SavedLineState, i, m_characterCount - 1); m_lineNumber += 1; isStartOfNewLine = true; // Check to make sure Array is large enough to hold a new line. if (m_lineNumber >= m_textInfo.lineInfo.Length) ResizeLineExtents(m_lineNumber); // Apply Line Spacing based on scale of the last character of the line. if (maxLineHeight == 0) { lineOffsetDelta = (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing + m_lineSpacingDelta) * m_fontScale; m_lineOffset += lineOffsetDelta; } else m_lineOffset += (maxLineHeight + m_lineSpacing) * baseScale; // Special handling when line-height tag is used. previousFontScale = m_fontScale; m_xAdvance = 0 + tag_Indent; m_maxFontScale = 0; maxLineHeight = 0; continue; } #endregion End Word Wrapping // Text Auto-Sizing (text exceeding Width of container. #region Handle Text Auto-Sizing if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { // Handle Character Width Adjustments #region Character Width Adjustments if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100) { loopCountA = 0; m_charWidthAdjDelta += 0.01f; GenerateTextMesh(); return; } #endregion // Adjust Point Size m_maxFontSize = m_fontSize; m_fontSize -= Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize = (int)(Mathf.Max(m_fontSize, m_fontSizeMin) * 20 + 0.5f) / 20f; m_recursiveCount = 0; if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } #endregion End Text Auto-Sizing // Handle Text Overflow #region Handle Text Overflow switch (m_overflowMode) { case TextOverflowModes.Overflow: if (m_isMaskingEnabled) DisableMasking(); break; case TextOverflowModes.Ellipsis: if (m_isMaskingEnabled) DisableMasking(); m_isTextTruncated = true; if (m_characterCount < 1) { m_textInfo.characterInfo[m_characterCount].isVisible = false; m_visibleCharacterCount -= 1; break; } m_char_buffer[i - 1] = 8230; m_char_buffer[i] = (char)0; GenerateTextMesh(); return; case TextOverflowModes.Masking: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.ScrollRect: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.Truncate: if (m_isMaskingEnabled) DisableMasking(); m_textInfo.characterInfo[m_characterCount].isVisible = false; break; } #endregion End Text Overflow } #endregion End Check for Characters Exceeding Width of Text Container if (charCode != 9) { // Setup Mesh int index_X4 = m_visibleCharacterCount * 4; //m_textInfo.characterInfo[m_characterCount].isVisible = true; m_textInfo.characterInfo[m_characterCount].vertexIndex = (short)(0 + index_X4); m_vertices[0 + index_X4] = m_textInfo.characterInfo[m_characterCount].bottomLeft; m_vertices[1 + index_X4] = m_textInfo.characterInfo[m_characterCount].topLeft; m_vertices[2 + index_X4] = m_textInfo.characterInfo[m_characterCount].bottomRight; m_vertices[3 + index_X4] = m_textInfo.characterInfo[m_characterCount].topRight; // Determine what color gets assigned to vertex. #region Handle Vertex Colors if (isMissingCharacter) vertexColor = Color.red; else if (m_overrideHtmlColors) vertexColor = m_fontColor32; else vertexColor = m_htmlColor; // Set Alpha to the lesser of vertex color or color tag alpha). vertexColor.a = m_fontColor32.a < vertexColor.a ? m_fontColor32.a : vertexColor.a; if (!m_enableVertexGradient) { m_vertColors[0 + index_X4] = vertexColor; m_vertColors[1 + index_X4] = vertexColor; m_vertColors[2 + index_X4] = vertexColor; m_vertColors[3 + index_X4] = vertexColor; } else { if (!m_overrideHtmlColors && !m_htmlColor.CompareRGB(m_fontColor32)) { m_vertColors[0 + index_X4] = vertexColor; m_vertColors[1 + index_X4] = vertexColor; m_vertColors[2 + index_X4] = vertexColor; m_vertColors[3 + index_X4] = vertexColor; } else { m_vertColors[0 + index_X4] = m_fontColorGradient.bottomLeft; m_vertColors[1 + index_X4] = m_fontColorGradient.topLeft; m_vertColors[2 + index_X4] = m_fontColorGradient.bottomRight; m_vertColors[3 + index_X4] = m_fontColorGradient.topRight; } m_vertColors[0 + index_X4].a = vertexColor.a; m_vertColors[1 + index_X4].a = vertexColor.a; m_vertColors[2 + index_X4].a = vertexColor.a; m_vertColors[3 + index_X4].a = vertexColor.a; } #endregion Handle Vertex Colors // Apply style_padding only if this is a SDF Shader. if (!m_sharedMaterial.HasProperty(ShaderUtilities.ID_WeightNormal)) style_padding = 0; // Setup UVs for the Mesh #region Setup UVs Vector2 uv0 = new Vector2((m_cached_GlyphInfo.x - m_padding - style_padding) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasWidth, 1 - (m_cached_GlyphInfo.y + m_padding + style_padding + m_cached_GlyphInfo.height) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasHeight); // bottom left Vector2 uv1 = new Vector2(uv0.x, 1 - (m_cached_GlyphInfo.y - m_padding - style_padding) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasHeight); // top left Vector2 uv2 = new Vector2((m_cached_GlyphInfo.x + m_padding + style_padding + m_cached_GlyphInfo.width) / m_fontAssetArray[m_fontIndex].fontInfo.AtlasWidth, uv0.y); // bottom right Vector2 uv3 = new Vector2(uv2.x, uv1.y); // top right m_uvs[0 + index_X4] = uv0; m_uvs[1 + index_X4] = uv1; m_uvs[2 + index_X4] = uv2; m_uvs[3 + index_X4] = uv3; #endregion Setup UVs } else { m_textInfo.characterInfo[m_characterCount].isVisible = false; m_lastVisibleCharacterOfLine = m_characterCount; m_textInfo.lineInfo[m_lineNumber].spaceCount += 1; m_textInfo.spaceCount += 1; } if (m_textInfo.characterInfo[m_characterCount].isVisible) { if (isStartOfNewLine) { isStartOfNewLine = false; m_firstVisibleCharacterOfLine = m_characterCount; } m_visibleCharacterCount += 1; m_lastVisibleCharacterOfLine = m_characterCount; } } else { // This is a Space, LineFeed or Carriage Return // Track # of spaces per line which is used for line justification. if (charCode == 9 || charCode == 32) { m_textInfo.lineInfo[m_lineNumber].spaceCount += 1; m_textInfo.spaceCount += 1; } } #endregion Handle Visible Characters // Store Rectangle positions for each Character. #region Store Character Data m_textInfo.characterInfo[m_characterCount].lineNumber = (short)m_lineNumber; m_textInfo.characterInfo[m_characterCount].pageNumber = (short)m_pageNumber; if (charCode != 10 && charCode != 13 && charCode != 8230 || m_textInfo.lineInfo[m_lineNumber].characterCount == 1) m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; #endregion Store Character Data // Check if text Exceeds the vertical bounds of the margin area. #region Check Vertical Bounds & Auto-Sizing if (m_maxAscender - descender + (m_alignmentPadding.w * 2 * m_fontScale) > marginHeight + 0.0001f && !m_textContainer.isDefaultHeight) { //Debug.Log((m_maxAscender - m_maxDescender) + " " + marginHeight); //Debug.Log("Character [" + (char)charCode + "] at Index: " + m_characterCount + " has exceeded the Height of the text container. Max Ascender: " + m_maxAscender + " Max Descender: " + m_maxDescender + " Margin Height: " + marginHeight + " Bottom Left: " + bottom_left.y); // Handle Line spacing adjustments #region Line Spacing Adjustments if (m_enableAutoSizing && m_lineSpacingDelta > m_lineSpacingMax && m_lineNumber > 0) { m_lineSpacingDelta -= 1; GenerateTextMesh(); return; } #endregion // Handle Text Auto-sizing resulting from text exceeding vertical bounds. #region Text Auto-Sizing (Text greater than vertical bounds) if (m_enableAutoSizing && m_fontSize > m_fontSizeMin) { m_maxFontSize = m_fontSize; m_fontSize -= Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize = (int)(Mathf.Max(m_fontSize, m_fontSizeMin) * 20 + 0.5f) / 20f; m_recursiveCount = 0; if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } #endregion Text Auto-Sizing // Handle Text Overflow #region Text Overflow switch (m_overflowMode) { case TextOverflowModes.Overflow: if (m_isMaskingEnabled) DisableMasking(); break; case TextOverflowModes.Ellipsis: if (m_isMaskingEnabled) DisableMasking(); if (m_lineNumber > 0) { m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index] = 8230; m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index + 1] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } else { m_char_buffer[0] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } case TextOverflowModes.Masking: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.ScrollRect: if (!m_isMaskingEnabled) EnableMasking(); break; case TextOverflowModes.Truncate: if (m_isMaskingEnabled) DisableMasking(); // TODO : Optimize if (m_lineNumber > 0) { m_char_buffer[m_textInfo.characterInfo[ellipsisIndex].index + 1] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } else { m_char_buffer[0] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } case TextOverflowModes.Page: if (m_isMaskingEnabled) DisableMasking(); // Ignore Page Break, Linefeed or carriage return if (charCode == 13 || charCode == 10) break; //Debug.Log("Character is [" + (char)charCode + "] with ASCII (" + charCode + ") on Page " + m_pageNumber); // Go back to previous line and re-layout i = RestoreWordWrappingState(ref m_SavedLineState); if (i == 0) { m_char_buffer[0] = (char)0; GenerateTextMesh(); m_isTextTruncated = true; return; } m_isNewPage = true; m_xAdvance = 0 + tag_Indent; m_lineOffset = 0; m_lineNumber += 1; m_pageNumber += 1; continue; } #endregion End Text Overflow } #endregion Check Vertical Bounds // Handle xAdvance & Tabulation Stops. Tab stops at every 25% of Font Size. #region XAdvance, Tabulation & Stops if (charCode == 9) { m_xAdvance += m_fontAsset.fontInfo.TabWidth * m_fontScale; // * m_fontAsset.TabSize; } else if (m_monoSpacing != 0) m_xAdvance += ((m_monoSpacing + m_cached_GlyphInfo.width / 2 + m_cached_GlyphInfo.xOffset + m_characterSpacing) * m_fontScale + m_cSpacing) * (1 - m_charWidthAdjDelta); else m_xAdvance += ((m_cached_GlyphInfo.xAdvance * xadvance_multiplier + m_characterSpacing) * m_fontScale + m_cSpacing) * (1 - m_charWidthAdjDelta); // Store xAdvance information m_textInfo.characterInfo[m_characterCount].xAdvance = m_xAdvance; #endregion Tabulation & Stops // Handle Carriage Return #region Carriage Return if (charCode == 13) { m_maxXAdvance = Mathf.Max(m_maxXAdvance, m_preferredWidth + m_xAdvance + (m_alignmentPadding.z * m_fontScale)); m_preferredWidth = 0; m_xAdvance = 0 + tag_Indent; } #endregion Carriage Return // Handle Line Spacing Adjustments + Word Wrapping & special case for last line. #region Check for Line Feed and Last Character if (charCode == 10 || m_characterCount == totalCharacterCount - 1) { //Debug.Log("Line # " + m_lineNumber + " Last Character of Line is [" + m_textInfo.characterInfo[m_characterCount].character + "]. Last Visible Character is [" + m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].character + "]."); // Handle Line Spacing Changes if (m_lineNumber > 0 && m_maxFontScale != 0 && m_lineHeight == 0 && m_maxFontScale != previousFontScale && !m_isNewPage) { float offsetDelta = (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_lineSpacing + m_paragraphSpacing + m_lineSpacingDelta) * m_maxFontScale; m_lineOffset += offsetDelta - lineOffsetDelta; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, offsetDelta - lineOffsetDelta); } m_isNewPage = false; // Calculate lineAscender & make sure if last character is superscript or subscript that we check that as well. float lineAscender = (m_fontAsset.fontInfo.Ascender + m_alignmentPadding.y) * m_maxFontScale - m_lineOffset; float lineAscender2 = (m_fontAsset.fontInfo.Ascender + m_alignmentPadding.y) * m_fontScale - m_lineOffset + m_baselineOffset; lineAscender = lineAscender > lineAscender2 ? lineAscender : lineAscender2; // Calculate lineDescender & make sure if last character is superscript or subscript that we check that as well. float lineDescender = (m_fontAsset.fontInfo.Descender + m_alignmentPadding.w) * m_maxFontScale - m_lineOffset; float lineDescender2 = (m_fontAsset.fontInfo.Descender + m_alignmentPadding.w) * m_fontScale - m_lineOffset + m_baselineOffset; lineDescender = lineDescender < lineDescender2 ? lineDescender : lineDescender2; // Update maxDescender and maxVisibleDescender m_maxDescender = m_maxDescender < lineDescender ? m_maxDescender : lineDescender; if (!isMaxVisibleDescenderSet) maxVisibleDescender = m_maxDescender; if (m_characterCount >= m_maxVisibleCharacters || m_lineNumber >= m_maxVisibleLines) isMaxVisibleDescenderSet = true; // Save Line Information m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex = m_firstCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].firstVisibleCharacterIndex = m_firstVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex = m_characterCount; m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex = m_lastVisibleCharacterOfLine >= m_firstVisibleCharacterOfLine ? m_lastVisibleCharacterOfLine : m_firstVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].characterCount = m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex - m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + 1; m_textInfo.lineInfo[m_lineNumber].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_firstVisibleCharacterOfLine].bottomLeft.x, lineDescender); m_textInfo.lineInfo[m_lineNumber].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].topRight.x, lineAscender); m_textInfo.lineInfo[m_lineNumber].lineLength = m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x - (m_padding * m_maxFontScale); m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance - m_characterSpacing * m_fontScale; m_firstCharacterOfLine = m_characterCount + 1; // Store PreferredWidth paying attention to linefeed and last character of text. if (charCode == 10 && m_characterCount != totalCharacterCount - 1) { m_maxXAdvance = Mathf.Max(m_maxXAdvance, m_preferredWidth + m_xAdvance + (m_alignmentPadding.z * m_fontScale)); m_preferredWidth = 0; } else m_preferredWidth = Mathf.Max(m_maxXAdvance, m_preferredWidth + m_xAdvance + (m_alignmentPadding.z * m_fontScale)); m_preferredHeight = m_maxAscender - m_maxDescender; //Debug.Log("LineInfo for line # " + (m_lineNumber) + " First character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[m_lineNumber].firstCharacterIndex + // " Last character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex + // " Last Visible character [" + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex].character + "] at index: " + m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex + // " Character Count of " + m_textInfo.lineInfo[m_lineNumber].characterCount + " Line Length of " + m_textInfo.lineInfo[m_lineNumber].lineLength /* + // //" Extent MinX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.x + " MinY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.min.y + // //" MaxX: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x + " MaxY: " + m_textInfo.lineInfo[m_lineNumber].lineExtents.max.y + // //" Line Ascender: " + lineAscender + " Line Descender: " + lineDescender + // //" Line Max: " + m_textInfo.characterInfo[m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex].topRight.x */ ); // Add new line if not last lines or character. if (charCode == 10) { // Store the state of the line before starting on the new line. SaveWordWrappingState(ref m_SavedLineState, i, m_characterCount); // Store the state of the last Character before the new line. SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); m_lineNumber += 1; isStartOfNewLine = true; // Check to make sure Array is large enough to hold a new line. if (m_lineNumber >= m_textInfo.lineInfo.Length) ResizeLineExtents(m_lineNumber); // Apply Line Spacing based on scale of the last character of the line. if (maxLineHeight == 0) { lineOffsetDelta = (m_fontAssetArray[m_fontIndex].fontInfo.LineHeight + m_paragraphSpacing + m_lineSpacing + m_lineSpacingDelta) * m_fontScale; m_lineOffset += lineOffsetDelta; } else m_lineOffset += (maxLineHeight + m_lineSpacing + m_paragraphSpacing) * baseScale; previousFontScale = m_fontScale; m_maxFontScale = 0; maxLineHeight = 0; m_xAdvance = 0 + tag_LineIndent + tag_Indent; ellipsisIndex = m_characterCount - 1; } } #endregion Check for Linefeed or Last Character // Store Rectangle positions for each Character. #region Save CharacterInfo for the current character. m_textInfo.characterInfo[m_characterCount].topLine = m_textInfo.characterInfo[m_characterCount].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Ascender + m_alignmentPadding.y) * m_fontScale; // Ascender m_textInfo.characterInfo[m_characterCount].bottomLine = m_textInfo.characterInfo[m_characterCount].baseLine + (m_fontAssetArray[m_fontIndex].fontInfo.Descender - m_alignmentPadding.w) * m_fontScale; // Descender m_textInfo.characterInfo[m_characterCount].padding = m_padding * m_fontScale; m_textInfo.characterInfo[m_characterCount].aspectRatio = m_cached_GlyphInfo.width / m_cached_GlyphInfo.height; m_textInfo.characterInfo[m_characterCount].scale = m_fontScale; //Debug.Log("Char [" + charCode + "] Ascender: " + m_textInfo.characterInfo[m_characterCount].topLine .ToString("f3") + " Descender: " + m_textInfo.characterInfo[m_characterCount].bottomLine.ToString("f3")); // Determine the bounds of the Mesh. if (m_textInfo.characterInfo[m_characterCount].isVisible) { m_meshExtents.min = new Vector2(Mathf.Min(m_meshExtents.min.x, m_textInfo.characterInfo[m_characterCount].bottomLeft.x), Mathf.Min(m_meshExtents.min.y, m_textInfo.characterInfo[m_characterCount].bottomLeft.y)); m_meshExtents.max = new Vector2(Mathf.Max(m_meshExtents.max.x, m_textInfo.characterInfo[m_characterCount].topRight.x), Mathf.Max(m_meshExtents.max.y, m_textInfo.characterInfo[m_characterCount].topLeft.y)); } // Save pageInfo Data if (charCode != 13 && charCode != 10 && m_pageNumber < 16) { m_textInfo.pageInfo[m_pageNumber].ascender = pageAscender; m_textInfo.pageInfo[m_pageNumber].descender = descender < m_textInfo.pageInfo[m_pageNumber].descender ? descender : m_textInfo.pageInfo[m_pageNumber].descender; //Debug.Log("Char [" + (char)charCode + "] with ASCII (" + charCode + ") on Page # " + m_pageNumber + " with Ascender: " + m_textInfo.pageInfo[m_pageNumber].ascender + ". Descender: " + m_textInfo.pageInfo[m_pageNumber].descender); if (m_pageNumber == 0 && m_characterCount == 0) m_textInfo.pageInfo[m_pageNumber].firstCharacterIndex = m_characterCount; else if (m_characterCount > 0 && m_pageNumber != m_textInfo.characterInfo[m_characterCount - 1].pageNumber) { m_textInfo.pageInfo[m_pageNumber - 1].lastCharacterIndex = m_characterCount - 1; m_textInfo.pageInfo[m_pageNumber].firstCharacterIndex = m_characterCount; } else if (m_characterCount == totalCharacterCount - 1) m_textInfo.pageInfo[m_pageNumber].lastCharacterIndex = m_characterCount; } #endregion Saving CharacterInfo // Save State of the last character #region Save Last Character State //if (m_overflowMode == TextOverflowModes.Ellipsis) //{ //Debug.Log("Char [" + (char)charCode + "] at Index " + (m_characterCount % 5)); // SaveWordWrappingState(ref m_SavedLastCharState, i); // m_SavedCharacterStates[m_characterCount % 5] = m_SavedLastCharState; //} #endregion End Last Character State // Save State of Mesh Creation for handling of Word Wrapping #region Save Word Wrapping State if (m_enableWordWrapping || m_overflowMode == TextOverflowModes.Truncate || m_overflowMode == TextOverflowModes.Ellipsis) { if (charCode == 9 || charCode == 32 && !m_isNonBreakingSpace) { // 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. SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); m_isCharacterWrappingEnabled = false; isFirstWord = false; //Debug.Log("Storing Word Wrapping Info at CharacterCount " + m_characterCount); } else if ((isFirstWord || m_isCharacterWrappingEnabled == true) && m_characterCount < totalCharacterCount - 1 && m_fontAsset.lineBreakingInfo.leadingCharacters.ContainsKey(charCode) == false && m_fontAsset.lineBreakingInfo.followingCharacters.ContainsKey(m_VisibleCharacters[m_characterCount + 1]) == false || isLastBreakingChar) //|| m_characterCount == m_firstVisibleCharacterOfLine) { //Debug.Log("Storing Character [" + (char)charCode + "] at Index: " + i); SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); } } #endregion Save Word Wrapping State m_characterCount += 1; } // Check Auto Sizing and increase font size to fill text container. #region Check Auto-Sizing (Upper Font Size Bounds) fontSizeDelta = m_maxFontSize - m_minFontSize; if ((!m_textContainer.isDefaultWidth || !m_textContainer.isDefaultHeight) && !m_isCharacterWrappingEnabled && (m_enableAutoSizing && fontSizeDelta > 0.051f && m_fontSize < m_fontSizeMax)) { m_minFontSize = m_fontSize; m_fontSize += Mathf.Max((m_maxFontSize - m_fontSize) / 2, 0.05f); m_fontSize = (int)(Mathf.Min(m_fontSize, m_fontSizeMax) * 20 + 0.5f) / 20f; if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } #endregion End Auto-sizing Check m_isCharacterWrappingEnabled = false; if (m_renderMode == TextRenderFlags.GetPreferredSizes) return; // DEBUG & PERFORMANCE CHECKS (0.006ms) //Debug.Log("Iteration Count: " + loopCountA + ". Final Point Size: " + m_fontSize); //for (int i = 0; i < m_lineNumber + 1; i++) //{ // Debug.Log("Line: " + (i + 1) + " # Char: " + m_textInfo.lineInfo[i].characterCount // + " Word Count: " + m_textInfo.lineInfo[i].wordCount // + " Space: " + m_textInfo.lineInfo[i].spaceCount // + " First: [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].firstCharacterIndex // + " Last [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].lastCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].lastCharacterIndex // + " Length: " + m_textInfo.lineInfo[i].lineLength // + " Line Extents: " + m_textInfo.lineInfo[i].lineExtents); //} // If there are no visible characters... no need to continue if (m_visibleCharacterCount == 0) { if (m_vertices != null) { Array.Clear(m_vertices, 0, m_vertices.Length); m_mesh.vertices = m_vertices; } return; } int last_vert_index = m_visibleCharacterCount * 4; // Partial clear of the vertices array to mark unused vertices as degenerate. Array.Clear(m_vertices, last_vert_index, m_vertices.Length - last_vert_index); // Handle Text Alignment #region Text Vertical Alignment switch (m_textAlignment) { // Top Vertically case TextAlignmentOptions.Top: case TextAlignmentOptions.TopLeft: case TextAlignmentOptions.TopJustified: case TextAlignmentOptions.TopRight: if (m_overflowMode != TextOverflowModes.Page) m_anchorOffset = corners[1] + new Vector3(0 + margins.x, 0 - m_maxAscender - margins.y, 0); else m_anchorOffset = corners[1] + new Vector3(0 + margins.x, 0 - m_textInfo.pageInfo[pageToDisplay].ascender - margins.y, 0); break; // Middle Vertically case TextAlignmentOptions.Left: case TextAlignmentOptions.Right: case TextAlignmentOptions.Center: case TextAlignmentOptions.Justified: if (m_overflowMode != TextOverflowModes.Page) m_anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_maxAscender + margins.y + maxVisibleDescender - margins.w) / 2, 0); else m_anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_textInfo.pageInfo[pageToDisplay].ascender + margins.y + m_textInfo.pageInfo[pageToDisplay].descender - margins.w) / 2, 0); break; // Bottom Vertically case TextAlignmentOptions.Bottom: case TextAlignmentOptions.BottomLeft: case TextAlignmentOptions.BottomRight: case TextAlignmentOptions.BottomJustified: if (m_overflowMode != TextOverflowModes.Page) m_anchorOffset = corners[0] + new Vector3(0 + margins.x, 0 - maxVisibleDescender + margins.w, 0); else m_anchorOffset = corners[0] + new Vector3(0 + margins.x, 0 - m_textInfo.pageInfo[pageToDisplay].descender + margins.w, 0); break; // Baseline Vertically case TextAlignmentOptions.BaselineLeft: case TextAlignmentOptions.BaselineRight: case TextAlignmentOptions.Baseline: m_anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0, 0); break; // Midline Vertically case TextAlignmentOptions.MidlineLeft: case TextAlignmentOptions.Midline: case TextAlignmentOptions.MidlineRight: case TextAlignmentOptions.MidlineJustified: m_anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_meshExtents.max.y + margins.y + m_meshExtents.min.y - margins.w) / 2, 0); break; } #endregion // Initialization for Second Pass Vector3 justificationOffset = Vector3.zero; Vector3 offset = Vector3.zero; int vert_index_X4 = 0; int wordCount = 0; int lineCount = 0; int lastLine = 0; bool isStartOfWord = false; int wordFirstChar = 0; int wordLastChar = 0; Color32 underlineColor = Color.white; Color32 strikethroughColor = Color.white; float underlineStartScale = 0; float underlineEndScale = 0; float underlineMaxScale = 0; float underlineBaseLine = Mathf.Infinity; int lastPage = 0; float strikethroughScale = 0; float strikethroughBaseline = 0; // Second Pass : Line Justification, UV Mapping, Character & Line Visibility & more. #region Handle Line Justification & UV Mapping & Character Visibility & More for (int i = 0; i < m_characterCount; i++) { int currentLine = m_textInfo.characterInfo[i].lineNumber; char currentCharacter = m_textInfo.characterInfo[i].character; TMP_LineInfo lineInfo = m_textInfo.lineInfo[currentLine]; TextAlignmentOptions lineAlignment = lineInfo.alignment; lineCount = currentLine + 1; // Process Line Justification #region Handle Line Justification switch (lineAlignment) { case TextAlignmentOptions.TopLeft: case TextAlignmentOptions.Left: case TextAlignmentOptions.BottomLeft: case TextAlignmentOptions.BaselineLeft: case TextAlignmentOptions.MidlineLeft: justificationOffset = new Vector3(0 + lineInfo.marginLeft, 0, 0); break; case TextAlignmentOptions.Top: case TextAlignmentOptions.Center: case TextAlignmentOptions.Bottom: case TextAlignmentOptions.Baseline: case TextAlignmentOptions.Midline: justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width / 2 - lineInfo.maxAdvance / 2, 0, 0); break; case TextAlignmentOptions.TopRight: case TextAlignmentOptions.Right: case TextAlignmentOptions.BottomRight: case TextAlignmentOptions.BaselineRight: case TextAlignmentOptions.MidlineRight: justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width - lineInfo.maxAdvance, 0, 0); break; case TextAlignmentOptions.TopJustified: case TextAlignmentOptions.Justified: case TextAlignmentOptions.BottomJustified: case TextAlignmentOptions.MidlineJustified: charCode = m_textInfo.characterInfo[i].character; char lastCharOfCurrentLine = m_textInfo.characterInfo[lineInfo.lastCharacterIndex].character; if (char.IsWhiteSpace(lastCharOfCurrentLine) && !char.IsControl(lastCharOfCurrentLine) && currentLine < m_lineNumber) { // All lines are justified accept the last one. //float gap = (corners[3].x - margins.z) - (corners[0].x + margins.x) - (lineInfo.maxAdvance); float gap = lineInfo.width - lineInfo.maxAdvance; if (currentLine != lastLine || i == 0) justificationOffset = new Vector3(lineInfo.marginLeft, 0, 0); else { if (charCode == 9 || charCode == 32) { justificationOffset += new Vector3(gap * (1 - m_wordWrappingRatios) / (lineInfo.spaceCount - 1), 0, 0); } else { justificationOffset += new Vector3(gap * m_wordWrappingRatios / (lineInfo.characterCount - lineInfo.spaceCount - 1), 0, 0); } } } else justificationOffset = new Vector3(lineInfo.marginLeft, 0, 0); // Keep last line left justified. //Debug.Log("Char [" + (char)charCode + "] Code: " + charCode + " Offset: " + justificationOffset + " # Spaces: " + lineInfo.spaceCount + " # Characters: " + lineInfo.characterCount + " CurrentLine: " + currentLine + " Last Line: " + lastLine + " i: " + i); break; } #endregion End Text Justification offset = m_anchorOffset + justificationOffset; // Handle UV2 mapping options and packing of scale information into UV2. #region Handling of UV2 mapping & Scale packing bool isCharacterVisible = m_textInfo.characterInfo[i].isVisible; if (isCharacterVisible) { Extents lineExtents = lineInfo.lineExtents; float uvOffset = (m_uvLineOffset * currentLine) % 1 + m_uvOffset.x; // Setup UV2 based on Character Mapping Options Selected #region Handle UV Mapping Options switch (m_horizontalMapping) { case TextureMappingOptions.Character: m_uv2s[vert_index_X4 + 0].x = 0 + m_uvOffset.x; m_uv2s[vert_index_X4 + 1].x = 0 + m_uvOffset.x; m_uv2s[vert_index_X4 + 2].x = 1 + m_uvOffset.x; m_uv2s[vert_index_X4 + 3].x = 1 + m_uvOffset.x; break; case TextureMappingOptions.Line: if (m_textAlignment != TextAlignmentOptions.Justified) { m_uv2s[vert_index_X4 + 0].x = (m_vertices[vert_index_X4 + 0].x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 1].x = (m_vertices[vert_index_X4 + 1].x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 2].x = (m_vertices[vert_index_X4 + 2].x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 3].x = (m_vertices[vert_index_X4 + 3].x - lineExtents.min.x) / (lineExtents.max.x - lineExtents.min.x) + uvOffset; break; } else // Special Case if Justified is used in Line Mode. { m_uv2s[vert_index_X4 + 0].x = (m_vertices[vert_index_X4 + 0].x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 1].x = (m_vertices[vert_index_X4 + 1].x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 2].x = (m_vertices[vert_index_X4 + 2].x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 3].x = (m_vertices[vert_index_X4 + 3].x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; break; } case TextureMappingOptions.Paragraph: m_uv2s[vert_index_X4 + 0].x = (m_vertices[vert_index_X4 + 0].x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 1].x = (m_vertices[vert_index_X4 + 1].x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 2].x = (m_vertices[vert_index_X4 + 2].x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; m_uv2s[vert_index_X4 + 3].x = (m_vertices[vert_index_X4 + 3].x + justificationOffset.x - m_meshExtents.min.x) / (m_meshExtents.max.x - m_meshExtents.min.x) + uvOffset; break; case TextureMappingOptions.MatchAspect: switch (m_verticalMapping) { case TextureMappingOptions.Character: m_uv2s[vert_index_X4 + 0].y = 0 + m_uvOffset.y; m_uv2s[vert_index_X4 + 1].y = 1 + m_uvOffset.y; m_uv2s[vert_index_X4 + 2].y = 0 + m_uvOffset.y; m_uv2s[vert_index_X4 + 3].y = 1 + m_uvOffset.y; break; case TextureMappingOptions.Line: m_uv2s[vert_index_X4 + 0].y = (m_vertices[vert_index_X4].y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + uvOffset; m_uv2s[vert_index_X4 + 1].y = (m_vertices[vert_index_X4 + 1].y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + uvOffset; m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; case TextureMappingOptions.Paragraph: m_uv2s[vert_index_X4 + 0].y = (m_vertices[vert_index_X4].y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + uvOffset; m_uv2s[vert_index_X4 + 1].y = (m_vertices[vert_index_X4 + 1].y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + uvOffset; m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; case TextureMappingOptions.MatchAspect: Debug.Log("ERROR: Cannot Match both Vertical & Horizontal."); break; } //float xDelta = 1 - (_uv2s[vert_index + 0].y * textMeshCharacterInfo[i].AspectRatio); // Left aligned float xDelta = (1 - ((m_uv2s[vert_index_X4 + 0].y + m_uv2s[vert_index_X4 + 1].y) * m_textInfo.characterInfo[i].aspectRatio)) / 2; // Center of Rectangle //float xDelta = 0; m_uv2s[vert_index_X4 + 0].x = (m_uv2s[vert_index_X4 + 0].y * m_textInfo.characterInfo[i].aspectRatio) + xDelta + uvOffset; m_uv2s[vert_index_X4 + 1].x = m_uv2s[vert_index_X4 + 0].x; m_uv2s[vert_index_X4 + 2].x = (m_uv2s[vert_index_X4 + 1].y * m_textInfo.characterInfo[i].aspectRatio) + xDelta + uvOffset; m_uv2s[vert_index_X4 + 3].x = m_uv2s[vert_index_X4 + 2].x; break; } switch (m_verticalMapping) { case TextureMappingOptions.Character: m_uv2s[vert_index_X4 + 0].y = 0 + m_uvOffset.y; m_uv2s[vert_index_X4 + 1].y = 1 + m_uvOffset.y; m_uv2s[vert_index_X4 + 2].y = 0 + m_uvOffset.y; m_uv2s[vert_index_X4 + 3].y = 1 + m_uvOffset.y; break; case TextureMappingOptions.Line: m_uv2s[vert_index_X4 + 0].y = (m_vertices[vert_index_X4].y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + m_uvOffset.y; m_uv2s[vert_index_X4 + 1].y = (m_vertices[vert_index_X4 + 1].y - lineExtents.min.y) / (lineExtents.max.y - lineExtents.min.y) + m_uvOffset.y; m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; case TextureMappingOptions.Paragraph: m_uv2s[vert_index_X4 + 0].y = (m_vertices[vert_index_X4].y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + m_uvOffset.y; m_uv2s[vert_index_X4 + 1].y = (m_vertices[vert_index_X4 + 1].y - m_meshExtents.min.y) / (m_meshExtents.max.y - m_meshExtents.min.y) + m_uvOffset.y; m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; case TextureMappingOptions.MatchAspect: //float yDelta = 1 - (_uv2s[vert_index + 2].x / textMeshCharacterInfo[i].AspectRatio); // Top Corner float yDelta = (1 - ((m_uv2s[vert_index_X4 + 0].x + m_uv2s[vert_index_X4 + 2].x) / m_textInfo.characterInfo[i].aspectRatio)) / 2; // Center of Rectangle //float yDelta = 0; m_uv2s[vert_index_X4 + 0].y = yDelta + (m_uv2s[vert_index_X4 + 0].x / m_textInfo.characterInfo[i].aspectRatio) + m_uvOffset.y; m_uv2s[vert_index_X4 + 1].y = yDelta + (m_uv2s[vert_index_X4 + 2].x / m_textInfo.characterInfo[i].aspectRatio) + m_uvOffset.y; m_uv2s[vert_index_X4 + 2].y = m_uv2s[vert_index_X4 + 0].y; m_uv2s[vert_index_X4 + 3].y = m_uv2s[vert_index_X4 + 1].y; break; } #endregion // Pack UV's so that we can pass Xscale needed for Shader to maintain 1:1 ratio. #region Pack Scale into UV2 float xScale = m_textInfo.characterInfo[i].scale * lossyScale * (1 - m_charWidthAdjDelta); if ((m_textInfo.characterInfo[i].style & FontStyles.Bold) == FontStyles.Bold) xScale *= -1; float x0 = m_uv2s[vert_index_X4 + 0].x; float y0 = m_uv2s[vert_index_X4 + 0].y; float x1 = m_uv2s[vert_index_X4 + 3].x; float y1 = m_uv2s[vert_index_X4 + 3].y; float dx = Mathf.Floor(x0); float dy = Mathf.Floor(y0); x0 = x0 - dx; x1 = x1 - dx; y0 = y0 - dy; y1 = y1 - dy; m_uv2s[vert_index_X4 + 0] = PackUV(x0, y0, xScale); m_uv2s[vert_index_X4 + 1] = PackUV(x0, y1, xScale); m_uv2s[vert_index_X4 + 2] = PackUV(x1, y0, xScale); m_uv2s[vert_index_X4 + 3] = PackUV(x1, y1, xScale); // Enables control of the visibility of Characters, Lines and Pages. if (i < m_maxVisibleCharacters && currentLine < m_maxVisibleLines && m_overflowMode != TextOverflowModes.Page) { m_vertices[vert_index_X4 + 0] += offset; m_vertices[vert_index_X4 + 1] += offset; m_vertices[vert_index_X4 + 2] += offset; m_vertices[vert_index_X4 + 3] += offset; } else if (i < m_maxVisibleCharacters && currentLine < m_maxVisibleLines && m_overflowMode == TextOverflowModes.Page && m_textInfo.characterInfo[i].pageNumber == pageToDisplay) { m_vertices[vert_index_X4 + 0] += offset; m_vertices[vert_index_X4 + 1] += offset; m_vertices[vert_index_X4 + 2] += offset; m_vertices[vert_index_X4 + 3] += offset; } else { m_vertices[vert_index_X4 + 0] *= 0; m_vertices[vert_index_X4 + 1] *= 0; m_vertices[vert_index_X4 + 2] *= 0; m_vertices[vert_index_X4 + 3] *= 0; } vert_index_X4 += 4; #endregion } #endregion // Apply Alignment and Justification Offset m_textInfo.characterInfo[i].bottomLeft += offset; m_textInfo.characterInfo[i].topLeft += offset; m_textInfo.characterInfo[i].topRight += offset; m_textInfo.characterInfo[i].bottomRight += offset; m_textInfo.characterInfo[i].topLine += offset.y; m_textInfo.characterInfo[i].bottomLine += offset.y; m_textInfo.characterInfo[i].baseLine += offset.y; // Store Max Ascender & Descender m_textInfo.lineInfo[currentLine].ascender = m_textInfo.characterInfo[i].topLine > m_textInfo.lineInfo[currentLine].ascender ? m_textInfo.characterInfo[i].topLine : m_textInfo.lineInfo[currentLine].ascender; m_textInfo.lineInfo[currentLine].descender = m_textInfo.characterInfo[i].bottomLine < m_textInfo.lineInfo[currentLine].descender ? m_textInfo.characterInfo[i].bottomLine : m_textInfo.lineInfo[currentLine].descender; // Need to recompute lineExtent to account for the offset from justification. #region Adjust lineExtents resulting from alignment offset if (currentLine != lastLine || i == m_characterCount - 1) { // Update the previous line's extents if (currentLine != lastLine) { m_textInfo.lineInfo[lastLine].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[lastLine].firstCharacterIndex].bottomLeft.x, m_textInfo.lineInfo[lastLine].descender); m_textInfo.lineInfo[lastLine].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[lastLine].lastVisibleCharacterIndex].topRight.x, m_textInfo.lineInfo[lastLine].ascender); } // Update the current line's extents if (i == m_characterCount - 1) { m_textInfo.lineInfo[currentLine].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[currentLine].firstCharacterIndex].bottomLeft.x, m_textInfo.lineInfo[currentLine].descender); m_textInfo.lineInfo[currentLine].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[currentLine].lastVisibleCharacterIndex].topRight.x, m_textInfo.lineInfo[currentLine].ascender); } } #endregion // Track Word Count per line and for the object #region Track Word Count if (char.IsLetterOrDigit(currentCharacter) || currentCharacter == 39 || currentCharacter == 8217) { if (isStartOfWord == false) { isStartOfWord = true; wordFirstChar = i; } // If last character is a word if (isStartOfWord && i == m_characterCount - 1) { wordLastChar = i; wordCount += 1; m_textInfo.lineInfo[currentLine].wordCount += 1; TMP_WordInfo wordInfo = new TMP_WordInfo(); wordInfo.firstCharacterIndex = wordFirstChar; wordInfo.lastCharacterIndex = wordLastChar; wordInfo.characterCount = wordLastChar - wordFirstChar + 1; m_textInfo.wordInfo.Add(wordInfo); } } else if (isStartOfWord || i == 0 && (char.IsPunctuation(currentCharacter) || char.IsWhiteSpace(currentCharacter) || i == m_characterCount - 1)) { wordLastChar = i == m_characterCount - 1 && char.IsLetterOrDigit(currentCharacter) ? i : i - 1; isStartOfWord = false; wordCount += 1; m_textInfo.lineInfo[currentLine].wordCount += 1; TMP_WordInfo wordInfo = new TMP_WordInfo(); wordInfo.firstCharacterIndex = wordFirstChar; wordInfo.lastCharacterIndex = wordLastChar; wordInfo.characterCount = wordLastChar - wordFirstChar + 1; m_textInfo.wordInfo.Add(wordInfo); //Debug.Log("Word #" + wordCount + " is [" + wordInfo.word + "] Start Index: " + wordInfo.firstCharacterIndex + " End Index: " + wordInfo.lastCharacterIndex); } #endregion // Setup & Handle Underline #region Underline // NOTE: Need to figure out how underline will be handled with multiple fonts and which font will be used for the underline. bool isUnderline = (m_textInfo.characterInfo[i].style & FontStyles.Underline) == FontStyles.Underline; if (isUnderline) { bool isUnderlineVisible = true; int currentPage = m_textInfo.characterInfo[i].pageNumber; if (i > m_maxVisibleCharacters || currentLine > m_maxVisibleLines || (m_overflowMode == TextOverflowModes.Page && currentPage + 1 != m_pageToDisplay)) isUnderlineVisible = false; // We only use the scale of visible characters. if (currentCharacter != 10 && currentCharacter != 13 && currentCharacter != 32) { underlineMaxScale = Mathf.Max(underlineMaxScale, m_textInfo.characterInfo[i].scale); underlineBaseLine = Mathf.Min(currentPage == lastPage ? underlineBaseLine : Mathf.Infinity, m_textInfo.characterInfo[i].baseLine + font.fontInfo.Underline * underlineMaxScale); lastPage = currentPage; // Need to track pages to ensure we reset baseline for the new pages. } if (beginUnderline == false && isUnderlineVisible == true && i <= lineInfo.lastVisibleCharacterIndex && currentCharacter != 10 && currentCharacter != 13) { if (i == lineInfo.lastVisibleCharacterIndex && currentCharacter == 32) { } else { beginUnderline = true; underlineStartScale = m_textInfo.characterInfo[i].scale; if (underlineMaxScale == 0) underlineMaxScale = underlineStartScale; underline_start = new Vector3(m_textInfo.characterInfo[i].bottomLeft.x, underlineBaseLine, 0); underlineColor = m_textInfo.characterInfo[i].color; } } // End Underline if text only contains one character. if (beginUnderline && m_characterCount == 1) { beginUnderline = false; underline_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[i].scale; DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index, underlineStartScale, underlineEndScale, underlineMaxScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = Mathf.Infinity; } else if (beginUnderline && (i == lineInfo.lastCharacterIndex || i >= lineInfo.lastVisibleCharacterIndex)) { // Terminate underline at previous visible character if space or carriage return. if (currentCharacter == 32 || currentCharacter == 10 || currentCharacter == 13) { int lastVisibleCharacterIndex = lineInfo.lastVisibleCharacterIndex; underline_end = new Vector3(m_textInfo.characterInfo[lastVisibleCharacterIndex].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[lastVisibleCharacterIndex].scale; } else { // End underline if last character of the line. underline_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[i].scale; } beginUnderline = false; DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index, underlineStartScale, underlineEndScale, underlineMaxScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = Mathf.Infinity; } else if (beginUnderline && !isUnderlineVisible) { beginUnderline = false; underline_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[i - 1].scale; DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index, underlineStartScale, underlineEndScale, underlineMaxScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = Mathf.Infinity; } } else { // End Underline if (beginUnderline == true) { beginUnderline = false; underline_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, underlineBaseLine, 0); underlineEndScale = m_textInfo.characterInfo[i - 1].scale; DrawUnderlineMesh(underline_start, underline_end, ref last_vert_index, underlineStartScale, underlineEndScale, underlineMaxScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = Mathf.Infinity; } } #endregion // Setup & Handle Strikethrough #region Strikethrough // NOTE: Need to figure out how underline will be handled with multiple fonts and which font will be used for the underline. bool isStrikethrough = (m_textInfo.characterInfo[i].style & FontStyles.Strikethrough) == FontStyles.Strikethrough; if (isStrikethrough) { bool isStrikeThroughVisible = true; if (i > m_maxVisibleCharacters || currentLine > m_maxVisibleLines || (m_overflowMode == TextOverflowModes.Page && m_textInfo.characterInfo[i].pageNumber + 1 != m_pageToDisplay)) isStrikeThroughVisible = false; if (beginStrikethrough == false && isStrikeThroughVisible && i <= lineInfo.lastVisibleCharacterIndex && currentCharacter != 10 && currentCharacter != 13) { if (i == lineInfo.lastVisibleCharacterIndex && currentCharacter == 32) { } else { beginStrikethrough = true; strikethroughScale = m_textInfo.characterInfo[i].scale; strikethrough_start = new Vector3(m_textInfo.characterInfo[i].bottomLeft.x, m_textInfo.characterInfo[i].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2.75f * strikethroughScale, 0); strikethroughColor = m_textInfo.characterInfo[i].color; strikethroughBaseline = m_textInfo.characterInfo[i].baseLine; //Debug.Log("Char [" + currentCharacter + "] Start Strikethrough POS: " + strikethrough_start); } } // End Strikethrough if text only contains one character. if (beginStrikethrough && m_characterCount == 1) { beginStrikethrough = false; strikethrough_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); } else if (beginStrikethrough && i == lineInfo.lastCharacterIndex) { // Terminate Strikethrough at previous visible character if space or carriage return. if (currentCharacter == 32 || currentCharacter == 10 || currentCharacter == 13) { int lastVisibleCharacterIndex = lineInfo.lastVisibleCharacterIndex; strikethrough_end = new Vector3(m_textInfo.characterInfo[lastVisibleCharacterIndex].topRight.x, m_textInfo.characterInfo[lastVisibleCharacterIndex].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); } else { // Terminate Strikethrough at last character of line. strikethrough_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); } beginStrikethrough = false; DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); } else if (beginStrikethrough && i < m_characterCount && (m_textInfo.characterInfo[i + 1].scale != strikethroughScale || !TMP_Math.Equals(m_textInfo.characterInfo[i + 1].baseLine + offset.y, strikethroughBaseline))) { // Terminate Strikethrough if scale changes. beginStrikethrough = false; int lastVisibleCharacterIndex = lineInfo.lastVisibleCharacterIndex; if (i > lastVisibleCharacterIndex) strikethrough_end = new Vector3(m_textInfo.characterInfo[lastVisibleCharacterIndex].topRight.x, m_textInfo.characterInfo[lastVisibleCharacterIndex].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); else strikethrough_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); //Debug.Log("Char [" + currentCharacter + "] at Index: " + i + " End Strikethrough POS: " + strikethrough_end + " Baseline: " + m_textInfo.characterInfo[i].baseLine.ToString("f3")); } else if (beginStrikethrough && !isStrikeThroughVisible) { // Terminate Strikethrough if character is not visible. beginStrikethrough = false; strikethrough_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, m_textInfo.characterInfo[i - 1].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); } } else { // End Underline if (beginStrikethrough == true) { beginStrikethrough = false; strikethrough_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, m_textInfo.characterInfo[i - 1].baseLine + (font.fontInfo.Ascender + font.fontInfo.Descender) / 2 * m_fontScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, strikethroughColor); } } #endregion lastLine = currentLine; } #endregion // METRICS ABOUT THE TEXT OBJECT m_textInfo.characterCount = (short)m_characterCount; m_textInfo.lineCount = (short)lineCount; m_textInfo.wordCount = wordCount != 0 && m_characterCount > 0 ? (short)wordCount : (short)1; m_textInfo.pageCount = m_pageNumber + 1; // Store Mesh information in MeshInfo m_textInfo.meshInfo.vertices = m_vertices; m_textInfo.meshInfo.uv0s = m_uvs; m_textInfo.meshInfo.uv2s = m_uv2s; m_textInfo.meshInfo.vertexColors = m_vertColors; // If Advanced Layout Component is present, don't upload the mesh. if (m_renderMode == TextRenderFlags.Render) { //Debug.Log("Uploading Mesh normally."); // Upload Mesh Data m_mesh.MarkDynamic(); m_mesh.vertices = m_vertices; m_mesh.uv = m_uvs; m_mesh.uv2 = m_uv2s; m_mesh.colors32 = m_vertColors; //m_maskOffset = new Vector4(m_mesh.bounds.center.x, m_mesh.bounds.center.y, m_mesh.bounds.size.x, m_mesh.bounds.size.y); } // Setting Mesh Bounds manually is more efficient. //m_mesh.bounds = new Bounds(new Vector3((m_meshExtents.max.x + m_meshExtents.min.x) / 2, (m_meshExtents.max.y + m_meshExtents.min.y) / 2, 0), new Vector3(m_meshExtents.max.x - m_meshExtents.min.x, m_meshExtents.max.y - m_meshExtents.min.y, 0)); m_mesh.RecalculateBounds(); //m_isCharacterWrappingEnabled = false; //Debug.Log("Corners [0] " + corners[0] + " [1] " + corners[1] + " [2] " + corners[2] + " [3] " + corners[3]); //Debug.Log("Done rendering."); // Option to re-size the Text Container to match the text. if ((m_textContainer.isDefaultWidth || m_textContainer.isDefaultHeight) && m_textContainer.isAutoFitting) { //Debug.Log("Auto-fitting Text. Default Width:" + m_textContainer.isDefaultWidth + " Default Height:" + m_textContainer.isDefaultHeight); if (m_textContainer.isDefaultWidth) { m_textContainer.width = m_preferredWidth + margins.x + margins.z; //Debug.Log("Text Container's Width adjusted to fit text."); } if (m_textContainer.isDefaultHeight) { //m_textContainer.height = m_maxAscender + margins.y - m_maxDescender + margins.w + m_alignmentPadding.y * m_fontScale * 2; m_textContainer.height = m_preferredHeight + margins.y + margins.w; //Debug.Log("Text Container's Height adjusted to fit text."); } //Debug.Log("Auto-fitting Text. Default Width:" + m_textContainer.width + " Default Height:" + m_textContainer.height); if (m_isMaskingEnabled) isMaskUpdateRequired = true; // Since changing the size of the Text Container with set the .hasChanged flag, the text object with be regenerated. GenerateTextMesh(); return; } //for (int i = 0; i < m_lineNumber + 1; i++) //{ // Debug.Log("Line: " + (i + 1) + " # Char: " + m_textInfo.lineInfo[i].characterCount // + " Word Count: " + m_textInfo.lineInfo[i].wordCount // + " Space: " + m_textInfo.lineInfo[i].spaceCount // + " First: [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].firstCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].firstCharacterIndex // + " Last [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].lastCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].lastCharacterIndex // + " Last visible [" + m_textInfo.characterInfo[m_textInfo.lineInfo[i].lastVisibleCharacterIndex].character + "] at Index: " + m_textInfo.lineInfo[i].lastVisibleCharacterIndex // + " Length: " + m_textInfo.lineInfo[i].lineLength // + " Line Extents: " + m_textInfo.lineInfo[i].lineExtents); //} //Profiler.EndSample(); //m_StopWatch.Stop(); //Debug.Log("Preferred Width: " + m_preferredWidth + " Height: " + m_preferredHeight); // + " Margin Width: " + marginWidth + " xAdvance Total: " + totalxAdvance); //Debug.Log("Done Rendering Text Object. Total Character Count is " + m_textInfo.characterCount + ". Preferred Width: " + m_preferredWidth + " Height: " + m_preferredHeight); //Debug.Log("TimeElapsed is:" + (m_StopWatch.ElapsedTicks / 10000f).ToString("f4")); //m_StopWatch.Reset(); }