//#define PROFILE_ON //#define PROFILE_PHASES_ON using UnityEngine; using System; using System.Collections.Generic; using UnityEngine.UI; #pragma warning disable 0414 // Disabled a few warnings related to serialized variables not used in this script but used in the editor. #pragma warning disable 0618 // Disabled warning due to SetVertices being deprecated until new release with SetMesh() is available. namespace TMPro { public partial class TextMeshProUGUI { [SerializeField] private bool m_hasFontAssetChanged = false; // Used to track when font properties have changed. [SerializeField] protected TMP_SubMeshUI[] m_subTextObjects = new TMP_SubMeshUI[8]; private float m_previousLossyScaleY = -1; // Used for Tracking lossy scale changes in the transform; private Vector3[] m_RectTransformCorners = new Vector3[4]; private CanvasRenderer m_canvasRenderer; private Canvas m_canvas; private bool m_isFirstAllocation; // Flag to determine if this is the first allocation of the buffers. private int m_max_characters = 8; // Determines the initial allocation and size of the character array / buffer. //private int m_max_numberOfLines = 4; // Determines the initial allocation and maximum number of lines of text. // MASKING RELATED PROPERTIES private bool m_isMaskingEnabled; // This property is now obsolete and used for compatibility with previous releases (prior to release 0.1.54). [SerializeField] private Material m_baseMaterial; private bool m_isScrollRegionSet; //private Mask m_mask; private int m_stencilID = 0; [SerializeField] private Vector4 m_maskOffset; // Matrix used to animated Env Map private Matrix4x4 m_EnvMapMatrix = new Matrix4x4(); //private bool m_isEnabled; [NonSerialized] private bool m_isRegisteredForEvents; // DEBUG Variables //private System.Diagnostics.Stopwatch m_StopWatch; //private int frame = 0; //private int m_recursiveCount = 0; private int m_recursiveCountA = 0; private int loopCountA = 0; //private int loopCountB = 0; //private int loopCountC = 0; //private int loopCountD = 0; //private int loopCountE = 0; //[SerializeField] //private new Material m_MaskMaterial; protected override void Awake() { //Debug.Log("***** Awake() *****"); #if UNITY_EDITOR // Special handling for TMP Settings and importing Essential Resources if (TMP_Settings.instance == null) { if (m_isWaitingOnResourceLoad == false) TMPro_EventManager.RESOURCE_LOAD_EVENT.Add(ON_RESOURCES_LOADED); m_isWaitingOnResourceLoad = true; return; } #endif // Cache Reference to the Canvas m_canvas = this.canvas; m_isOrthographic = true; // Cache Reference to RectTransform. m_rectTransform = gameObject.GetComponent(); if (m_rectTransform == null) m_rectTransform = gameObject.AddComponent(); // Cache a reference to the CanvasRenderer. m_canvasRenderer = GetComponent(); if (m_canvasRenderer == null) m_canvasRenderer = gameObject.AddComponent (); if (m_mesh == null) { //Debug.Log("Creating new mesh."); m_mesh = new Mesh(); m_mesh.hideFlags = HideFlags.HideAndDontSave; //m_mesh.bounds = new Bounds(transform.position, new Vector3(1000, 1000, 0)); } // Load TMP Settings for new text object instances. LoadDefaultSettings(); // Load the font asset and assign material to renderer. LoadFontAsset(); // Load Default TMP StyleSheet TMP_StyleSheet.LoadDefaultStyleSheet(); // Allocate our initial buffers. if (m_char_buffer == null) m_char_buffer = new int[m_max_characters]; m_cached_TextElement = new TMP_Glyph(); m_isFirstAllocation = true; if (m_textInfo == null) m_textInfo = new TMP_TextInfo(this); // Check if we have a font asset assigned. Return if we don't because no one likes to see purple squares on screen. if (m_fontAsset == null) { Debug.LogWarning("Please assign a Font Asset to this " + transform.name + " gameobject.", this); return; } // Check to make sure Sub Text Objects are tracked correctly in the event a Prefab is used. TMP_SubMeshUI[] subTextObjects = GetComponentsInChildren(); if (subTextObjects.Length > 0) { for (int i = 0; i < subTextObjects.Length; i++) m_subTextObjects[i + 1] = subTextObjects[i]; } // Set flags to ensure our text is parsed and redrawn. m_isInputParsingRequired = true; m_havePropertiesChanged = true; m_isCalculateSizeRequired = true; m_isAwake = true; } protected override void OnEnable() { //Debug.Log("*** OnEnable() ***", this); // Return if Awake() has not been called on the text object. if (m_isAwake == false) return; if (!m_isRegisteredForEvents) { //Debug.Log("Registering for Events."); #if UNITY_EDITOR // Register Callbacks for various events. TMPro_EventManager.MATERIAL_PROPERTY_EVENT.Add(ON_MATERIAL_PROPERTY_CHANGED); TMPro_EventManager.FONT_PROPERTY_EVENT.Add(ON_FONT_PROPERTY_CHANGED); TMPro_EventManager.TEXTMESHPRO_UGUI_PROPERTY_EVENT.Add(ON_TEXTMESHPRO_UGUI_PROPERTY_CHANGED); TMPro_EventManager.DRAG_AND_DROP_MATERIAL_EVENT.Add(ON_DRAG_AND_DROP_MATERIAL); TMPro_EventManager.TEXT_STYLE_PROPERTY_EVENT.Add(ON_TEXT_STYLE_CHANGED); TMPro_EventManager.COLOR_GRADIENT_PROPERTY_EVENT.Add(ON_COLOR_GRADIENT_CHANGED); TMPro_EventManager.TMP_SETTINGS_PROPERTY_EVENT.Add(ON_TMP_SETTINGS_CHANGED); #endif m_isRegisteredForEvents = true; } // Cache Reference to the Canvas m_canvas = GetCanvas(); SetActiveSubMeshes(true); // Register Graphic Component to receive event triggers GraphicRegistry.RegisterGraphicForCanvas(m_canvas, this); ComputeMarginSize(); m_verticesAlreadyDirty = false; m_layoutAlreadyDirty = false; m_ShouldRecalculateStencil = true; m_isInputParsingRequired = true; SetAllDirty(); RecalculateClipping(); } protected override void OnDisable() { //base.OnDisable(); //Debug.Log("***** OnDisable() *****"); //for " + this.name + " with ID: " + this.GetInstanceID() + " has been called."); // Return if Awake() has not been called on the text object. if (m_isAwake == false) return; if (m_MaskMaterial != null) { TMP_MaterialManager.ReleaseStencilMaterial(m_MaskMaterial); m_MaskMaterial = null; } // UnRegister Graphic Component GraphicRegistry.UnregisterGraphicForCanvas(m_canvas, this); CanvasUpdateRegistry.UnRegisterCanvasElementForRebuild((ICanvasElement)this); if (m_canvasRenderer != null) m_canvasRenderer.Clear(); SetActiveSubMeshes(false); LayoutRebuilder.MarkLayoutForRebuild(m_rectTransform); RecalculateClipping(); } protected override void OnDestroy() { //base.OnDestroy(); //Debug.Log("***** OnDestroy() *****"); // UnRegister Graphic Component GraphicRegistry.UnregisterGraphicForCanvas(m_canvas, this); // Clean up remaining mesh if (m_mesh != null) DestroyImmediate(m_mesh); // Clean up mask material if (m_MaskMaterial != null) { TMP_MaterialManager.ReleaseStencilMaterial(m_MaskMaterial); m_MaskMaterial = null; } #if UNITY_EDITOR // Unregister the event this object was listening to TMPro_EventManager.MATERIAL_PROPERTY_EVENT.Remove(ON_MATERIAL_PROPERTY_CHANGED); TMPro_EventManager.FONT_PROPERTY_EVENT.Remove(ON_FONT_PROPERTY_CHANGED); TMPro_EventManager.TEXTMESHPRO_UGUI_PROPERTY_EVENT.Remove(ON_TEXTMESHPRO_UGUI_PROPERTY_CHANGED); TMPro_EventManager.DRAG_AND_DROP_MATERIAL_EVENT.Remove(ON_DRAG_AND_DROP_MATERIAL); TMPro_EventManager.TEXT_STYLE_PROPERTY_EVENT.Remove(ON_TEXT_STYLE_CHANGED); TMPro_EventManager.COLOR_GRADIENT_PROPERTY_EVENT.Remove(ON_COLOR_GRADIENT_CHANGED); TMPro_EventManager.TMP_SETTINGS_PROPERTY_EVENT.Remove(ON_TMP_SETTINGS_CHANGED); TMPro_EventManager.RESOURCE_LOAD_EVENT.Remove(ON_RESOURCES_LOADED); #endif m_isRegisteredForEvents = false; } #if UNITY_EDITOR protected override void Reset() { //Debug.Log("***** Reset() *****"); //has been called."); // Return if Awake() has not been called on the text object. if (m_isAwake == false) return; LoadDefaultSettings(); LoadFontAsset(); m_isInputParsingRequired = true; m_havePropertiesChanged = true; } protected override void OnValidate() { //Debug.Log("***** OnValidate() ***** Frame:" + Time.frameCount); // ID " + GetInstanceID()); // New Material [" + m_sharedMaterial.name + "] with ID " + m_sharedMaterial.GetInstanceID() + ". Base Material is [" + m_baseMaterial.name + "] with ID " + m_baseMaterial.GetInstanceID() + ". Previous Base Material is [" + (m_lastBaseMaterial == null ? "Null" : m_lastBaseMaterial.name) + "]."); // Return if Awake() has not been called on the text object. if (m_isAwake == false) return; // Handle Font Asset changes in the inspector. if (m_fontAsset == null || m_hasFontAssetChanged) { LoadFontAsset(); m_isCalculateSizeRequired = true; m_hasFontAssetChanged = false; } if (m_canvasRenderer == null || m_canvasRenderer.GetMaterial() == null || m_canvasRenderer.GetMaterial().GetTexture(ShaderUtilities.ID_MainTex) == null || m_fontAsset == null || m_fontAsset.atlas.GetInstanceID() != m_canvasRenderer.GetMaterial().GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID()) { LoadFontAsset(); m_isCalculateSizeRequired = true; m_hasFontAssetChanged = false; } m_padding = GetPaddingForMaterial(); //ComputeMarginSize(); m_isInputParsingRequired = true; m_inputSource = TextInputSources.Text; m_havePropertiesChanged = true; m_isCalculateSizeRequired = true; m_isPreferredWidthDirty = true; m_isPreferredHeightDirty = true; SetAllDirty(); } // Event received when TMP resources have been loaded. void ON_RESOURCES_LOADED() { TMPro_EventManager.RESOURCE_LOAD_EVENT.Remove(ON_RESOURCES_LOADED); Awake(); OnEnable(); } // Event received when custom material editor properties are changed. void ON_MATERIAL_PROPERTY_CHANGED(bool isChanged, Material mat) { //Debug.Log("ON_MATERIAL_PROPERTY_CHANGED event received."); // Targeted Material is: " + mat.name + " m_sharedMaterial: " + m_sharedMaterial.name + " with ID:" + m_sharedMaterial.GetInstanceID() + " m_renderer.sharedMaterial: " + m_canvasRenderer.GetMaterial() + " Masking Material:" + m_MaskMaterial.GetInstanceID()); ShaderUtilities.GetShaderPropertyIDs(); // Initialize ShaderUtilities and get shader property IDs. int materialID = mat.GetInstanceID(); int sharedMaterialID = m_sharedMaterial.GetInstanceID(); int maskingMaterialID = m_MaskMaterial == null ? 0 : m_MaskMaterial.GetInstanceID(); if (m_canvasRenderer == null || m_canvasRenderer.GetMaterial() == null) { if (m_canvasRenderer == null) return; if (m_fontAsset != null) { m_canvasRenderer.SetMaterial(m_fontAsset.material, m_sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex)); //Debug.LogWarning("No Material was assigned to " + name + ". " + m_fontAsset.material.name + " was assigned."); } else Debug.LogWarning("No Font Asset assigned to " + name + ". Please assign a Font Asset.", this); } if (m_canvasRenderer.GetMaterial() != m_sharedMaterial && m_fontAsset == null) // || m_renderer.sharedMaterials.Contains(mat)) { //Debug.Log("ON_MATERIAL_PROPERTY_CHANGED Called on Target ID: " + GetInstanceID() + ". Previous Material:" + m_sharedMaterial + " New Material:" + m_uiRenderer.GetMaterial()); // on Object ID:" + GetInstanceID() + ". m_sharedMaterial: " + m_sharedMaterial.name + " m_renderer.sharedMaterial: " + m_renderer.sharedMaterial.name); m_sharedMaterial = m_canvasRenderer.GetMaterial(); } // Make sure material properties are synchronized between the assigned material and masking material. if (m_MaskMaterial != null) { UnityEditor.Undo.RecordObject(m_MaskMaterial, "Material Property Changes"); UnityEditor.Undo.RecordObject(m_sharedMaterial, "Material Property Changes"); if (materialID == sharedMaterialID) { //Debug.Log("Copy base material properties to masking material if not null."); float stencilID = m_MaskMaterial.GetFloat(ShaderUtilities.ID_StencilID); float stencilComp = m_MaskMaterial.GetFloat(ShaderUtilities.ID_StencilComp); //float stencilOp = m_MaskMaterial.GetFloat(ShaderUtilities.ID_StencilOp); //float stencilRead = m_MaskMaterial.GetFloat(ShaderUtilities.ID_StencilReadMask); //float stencilWrite = m_MaskMaterial.GetFloat(ShaderUtilities.ID_StencilWriteMask); m_MaskMaterial.CopyPropertiesFromMaterial(mat); m_MaskMaterial.shaderKeywords = mat.shaderKeywords; m_MaskMaterial.SetFloat(ShaderUtilities.ID_StencilID, stencilID); m_MaskMaterial.SetFloat(ShaderUtilities.ID_StencilComp, stencilComp); //m_MaskMaterial.SetFloat(ShaderUtilities.ID_StencilOp, stencilOp); //m_MaskMaterial.SetFloat(ShaderUtilities.ID_StencilReadMask, stencilID); //m_MaskMaterial.SetFloat(ShaderUtilities.ID_StencilWriteMask, 0); } else if (materialID == maskingMaterialID) { // Update the padding GetPaddingForMaterial(mat); m_sharedMaterial.CopyPropertiesFromMaterial(mat); m_sharedMaterial.shaderKeywords = mat.shaderKeywords; m_sharedMaterial.SetFloat(ShaderUtilities.ID_StencilID, 0); m_sharedMaterial.SetFloat(ShaderUtilities.ID_StencilComp, 8); //m_sharedMaterial.SetFloat(ShaderUtilities.ID_StencilOp, 0); //m_sharedMaterial.SetFloat(ShaderUtilities.ID_StencilReadMask, 255); //m_sharedMaterial.SetFloat(ShaderUtilities.ID_StencilWriteMask, 255); } } m_padding = GetPaddingForMaterial(); m_havePropertiesChanged = true; SetVerticesDirty(); //SetMaterialDirty(); } // Event received when font asset properties are changed in Font Inspector void ON_FONT_PROPERTY_CHANGED(bool isChanged, TMP_FontAsset font) { if (MaterialReference.Contains(m_materialReferences, font)) { //Debug.Log("ON_FONT_PROPERTY_CHANGED event received."); m_isInputParsingRequired = true; m_havePropertiesChanged = true; SetLayoutDirty(); SetVerticesDirty(); } } // Event received when UNDO / REDO Event alters the properties of the object. void ON_TEXTMESHPRO_UGUI_PROPERTY_CHANGED(bool isChanged, TextMeshProUGUI obj) { Debug.Log("Event Received by " + obj); if (obj == this) { //Debug.Log("Undo / Redo Event Received by Object ID:" + GetInstanceID()); m_havePropertiesChanged = true; m_isInputParsingRequired = true; SetVerticesDirty(); } } // Event to Track Material Changed resulting from Drag-n-drop. void ON_DRAG_AND_DROP_MATERIAL(GameObject obj, Material currentMaterial, Material newMaterial) { //Debug.Log("Drag-n-Drop Event - Receiving Object ID " + GetInstanceID() + ". Sender ID " + obj.GetInstanceID()); // + ". Prefab Parent is " + UnityEditor.PrefabUtility.GetPrefabParent(gameObject).GetInstanceID()); // + ". New Material is " + newMaterial.name + " with ID " + newMaterial.GetInstanceID() + ". Base Material is " + m_baseMaterial.name + " with ID " + m_baseMaterial.GetInstanceID()); // Check if event applies to this current object if (obj == gameObject || UnityEditor.PrefabUtility.GetPrefabParent(gameObject) == obj) { UnityEditor.Undo.RecordObject(this, "Material Assignment"); UnityEditor.Undo.RecordObject(m_canvasRenderer, "Material Assignment"); m_sharedMaterial = newMaterial; m_padding = GetPaddingForMaterial(); m_havePropertiesChanged = true; SetVerticesDirty(); SetMaterialDirty(); } } // Event received when Text Styles are changed. void ON_TEXT_STYLE_CHANGED(bool isChanged) { m_havePropertiesChanged = true; m_isInputParsingRequired = true; SetVerticesDirty(); } /// /// Event received when a Color Gradient Preset is modified. /// /// void ON_COLOR_GRADIENT_CHANGED(TMP_ColorGradient gradient) { if (m_fontColorGradientPreset != null && gradient.GetInstanceID() == m_fontColorGradientPreset.GetInstanceID()) { m_havePropertiesChanged = true; SetVerticesDirty(); } } /// /// Event received when the TMP Settings are changed. /// void ON_TMP_SETTINGS_CHANGED() { m_defaultSpriteAsset = null; m_havePropertiesChanged = true; m_isInputParsingRequired = true; SetAllDirty(); } #endif // Function which loads either the default font or a newly assigned font asset. This function also assigned the appropriate material to the renderer. protected override void LoadFontAsset() { //Debug.Log("***** LoadFontAsset() *****"); //TextMeshPro LoadFontAsset() has been called."); // Current Font Asset is " + (font != null ? font.name: "Null") ); ShaderUtilities.GetShaderPropertyIDs(); // Initialize & Get shader property IDs. if (m_fontAsset == null) { if (TMP_Settings.defaultFontAsset != null) m_fontAsset = TMP_Settings.defaultFontAsset; else m_fontAsset = Resources.Load("Fonts & Materials/LiberationSans SDF", typeof(TMP_FontAsset)) as TMP_FontAsset; if (m_fontAsset == null) { Debug.LogWarning("The LiberationSans SDF Font Asset was not found. There is no Font Asset assigned to " + gameObject.name + ".", this); return; } if (m_fontAsset.characterDictionary == null) { Debug.Log("Dictionary is Null!"); } m_sharedMaterial = m_fontAsset.material; } else { // Read font definition if needed. if (m_fontAsset.characterDictionary == null) m_fontAsset.ReadFontDefinition(); // Added for compatibility with previous releases. if (m_sharedMaterial == null && m_baseMaterial != null) { m_sharedMaterial = m_baseMaterial; m_baseMaterial = null; } // If font atlas texture doesn't match the assigned material font atlas, switch back to default material specified in the Font Asset. if (m_sharedMaterial == null || m_sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex) == null || m_fontAsset.atlas.GetInstanceID() != m_sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID()) { if (m_fontAsset.material == null) Debug.LogWarning("The Font Atlas Texture of the Font Asset " + m_fontAsset.name + " assigned to " + gameObject.name + " is missing.", this); else m_sharedMaterial = m_fontAsset.material; } } // Find and cache Underline & Ellipsis characters. GetSpecialCharacters(m_fontAsset); m_padding = GetPaddingForMaterial(); SetMaterialDirty(); } /// /// Method to retrieve the parent Canvas. /// private Canvas GetCanvas() { Canvas canvas = null; var list = TMP_ListPool.Get(); gameObject.GetComponentsInParent(false, list); if (list.Count > 0) { // Find the first active and enabled canvas. for (int i = 0; i < list.Count; ++i) { if (list[i].isActiveAndEnabled) { canvas = list[i]; break; } } } TMP_ListPool.Release(list); return canvas; } /// /// Method used when animating the Env Map on the material. /// void UpdateEnvMapMatrix() { if (!m_sharedMaterial.HasProperty(ShaderUtilities.ID_EnvMap) || m_sharedMaterial.GetTexture(ShaderUtilities.ID_EnvMap) == null) return; //Debug.Log("Updating Env Matrix..."); Vector3 rotation = m_sharedMaterial.GetVector(ShaderUtilities.ID_EnvMatrixRotation); m_EnvMapMatrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(rotation), Vector3.one); m_sharedMaterial.SetMatrix(ShaderUtilities.ID_EnvMatrix, m_EnvMapMatrix); } // Enable Masking in the Shader void EnableMasking() { if (m_fontMaterial == null) { m_fontMaterial = CreateMaterialInstance(m_sharedMaterial); m_canvasRenderer.SetMaterial(m_fontMaterial, m_sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex)); } m_sharedMaterial = m_fontMaterial; if (m_sharedMaterial.HasProperty(ShaderUtilities.ID_ClipRect)) { m_sharedMaterial.EnableKeyword(ShaderUtilities.Keyword_MASK_SOFT); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_HARD); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_TEX); UpdateMask(); // Update Masking Coordinates } m_isMaskingEnabled = true; //m_uiRenderer.SetMaterial(m_sharedMaterial, null); //m_padding = ShaderUtilities.GetPadding(m_sharedMaterial, m_enableExtraPadding, m_isUsingBold); //m_alignmentPadding = ShaderUtilities.GetFontExtent(m_sharedMaterial); /* Material mat = m_uiRenderer.GetMaterial(); if (mat.HasProperty(ShaderUtilities.ID_MaskCoord)) { mat.EnableKeyword("MASK_SOFT"); mat.DisableKeyword("MASK_HARD"); mat.DisableKeyword("MASK_OFF"); m_isMaskingEnabled = true; UpdateMask(); } */ } // Enable Masking in the Shader void DisableMasking() { if (m_fontMaterial != null) { if (m_stencilID > 0) m_sharedMaterial = m_MaskMaterial; //else // m_sharedMaterial = m_baseMaterial; m_canvasRenderer.SetMaterial(m_sharedMaterial, m_sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex)); DestroyImmediate(m_fontMaterial); } m_isMaskingEnabled = false; /* if (m_maskingMaterial != null && m_stencilID == 0) { m_sharedMaterial = m_baseMaterial; m_uiRenderer.SetMaterial(m_sharedMaterial, null); } else if (m_stencilID > 0) { m_sharedMaterial.EnableKeyword("MASK_OFF"); m_sharedMaterial.DisableKeyword("MASK_HARD"); m_sharedMaterial.DisableKeyword("MASK_SOFT"); } */ /* Material mat = m_uiRenderer.GetMaterial(); if (mat.HasProperty(ShaderUtilities.ID_MaskCoord)) { mat.EnableKeyword("MASK_OFF"); mat.DisableKeyword("MASK_HARD"); mat.DisableKeyword("MASK_SOFT"); m_isMaskingEnabled = false; UpdateMask(); } */ } // Update & recompute Mask offset void UpdateMask() { //Debug.Log("Updating Mask..."); if (m_rectTransform != null) { //Material mat = m_uiRenderer.GetMaterial(); //if (mat == null || (m_overflowMode == TextOverflowModes.ScrollRect && m_isScrollRegionSet)) // return; if (!ShaderUtilities.isInitialized) ShaderUtilities.GetShaderPropertyIDs(); //Debug.Log("Setting Mask for the first time."); m_isScrollRegionSet = true; float softnessX = Mathf.Min(Mathf.Min(m_margin.x, m_margin.z), m_sharedMaterial.GetFloat(ShaderUtilities.ID_MaskSoftnessX)); float softnessY = Mathf.Min(Mathf.Min(m_margin.y, m_margin.w), m_sharedMaterial.GetFloat(ShaderUtilities.ID_MaskSoftnessY)); softnessX = softnessX > 0 ? softnessX : 0; softnessY = softnessY > 0 ? softnessY : 0; float width = (m_rectTransform.rect.width - Mathf.Max(m_margin.x, 0) - Mathf.Max(m_margin.z, 0)) / 2 + softnessX; float height = (m_rectTransform.rect.height - Mathf.Max(m_margin.y, 0) - Mathf.Max(m_margin.w, 0)) / 2 + softnessY; Vector2 center = m_rectTransform.localPosition + new Vector3((0.5f - m_rectTransform.pivot.x) * m_rectTransform.rect.width + (Mathf.Max(m_margin.x, 0) - Mathf.Max(m_margin.z, 0)) / 2, (0.5f - m_rectTransform.pivot.y) * m_rectTransform.rect.height + (-Mathf.Max(m_margin.y, 0) + Mathf.Max(m_margin.w, 0)) / 2); //Vector2 center = m_rectTransform.localPosition + new Vector3((0.5f - m_rectTransform.pivot.x) * m_rectTransform.rect.width + (margin.x - margin.z) / 2, (0.5f - m_rectTransform.pivot.y) * m_rectTransform.rect.height + (-margin.y + margin.w) / 2); Vector4 mask = new Vector4(center.x, center.y, width, height); //Debug.Log(mask); //Rect rect = new Rect(0, 0, m_rectTransform.rect.width + margin.x + margin.z, m_rectTransform.rect.height + margin.y + margin.w); //int softness = (int)m_sharedMaterial.GetFloat(ShaderUtilities.ID_MaskSoftnessX) / 2; m_sharedMaterial.SetVector(ShaderUtilities.ID_ClipRect, mask); } } // Function called internally when a new material is assigned via the fontMaterial property. protected override Material GetMaterial(Material mat) { // Get Shader PropertyIDs if they haven't been cached already. ShaderUtilities.GetShaderPropertyIDs(); // Check in case Object is disabled. If so, we don't have a valid reference to the Renderer. // This can occur when the Duplicate Material Context menu is used on an inactive object. //if (m_canvasRenderer == null) // m_canvasRenderer = GetComponent(); // Create Instance Material only if the new material is not the same instance previously used. if (m_fontMaterial == null || m_fontMaterial.GetInstanceID() != mat.GetInstanceID()) m_fontMaterial = CreateMaterialInstance(mat); m_sharedMaterial = m_fontMaterial; m_padding = GetPaddingForMaterial(); m_ShouldRecalculateStencil = true; SetVerticesDirty(); SetMaterialDirty(); return m_sharedMaterial; } /// /// Method returning instances of the materials used by the text object. /// /// protected override Material[] GetMaterials(Material[] mats) { int materialCount = m_textInfo.materialCount; if (m_fontMaterials == null) m_fontMaterials = new Material[materialCount]; else if (m_fontMaterials.Length != materialCount) TMP_TextInfo.Resize(ref m_fontMaterials, materialCount, false); // Get instances of the materials for (int i = 0; i < materialCount; i++) { if (i == 0) m_fontMaterials[i] = fontMaterial; else m_fontMaterials[i] = m_subTextObjects[i].material; } m_fontSharedMaterials = m_fontMaterials; return m_fontMaterials; } // Function called internally when a new shared material is assigned via the fontSharedMaterial property. protected override void SetSharedMaterial(Material mat) { // Check in case Object is disabled. If so, we don't have a valid reference to the Renderer. // This can occur when the Duplicate Material Context menu is used on an inactive object. //if (m_canvasRenderer == null) // m_canvasRenderer = GetComponent(); m_sharedMaterial = mat; m_padding = GetPaddingForMaterial(); SetMaterialDirty(); } /// /// Method returning an array containing the materials used by the text object. /// /// protected override Material[] GetSharedMaterials() { int materialCount = m_textInfo.materialCount; if (m_fontSharedMaterials == null) m_fontSharedMaterials = new Material[materialCount]; else if (m_fontSharedMaterials.Length != materialCount) TMP_TextInfo.Resize(ref m_fontSharedMaterials, materialCount, false); for (int i = 0; i < materialCount; i++) { if (i == 0) m_fontSharedMaterials[i] = m_sharedMaterial; else m_fontSharedMaterials[i] = m_subTextObjects[i].sharedMaterial; } return m_fontSharedMaterials; } /// /// Method used to assign new materials to the text and sub text objects. /// protected override void SetSharedMaterials(Material[] materials) { int materialCount = m_textInfo.materialCount; // Check allocation of the fontSharedMaterials array. if (m_fontSharedMaterials == null) m_fontSharedMaterials = new Material[materialCount]; else if (m_fontSharedMaterials.Length != materialCount) TMP_TextInfo.Resize(ref m_fontSharedMaterials, materialCount, false); // Only assign as many materials as the text object contains. for (int i = 0; i < materialCount; i++) { if (i == 0) { // Only assign new material if the font atlas textures match. if (materials[i].GetTexture(ShaderUtilities.ID_MainTex) == null || materials[i].GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID() != m_sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID()) continue; m_sharedMaterial = m_fontSharedMaterials[i] = materials[i]; m_padding = GetPaddingForMaterial(m_sharedMaterial); } else { // Only assign new material if the font atlas textures match. if (materials[i].GetTexture(ShaderUtilities.ID_MainTex) == null || materials[i].GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID() != m_subTextObjects[i].sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID()) continue; // Only assign a new material if none were specified in the text input. if (m_subTextObjects[i].isDefaultMaterial) m_subTextObjects[i].sharedMaterial = m_fontSharedMaterials[i] = materials[i]; } } } // This function will create an instance of the Font Material. protected override void SetOutlineThickness(float thickness) { // Use material instance if one exists. Otherwise, create a new instance of the shared material. if (m_fontMaterial != null && m_sharedMaterial.GetInstanceID() != m_fontMaterial.GetInstanceID()) { m_sharedMaterial = m_fontMaterial; m_canvasRenderer.SetMaterial(m_sharedMaterial, m_sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex)); } else if(m_fontMaterial == null) { m_fontMaterial = CreateMaterialInstance(m_sharedMaterial); m_sharedMaterial = m_fontMaterial; m_canvasRenderer.SetMaterial(m_sharedMaterial, m_sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex)); } thickness = Mathf.Clamp01(thickness); m_sharedMaterial.SetFloat(ShaderUtilities.ID_OutlineWidth, thickness); m_padding = GetPaddingForMaterial(); } // This function will create an instance of the Font Material. protected override void SetFaceColor(Color32 color) { // Use material instance if one exists. Otherwise, create a new instance of the shared material. if (m_fontMaterial == null) m_fontMaterial = CreateMaterialInstance(m_sharedMaterial); m_sharedMaterial = m_fontMaterial; m_padding = GetPaddingForMaterial(); m_sharedMaterial.SetColor(ShaderUtilities.ID_FaceColor, color); } // This function will create an instance of the Font Material. protected override void SetOutlineColor(Color32 color) { // Use material instance if one exists. Otherwise, create a new instance of the shared material. if (m_fontMaterial == null) m_fontMaterial = CreateMaterialInstance(m_sharedMaterial); m_sharedMaterial = m_fontMaterial; m_padding = GetPaddingForMaterial(); m_sharedMaterial.SetColor(ShaderUtilities.ID_OutlineColor, color); } // Sets the Render Queue and Ztest mode protected override void SetShaderDepth() { if (m_canvas == null || m_sharedMaterial == null) return; if (m_canvas.renderMode == RenderMode.ScreenSpaceOverlay || m_isOverlay) { // Should this use an instanced material? m_sharedMaterial.SetFloat(ShaderUtilities.ShaderTag_ZTestMode, 0); } else { // TODO: This section needs to be tested. m_sharedMaterial.SetFloat(ShaderUtilities.ShaderTag_ZTestMode, 4); } } // Sets the Culling mode of the material protected override void SetCulling() { if (m_isCullingEnabled) { Material mat = materialForRendering; if (mat != null) mat.SetFloat("_CullMode", 2); for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { mat = m_subTextObjects[i].materialForRendering; if (mat != null) { mat.SetFloat(ShaderUtilities.ShaderTag_CullMode, 2); } } } else { Material mat = materialForRendering; if (mat != null) mat.SetFloat("_CullMode", 0); for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { mat = m_subTextObjects[i].materialForRendering; if (mat != null) { mat.SetFloat(ShaderUtilities.ShaderTag_CullMode, 0); } } } } // Set Perspective Correction Mode based on whether Camera is Orthographic or Perspective void SetPerspectiveCorrection() { if (m_isOrthographic) m_sharedMaterial.SetFloat(ShaderUtilities.ID_PerspectiveFilter, 0.0f); else m_sharedMaterial.SetFloat(ShaderUtilities.ID_PerspectiveFilter, 0.875f); } /// /// Get the padding value for the currently assigned material. /// /// protected override float GetPaddingForMaterial(Material mat) { m_padding = ShaderUtilities.GetPadding(mat, m_enableExtraPadding, m_isUsingBold); m_isMaskingEnabled = ShaderUtilities.IsMaskingEnabled(m_sharedMaterial); m_isSDFShader = mat.HasProperty(ShaderUtilities.ID_WeightNormal); return m_padding; } /// /// Get the padding value for the currently assigned material. /// /// protected override float GetPaddingForMaterial() { ShaderUtilities.GetShaderPropertyIDs(); m_padding = ShaderUtilities.GetPadding(m_sharedMaterial, m_enableExtraPadding, m_isUsingBold); m_isMaskingEnabled = ShaderUtilities.IsMaskingEnabled(m_sharedMaterial); m_isSDFShader = m_sharedMaterial.HasProperty(ShaderUtilities.ID_WeightNormal); return m_padding; } // Function to allocate the necessary buffers to render the text. This function is called whenever the buffer size needs to be increased. void SetMeshArrays(int size) { m_textInfo.meshInfo[0].ResizeMeshInfo(size); m_canvasRenderer.SetMesh(m_textInfo.meshInfo[0].mesh); } // This function parses through the Char[] to determine how many characters will be visible. It then makes sure the arrays are large enough for all those characters. protected override int SetArraySizes(int[] chars) { //Debug.Log("*** SetArraySizes() on Instance ID (" + GetInstanceID() + ") ***"); #if PROFILE_ON Profiler.BeginSample("SetArraySizes"); #endif int tagEnd = 0; int spriteCount = 0; m_totalCharacterCount = 0; m_isUsingBold = false; m_isParsingText = false; tag_NoParsing = false; m_style = m_fontStyle; m_fontWeightInternal = (m_style & FontStyles.Bold) == FontStyles.Bold ? 700 : m_fontWeight; m_fontWeightStack.SetDefault(m_fontWeightInternal); m_currentFontAsset = m_fontAsset; m_currentMaterial = m_sharedMaterial; m_currentMaterialIndex = 0; m_materialReferenceStack.SetDefault(new MaterialReference(m_currentMaterialIndex, m_currentFontAsset, null, m_currentMaterial, m_padding)); m_materialReferenceIndexLookup.Clear(); MaterialReference.AddMaterialReference(m_currentMaterial, m_currentFontAsset, m_materialReferences, m_materialReferenceIndexLookup); if (m_textInfo == null) m_textInfo = new TMP_TextInfo(); m_textElementType = TMP_TextElementType.Character; // Clear Linked Text object if we have one. if (m_linkedTextComponent != null) { m_linkedTextComponent.text = string.Empty; // SetText(string.Empty, false); m_linkedTextComponent.ForceMeshUpdate(); } // Parsing XML tags in the text for (int i = 0; i < chars.Length && chars[i] != 0; i++) { //Make sure the characterInfo array can hold the next text element. if (m_textInfo.characterInfo == null || m_totalCharacterCount >= m_textInfo.characterInfo.Length) TMP_TextInfo.Resize(ref m_textInfo.characterInfo, m_totalCharacterCount + 1, true); int c = chars[i]; // PARSE XML TAGS #region PARSE XML TAGS if (m_isRichText && c == 60) // if Char '<' { int prev_MaterialIndex = m_currentMaterialIndex; // Check if Tag is Valid if (ValidateHtmlTag(chars, i + 1, out tagEnd)) { i = tagEnd; if ((m_style & FontStyles.Bold) == FontStyles.Bold) m_isUsingBold = true; if (m_textElementType == TMP_TextElementType.Sprite) { m_materialReferences[m_currentMaterialIndex].referenceCount += 1; m_textInfo.characterInfo[m_totalCharacterCount].character = (char)(57344 + m_spriteIndex); m_textInfo.characterInfo[m_totalCharacterCount].spriteIndex = m_spriteIndex; m_textInfo.characterInfo[m_totalCharacterCount].fontAsset = m_currentFontAsset; m_textInfo.characterInfo[m_totalCharacterCount].spriteAsset = m_currentSpriteAsset; m_textInfo.characterInfo[m_totalCharacterCount].materialReferenceIndex = m_currentMaterialIndex; m_textInfo.characterInfo[m_totalCharacterCount].elementType = m_textElementType; // Restore element type and material index to previous values. m_textElementType = TMP_TextElementType.Character; m_currentMaterialIndex = prev_MaterialIndex; spriteCount += 1; m_totalCharacterCount += 1; } continue; } } #endregion bool isUsingFallback = false; bool isUsingAlternativeTypeface = false; TMP_Glyph glyph; TMP_FontAsset tempFontAsset; TMP_FontAsset prev_fontAsset = m_currentFontAsset; Material prev_material = m_currentMaterial; int prev_materialIndex = m_currentMaterialIndex; // Handle Font Styles like LowerCase, UpperCase and SmallCaps. #region Handling of LowerCase, UpperCase and SmallCaps Font Styles 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)c)) c = char.ToUpper((char)c); } else if ((m_style & FontStyles.LowerCase) == FontStyles.LowerCase) { // If this character is uppercase, switch to lowercase. if (char.IsUpper((char)c)) c = char.ToLower((char)c); } else if ((m_fontStyle & FontStyles.SmallCaps) == FontStyles.SmallCaps || (m_style & FontStyles.SmallCaps) == FontStyles.SmallCaps) { // Only convert lowercase characters to uppercase. if (char.IsLower((char)c)) c = char.ToUpper((char)c); } } #endregion // Handling of font weights. #region HANDLING OF FONT WEIGHT tempFontAsset = GetFontAssetForWeight(m_fontWeightInternal); if (tempFontAsset != null) { isUsingFallback = true; isUsingAlternativeTypeface = true; m_currentFontAsset = tempFontAsset; //m_currentMaterialIndex = MaterialReference.AddMaterialReference(m_currentMaterial, tempFontAsset, m_materialReferences, m_materialReferenceIndexLookup); } #endregion // Lookup the Glyph data for each character and cache it. #region LOOKUP GLYPH tempFontAsset = TMP_FontUtilities.SearchForGlyph(m_currentFontAsset, c, out glyph); // Search for the glyph in the Sprite Asset assigned to the text object. if (glyph == null) { TMP_SpriteAsset spriteAsset = this.spriteAsset; if (spriteAsset != null) { int spriteIndex = -1; // Check Default Sprite Asset and its Fallbacks spriteAsset = TMP_SpriteAsset.SearchFallbackForSprite(spriteAsset, c, out spriteIndex); if (spriteIndex != -1) { m_textElementType = TMP_TextElementType.Sprite; m_textInfo.characterInfo[m_totalCharacterCount].elementType = m_textElementType; m_currentMaterialIndex = MaterialReference.AddMaterialReference(spriteAsset.material, spriteAsset, m_materialReferences, m_materialReferenceIndexLookup); m_materialReferences[m_currentMaterialIndex].referenceCount += 1; m_textInfo.characterInfo[m_totalCharacterCount].character = (char)c; m_textInfo.characterInfo[m_totalCharacterCount].spriteIndex = spriteIndex; m_textInfo.characterInfo[m_totalCharacterCount].fontAsset = m_currentFontAsset; m_textInfo.characterInfo[m_totalCharacterCount].spriteAsset = spriteAsset; m_textInfo.characterInfo[m_totalCharacterCount].materialReferenceIndex = m_currentMaterialIndex; // Restore element type and material index to previous values. m_textElementType = TMP_TextElementType.Character; m_currentMaterialIndex = prev_materialIndex; spriteCount += 1; m_totalCharacterCount += 1; continue; } } } // Search for the glyph in the list of fallback assigned in the TMP Settings (General Fallbacks). if (glyph == null) { if (TMP_Settings.fallbackFontAssets != null && TMP_Settings.fallbackFontAssets.Count > 0) tempFontAsset = TMP_FontUtilities.SearchForGlyph(TMP_Settings.fallbackFontAssets, c, out glyph); } // Search for the glyph in the Default Font Asset assigned in the TMP Settings file. if (glyph == null) { if (TMP_Settings.defaultFontAsset != null) tempFontAsset = TMP_FontUtilities.SearchForGlyph(TMP_Settings.defaultFontAsset, c, out glyph); } // TODO: Add support for using Sprite Assets like a special Emoji only Sprite Asset when UTF16 or UTF32 glyphs are requested. // This would kind of mirror native Emoji support. if (glyph == null) { TMP_SpriteAsset spriteAsset = TMP_Settings.defaultSpriteAsset; if (spriteAsset != null) { int spriteIndex = -1; // Check Default Sprite Asset and its Fallbacks spriteAsset = TMP_SpriteAsset.SearchFallbackForSprite(spriteAsset, c, out spriteIndex); if (spriteIndex != -1) { m_textElementType = TMP_TextElementType.Sprite; m_textInfo.characterInfo[m_totalCharacterCount].elementType = m_textElementType; m_currentMaterialIndex = MaterialReference.AddMaterialReference(spriteAsset.material, spriteAsset, m_materialReferences, m_materialReferenceIndexLookup); m_materialReferences[m_currentMaterialIndex].referenceCount += 1; m_textInfo.characterInfo[m_totalCharacterCount].character = (char)c; m_textInfo.characterInfo[m_totalCharacterCount].spriteIndex = spriteIndex; m_textInfo.characterInfo[m_totalCharacterCount].fontAsset = m_currentFontAsset; m_textInfo.characterInfo[m_totalCharacterCount].spriteAsset = spriteAsset; m_textInfo.characterInfo[m_totalCharacterCount].materialReferenceIndex = m_currentMaterialIndex; // Restore element type and material index to previous values. m_textElementType = TMP_TextElementType.Character; m_currentMaterialIndex = prev_materialIndex; spriteCount += 1; m_totalCharacterCount += 1; continue; } } } //Check if Lowercase or Uppercase variant of the character is available. // Not sure this is necessary anyone as it is very unlikely with recursive search through fallback fonts. //if (glyph == null) //{ // if (char.IsLower((char)c)) // { // if (m_currentFontAsset.characterDictionary.TryGetValue(char.ToUpper((char)c), out glyph)) // c = chars[i] = char.ToUpper((char)c); // } // else if (char.IsUpper((char)c)) // { // if (m_currentFontAsset.characterDictionary.TryGetValue(char.ToLower((char)c), out glyph)) // c = chars[i] = char.ToLower((char)c); // } //} // Replace missing glyph by the Square (9633) glyph or possibly the Space (32) glyph. if (glyph == null) { // Save the original unicode character int srcGlyph = c; // Try replacing the missing glyph character by TMP Settings Missing Glyph or Square (9633) character. c = chars[i] = TMP_Settings.missingGlyphCharacter == 0 ? 9633 : TMP_Settings.missingGlyphCharacter; // Check for the missing glyph character in the currently assigned font asset. tempFontAsset = TMP_FontUtilities.SearchForGlyph(m_currentFontAsset, c, out glyph); if (glyph == null) { // Search for the missing glyph character in the TMP Settings Fallback list. if (TMP_Settings.fallbackFontAssets != null && TMP_Settings.fallbackFontAssets.Count > 0) tempFontAsset = TMP_FontUtilities.SearchForGlyph(TMP_Settings.fallbackFontAssets, c, out glyph); } if (glyph == null) { // Search for the missing glyph in the TMP Settings Default Font Asset. if (TMP_Settings.defaultFontAsset != null) tempFontAsset = TMP_FontUtilities.SearchForGlyph(TMP_Settings.defaultFontAsset, c, out glyph); } if (glyph == null) { // Use Space (32) Glyph from the currently assigned font asset. c = chars[i] = 32; tempFontAsset = TMP_FontUtilities.SearchForGlyph(m_currentFontAsset, c, out glyph); if (!TMP_Settings.warningsDisabled) Debug.LogWarning("Character with ASCII value of " + srcGlyph + " was not found in the Font Asset Glyph Table. It was replaced by a space.", this); } } // Determine if the font asset is still the current font asset or a fallback. if (tempFontAsset != null) { if (tempFontAsset.GetInstanceID() != m_currentFontAsset.GetInstanceID()) { isUsingFallback = true; isUsingAlternativeTypeface = false; m_currentFontAsset = tempFontAsset; } } #endregion m_textInfo.characterInfo[m_totalCharacterCount].elementType = TMP_TextElementType.Character; m_textInfo.characterInfo[m_totalCharacterCount].textElement = glyph; m_textInfo.characterInfo[m_totalCharacterCount].isUsingAlternateTypeface = isUsingAlternativeTypeface; m_textInfo.characterInfo[m_totalCharacterCount].character = (char)c; m_textInfo.characterInfo[m_totalCharacterCount].fontAsset = m_currentFontAsset; if (isUsingFallback) { // Create Fallback material instance matching current material preset if necessary if (TMP_Settings.matchMaterialPreset) m_currentMaterial = TMP_MaterialManager.GetFallbackMaterial(m_currentMaterial, m_currentFontAsset.material); else m_currentMaterial = m_currentFontAsset.material; m_currentMaterialIndex = MaterialReference.AddMaterialReference(m_currentMaterial, m_currentFontAsset, m_materialReferences, m_materialReferenceIndexLookup); } if (!char.IsWhiteSpace((char)c) && c != 0x200B) { // Limit the mesh of the main text object to 65535 vertices and use sub objects for the overflow. if (m_materialReferences[m_currentMaterialIndex].referenceCount < 16383) m_materialReferences[m_currentMaterialIndex].referenceCount += 1; else { m_currentMaterialIndex = MaterialReference.AddMaterialReference(new Material(m_currentMaterial), m_currentFontAsset, m_materialReferences, m_materialReferenceIndexLookup); m_materialReferences[m_currentMaterialIndex].referenceCount += 1; } } m_textInfo.characterInfo[m_totalCharacterCount].material = m_currentMaterial; m_textInfo.characterInfo[m_totalCharacterCount].materialReferenceIndex = m_currentMaterialIndex; m_materialReferences[m_currentMaterialIndex].isFallbackMaterial = isUsingFallback; // Restore previous font asset and material if fallback font was used. if (isUsingFallback) { m_materialReferences[m_currentMaterialIndex].fallbackMaterial = prev_material; m_currentFontAsset = prev_fontAsset; m_currentMaterial = prev_material; m_currentMaterialIndex = prev_materialIndex; } m_totalCharacterCount += 1; } // Early return if we are calculating the preferred values. if (m_isCalculatingPreferredValues) { m_isCalculatingPreferredValues = false; m_isInputParsingRequired = true; return m_totalCharacterCount; } // Save material and sprite count. m_textInfo.spriteCount = spriteCount; int materialCount = m_textInfo.materialCount = m_materialReferenceIndexLookup.Count; // Check if we need to resize the MeshInfo array for handling different materials. if (materialCount > m_textInfo.meshInfo.Length) TMP_TextInfo.Resize(ref m_textInfo.meshInfo, materialCount, false); // Resize SubTextObject array if necessary if (materialCount > m_subTextObjects.Length) TMP_TextInfo.Resize(ref m_subTextObjects, Mathf.NextPowerOfTwo(materialCount + 1)); // Resize CharacterInfo[] if allocations are excessive if (m_textInfo.characterInfo.Length - m_totalCharacterCount > 256) TMP_TextInfo.Resize(ref m_textInfo.characterInfo, Mathf.Max(m_totalCharacterCount + 1, 256), true); // Iterate through the material references to set the mesh buffer allocations for (int i = 0; i < materialCount; i++) { // Add new sub text object for each material reference if (i > 0) { if (m_subTextObjects[i] == null) { m_subTextObjects[i] = TMP_SubMeshUI.AddSubTextObject(this, m_materialReferences[i]); // Not sure this is necessary m_textInfo.meshInfo[i].vertices = null; } //else if (m_subTextObjects[i].gameObject.activeInHierarchy == false) // m_subTextObjects[i].gameObject.SetActive(true); // Make sure the pivots are synchronized if (m_rectTransform.pivot != m_subTextObjects[i].rectTransform.pivot) m_subTextObjects[i].rectTransform.pivot = m_rectTransform.pivot; // Check if the material has changed. if (m_subTextObjects[i].sharedMaterial == null || m_subTextObjects[i].sharedMaterial.GetInstanceID() != m_materialReferences[i].material.GetInstanceID()) { bool isDefaultMaterial = m_materialReferences[i].isDefaultMaterial; m_subTextObjects[i].isDefaultMaterial = isDefaultMaterial; // Assign new material if we are not using the default material or if the font asset has changed. if (!isDefaultMaterial || m_subTextObjects[i].sharedMaterial == null || m_subTextObjects[i].sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID() != m_materialReferences[i].material.GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID()) { m_subTextObjects[i].sharedMaterial = m_materialReferences[i].material; m_subTextObjects[i].fontAsset = m_materialReferences[i].fontAsset; m_subTextObjects[i].spriteAsset = m_materialReferences[i].spriteAsset; } } // Check if we need to use a Fallback Material if (m_materialReferences[i].isFallbackMaterial) { m_subTextObjects[i].fallbackMaterial = m_materialReferences[i].material; m_subTextObjects[i].fallbackSourceMaterial = m_materialReferences[i].fallbackMaterial; } } int referenceCount = m_materialReferences[i].referenceCount; // Check to make sure our buffers allocations can accommodate the required text elements. if (m_textInfo.meshInfo[i].vertices == null || m_textInfo.meshInfo[i].vertices.Length < referenceCount * 4) { if (m_textInfo.meshInfo[i].vertices == null) { if (i == 0) m_textInfo.meshInfo[i] = new TMP_MeshInfo(m_mesh, referenceCount + 1); else m_textInfo.meshInfo[i] = new TMP_MeshInfo(m_subTextObjects[i].mesh, referenceCount + 1); } else m_textInfo.meshInfo[i].ResizeMeshInfo(referenceCount > 1024 ? referenceCount + 256 : Mathf.NextPowerOfTwo(referenceCount)); } else if (m_textInfo.meshInfo[i].vertices.Length - referenceCount * 4 > 1024) { // Resize vertex buffers if allocations are excessive. //Debug.Log("Reducing the size of the vertex buffers."); m_textInfo.meshInfo[i].ResizeMeshInfo(referenceCount > 1024 ? referenceCount + 256 : Mathf.Max(Mathf.NextPowerOfTwo(referenceCount), 256)); } } //TMP_MaterialManager.CleanupFallbackMaterials(); // Clean up unused SubMeshes for (int i = materialCount; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { if (i < m_textInfo.meshInfo.Length) { m_subTextObjects[i].canvasRenderer.SetMesh(null); // TODO: Figure out a way to handle this without running into Unity's Rebuild loop issue. //m_subTextObjects[i].gameObject.SetActive(false); } } #if PROFILE_ON Profiler.EndSample(); #endif return m_totalCharacterCount; } // Added to sort handle the potential issue with OnWillRenderObject() not getting called when objects are not visible by camera. //void OnBecameInvisible() //{ // if (m_mesh != null) // m_mesh.bounds = new Bounds(transform.position, new Vector3(1000, 1000, 0)); //} /// /// Update the margin width and height /// protected override void ComputeMarginSize() { if (this.rectTransform != null) { //Debug.Log("*** ComputeMarginSize() *** Current RectTransform's Width is " + m_rectTransform.rect.width + " and Height is " + m_rectTransform.rect.height); // + " and size delta is " + m_rectTransform.sizeDelta); m_marginWidth = m_rectTransform.rect.width - m_margin.x - m_margin.z; m_marginHeight = m_rectTransform.rect.height - m_margin.y - m_margin.w; // Update the corners of the RectTransform m_RectTransformCorners = GetTextContainerLocalCorners(); } } /// /// /// protected override void OnDidApplyAnimationProperties() { m_havePropertiesChanged = true; SetVerticesDirty(); SetLayoutDirty(); //Debug.Log("Animation Properties have changed."); } protected override void OnCanvasHierarchyChanged() { base.OnCanvasHierarchyChanged(); m_canvas = this.canvas; } protected override void OnTransformParentChanged() { //Debug.Log("***** OnTransformParentChanged *****"); base.OnTransformParentChanged(); m_canvas = this.canvas; ComputeMarginSize(); m_havePropertiesChanged = true; } protected override void OnRectTransformDimensionsChange() { //Debug.Log("*** OnRectTransformDimensionsChange() *** ActiveInHierarchy: " + this.gameObject.activeInHierarchy + " Frame: " + Time.frameCount); // Make sure object is active in Hierarchy if (!this.gameObject.activeInHierarchy) return; ComputeMarginSize(); UpdateSubObjectPivot(); SetVerticesDirty(); SetLayoutDirty(); } /// /// Unity standard function used to check if the transform or scale of the text object has changed. /// void LateUpdate() { if (m_rectTransform.hasChanged) { // We need to update the SDF scale or possibly regenerate the text object if lossy scale has changed. float lossyScaleY = m_rectTransform.lossyScale.y; if (!m_havePropertiesChanged && lossyScaleY != m_previousLossyScaleY && m_text != string.Empty && m_text != null) { UpdateSDFScale(lossyScaleY); m_previousLossyScaleY = lossyScaleY; } m_rectTransform.hasChanged = false; } // Added to handle legacy animation mode. if (m_isUsingLegacyAnimationComponent) { //if (m_havePropertiesChanged) m_havePropertiesChanged = true; OnPreRenderCanvas(); } } // Called just before the Canvas is rendered. void OnPreRenderCanvas() { //Debug.Log("*** OnPreRenderCanvas() *** Frame: " + Time.frameCount); // Make sure object is active and that we have a valid Canvas. if (!m_isAwake || !m_ignoreActiveState && !this.IsActive()) return; if (m_canvas == null) { m_canvas = this.canvas; if (m_canvas == null) return; } // Debug Variables loopCountA = 0; //loopCountB = 0; //loopCountC = 0; //loopCountD = 0; //loopCountE = 0; // Update Margins //ComputeMarginSize(); // Update Mask // if (m_isMaskingEnabled) // { // UpdateMask(); // } if (m_havePropertiesChanged || m_isLayoutDirty) { //Debug.Log("Properties have changed!"); // Assigned Material is:" + m_sharedMaterial); // New Text is: " + m_text + "."); // Update mesh padding if necessary. if (checkPaddingRequired) UpdateMeshPadding(); // Reparse the text if the input has changed or text was truncated. if (m_isInputParsingRequired || m_isTextTruncated) ParseInputText(); // Reset Font min / max used with Auto-sizing if (m_enableAutoSizing) m_fontSize = Mathf.Clamp(m_fontSizeBase, m_fontSizeMin, m_fontSizeMax); m_maxFontSize = m_fontSizeMax; m_minFontSize = m_fontSizeMin; m_lineSpacingDelta = 0; m_charWidthAdjDelta = 0; //m_recursiveCount = 0; m_isCharacterWrappingEnabled = false; m_isTextTruncated = false; m_havePropertiesChanged = false; m_isLayoutDirty = false; m_ignoreActiveState = false; GenerateTextMesh(); } } /// /// This is the main function that is responsible for creating / displaying the text. /// protected override 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); //Profiler.BeginSample("TMP Generate Text - Pre"); // Early exit if no font asset was assigned. This should not be needed since LiberationSans 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; } // Clear 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) { // Clear mesh and upload changes to the mesh. ClearMesh(); m_preferredWidth = 0; m_preferredHeight = 0; // Event indicating the text has been regenerated. TMPro_EventManager.ON_TEXT_CHANGED(this); return; } m_currentFontAsset = m_fontAsset; m_currentMaterial = m_sharedMaterial; m_currentMaterialIndex = 0; m_materialReferenceStack.SetDefault(new MaterialReference(m_currentMaterialIndex, m_currentFontAsset, null, m_currentMaterial, m_padding)); m_currentSpriteAsset = m_spriteAsset; // Stop all Sprite Animations if (m_spriteAnimator != null) m_spriteAnimator.StopAllAnimations(); // Total character count is computed when the text is parsed. int totalCharacterCount = m_totalCharacterCount; // Calculate the scale of the font based on selected font size and sampling point size. // baseScale is calculated using the font asset assigned to the text object. float baseScale = m_fontScale = (m_fontSize / m_fontAsset.fontInfo.PointSize * m_fontAsset.fontInfo.Scale); float currentElementScale = baseScale; m_fontScaleMultiplier = 1; m_currentFontSize = m_fontSize; m_sizeStack.SetDefault(m_currentFontSize); float fontSizeDelta = 0; int charCode = 0; // Holds the character code of the currently being processed character. m_style = m_fontStyle; // Set the default style. m_fontWeightInternal = (m_style & FontStyles.Bold) == FontStyles.Bold ? 700 : m_fontWeight; m_fontWeightStack.SetDefault(m_fontWeightInternal); m_fontStyleStack.Clear(); m_lineJustification = m_textAlignment; // Sets the line justification mode to match editor alignment. m_lineJustificationStack.SetDefault(m_lineJustification); float padding = 0; float style_padding = 0; // Extra padding required to accommodate Bold style. float bold_xAdvance_multiplier = 1; // Used to increase spacing between character when style is bold. m_baselineOffset = 0; // Used by subscript characters. m_baselineOffsetStack.Clear(); // 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; // Text Highlight bool beginHighlight = false; Vector3 highlight_start = Vector3.zero; Vector3 highlight_end = Vector3.zero; m_fontColor32 = m_fontColor; Color32 vertexColor; m_htmlColor = m_fontColor32; m_underlineColor = m_htmlColor; m_strikethroughColor = m_htmlColor; m_colorStack.SetDefault(m_htmlColor); m_underlineColorStack.SetDefault(m_htmlColor); m_strikethroughColorStack.SetDefault(m_htmlColor); m_highlightColorStack.SetDefault(m_htmlColor); m_colorGradientPreset = null; m_colorGradientStack.SetDefault(null); // Clear the Style stack. //m_styleStack.Clear(); // Clear the Action stack. m_actionStack.Clear(); m_isFXMatrixSet = false; m_lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). m_lineHeight = TMP_Math.FLOAT_UNSET; 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 tag. m_monoSpacing = 0; float lineOffsetDelta = 0; m_xAdvance = 0; // Used to track the position of each character. 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_lastCharacterOfLine = 0; m_firstVisibleCharacterOfLine = 0; m_lastVisibleCharacterOfLine = 0; m_maxLineAscender = k_LargeNegativeFloat; m_maxLineDescender = k_LargePositiveFloat; m_lineNumber = 0; m_lineVisibleCharacterCount = 0; bool isStartOfNewLine = true; m_firstOverflowCharacterIndex = -1; m_pageNumber = 0; int pageToDisplay = Mathf.Clamp(m_pageToDisplay - 1, 0, m_textInfo.pageInfo.Length - 1); int previousPageOverflowChar = 0; int ellipsisIndex = 0; Vector4 margins = m_margin; float marginWidth = m_marginWidth; float marginHeight = m_marginHeight; m_marginLeft = 0; m_marginRight = 0; m_width = -1; float width = marginWidth + 0.0001f - m_marginLeft - m_marginRight; // Need to initialize these Extents structures m_meshExtents.min = k_LargePositiveVector2; m_meshExtents.max = k_LargeNegativeVector2; // Initialize lineInfo m_textInfo.ClearLineInfo(); // Tracking of the highest Ascender m_maxCapHeight = 0; m_maxAscender = 0; m_maxDescender = 0; float pageAscender = 0; float maxVisibleDescender = 0; bool isMaxVisibleDescenderSet = false; m_isNewPage = false; // Initialize struct to track states of word wrapping bool isFirstWord = true; m_isNonBreakingSpace = false; bool ignoreNonBreakingSpace = false; bool isLastBreakingChar = false; float linebreakingWidth = 0; int wrappingIndex = 0; // Save character and line state before we begin layout. SaveWordWrappingState(ref m_SavedWordWrapState, -1, -1); SaveWordWrappingState(ref m_SavedLineState, -1, -1); loopCountA += 1; //Profiler.EndSample(); #if PROFILE_PHASES_ON Profiler.BeginSample("TMP Generate Text - Phase I"); #endif int endTagIndex = 0; // Parse through Character buffer to read HTML tags and begin creating mesh. for (int i = 0; i < m_char_buffer.Length && m_char_buffer[i] != 0; i++) { charCode = m_char_buffer[i]; m_textElementType = m_textInfo.characterInfo[m_characterCount].elementType; 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; m_textElementType = TMP_TextElementType.Character; // 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; bool isUsingAltTypeface = m_textInfo.characterInfo[m_characterCount].isUsingAlternateTypeface; // When using Linked text, mark character as ignored and skip to next character. if (m_characterCount < m_firstVisibleCharacter) { m_textInfo.characterInfo[m_characterCount].isVisible = false; m_textInfo.characterInfo[m_characterCount].character = (char)0x200B; m_characterCount += 1; continue; } // Handle Font Styles like LowerCase, UpperCase and SmallCaps. #region Handling of LowerCase, UpperCase and SmallCaps Font Styles #if PROFILE_ON Profiler.BeginSample("Handle Font Style"); #endif 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); } } } #if PROFILE_ON Profiler.EndSample(); #endif #endregion // Look up Character Data from Dictionary and cache it. #region Look up Character Data #if PROFILE_ON Profiler.BeginSample("Lookup Character Data"); #endif if (m_textElementType == TMP_TextElementType.Sprite) { // If a sprite is used as a fallback then get a reference to it and set the color to white. m_currentSpriteAsset = m_textInfo.characterInfo[m_characterCount].spriteAsset; m_spriteIndex = m_textInfo.characterInfo[m_characterCount].spriteIndex; TMP_Sprite sprite = m_currentSpriteAsset.spriteInfoList[m_spriteIndex]; if (sprite == null) continue; // Sprites are assigned in the E000 Private Area + sprite Index if (charCode == 60) charCode = 57344 + m_spriteIndex; else m_spriteColor = s_colorWhite; m_currentFontAsset = m_fontAsset; // The sprite scale calculations are based on the font asset assigned to the text object. float spriteScale = (m_currentFontSize / m_fontAsset.fontInfo.PointSize * m_fontAsset.fontInfo.Scale); currentElementScale = m_fontAsset.fontInfo.Ascender / sprite.height * sprite.scale * spriteScale; m_cached_TextElement = sprite; m_textInfo.characterInfo[m_characterCount].elementType = TMP_TextElementType.Sprite; m_textInfo.characterInfo[m_characterCount].scale = spriteScale; m_textInfo.characterInfo[m_characterCount].spriteAsset = m_currentSpriteAsset; m_textInfo.characterInfo[m_characterCount].fontAsset = m_currentFontAsset; m_textInfo.characterInfo[m_characterCount].materialReferenceIndex = m_currentMaterialIndex; m_currentMaterialIndex = prev_MaterialIndex; padding = 0; } else if (m_textElementType == TMP_TextElementType.Character) { m_cached_TextElement = m_textInfo.characterInfo[m_characterCount].textElement; if (m_cached_TextElement == null) continue; m_currentFontAsset = m_textInfo.characterInfo[m_characterCount].fontAsset; m_currentMaterial = m_textInfo.characterInfo[m_characterCount].material; 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; currentElementScale = m_fontScale * m_fontScaleMultiplier * m_cached_TextElement.scale; m_textInfo.characterInfo[m_characterCount].elementType = TMP_TextElementType.Character; m_textInfo.characterInfo[m_characterCount].scale = currentElementScale; padding = m_currentMaterialIndex == 0 ? m_padding : m_subTextObjects[m_currentMaterialIndex].padding; } #if PROFILE_ON Profiler.EndSample(); #endif #endregion // Handle Soft Hyphen #region Handle Soft Hyphen float old_scale = currentElementScale; if (charCode == 0xAD) { currentElementScale = 0; } #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].underlineColor = m_underlineColor; m_textInfo.characterInfo[m_characterCount].strikethroughColor = m_strikethroughColor; m_textInfo.characterInfo[m_characterCount].highlightColor = m_highlightColor; m_textInfo.characterInfo[m_characterCount].style = m_style; m_textInfo.characterInfo[m_characterCount].index = i; //m_textInfo.characterInfo[m_characterCount].isIgnoringAlignment = m_isIgnoringAlignment; // Handle Kerning if Enabled. #region Handle Kerning GlyphValueRecord glyphAdjustments = new GlyphValueRecord(); if (m_enableKerning) { KerningPair adjustmentPair = null; if (m_characterCount < totalCharacterCount - 1) { uint nextGlyph = m_textInfo.characterInfo[m_characterCount + 1].character; KerningPairKey keyValue = new KerningPairKey((uint)charCode, nextGlyph); m_currentFontAsset.kerningDictionary.TryGetValue((int)keyValue.key, out adjustmentPair); if (adjustmentPair != null) glyphAdjustments = adjustmentPair.firstGlyphAdjustments; } if (m_characterCount >= 1) { uint previousGlyph = m_textInfo.characterInfo[m_characterCount - 1].character; KerningPairKey keyValue = new KerningPairKey(previousGlyph, (uint)charCode); m_currentFontAsset.kerningDictionary.TryGetValue((int)keyValue.key, out adjustmentPair); if (adjustmentPair != null) glyphAdjustments += adjustmentPair.secondGlyphAdjustments; } } #endregion // Initial Implementation for RTL support. #region Handle Right-to-Left if (m_isRightToLeft) { m_xAdvance -= ((m_cached_TextElement.xAdvance * bold_xAdvance_multiplier + m_characterSpacing + m_wordSpacing + m_currentFontAsset.normalSpacingOffset) * currentElementScale + m_cSpacing) * (1 - m_charWidthAdjDelta); if (char.IsWhiteSpace((char)charCode) || charCode == 0x200B) m_xAdvance -= m_wordSpacing * 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) * (1 - m_charWidthAdjDelta); m_xAdvance += monoAdvance; } #endregion // Set Padding based on selected font style #region Handle Style Padding if (m_textElementType == TMP_TextElementType.Character && !isUsingAltTypeface && ((m_style & FontStyles.Bold) == FontStyles.Bold || (m_fontStyle & FontStyles.Bold) == FontStyles.Bold)) // Checks for any combination of Bold Style. { if (m_currentMaterial.HasProperty(ShaderUtilities.ID_GradientScale)) { float gradientScale = m_currentMaterial.GetFloat(ShaderUtilities.ID_GradientScale); style_padding = m_currentFontAsset.boldStyle / 4.0f * gradientScale * m_currentMaterial.GetFloat(ShaderUtilities.ID_ScaleRatio_A); // Clamp overall padding to Gradient Scale size. if (style_padding + padding > gradientScale) padding = gradientScale - style_padding; } else style_padding = 0; bold_xAdvance_multiplier = 1 + m_currentFontAsset.boldSpacing * 0.01f; } else { if (m_currentMaterial.HasProperty(ShaderUtilities.ID_GradientScale)) { float gradientScale = m_currentMaterial.GetFloat(ShaderUtilities.ID_GradientScale); style_padding = m_currentFontAsset.normalStyle / 4.0f * gradientScale * m_currentMaterial.GetFloat(ShaderUtilities.ID_ScaleRatio_A); // Clamp overall padding to Gradient Scale size. if (style_padding + padding > gradientScale) padding = gradientScale - style_padding; } else style_padding = 0; bold_xAdvance_multiplier = 1.0f; } #endregion Handle Style Padding // Determine the position of the vertices of the Character or Sprite. #region Calculate Vertices Position #if PROFILE_ON Profiler.BeginSample("Calculate Vertices Position"); #endif float fontBaseLineOffset = m_currentFontAsset.fontInfo.Baseline; Vector3 top_left; top_left.x = m_xAdvance + ((m_cached_TextElement.xOffset - padding - style_padding + glyphAdjustments.xPlacement) * currentElementScale * (1 - m_charWidthAdjDelta)); top_left.y = (fontBaseLineOffset + m_cached_TextElement.yOffset + padding + glyphAdjustments.yPlacement) * currentElementScale - m_lineOffset + m_baselineOffset; top_left.z = 0; Vector3 bottom_left; bottom_left.x = top_left.x; bottom_left.y = top_left.y - ((m_cached_TextElement.height + padding * 2) * currentElementScale); bottom_left.z = 0; Vector3 top_right; top_right.x = bottom_left.x + ((m_cached_TextElement.width + padding * 2 + style_padding * 2) * currentElementScale * (1 - m_charWidthAdjDelta)); top_right.y = top_left.y; top_right.z = 0; Vector3 bottom_right; bottom_right.x = top_right.x; bottom_right.y = bottom_left.y; bottom_right.z = 0; #if PROFILE_ON Profiler.EndSample(); #endif #endregion // Check if we need to Shear the rectangles for Italic styles #region Handle Italic & Shearing if (m_textElementType == TMP_TextElementType.Character && !isUsingAltTypeface && ((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_TextElement.yOffset + padding + style_padding) * currentElementScale), 0, 0); Vector3 bottomShear = new Vector3(shear_value * (((m_cached_TextElement.yOffset - m_cached_TextElement.height - padding - style_padding)) * currentElementScale), 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 // Handle Character Rotation #region Handle Character Rotation if (m_isFXMatrixSet) { // Apply scale matrix when simulating Condensed text. if (m_FXMatrix.m00 != 1) { //top_left = m_FXMatrix.MultiplyPoint3x4(top_left); //bottom_left = m_FXMatrix.MultiplyPoint3x4(bottom_left); //top_right = m_FXMatrix.MultiplyPoint3x4(top_right); //bottom_right = m_FXMatrix.MultiplyPoint3x4(bottom_right); } Vector3 positionOffset = (top_right + bottom_left) / 2; top_left = m_FXMatrix.MultiplyPoint3x4(top_left - positionOffset) + positionOffset; bottom_left = m_FXMatrix.MultiplyPoint3x4(bottom_left - positionOffset) + positionOffset; top_right = m_FXMatrix.MultiplyPoint3x4(top_right - positionOffset) + positionOffset; bottom_right = m_FXMatrix.MultiplyPoint3x4(bottom_right - positionOffset) + positionOffset; } #endregion // Store vertex information 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].origin = m_xAdvance; m_textInfo.characterInfo[m_characterCount].baseLine = 0 - m_lineOffset + m_baselineOffset; m_textInfo.characterInfo[m_characterCount].aspectRatio = (top_right.x - bottom_left.x) / (top_left.y - bottom_left.y); // Compute and save text element Ascender and maximum line Ascender. float elementAscender = m_currentFontAsset.fontInfo.Ascender * (m_textElementType == TMP_TextElementType.Character ? currentElementScale / smallCapsMultiplier : m_textInfo.characterInfo[m_characterCount].scale) + m_baselineOffset; m_textInfo.characterInfo[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 / smallCapsMultiplier : m_textInfo.characterInfo[m_characterCount].scale) + m_baselineOffset; float elementDescenderII = m_textInfo.characterInfo[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_isNewPage) { m_maxAscender = m_maxAscender > elementAscender ? m_maxAscender : elementAscender; m_maxCapHeight = Mathf.Max(m_maxCapHeight, m_currentFontAsset.fontInfo.CapHeight * currentElementScale / smallCapsMultiplier); } if (m_lineOffset == 0) pageAscender = pageAscender > elementAscender ? pageAscender : elementAscender; // Set Characters to not visible by default. m_textInfo.characterInfo[m_characterCount].isVisible = false; // Setup Mesh for visible text elements. ie. not a SPACE / LINEFEED / CARRIAGE RETURN. #region Handle Visible Characters //#if PROFILE_ON //Profiler.BeginSample("Handle Visible Characters"); //#endif if (charCode == 9 || (!char.IsWhiteSpace((char)charCode) && charCode != 0x200B) || m_textElementType == TMP_TextElementType.Sprite) { m_textInfo.characterInfo[m_characterCount].isVisible = true; #region Experimental Margin Shaper //Vector2 shapedMargins; //if (marginShaper) //{ // shapedMargins = m_marginShaper.GetShapedMargins(m_textInfo.characterInfo[m_characterCount].baseLine); // if (shapedMargins.x < margins.x) // { // shapedMargins.x = m_marginLeft; // } // else // { // shapedMargins.x += m_marginLeft - margins.x; // } // if (shapedMargins.y < margins.z) // { // shapedMargins.y = m_marginRight; // } // else // { // shapedMargins.y += m_marginRight - margins.z; // } //} //else //{ // shapedMargins.x = m_marginLeft; // shapedMargins.y = m_marginRight; //} //width = marginWidth + 0.0001f - shapedMargins.x - shapedMargins.y; //if (m_width != -1 && m_width < width) //{ // width = m_width; //} //m_textInfo.lineInfo[m_lineNumber].marginLeft = shapedMargins.x; #endregion 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].marginLeft = m_marginLeft; bool isJustifiedOrFlush = ((_HorizontalAlignmentOptions)m_lineJustification & _HorizontalAlignmentOptions.Flush) == _HorizontalAlignmentOptions.Flush || ((_HorizontalAlignmentOptions)m_lineJustification & _HorizontalAlignmentOptions.Justified) == _HorizontalAlignmentOptions.Justified; // Calculate the line breaking width of the text. linebreakingWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? m_cached_TextElement.xAdvance : 0) * (1 - m_charWidthAdjDelta) * (charCode != 0xAD ? currentElementScale : old_scale); // Check if Character exceeds the width of the Text Container #region Handle Line Breaking, Text Auto-Sizing and Horizontal Overflow if (linebreakingWidth > width * (isJustifiedOrFlush ? 1.05f : 1.0f)) { ellipsisIndex = m_characterCount - 1; // Last safely rendered character // 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 == 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) { if (ignoreNonBreakingSpace == false) ignoreNonBreakingSpace = true; else 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 m_SavedWordWrapState); wrappingIndex = i; // Used to detect when line length can no longer be reduced. // Handling for Soft Hyphen if (m_char_buffer[i] == 0xAD) // && !m_isCharacterWrappingEnabled) // && ellipsisIndex != i && !m_isCharacterWrappingEnabled) { m_isTextTruncated = true; m_char_buffer[i] = 0x2D; GenerateTextMesh(); return; } //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. if (m_lineNumber > 0 && !TMP_Math.Approximately(m_maxLineAscender, m_startOfLineAscender) && m_lineHeight == TMP_Math.FLOAT_UNSET && !m_isNewPage) { //Debug.Log("(Line Break - Adjusting Line Spacing on line #" + m_lineNumber); float offsetDelta = m_maxLineAscender - m_startOfLineAscender; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, offsetDelta); m_lineOffset += offsetDelta; m_SavedWordWrapState.lineOffset = m_lineOffset; m_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; if (!isMaxVisibleDescenderSet) maxVisibleDescender = m_maxDescender; if (m_useMaxVisibleDescender && (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_firstCharacterOfLine > m_firstVisibleCharacterOfLine ? m_firstCharacterOfLine : m_firstVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex = m_lastCharacterOfLine = m_characterCount - 1 > 0 ? m_characterCount - 1 : 0; m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex = m_lastVisibleCharacterOfLine = m_lastVisibleCharacterOfLine < m_firstVisibleCharacterOfLine ? m_firstVisibleCharacterOfLine : 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].visibleCharacterCount = m_lineVisibleCharacterCount; 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].length = m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x; m_textInfo.lineInfo[m_lineNumber].width = width; //m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance - (m_characterSpacing + m_currentFontAsset.normalSpacingOffset) * currentElementScale - m_cSpacing; m_textInfo.lineInfo[m_lineNumber].baseline = 0 - m_lineOffset; m_textInfo.lineInfo[m_lineNumber].ascender = lineAscender; m_textInfo.lineInfo[m_lineNumber].descender = lineDescender; m_textInfo.lineInfo[m_lineNumber].lineHeight = lineAscender - lineDescender + lineGap * baseScale; m_firstCharacterOfLine = m_characterCount; // Store first character of the next line. m_lineVisibleCharacterCount = 0; // 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 (m_lineHeight == TMP_Math.FLOAT_UNSET) { float ascender = m_textInfo.characterInfo[m_characterCount].ascender - m_textInfo.characterInfo[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 = k_LargeNegativeFloat; m_maxLineDescender = k_LargePositiveFloat; 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 = 0; break; } m_char_buffer[i - 1] = 8230; m_char_buffer[i] = (char)0; if (m_cached_Ellipsis_GlyphInfo != null) { m_textInfo.characterInfo[ellipsisIndex].character = (char)8230; m_textInfo.characterInfo[ellipsisIndex].textElement = m_cached_Ellipsis_GlyphInfo; m_textInfo.characterInfo[ellipsisIndex].fontAsset = m_materialReferences[0].fontAsset; m_textInfo.characterInfo[ellipsisIndex].material = m_materialReferences[0].material; m_textInfo.characterInfo[ellipsisIndex].materialReferenceIndex = 0; } else { Debug.LogWarning("Unable to use Ellipsis character since it wasn't found in the current Font Asset [" + m_fontAsset.name + "]. Consider regenerating this font asset to include the Ellipsis character (u+2026).\nNote: Warnings can be disabled in the TMP Settings file.", this); } m_totalCharacterCount = ellipsisIndex + 1; 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; case TextOverflowModes.Linked: //m_textInfo.characterInfo[m_characterCount].isVisible = false; //if (m_linkedTextComponent != null) //{ // m_linkedTextComponent.text = text; // m_linkedTextComponent.firstVisibleCharacter = m_characterCount; // m_linkedTextComponent.ForceMeshUpdate(); //} break; } #endregion End Text Overflow } #endregion End Check for Characters Exceeding Width of Text Container if (charCode != 9) { // Determine Vertex Color if (m_overrideHtmlColors) vertexColor = m_fontColor32; else vertexColor = m_htmlColor; // Store Character & Sprite Vertex Information if (m_textElementType == TMP_TextElementType.Character) { // Save Character Vertex Data SaveGlyphVertexInfo(padding, style_padding, vertexColor); } else if (m_textElementType == TMP_TextElementType.Sprite) { 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 && charCode != 0xAD) { if (isStartOfNewLine) { isStartOfNewLine = false; m_firstVisibleCharacterOfLine = m_characterCount; } m_lineVisibleCharacterCount += 1; 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 == 10 || char.IsSeparator((char)charCode)) && charCode != 0xAD && charCode != 0x200B && charCode != 0x2060) { m_textInfo.lineInfo[m_lineNumber].spaceCount += 1; m_textInfo.spaceCount += 1; } } //#if PROFILE_ON //Profiler.EndSample(); //#endif #endregion Handle Visible Characters // Check if Line Spacing of previous line needs to be adjusted. #region Adjust Line Spacing #if PROFILE_ON Profiler.BeginSample("Adjust Line Spacing"); #endif if (m_lineNumber > 0 && !TMP_Math.Approximately(m_maxLineAscender, m_startOfLineAscender) && m_lineHeight == TMP_Math.FLOAT_UNSET && !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; m_SavedWordWrapState.lineOffset = m_lineOffset; m_SavedWordWrapState.previousLineAscender = m_startOfLineAscender; } #if PROFILE_ON Profiler.EndSample(); #endif #endregion // Store Rectangle positions for each Character. #region Store Character Data m_textInfo.characterInfo[m_characterCount].lineNumber = m_lineNumber; m_textInfo.characterInfo[m_characterCount].pageNumber = 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 PROFILE_ON Profiler.BeginSample("Check Vertical Bounds"); #endif if (m_maxAscender - elementDescenderII > marginHeight + 0.0001f) { // Handle Line spacing adjustments #region Line Spacing Adjustments if (m_enableAutoSizing && m_lineSpacingDelta > m_lineSpacingMax && m_lineNumber > 0) { loopCountA = 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 // Set isTextOverflowing and firstOverflowCharacterIndex if (m_firstOverflowCharacterIndex == -1) m_firstOverflowCharacterIndex = m_characterCount; // 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; if (m_cached_Ellipsis_GlyphInfo != null) { m_textInfo.characterInfo[ellipsisIndex].character = (char)8230; m_textInfo.characterInfo[ellipsisIndex].textElement = m_cached_Ellipsis_GlyphInfo; m_textInfo.characterInfo[ellipsisIndex].fontAsset = m_materialReferences[0].fontAsset; m_textInfo.characterInfo[ellipsisIndex].material = m_materialReferences[0].material; m_textInfo.characterInfo[ellipsisIndex].materialReferenceIndex = 0; } else { Debug.LogWarning("Unable to use Ellipsis character since it wasn't found in the current Font Asset [" + m_fontAsset.name + "]. Consider regenerating this font asset to include the Ellipsis character (u+2026).\nNote: Warnings can be disabled in the TMP Settings file.", this); } m_totalCharacterCount = ellipsisIndex + 1; GenerateTextMesh(); m_isTextTruncated = true; return; } else { ClearMesh(); 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; m_totalCharacterCount = ellipsisIndex + 1; GenerateTextMesh(); m_isTextTruncated = true; return; } else { ClearMesh(); return; } case TextOverflowModes.Page: if (m_isMaskingEnabled) DisableMasking(); // Ignore Page Break, Linefeed or carriage return if (charCode == 13 || charCode == 10) break; // Return if the first character doesn't fit. if (i == 0) { ClearMesh(); return; } else if (previousPageOverflowChar == i) { m_char_buffer[i] = 0; m_isTextTruncated = true; } previousPageOverflowChar = i; // Go back to previous line and re-layout i = RestoreWordWrappingState(ref m_SavedLineState); m_isNewPage = true; m_xAdvance = 0 + tag_Indent; m_lineOffset = 0; m_maxAscender = 0; pageAscender = 0; m_lineNumber += 1; m_pageNumber += 1; continue; case TextOverflowModes.Linked: if (m_linkedTextComponent != null) { m_linkedTextComponent.text = text; m_linkedTextComponent.firstVisibleCharacter = m_characterCount; m_linkedTextComponent.ForceMeshUpdate(); } // Truncate remaining text if (m_lineNumber > 0) { m_char_buffer[i] = (char)0; m_totalCharacterCount = m_characterCount; // TODO : Optimize as we should be able to end the layout phase here without having to do another pass. GenerateTextMesh(); m_isTextTruncated = true; return; } else { ClearMesh(); return; } } #endregion End Text Overflow } #if PROFILE_ON Profiler.EndSample(); #endif #endregion Check Vertical Bounds // Handle xAdvance & Tabulation Stops. Tab stops at every 25% of Font Size. #region XAdvance, Tabulation & Stops if (charCode == 9) { float tabSize = m_currentFontAsset.fontInfo.TabWidth * currentElementScale; float tabs = Mathf.Ceil(m_xAdvance / tabSize) * tabSize; m_xAdvance = tabs > m_xAdvance ? tabs : m_xAdvance + tabSize; } else if (m_monoSpacing != 0) { m_xAdvance += (m_monoSpacing - monoAdvance + ((m_characterSpacing + m_currentFontAsset.normalSpacingOffset) * currentElementScale) + m_cSpacing) * (1 - m_charWidthAdjDelta); if (char.IsWhiteSpace((char)charCode) || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentElementScale; } else if (!m_isRightToLeft) { float scaleFXMultiplier = 1; if (m_isFXMatrixSet) scaleFXMultiplier = m_FXMatrix.m00; m_xAdvance += ((m_cached_TextElement.xAdvance * scaleFXMultiplier * bold_xAdvance_multiplier + m_characterSpacing + m_currentFontAsset.normalSpacingOffset + glyphAdjustments.xAdvance) * currentElementScale + m_cSpacing) * (1 - m_charWidthAdjDelta); if (char.IsWhiteSpace((char)charCode) || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentElementScale; } else { m_xAdvance -= glyphAdjustments.xAdvance * currentElementScale; } // Store xAdvance information m_textInfo.characterInfo[m_characterCount].xAdvance = m_xAdvance; #endregion Tabulation & Stops // Handle Carriage Return #region Carriage Return if (charCode == 13) { 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 PROFILE_ON Profiler.BeginSample("Process Linefeed"); #endif 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 == TMP_Math.FLOAT_UNSET && !m_isNewPage) { //Debug.Log("Line Feed - Adjusting Line Spacing on line #" + m_lineNumber); float offsetDelta = m_maxLineAscender - m_startOfLineAscender; AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, offsetDelta); elementDescenderII -= offsetDelta; m_lineOffset += offsetDelta; } 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; if (!isMaxVisibleDescenderSet) maxVisibleDescender = m_maxDescender; if (m_useMaxVisibleDescender && (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_firstCharacterOfLine > m_firstVisibleCharacterOfLine ? m_firstCharacterOfLine : m_firstVisibleCharacterOfLine; m_textInfo.lineInfo[m_lineNumber].lastCharacterIndex = m_lastCharacterOfLine = m_characterCount; m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex = m_lastVisibleCharacterOfLine = m_lastVisibleCharacterOfLine < m_firstVisibleCharacterOfLine ? m_firstVisibleCharacterOfLine : 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].visibleCharacterCount = m_lineVisibleCharacterCount; 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].length = m_textInfo.lineInfo[m_lineNumber].lineExtents.max.x - (padding * currentElementScale); m_textInfo.lineInfo[m_lineNumber].width = width; if (m_textInfo.lineInfo[m_lineNumber].characterCount == 1) m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; if (m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].isVisible) m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance - (m_characterSpacing + m_currentFontAsset.normalSpacingOffset) * currentElementScale - m_cSpacing; else m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastCharacterOfLine].xAdvance - (m_characterSpacing + m_currentFontAsset.normalSpacingOffset) * currentElementScale - m_cSpacing; m_textInfo.lineInfo[m_lineNumber].baseline = 0 - m_lineOffset; m_textInfo.lineInfo[m_lineNumber].ascender = lineAscender; m_textInfo.lineInfo[m_lineNumber].descender = lineDescender; m_textInfo.lineInfo[m_lineNumber].lineHeight = lineAscender - lineDescender + lineGap * baseScale; m_firstCharacterOfLine = m_characterCount + 1; m_lineVisibleCharacterCount = 0; // Add new line if not last line 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; ignoreNonBreakingSpace = false; // 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 if (m_lineHeight == TMP_Math.FLOAT_UNSET) { 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 = k_LargeNegativeFloat; m_maxLineDescender = k_LargePositiveFloat; m_startOfLineAscender = elementAscender; m_xAdvance = 0 + tag_LineIndent + tag_Indent; ellipsisIndex = m_characterCount - 1; m_characterCount += 1; continue; } } #if PROFILE_ON Profiler.EndSample(); #endif #endregion Check for Linefeed or Last Character // Store Rectangle positions for each Character. #region Save CharacterInfo for the current character. #if PROFILE_ON Profiler.BeginSample("Save CharacterInfo & Extents"); #endif // Determine the bounds of the Mesh. if (m_textInfo.characterInfo[m_characterCount].isVisible) { m_meshExtents.min.x = Mathf.Min(m_meshExtents.min.x, m_textInfo.characterInfo[m_characterCount].bottomLeft.x); m_meshExtents.min.y = Mathf.Min(m_meshExtents.min.y, m_textInfo.characterInfo[m_characterCount].bottomLeft.y); m_meshExtents.max.x = Mathf.Max(m_meshExtents.max.x, m_textInfo.characterInfo[m_characterCount].topRight.x); m_meshExtents.max.y = Mathf.Max(m_meshExtents.max.y, m_textInfo.characterInfo[m_characterCount].topRight.y); //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].topRight.y)); } // Save pageInfo Data if (m_overflowMode == TextOverflowModes.Page && charCode != 13 && charCode != 10) // && m_pageNumber < 16) { // Check if we need to increase allocations for the pageInfo array. if (m_pageNumber + 1 > m_textInfo.pageInfo.Length) TMP_TextInfo.Resize(ref m_textInfo.pageInfo, m_pageNumber + 1, true); m_textInfo.pageInfo[m_pageNumber].ascender = pageAscender; m_textInfo.pageInfo[m_pageNumber].descender = elementDescender < m_textInfo.pageInfo[m_pageNumber].descender ? elementDescender : 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; } #if PROFILE_ON Profiler.EndSample(); #endif #endregion Saving CharacterInfo // Save State of Mesh Creation for handling of Word Wrapping #region Save Word Wrapping State #if PROFILE_ON Profiler.BeginSample("Save Word Wrapping State"); #endif if (m_enableWordWrapping || m_overflowMode == TextOverflowModes.Truncate || m_overflowMode == TextOverflowModes.Ellipsis) { if ((char.IsWhiteSpace((char)charCode) || charCode == 0x200B || charCode == 0x2D || charCode == 0xAD) && (!m_isNonBreakingSpace || ignoreNonBreakingSpace) && charCode != 0xA0 && charCode != 0x2011 && charCode != 0x202F && charCode != 0x2060) { // 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; } // Handling for East Asian languages else if (( charCode > 0x1100 && charCode < 0x11ff || /* Hangul Jamo */ charCode > 0x2E80 && charCode < 0x9FFF || /* CJK */ charCode > 0xA960 && charCode < 0xA97F || /* Hangul Jame Extended-A */ charCode > 0xAC00 && charCode < 0xD7FF || /* Hangul Syllables */ charCode > 0xF900 && charCode < 0xFAFF || /* CJK Compatibility Ideographs */ charCode > 0xFE30 && charCode < 0xFE4F || /* CJK Compatibility Forms */ charCode > 0xFF00 && charCode < 0xFFEF) /* CJK Halfwidth */ && !m_isNonBreakingSpace) { if (isFirstWord || isLastBreakingChar || TMP_Settings.linebreakingRules.leadingCharacters.ContainsKey(charCode) == false && (m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.ContainsKey(m_textInfo.characterInfo[m_characterCount + 1].character) == 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); } #if PROFILE_ON Profiler.EndSample(); #endif #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; //Debug.Log(m_fontSize); if (loopCountA > 20) return; // Added to debug GenerateTextMesh(); return; } #endregion End Auto-sizing Check m_isCharacterWrappingEnabled = false; #if PROFILE_PHASES_ON Profiler.EndSample(); #endif //Debug.Log("Iteration Count: " + loopCountA + ". Final Point Size: " + m_fontSize); // + " B: " + loopCountB + " C: " + loopCountC + " D: " + loopCountD); // *** PHASE II of Text Generation *** #if PROFILE_PHASES_ON Profiler.BeginSample("TMP Generate Text - Phase II"); #endif // If there are no visible characters... no need to continue if (m_characterCount == 0) // && m_visibleSpriteCount == 0) { ClearMesh(); // Event indicating the text has been regenerated. TMPro_EventManager.ON_TEXT_CHANGED(this); return; } // *** PHASE II of Text Generation *** int last_vert_index = m_materialReferences[0].referenceCount * 4; // Partial clear of the vertices array to mark unused vertices as degenerate. m_textInfo.meshInfo[0].Clear(false); // Handle Text Alignment #region Text Vertical Alignment #if PROFILE_ON Profiler.BeginSample("Vertical Text Alignment"); #endif Vector3 anchorOffset = Vector3.zero; Vector3[] corners = m_RectTransformCorners; // GetTextContainerLocalCorners(); switch (m_textAlignment) { // Top Vertically case TextAlignmentOptions.Top: case TextAlignmentOptions.TopLeft: case TextAlignmentOptions.TopRight: case TextAlignmentOptions.TopJustified: case TextAlignmentOptions.TopFlush: case TextAlignmentOptions.TopGeoAligned: if (m_overflowMode != TextOverflowModes.Page) anchorOffset = corners[1] + new Vector3(0 + margins.x, 0 - m_maxAscender - margins.y, 0); else 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: case TextAlignmentOptions.Flush: case TextAlignmentOptions.CenterGeoAligned: if (m_overflowMode != TextOverflowModes.Page) anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_maxAscender + margins.y + maxVisibleDescender - margins.w) / 2, 0); else 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: case TextAlignmentOptions.BottomFlush: case TextAlignmentOptions.BottomGeoAligned: if (m_overflowMode != TextOverflowModes.Page) anchorOffset = corners[0] + new Vector3(0 + margins.x, 0 - maxVisibleDescender + margins.w, 0); else anchorOffset = corners[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: case TextAlignmentOptions.BaselineFlush: case TextAlignmentOptions.BaselineGeoAligned: 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: case TextAlignmentOptions.MidlineFlush: case TextAlignmentOptions.MidlineGeoAligned: 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; // Capline Vertically case TextAlignmentOptions.CaplineLeft: case TextAlignmentOptions.Capline: case TextAlignmentOptions.CaplineRight: case TextAlignmentOptions.CaplineJustified: case TextAlignmentOptions.CaplineFlush: case TextAlignmentOptions.CaplineGeoAligned: anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_maxCapHeight - margins.y - margins.w) / 2, 0); break; } #if PROFILE_ON Profiler.EndSample(); #endif #endregion // Initialization for Second Pass Vector3 justificationOffset = Vector3.zero; Vector3 offset = Vector3.zero; int vert_index_X4 = 0; int sprite_index_X4 = 0; int wordCount = 0; int lineCount = 0; int lastLine = 0; bool isFirstSeperator = false; 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_previousLossyScaleY = this.transform.lossyScale.y; RenderMode canvasRenderMode = m_canvas.renderMode; float canvasScaleFactor = m_canvas.scaleFactor; Color32 underlineColor = Color.white; Color32 strikethroughColor = Color.white; Color32 highlightColor = new Color32(255, 255, 0, 64); float xScale = 0; float underlineStartScale = 0; float underlineEndScale = 0; float underlineMaxScale = 0; float underlineBaseLine = k_LargePositiveFloat; int lastPage = 0; float strikethroughPointSize = 0; float strikethroughScale = 0; float strikethroughBaseline = 0; TMP_CharacterInfo[] characterInfos = m_textInfo.characterInfo; #region Handle Line Justification & UV Mapping & Character Visibility & More for (int i = 0; i < m_characterCount; i++) { TMP_FontAsset currentFontAsset = characterInfos[i].fontAsset; char currentCharacter = characterInfos[i].character; int currentLine = characterInfos[i].lineNumber; TMP_LineInfo lineInfo = m_textInfo.lineInfo[currentLine]; lineCount = currentLine + 1; TextAlignmentOptions lineAlignment = lineInfo.alignment; // Process Line Justification #region Handle Line Justification #if PROFILE_ON Profiler.BeginSample("Horizontal Text Alignment"); #endif //if (!characterInfos[i].isIgnoringAlignment) //{ switch (lineAlignment) { case TextAlignmentOptions.TopLeft: case TextAlignmentOptions.Left: case TextAlignmentOptions.BottomLeft: case TextAlignmentOptions.BaselineLeft: case TextAlignmentOptions.MidlineLeft: case TextAlignmentOptions.CaplineLeft: if (!m_isRightToLeft) justificationOffset = new Vector3(0 + lineInfo.marginLeft, 0, 0); else justificationOffset = new Vector3(0 - lineInfo.maxAdvance, 0, 0); break; case TextAlignmentOptions.Top: case TextAlignmentOptions.Center: case TextAlignmentOptions.Bottom: case TextAlignmentOptions.Baseline: case TextAlignmentOptions.Midline: case TextAlignmentOptions.Capline: justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width / 2 - lineInfo.maxAdvance / 2, 0, 0); break; case TextAlignmentOptions.TopGeoAligned: case TextAlignmentOptions.CenterGeoAligned: case TextAlignmentOptions.BottomGeoAligned: case TextAlignmentOptions.BaselineGeoAligned: case TextAlignmentOptions.MidlineGeoAligned: case TextAlignmentOptions.CaplineGeoAligned: justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width / 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: case TextAlignmentOptions.MidlineRight: case TextAlignmentOptions.CaplineRight: if (!m_isRightToLeft) justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width - lineInfo.maxAdvance, 0, 0); else justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width, 0, 0); break; case TextAlignmentOptions.TopJustified: case TextAlignmentOptions.Justified: case TextAlignmentOptions.BottomJustified: case TextAlignmentOptions.BaselineJustified: case TextAlignmentOptions.MidlineJustified: case TextAlignmentOptions.CaplineJustified: case TextAlignmentOptions.TopFlush: case TextAlignmentOptions.Flush: case TextAlignmentOptions.BottomFlush: case TextAlignmentOptions.BaselineFlush: case TextAlignmentOptions.MidlineFlush: case TextAlignmentOptions.CaplineFlush: // Skip Zero Width Characters if (currentCharacter == 0xAD || currentCharacter == 0x200B || currentCharacter == 0x2060) break; char lastCharOfCurrentLine = characterInfos[lineInfo.lastCharacterIndex].character; bool isFlush = ((_HorizontalAlignmentOptions)lineAlignment & _HorizontalAlignmentOptions.Flush) == _HorizontalAlignmentOptions.Flush; // In Justified mode, all lines are justified except the last one. // In Flush mode, all lines are justified. if (char.IsControl(lastCharOfCurrentLine) == false && currentLine < m_lineNumber || isFlush || lineInfo.maxAdvance > lineInfo.width) { // First character of each line. if (currentLine != lastLine || i == 0 || i == m_firstVisibleCharacter) { if (!m_isRightToLeft) justificationOffset = new Vector3(lineInfo.marginLeft, 0, 0); else justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width, 0, 0); if (char.IsSeparator(currentCharacter)) isFirstSeperator = true; else isFirstSeperator = false; } else { float gap = !m_isRightToLeft ? lineInfo.width - lineInfo.maxAdvance : lineInfo.width + lineInfo.maxAdvance; int visibleCount = lineInfo.visibleCharacterCount - 1; // Get the number of spaces for each line ignoring the last character if it is not visible (ie. a space or linefeed). int spaces = characterInfos[lineInfo.lastCharacterIndex].isVisible ? lineInfo.spaceCount : lineInfo.spaceCount - 1; if (isFirstSeperator) { spaces -= 1; visibleCount += 1; } float ratio = spaces > 0 ? m_wordWrappingRatios : 1; if (spaces < 1) spaces = 1; if (currentCharacter == 9 || char.IsSeparator((char)currentCharacter)) { if (!m_isRightToLeft) justificationOffset += new Vector3(gap * (1 - ratio) / spaces, 0, 0); else justificationOffset -= new Vector3(gap * (1 - ratio) / spaces, 0, 0); } else { if (!m_isRightToLeft) justificationOffset += new Vector3(gap * ratio / visibleCount, 0, 0); else justificationOffset -= new Vector3(gap * ratio / visibleCount, 0, 0); } } } else { if (!m_isRightToLeft) justificationOffset = new Vector3(lineInfo.marginLeft, 0, 0); // Keep last line left justified. else justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width, 0, 0); // Keep last line right justified. } //Debug.Log("Char [" + (char)charCode + "] Code:" + charCode + " Line # " + currentLine + " Offset:" + justificationOffset + " # Spaces:" + lineInfo.spaceCount + " # Characters:" + lineInfo.characterCount); break; } //} #if PROFILE_ON Profiler.EndSample(); #endif #endregion End Text Justification offset = anchorOffset + justificationOffset; // Handle UV2 mapping options and packing of scale information into UV2. #region Handling of UV2 mapping & Scale packing bool isCharacterVisible = characterInfos[i].isVisible; if (isCharacterVisible) { TMP_TextElementType elementType = characterInfos[i].elementType; switch (elementType) { // CHARACTERS case TMP_TextElementType.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 #if PROFILE_ON Profiler.BeginSample("UV MAPPING"); #endif 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 - lineInfo.descender) / (lineInfo.ascender - lineInfo.descender); // + m_uvOffset.y; characterInfos[i].vertex_TL.uv2.y = (characterInfos[i].vertex_TL.position.y - lineInfo.descender) / (lineInfo.ascender - lineInfo.descender); // + m_uvOffset.y; characterInfos[i].vertex_TR.uv2.y = characterInfos[i].vertex_TL.uv2.y; characterInfos[i].vertex_BR.uv2.y = characterInfos[i].vertex_BL.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_TR.uv2.y = characterInfos[i].vertex_TL.uv2.y; characterInfos[i].vertex_BR.uv2.y = characterInfos[i].vertex_BL.uv2.y; break; case TextureMappingOptions.MatchAspect: float yDelta = (1 - ((characterInfos[i].vertex_BL.uv2.x + characterInfos[i].vertex_TR.uv2.x) / characterInfos[i].aspectRatio)) / 2; // Center of Rectangle 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; } #if PROFILE_ON Profiler.EndSample(); #endif #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 #if PROFILE_ON Profiler.BeginSample("Pack UV"); #endif xScale = characterInfos[i].scale * (1 - m_charWidthAdjDelta); if (!characterInfos[i].isUsingAlternateTypeface && (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; } // isBold is encoded in the X value and SDF Scale in Y. //Vector2 vertexData = new Vector2((characterInfos[i].style & FontStyles.Bold) == FontStyles.Bold ? 1 : 0, xScale); //characterInfos[i].vertex_BL.uv2 = vertexData; //characterInfos[i].vertex_TL.uv2 = vertexData; //characterInfos[i].vertex_TR.uv2 = vertexData; //characterInfos[i].vertex_BR.uv2 = vertexData; 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 = (int)x0; float dy = (int)y0; x0 = x0 - dx; x1 = x1 - dx; y0 = y0 - dy; y1 = y1 - dy; // Optimization to avoid having a vector2 returned from the Pack UV function. characterInfos[i].vertex_BL.uv2.x = PackUV(x0, y0); characterInfos[i].vertex_BL.uv2.y = xScale; characterInfos[i].vertex_TL.uv2.x = PackUV(x0, y1); characterInfos[i].vertex_TL.uv2.y = xScale; characterInfos[i].vertex_TR.uv2.x = PackUV(x1, y1); characterInfos[i].vertex_TR.uv2.y = xScale; characterInfos[i].vertex_BR.uv2.x = PackUV(x1, y0); characterInfos[i].vertex_BR.uv2.y = xScale; #if PROFILE_ON Profiler.EndSample(); #endif #endregion break; // SPRITES case TMP_TextElementType.Sprite: // Nothing right now break; } // Handle maxVisibleCharacters, maxVisibleLines and Overflow Page Mode. #region Handle maxVisibleCharacters / maxVisibleLines / Page Mode #if PROFILE_ON Profiler.BeginSample("Process MaxVisible Characters & Lines"); #endif if (i < m_maxVisibleCharacters && wordCount < m_maxVisibleWords && 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 && wordCount < m_maxVisibleWords && 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 = Vector3.zero; characterInfos[i].vertex_TL.position = Vector3.zero; characterInfos[i].vertex_TR.position = Vector3.zero; characterInfos[i].vertex_BR.position = Vector3.zero; characterInfos[i].isVisible = false; } #if PROFILE_ON Profiler.EndSample(); #endif #endregion // Fill Vertex Buffers for the various types of element if (elementType == TMP_TextElementType.Character) { FillCharacterVertexBuffers(i, vert_index_X4); } else if (elementType == TMP_TextElementType.Sprite) { FillSpriteVertexBuffers(i, sprite_index_X4); } } #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].origin += offset.x; m_textInfo.characterInfo[i].xAdvance += offset.x; m_textInfo.characterInfo[i].ascender += offset.y; m_textInfo.characterInfo[i].descender += offset.y; m_textInfo.characterInfo[i].baseLine += offset.y; // Update MeshExtents if (isCharacterVisible) { //m_meshExtents.min = new Vector2(Mathf.Min(m_meshExtents.min.x, m_textInfo.characterInfo[i].bottomLeft.x), Mathf.Min(m_meshExtents.min.y, m_textInfo.characterInfo[i].bottomLeft.y)); //m_meshExtents.max = new Vector2(Mathf.Max(m_meshExtents.max.x, m_textInfo.characterInfo[i].topRight.x), Mathf.Max(m_meshExtents.max.y, m_textInfo.characterInfo[i].topLeft.y)); } // Need to recompute lineExtent to account for the offset from justification. #region Adjust lineExtents resulting from alignment offset #if PROFILE_ON Profiler.BeginSample("Adjust LineExtents"); #endif if (currentLine != lastLine || i == m_characterCount - 1) { // Update the previous line's extents if (currentLine != lastLine) { m_textInfo.lineInfo[lastLine].baseline += offset.y; m_textInfo.lineInfo[lastLine].ascender += offset.y; m_textInfo.lineInfo[lastLine].descender += offset.y; 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].baseline += offset.y; m_textInfo.lineInfo[currentLine].ascender += offset.y; m_textInfo.lineInfo[currentLine].descender += offset.y; 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); } } #if PROFILE_ON Profiler.EndSample(); #endif #endregion // Track Word Count per line and for the object #region Track Word Count #if PROFILE_ON Profiler.BeginSample("Track Word Count"); #endif if (char.IsLetterOrDigit(currentCharacter) || currentCharacter == 0x2D || currentCharacter == 0xAD || currentCharacter == 0x2010 || currentCharacter == 0x2011) { if (isStartOfWord == false) { isStartOfWord = true; wordFirstChar = i; } // If last character is a word if (isStartOfWord && i == m_characterCount - 1) { int size = m_textInfo.wordInfo.Length; int index = m_textInfo.wordCount; if (m_textInfo.wordCount + 1 > size) TMP_TextInfo.Resize(ref m_textInfo.wordInfo, size + 1); wordLastChar = i; m_textInfo.wordInfo[index].firstCharacterIndex = wordFirstChar; m_textInfo.wordInfo[index].lastCharacterIndex = wordLastChar; m_textInfo.wordInfo[index].characterCount = wordLastChar - wordFirstChar + 1; m_textInfo.wordInfo[index].textComponent = this; wordCount += 1; m_textInfo.wordCount += 1; m_textInfo.lineInfo[currentLine].wordCount += 1; } } else if (isStartOfWord || i == 0 && (!char.IsPunctuation(currentCharacter) || char.IsWhiteSpace(currentCharacter) || currentCharacter == 0x200B || i == m_characterCount - 1)) { if (i > 0 && i < characterInfos.Length - 1 && i < m_characterCount && (currentCharacter == 39 || currentCharacter == 8217) && char.IsLetterOrDigit(characterInfos[i - 1].character) && char.IsLetterOrDigit(characterInfos[i + 1].character)) { } else { wordLastChar = i == m_characterCount - 1 && char.IsLetterOrDigit(currentCharacter) ? i : i - 1; isStartOfWord = false; int size = m_textInfo.wordInfo.Length; int index = m_textInfo.wordCount; if (m_textInfo.wordCount + 1 > size) TMP_TextInfo.Resize(ref m_textInfo.wordInfo, size + 1); m_textInfo.wordInfo[index].firstCharacterIndex = wordFirstChar; m_textInfo.wordInfo[index].lastCharacterIndex = wordLastChar; m_textInfo.wordInfo[index].characterCount = wordLastChar - wordFirstChar + 1; m_textInfo.wordInfo[index].textComponent = this; wordCount += 1; m_textInfo.wordCount += 1; m_textInfo.lineInfo[currentLine].wordCount += 1; } } #if PROFILE_ON Profiler.EndSample(); #endif #endregion // Setup & Handle Underline #region Underline #if PROFILE_ON Profiler.BeginSample("Process Underline & Strikethrough"); #endif // 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 (!char.IsWhiteSpace(currentCharacter) && currentCharacter != 0x200B) { underlineMaxScale = Mathf.Max(underlineMaxScale, m_textInfo.characterInfo[i].scale); underlineBaseLine = Mathf.Min(currentPage == lastPage ? underlineBaseLine : k_LargePositiveFloat, 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 && char.IsSeparator(currentCharacter)) { } 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].underlineColor; } } // 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, xScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = k_LargePositiveFloat; } else if (beginUnderline && (i == lineInfo.lastCharacterIndex || i >= lineInfo.lastVisibleCharacterIndex)) { // Terminate underline at previous visible character if space or carriage return. if (char.IsWhiteSpace(currentCharacter) || currentCharacter == 0x200B) { 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, xScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = k_LargePositiveFloat; } 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, xScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = k_LargePositiveFloat; } else if (beginUnderline && i < m_characterCount - 1 && !underlineColor.Compare(m_textInfo.characterInfo[i + 1].underlineColor)) { // End underline if underline color has changed. 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, xScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = k_LargePositiveFloat; } } 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, xScale, underlineColor); underlineMaxScale = 0; underlineBaseLine = k_LargePositiveFloat; } } #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; float strikethroughOffset = currentFontAsset.fontInfo.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 && char.IsSeparator(currentCharacter)) { } 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 + strikethroughOffset * strikethroughScale, 0); strikethroughColor = m_textInfo.characterInfo[i].strikethroughColor; 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 + strikethroughOffset * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, xScale, strikethroughColor); } else if (beginStrikethrough && i == lineInfo.lastCharacterIndex) { // Terminate Strikethrough at previous visible character if space or carriage return. if (char.IsWhiteSpace(currentCharacter) || currentCharacter == 0x200B) { int lastVisibleCharacterIndex = lineInfo.lastVisibleCharacterIndex; strikethrough_end = new Vector3(m_textInfo.characterInfo[lastVisibleCharacterIndex].topRight.x, m_textInfo.characterInfo[lastVisibleCharacterIndex].baseLine + strikethroughOffset * 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 + strikethroughOffset * strikethroughScale, 0); } beginStrikethrough = false; DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, xScale, strikethroughColor); } else if (beginStrikethrough && i < m_characterCount && (m_textInfo.characterInfo[i + 1].pointSize != strikethroughPointSize || !TMP_Math.Approximately(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 + strikethroughOffset * strikethroughScale, 0); else strikethrough_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + strikethroughOffset * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, xScale, strikethroughColor); //Debug.Log("Char [" + currentCharacter + "] at Index: " + i + " End Strikethrough POS: " + strikethrough_end + " Baseline: " + m_textInfo.characterInfo[i].baseLine.ToString("f3")); } else if (beginStrikethrough && i < m_characterCount && currentFontAsset.GetInstanceID() != characterInfos[i + 1].fontAsset.GetInstanceID()) { // Terminate Strikethrough if font asset changes. beginStrikethrough = false; strikethrough_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].baseLine + strikethroughOffset * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, xScale, strikethroughColor); } 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 + strikethroughOffset * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, xScale, strikethroughColor); } } else { // End Strikethrough if (beginStrikethrough == true) { beginStrikethrough = false; strikethrough_end = new Vector3(m_textInfo.characterInfo[i - 1].topRight.x, m_textInfo.characterInfo[i - 1].baseLine + strikethroughOffset * strikethroughScale, 0); DrawUnderlineMesh(strikethrough_start, strikethrough_end, ref last_vert_index, strikethroughScale, strikethroughScale, strikethroughScale, xScale, strikethroughColor); } } #endregion // HANDLE TEXT HIGHLIGHTING #region Text Highlighting bool isHighlight = (m_textInfo.characterInfo[i].style & FontStyles.Highlight) == FontStyles.Highlight; if (isHighlight) { bool isHighlightVisible = true; int currentPage = m_textInfo.characterInfo[i].pageNumber; if (i > m_maxVisibleCharacters || currentLine > m_maxVisibleLines || (m_overflowMode == TextOverflowModes.Page && currentPage + 1 != m_pageToDisplay)) isHighlightVisible = false; if (beginHighlight == false && isHighlightVisible == true && i <= lineInfo.lastVisibleCharacterIndex && currentCharacter != 10 && currentCharacter != 13) { if (i == lineInfo.lastVisibleCharacterIndex && char.IsSeparator(currentCharacter)) { } else { beginHighlight = true; highlight_start = k_LargePositiveVector2; highlight_end = k_LargeNegativeVector2; highlightColor = m_textInfo.characterInfo[i].highlightColor; } } if (beginHighlight) { Color32 currentHighlightColor = m_textInfo.characterInfo[i].highlightColor; bool isColorTransition = false; // Handle Highlight color changes if (!highlightColor.Compare(currentHighlightColor)) { // End drawing at the start of new highlight color to prevent a gap between highlight sections. highlight_end.x = (highlight_end.x + m_textInfo.characterInfo[i].bottomLeft.x) / 2; highlight_start.y = Mathf.Min(highlight_start.y, m_textInfo.characterInfo[i].descender); highlight_end.y = Mathf.Max(highlight_end.y, m_textInfo.characterInfo[i].ascender); DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightColor); beginHighlight = true; highlight_start = highlight_end; highlight_end = new Vector3(m_textInfo.characterInfo[i].topRight.x, m_textInfo.characterInfo[i].descender, 0); highlightColor = m_textInfo.characterInfo[i].highlightColor; isColorTransition = true; } if (!isColorTransition) { // Use the Min / Max Extents of the Highlight area to handle different character sizes and fonts. highlight_start.x = Mathf.Min(highlight_start.x, m_textInfo.characterInfo[i].bottomLeft.x); highlight_start.y = Mathf.Min(highlight_start.y, m_textInfo.characterInfo[i].descender); highlight_end.x = Mathf.Max(highlight_end.x, m_textInfo.characterInfo[i].topRight.x); highlight_end.y = Mathf.Max(highlight_end.y, m_textInfo.characterInfo[i].ascender); } } // End Highlight if text only contains one character. if (beginHighlight && m_characterCount == 1) { beginHighlight = false; DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightColor); } else if (beginHighlight && (i == lineInfo.lastCharacterIndex || i >= lineInfo.lastVisibleCharacterIndex)) { beginHighlight = false; DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightColor); } else if (beginHighlight && !isHighlightVisible) { beginHighlight = false; DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightColor); } } else { // End Highlight if (beginHighlight == true) { beginHighlight = false; DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightColor); } } #endregion #if PROFILE_ON Profiler.EndSample(); #endif #endregion lastLine = currentLine; } #endregion // METRICS ABOUT THE TEXT OBJECT m_textInfo.characterCount = m_characterCount; m_textInfo.spriteCount = m_spriteCount; m_textInfo.lineCount = lineCount; m_textInfo.wordCount = wordCount != 0 && m_characterCount > 0 ? wordCount : 1; m_textInfo.pageCount = m_pageNumber + 1; #if PROFILE_PHASES_ON Profiler.EndSample(); #endif // *** UPLOAD MESH DATA *** #if PROFILE_PHASES_ON Profiler.BeginSample("TMP Generate Text - Phase III"); #endif if (m_renderMode == TextRenderFlags.Render) { // Clear unused vertices //m_textInfo.meshInfo[0].ClearUnusedVertices(); // Must ensure the Canvas support the additon vertex attributes used by TMP. // if (m_canvas.additionalShaderChannels != (AdditionalCanvasShaderChannels)25) // m_canvas.additionalShaderChannels |= (AdditionalCanvasShaderChannels)25; // Sort the geometry of the text object if needed. if (m_geometrySortingOrder != VertexSortingOrder.Normal) m_textInfo.meshInfo[0].SortGeometry(VertexSortingOrder.Reverse); // Upload Mesh Data m_mesh.MarkDynamic(); m_mesh.vertices = m_textInfo.meshInfo[0].vertices; m_mesh.uv = m_textInfo.meshInfo[0].uvs0; m_mesh.uv2 = m_textInfo.meshInfo[0].uvs2; //m_mesh.uv4 = m_textInfo.meshInfo[0].uvs4; m_mesh.colors32 = m_textInfo.meshInfo[0].colors32; // Compute Bounds for the mesh. Manual computation is more efficient then using Mesh.recalcualteBounds. m_mesh.RecalculateBounds(); //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) + offset, new Vector3(m_meshExtents.max.x - m_meshExtents.min.x, m_meshExtents.max.y - m_meshExtents.min.y, 0)); m_canvasRenderer.SetMesh(m_mesh); // Cache CanvasRenderer color of the parent text object. Color parentBaseColor = m_canvasRenderer.GetColor(); for (int i = 1; i < m_textInfo.materialCount; i++) { // Clear unused vertices m_textInfo.meshInfo[i].ClearUnusedVertices(); if (m_subTextObjects[i] == null) continue; // Sort the geometry of the sub-text objects if needed. if (m_geometrySortingOrder != VertexSortingOrder.Normal) m_textInfo.meshInfo[i].SortGeometry(VertexSortingOrder.Reverse); //m_subTextObjects[i].mesh.MarkDynamic(); m_subTextObjects[i].mesh.vertices = m_textInfo.meshInfo[i].vertices; m_subTextObjects[i].mesh.uv = m_textInfo.meshInfo[i].uvs0; m_subTextObjects[i].mesh.uv2 = m_textInfo.meshInfo[i].uvs2; //m_subTextObjects[i].mesh.uv4 = m_textInfo.meshInfo[i].uvs4; m_subTextObjects[i].mesh.colors32 = m_textInfo.meshInfo[i].colors32; m_subTextObjects[i].mesh.RecalculateBounds(); m_subTextObjects[i].canvasRenderer.SetMesh(m_subTextObjects[i].mesh); // Set CanvasRenderer color to match the parent text object. m_subTextObjects[i].canvasRenderer.SetColor(parentBaseColor); } } // Event indicating the text has been regenerated. TMPro_EventManager.ON_TEXT_CHANGED(this); //SendOnTextChanged(); #if PROFILE_PHASES_ON Profiler.EndSample(); #endif //Debug.Log("Done Rendering Text."); } /// /// Method to return the local corners of the Text Container or RectTransform. /// /// protected override Vector3[] GetTextContainerLocalCorners() { if (m_rectTransform == null) m_rectTransform = this.rectTransform; m_rectTransform.GetLocalCorners(m_RectTransformCorners); return m_RectTransformCorners; } /// /// Method to Enable or Disable child SubMesh objects. /// /// protected override void SetActiveSubMeshes(bool state) { for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { if (m_subTextObjects[i].enabled != state) m_subTextObjects[i].enabled = state; } } /// /// Method returning the compound bounds of the text object and child sub objects. /// /// protected override Bounds GetCompoundBounds() { Bounds mainBounds = m_mesh.bounds; Vector3 min = mainBounds.min; Vector3 max = mainBounds.max; for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { Bounds subBounds = m_subTextObjects[i].mesh.bounds; min.x = min.x < subBounds.min.x ? min.x : subBounds.min.x; min.y = min.y < subBounds.min.y ? min.y : subBounds.min.y; max.x = max.x > subBounds.max.x ? max.x : subBounds.max.x; max.y = max.y > subBounds.max.y ? max.y : subBounds.max.y; } Vector3 center = (min + max) / 2; Vector2 size = max - min; return new Bounds(center, size); } //public override void UpdateGeometry() //{ //} /// /// Method to Update Scale in UV2 /// void UpdateSDFScale(float lossyScale) { //Debug.Log("Updating SDF Scale."); // Return if we don't have a valid reference to a Canvas. if (m_canvas == null) { m_canvas = GetCanvas(); if (m_canvas == null) return; } lossyScale = lossyScale == 0 ? 1 : lossyScale; float xScale = 0; float canvasScaleFactor = m_canvas.scaleFactor; if (m_canvas.renderMode == RenderMode.ScreenSpaceOverlay) xScale = lossyScale / canvasScaleFactor; else if (m_canvas.renderMode == RenderMode.ScreenSpaceCamera) xScale = m_canvas.worldCamera != null ? lossyScale : 1; else xScale = lossyScale; // Iterate through each of the characters. for (int i = 0; i < m_textInfo.characterCount; i++) { // Only update scale for visible characters. if (m_textInfo.characterInfo[i].isVisible && m_textInfo.characterInfo[i].elementType == TMP_TextElementType.Character) { float scale = xScale * m_textInfo.characterInfo[i].scale * (1 - m_charWidthAdjDelta); if (!m_textInfo.characterInfo[i].isUsingAlternateTypeface && (m_textInfo.characterInfo[i].style & FontStyles.Bold) == FontStyles.Bold) scale *= -1; int index = m_textInfo.characterInfo[i].materialReferenceIndex; int vertexIndex = m_textInfo.characterInfo[i].vertexIndex; m_textInfo.meshInfo[index].uvs2[vertexIndex + 0].y = scale; m_textInfo.meshInfo[index].uvs2[vertexIndex + 1].y = scale; m_textInfo.meshInfo[index].uvs2[vertexIndex + 2].y = scale; m_textInfo.meshInfo[index].uvs2[vertexIndex + 3].y = scale; } } // Push the updated uv2 scale information to the meshes. for (int i = 0; i < m_textInfo.materialCount; i++) { if (i == 0) { m_mesh.uv2 = m_textInfo.meshInfo[0].uvs2; m_canvasRenderer.SetMesh(m_mesh); } else { m_subTextObjects[i].mesh.uv2 = m_textInfo.meshInfo[i].uvs2; m_subTextObjects[i].canvasRenderer.SetMesh(m_subTextObjects[i].mesh); } } } // Function to offset vertices position to account for line spacing changes. protected override void AdjustLineOffset(int startIndex, int endIndex, float offset) { Vector3 vertexOffset = new Vector3(0, offset, 0); for (int i = startIndex; i <= endIndex; i++) { m_textInfo.characterInfo[i].bottomLeft -= vertexOffset; m_textInfo.characterInfo[i].topLeft -= vertexOffset; m_textInfo.characterInfo[i].topRight -= vertexOffset; m_textInfo.characterInfo[i].bottomRight -= vertexOffset; m_textInfo.characterInfo[i].ascender -= vertexOffset.y; m_textInfo.characterInfo[i].baseLine -= vertexOffset.y; m_textInfo.characterInfo[i].descender -= vertexOffset.y; if (m_textInfo.characterInfo[i].isVisible) { m_textInfo.characterInfo[i].vertex_BL.position -= vertexOffset; m_textInfo.characterInfo[i].vertex_TL.position -= vertexOffset; m_textInfo.characterInfo[i].vertex_TR.position -= vertexOffset; m_textInfo.characterInfo[i].vertex_BR.position -= vertexOffset; } } } } }