using System; using System.Collections.Generic; using UnityEngine; using UnityEngine.UI; using UnityEngine.TextCore; using UnityEngine.TextCore.LowLevel; using Unity.Profiling; using Object = UnityEngine.Object; #pragma warning disable 0414 // Disabled a few warnings related to serialized variables not used in this script but used in the editor. namespace TMPro { [DisallowMultipleComponent] [RequireComponent(typeof(MeshRenderer))] [AddComponentMenu("Mesh/TextMeshPro - Text")] [ExecuteAlways] #if UNITY_2023_2_OR_NEWER [HelpURL("https://docs.unity3d.com/Packages/com.unity.ugui@2.0/manual/TextMeshPro/index.html")] #else [HelpURL("https://docs.unity3d.com/Packages/com.unity.textmeshpro@3.2")] #endif public class TextMeshPro : TMP_Text, ILayoutElement { // Public Properties and Serializable Properties [SerializeField] internal int _SortingLayer; /// /// Sets the Renderer's sorting Layer ID /// public int sortingLayerID { get { if (renderer == null) return 0; return m_renderer.sortingLayerID; } set { if (renderer == null) return; m_renderer.sortingLayerID = value; _SortingLayerID = value; // Make sure sorting layer ID change is also reflected on sub text objects. UpdateSubMeshSortingLayerID(value); } } [SerializeField] internal int _SortingLayerID; /// /// Sets the Renderer's sorting order within the assigned layer. /// public int sortingOrder { get { if (renderer == null) return 0; return m_renderer.sortingOrder; } set { if (renderer == null) return; m_renderer.sortingOrder = value; _SortingOrder = value; // Make sure sorting order change is also reflected on sub text objects. UpdateSubMeshSortingOrder(value); } } [SerializeField] internal int _SortingOrder; /// /// Determines if the size of the text container will be adjusted to fit the text object when it is first created. /// public override bool autoSizeTextContainer { get { return m_autoSizeTextContainer; } set { if (m_autoSizeTextContainer == value) return; m_autoSizeTextContainer = value; if (m_autoSizeTextContainer) { TMP_UpdateManager.RegisterTextElementForLayoutRebuild(this); SetLayoutDirty(); } } } /// /// Returns a reference to the Text Container /// [Obsolete("The TextContainer is now obsolete. Use the RectTransform instead.")] public TextContainer textContainer { get { return null; } } /// /// Returns a reference to the Transform /// public new Transform transform { get { if (m_transform == null) m_transform = GetComponent(); return m_transform; } } #pragma warning disable 0108 /// /// Returns the rendered assigned to the text object. /// public Renderer renderer { get { if (m_renderer == null) m_renderer = GetComponent(); return m_renderer; } } /// /// Returns the mesh assigned to the text object. /// public override Mesh mesh { get { if (m_mesh == null) { m_mesh = new Mesh(); m_mesh.hideFlags = HideFlags.HideAndDontSave; } return m_mesh; } } /// /// Returns the Mesh Filter of the text object. /// public MeshFilter meshFilter { get { if (m_meshFilter == null) { m_meshFilter = GetComponent(); if (m_meshFilter == null) { m_meshFilter = gameObject.AddComponent(); m_meshFilter.hideFlags = HideFlags.HideInInspector | HideFlags.HideAndDontSave; } } return m_meshFilter; } } // MASKING RELATED PROPERTIES /// /// Sets the mask type /// public MaskingTypes maskType { get { return m_maskType; } set { m_maskType = value; SetMask(m_maskType); } } /// /// Function used to set the mask type and coordinates in World Space /// /// /// public void SetMask(MaskingTypes type, Vector4 maskCoords) { SetMask(type); SetMaskCoordinates(maskCoords); } /// /// Function used to set the mask type, coordinates and softness /// /// /// /// /// public void SetMask(MaskingTypes type, Vector4 maskCoords, float softnessX, float softnessY) { SetMask(type); SetMaskCoordinates(maskCoords, softnessX, softnessY); } /// /// Schedule rebuilding of the text geometry. /// public override void SetVerticesDirty() { //Debug.Log("***** SetVerticesDirty() called on object [" + this.name + "] at frame [" + Time.frameCount + "] *****"); if (this == null || !this.IsActive()) return; TMP_UpdateManager.RegisterTextElementForGraphicRebuild(this); } /// /// /// public override void SetLayoutDirty() { m_isPreferredWidthDirty = true; m_isPreferredHeightDirty = true; if (this == null || !this.IsActive()) return; LayoutRebuilder.MarkLayoutForRebuild(this.rectTransform); m_isLayoutDirty = true; } /// /// Schedule updating of the material used by the text object. /// public override void SetMaterialDirty() { //Debug.Log("SetMaterialDirty()"); //if (!this.IsActive()) // return; //m_isMaterialDirty = true; UpdateMaterial(); //TMP_UpdateManager.RegisterTextElementForGraphicRebuild(this); } /// /// /// public override void SetAllDirty() { SetLayoutDirty(); SetVerticesDirty(); SetMaterialDirty(); } /// /// /// /// public override void Rebuild(CanvasUpdate update) { if (this == null) return; if (update == CanvasUpdate.Prelayout) { if (m_autoSizeTextContainer) { m_rectTransform.sizeDelta = GetPreferredValues(Mathf.Infinity, Mathf.Infinity); } } else if (update == CanvasUpdate.PreRender) { this.OnPreRenderObject(); if (!m_isMaterialDirty) return; UpdateMaterial(); m_isMaterialDirty = false; } } /// /// /// protected override void UpdateMaterial() { //Debug.Log("***** UpdateMaterial() called on object ID " + GetInstanceID() + ". *****"); //if (!this.IsActive()) // return; if (renderer == null || m_sharedMaterial == null) return; // Only update the material if it has changed. if (m_renderer.sharedMaterial == null || m_renderer.sharedMaterial.GetInstanceID() != m_sharedMaterial.GetInstanceID()) m_renderer.sharedMaterial = m_sharedMaterial; } /// /// Function to be used to force recomputing of character padding when Shader / Material properties have been changed via script. /// public override void UpdateMeshPadding() { m_padding = ShaderUtilities.GetPadding(m_sharedMaterial, m_enableExtraPadding, m_isUsingBold); m_isMaskingEnabled = ShaderUtilities.IsMaskingEnabled(m_sharedMaterial); m_havePropertiesChanged = true; checkPaddingRequired = false; // Return if text object is not awake yet. if (m_textInfo == null) return; // Update sub text objects for (int i = 1; i < m_textInfo.materialCount; i++) m_subTextObjects[i].UpdateMeshPadding(m_enableExtraPadding, m_isUsingBold); } /// /// Function to force regeneration of the text object before its normal process time. This is useful when changes to the text object properties need to be applied immediately. /// /// Ignore Active State of text objects. Inactive objects are ignored by default. /// Force re-parsing of the text. public override void ForceMeshUpdate(bool ignoreActiveState = false, bool forceTextReparsing = false) { m_havePropertiesChanged = true; m_ignoreActiveState = ignoreActiveState; OnPreRenderObject(); } /// /// Function used to evaluate the length of a text string. /// /// /// public override TMP_TextInfo GetTextInfo(string text) { SetText(text); SetArraySizes(m_TextProcessingArray); m_renderMode = TextRenderFlags.DontRender; ComputeMarginSize(); GenerateTextMesh(); m_renderMode = TextRenderFlags.Render; return this.textInfo; } /// /// Function to clear the geometry of the Primary and Sub Text objects. /// public override void ClearMesh(bool updateMesh) { if (m_textInfo.meshInfo[0].mesh == null) m_textInfo.meshInfo[0].mesh = m_mesh; m_textInfo.ClearMeshInfo(updateMesh); } /// /// Event to allow users to modify the content of the text info before the text is rendered. /// public override event Action OnPreRenderText; /// /// Function to update the geometry of the main and sub text objects. /// /// /// public override void UpdateGeometry(Mesh mesh, int index) { mesh.RecalculateBounds(); } /// /// Function to upload the updated vertex data and renderer. /// public override void UpdateVertexData(TMP_VertexDataUpdateFlags flags) { int materialCount = m_textInfo.materialCount; for (int i = 0; i < materialCount; i++) { Mesh mesh; if (i == 0) mesh = m_mesh; else { // Clear unused vertices // TODO: Causes issues when sorting geometry as last vertex data attribute get wiped out. //m_textInfo.meshInfo[i].ClearUnusedVertices(); mesh = m_subTextObjects[i].mesh; } //mesh.MarkDynamic(); if ((flags & TMP_VertexDataUpdateFlags.Vertices) == TMP_VertexDataUpdateFlags.Vertices) mesh.vertices = m_textInfo.meshInfo[i].vertices; if ((flags & TMP_VertexDataUpdateFlags.Uv0) == TMP_VertexDataUpdateFlags.Uv0) mesh.SetUVs(0, m_textInfo.meshInfo[i].uvs0); if ((flags & TMP_VertexDataUpdateFlags.Uv2) == TMP_VertexDataUpdateFlags.Uv2) mesh.uv2 = m_textInfo.meshInfo[i].uvs2; //if ((flags & TMP_VertexDataUpdateFlags.Uv4) == TMP_VertexDataUpdateFlags.Uv4) // mesh.uv4 = m_textInfo.meshInfo[i].uvs4; if ((flags & TMP_VertexDataUpdateFlags.Colors32) == TMP_VertexDataUpdateFlags.Colors32) mesh.colors32 = m_textInfo.meshInfo[i].colors32; mesh.RecalculateBounds(); } } /// /// Function to upload the updated vertex data and renderer. /// public override void UpdateVertexData() { int materialCount = m_textInfo.materialCount; for (int i = 0; i < materialCount; i++) { Mesh mesh; if (i == 0) mesh = m_mesh; else { // Clear unused vertices m_textInfo.meshInfo[i].ClearUnusedVertices(); mesh = m_subTextObjects[i].mesh; } //mesh.MarkDynamic(); mesh.vertices = m_textInfo.meshInfo[i].vertices; mesh.SetUVs(0, m_textInfo.meshInfo[i].uvs0); mesh.uv2 = m_textInfo.meshInfo[i].uvs2; //mesh.uv4 = m_textInfo.meshInfo[i].uvs4; mesh.colors32 = m_textInfo.meshInfo[i].colors32; mesh.RecalculateBounds(); } } public void UpdateFontAsset() { LoadFontAsset(); } public void CalculateLayoutInputHorizontal() { } public void CalculateLayoutInputVertical() { } #region TMPro_Private [SerializeField] private bool m_hasFontAssetChanged = false; // Used to track when font properties have changed. float m_previousLossyScaleY = -1; // Used for Tracking lossy scale changes in the transform; [SerializeField] private Renderer m_renderer; private MeshFilter m_meshFilter; 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. private TMP_SubMesh[] m_subTextObjects = new TMP_SubMesh[8]; // MASKING RELATED PROPERTIES [SerializeField] private MaskingTypes m_maskType; // Matrix used to animated Env Map private Matrix4x4 m_EnvMapMatrix = new Matrix4x4(); // Text Container / RectTransform Component private Vector3[] m_RectTransformCorners = new Vector3[4]; [NonSerialized] private bool m_isRegisteredForEvents; // Profiler Marker declarations private static ProfilerMarker k_GenerateTextMarker = new ProfilerMarker("TMP Layout Text"); private static ProfilerMarker k_SetArraySizesMarker = new ProfilerMarker("TMP.SetArraySizes"); private static ProfilerMarker k_GenerateTextPhaseIMarker = new ProfilerMarker("TMP GenerateText - Phase I"); private static ProfilerMarker k_ParseMarkupTextMarker = new ProfilerMarker("TMP Parse Markup Text"); private static ProfilerMarker k_CharacterLookupMarker = new ProfilerMarker("TMP Lookup Character & Glyph Data"); private static ProfilerMarker k_HandleGPOSFeaturesMarker = new ProfilerMarker("TMP Handle GPOS Features"); private static ProfilerMarker k_CalculateVerticesPositionMarker = new ProfilerMarker("TMP Calculate Vertices Position"); private static ProfilerMarker k_ComputeTextMetricsMarker = new ProfilerMarker("TMP Compute Text Metrics"); private static ProfilerMarker k_HandleVisibleCharacterMarker = new ProfilerMarker("TMP Handle Visible Character"); private static ProfilerMarker k_HandleWhiteSpacesMarker = new ProfilerMarker("TMP Handle White Space & Control Character"); private static ProfilerMarker k_HandleHorizontalLineBreakingMarker = new ProfilerMarker("TMP Handle Horizontal Line Breaking"); private static ProfilerMarker k_HandleVerticalLineBreakingMarker = new ProfilerMarker("TMP Handle Vertical Line Breaking"); private static ProfilerMarker k_SaveGlyphVertexDataMarker = new ProfilerMarker("TMP Save Glyph Vertex Data"); private static ProfilerMarker k_ComputeCharacterAdvanceMarker = new ProfilerMarker("TMP Compute Character Advance"); private static ProfilerMarker k_HandleCarriageReturnMarker = new ProfilerMarker("TMP Handle Carriage Return"); private static ProfilerMarker k_HandleLineTerminationMarker = new ProfilerMarker("TMP Handle Line Termination"); private static ProfilerMarker k_SavePageInfoMarker = new ProfilerMarker("TMP Save Page Info"); private static ProfilerMarker k_SaveTextExtentMarker = new ProfilerMarker("TMP Save Text Extent"); private static ProfilerMarker k_SaveProcessingStatesMarker = new ProfilerMarker("TMP Save Processing States"); private static ProfilerMarker k_GenerateTextPhaseIIMarker = new ProfilerMarker("TMP GenerateText - Phase II"); private static ProfilerMarker k_GenerateTextPhaseIIIMarker = new ProfilerMarker("TMP GenerateText - Phase III"); protected override void Awake() { //Debug.Log("***** Awake() called on object ID " + GetInstanceID() + ". *****"); #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 Mesh Renderer. m_renderer = GetComponent(); if (m_renderer == null) m_renderer = gameObject.AddComponent(); // Cache Reference to RectTransform m_rectTransform = this.rectTransform; // Cache Reference to the transform; m_transform = this.transform; // Cache a reference to the Mesh Filter. m_meshFilter = GetComponent(); if (m_meshFilter == null) m_meshFilter = gameObject.AddComponent(); // Create new Mesh if necessary and cache reference to it. if (m_mesh == null) { m_mesh = new Mesh(); m_mesh.hideFlags = HideFlags.HideAndDontSave; #if DEVELOPMENT_BUILD || UNITY_EDITOR m_mesh.name = "TextMeshPro Mesh"; #endif m_meshFilter.sharedMesh = m_mesh; // Create new TextInfo for the text object. m_textInfo = new TMP_TextInfo(this); } m_meshFilter.hideFlags = HideFlags.HideInInspector | HideFlags.HideAndDontSave; #if UNITY_EDITOR // Special handling for the CanvasRenderer which used to be automatically added by the Graphic class. CanvasRenderer canvasRendererComponent = GetComponent(); if (canvasRendererComponent != null) { Debug.LogWarning("Please remove the CanvasRenderer component from the [" + this.name + "] GameObject as this component is no longer necessary.", this); canvasRendererComponent.hideFlags = HideFlags.None; } #endif // Load TMP Settings for new text object instances. LoadDefaultSettings(); // Load the font asset and assign material to renderer. LoadFontAsset(); // Allocate our initial buffers. if (m_TextProcessingArray == null) m_TextProcessingArray = new TextProcessingElement[m_max_characters]; m_cached_TextElement = new TMP_Character(); m_isFirstAllocation = true; // Set flags to ensure our text is parsed and redrawn. m_havePropertiesChanged = true; m_isAwake = true; } protected override void OnEnable() { //Debug.Log("***** OnEnable() called on object ID " + GetInstanceID() + ". *****"); // Return if Awake() has not been called on the text object. if (m_isAwake == false) return; // Register Callbacks for various events. if (!m_isRegisteredForEvents) { #if UNITY_EDITOR TMPro_EventManager.MATERIAL_PROPERTY_EVENT.Add(ON_MATERIAL_PROPERTY_CHANGED); TMPro_EventManager.FONT_PROPERTY_EVENT.Add(ON_FONT_PROPERTY_CHANGED); TMPro_EventManager.TEXTMESHPRO_PROPERTY_EVENT.Add(ON_TEXTMESHPRO_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); UnityEditor.PrefabUtility.prefabInstanceUpdated += OnPrefabInstanceUpdate; #endif m_isRegisteredForEvents = true; } // Register text object for internal updates if (m_IsTextObjectScaleStatic == false) TMP_UpdateManager.RegisterTextObjectForUpdate(this); meshFilter.sharedMesh = mesh; SetActiveSubMeshes(true); // Schedule potential text object update (if any of the properties have changed. ComputeMarginSize(); SetAllDirty(); //m_havePropertiesChanged = true; } protected override void OnDisable() { //Debug.Log("***** OnDisable() called on object ID " + GetInstanceID() + ". *****"); // Return if Awake() has not been called on the text object. if (m_isAwake == false) return; TMP_UpdateManager.UnRegisterTextElementForRebuild(this); TMP_UpdateManager.UnRegisterTextObjectForUpdate(this); meshFilter.sharedMesh = null; SetActiveSubMeshes(false); } protected override void OnDestroy() { //Debug.Log("***** OnDestroy() called on object ID " + GetInstanceID() + ". *****"); // Destroy the mesh if we have one. if (m_mesh != null) DestroyImmediate(m_mesh); // Unregister the event this object was listening to #if UNITY_EDITOR TMPro_EventManager.MATERIAL_PROPERTY_EVENT.Remove(ON_MATERIAL_PROPERTY_CHANGED); TMPro_EventManager.FONT_PROPERTY_EVENT.Remove(ON_FONT_PROPERTY_CHANGED); TMPro_EventManager.TEXTMESHPRO_PROPERTY_EVENT.Remove(ON_TEXTMESHPRO_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); UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabInstanceUpdate; #endif m_isRegisteredForEvents = false; TMP_UpdateManager.UnRegisterTextElementForRebuild(this); TMP_UpdateManager.UnRegisterTextObjectForUpdate(this); } #if UNITY_EDITOR protected override void Reset() { //Debug.Log("***** Reset() called on object ID " + GetInstanceID() + ". *****"); // Return if Awake() has not been called on the text object. if (m_isAwake == false) return; if (m_mesh != null) DestroyImmediate(m_mesh); Awake(); } protected override void OnValidate() { //Debug.Log("***** OnValidate() called on object ID " + GetInstanceID() + ". *****", this); if (m_isAwake == false) return; if (meshFilter != null && m_meshFilter.hideFlags != (HideFlags.HideInInspector | HideFlags.HideAndDontSave)) m_meshFilter.hideFlags = HideFlags.HideInInspector | HideFlags.HideAndDontSave; // Handle Font Asset changes in the inspector if (m_fontAsset == null || m_hasFontAssetChanged) { LoadFontAsset(); m_hasFontAssetChanged = false; } m_padding = GetPaddingForMaterial(); ComputeMarginSize(); m_inputSource = TextInputSources.TextInputBox; m_havePropertiesChanged = true; m_isPreferredWidthDirty = true; m_isPreferredHeightDirty = true; SetAllDirty(); } private void OnBecameVisible() { // Keep the parent text object's renderer in sync with child sub objects' renderers. SetActiveSubTextObjectRenderers(true); } private void OnBecameInvisible() { // Keep the parent text object's renderer in sync with child sub objects' renderers. SetActiveSubTextObjectRenderers(false); } /// /// Callback received when Prefabs are updated. /// /// The affected GameObject void OnPrefabInstanceUpdate(GameObject go) { // Remove Callback if this prefab has been deleted. if (this == null) { UnityEditor.PrefabUtility.prefabInstanceUpdated -= OnPrefabInstanceUpdate; return; } if (go == this.gameObject) { TMP_SubMesh[] subTextObjects = GetComponentsInChildren(); if (subTextObjects.Length > 0) { for (int i = 0; i < subTextObjects.Length; i++) m_subTextObjects[i + 1] = subTextObjects[i]; } } } // Event received when TMP resources have been loaded. void ON_RESOURCES_LOADED() { TMPro_EventManager.RESOURCE_LOAD_EVENT.Remove(ON_RESOURCES_LOADED); if (this == null) return; m_isWaitingOnResourceLoad = false; 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 + " m_renderer.sharedMaterial: " + m_renderer.sharedMaterial); if (m_renderer.sharedMaterial == null) { if (m_fontAsset != null) { m_renderer.sharedMaterial = m_fontAsset.material; Debug.LogWarning("No Material was assigned to " + name + ". " + m_fontAsset.material.name + " was assigned.", this); } else { Debug.LogWarning("No Font Asset assigned to " + name + ". Please assign a Font Asset.", this); return; } } // if (m_fontAsset.atlasTexture != null && m_fontAsset.atlasTexture.GetInstanceID() != m_renderer.sharedMaterial.GetTexture(ShaderUtilities.ID_MainTex).GetInstanceID()) // { // m_renderer.sharedMaterial = m_sharedMaterial; // //m_renderer.sharedMaterial = m_fontAsset.material; // Debug.LogWarning("Font Asset Atlas doesn't match the Atlas in the newly assigned material. Select a matching material or a different font asset.", this); // } if (m_renderer.sharedMaterial != m_sharedMaterial) { //Debug.Log("ON_MATERIAL_PROPERTY_CHANGED Called on Target ID: " + GetInstanceID() + ". Previous Material:" + m_sharedMaterial + " New Material:" + m_renderer.sharedMaterial); // on Object ID:" + GetInstanceID() + ". m_sharedMaterial: " + m_sharedMaterial.name + " m_renderer.sharedMaterial: " + m_renderer.sharedMaterial.name); m_sharedMaterial = m_renderer.sharedMaterial; } m_padding = GetPaddingForMaterial(); //m_sharedMaterialHashCode = TMP_TextUtilities.GetSimpleHashCode(m_sharedMaterial.name); UpdateMask(); ValidateEnvMapProperty(); m_havePropertiesChanged = true; SetVerticesDirty(); } // Event received when font asset properties are changed in Font Inspector void ON_FONT_PROPERTY_CHANGED(bool isChanged, Object fontAsset) { //Debug.Log("ON_FONT_PROPERTY_CHANGED event received. Target is [" + font.name + "]"); // TODO: Optimize so we don't update all text objects when font asset properties are changed. //if (MaterialReference.Contains(m_materialReferences, (TMP_FontAsset)fontAsset)) { m_havePropertiesChanged = true; UpdateMeshPadding(); SetAllDirty(); } } // Event received when UNDO / REDO Event alters the properties of the object. void ON_TEXTMESHPRO_PROPERTY_CHANGED(bool isChanged, Object textComponent) { if (textComponent == this) { //Debug.Log("Undo / Redo Event Received by Object ID:" + GetInstanceID()); m_havePropertiesChanged = true; m_padding = GetPaddingForMaterial(); ComputeMarginSize(); // Verify this change 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()); // + ". Target Object ID " + obj.GetInstanceID() + ". New Material is " + mat.name + " with ID " + mat.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.GetCorrespondingObjectFromSource(gameObject) == obj) { UnityEditor.Undo.RecordObject(this, "Material Assignment"); UnityEditor.Undo.RecordObject(m_renderer, "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; SetVerticesDirty(); } /// /// Event received when a Color Gradient Preset is modified. /// /// void ON_COLOR_GRADIENT_CHANGED(Object gradient) { m_havePropertiesChanged = true; SetVerticesDirty(); } /// /// Event received when the TMP Settings are changed. /// void ON_TMP_SETTINGS_CHANGED() { m_defaultSpriteAsset = null; m_havePropertiesChanged = 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("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; 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.characterLookupTable == null) { Debug.Log("Dictionary is Null!"); } m_sharedMaterial = m_fontAsset.material; m_sharedMaterial.SetFloat("_CullMode", 0); m_renderer.receiveShadows = false; m_renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; } else { if (m_fontAsset.characterLookupTable == null) m_fontAsset.ReadFontAssetDefinition(); // 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.atlasTexture.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; } } // Cache environment map property validation. ValidateEnvMapProperty(); m_padding = GetPaddingForMaterial(); m_isMaskingEnabled = ShaderUtilities.IsMaskingEnabled(m_sharedMaterial); // Find and cache Underline & Ellipsis characters. GetSpecialCharacters(m_fontAsset); SetMaterialDirty(); } /// /// Method to check if the environment map property is valid. /// void ValidateEnvMapProperty() { if (m_sharedMaterial != null) m_hasEnvMapProperty = m_sharedMaterial.HasProperty(ShaderUtilities.ID_EnvMap) && m_sharedMaterial.GetTexture(ShaderUtilities.ID_EnvMap) != null; else m_hasEnvMapProperty = false; } void UpdateEnvMapMatrix() { if (!m_hasEnvMapProperty) return; //Debug.Log("Updating Env Matrix..."); Vector3 rotation = m_sharedMaterial.GetVector(ShaderUtilities.ID_EnvMatrixRotation); #if !UNITY_EDITOR // The matrix property is reverted on editor save because m_sharedMaterial will be replaced with a new material instance. // Disable rotation change check if editor to handle this material change. if (m_currentEnvMapRotation == rotation) return; #endif m_currentEnvMapRotation = rotation; m_EnvMapMatrix = Matrix4x4.TRS(Vector3.zero, Quaternion.Euler(rotation), Vector3.one); m_sharedMaterial.SetMatrix(ShaderUtilities.ID_EnvMatrix, m_EnvMapMatrix); } // void SetMask(MaskingTypes maskType) { switch(maskType) { case MaskingTypes.MaskOff: m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_SOFT); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_HARD); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_TEX); break; case MaskingTypes.MaskSoft: m_sharedMaterial.EnableKeyword(ShaderUtilities.Keyword_MASK_SOFT); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_HARD); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_TEX); break; case MaskingTypes.MaskHard: m_sharedMaterial.EnableKeyword(ShaderUtilities.Keyword_MASK_HARD); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_SOFT); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_TEX); break; //case MaskingTypes.MaskTex: // m_sharedMaterial.EnableKeyword(ShaderUtilities.Keyword_MASK_TEX); // m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_HARD); // m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_SOFT); // break; } } // Method used to set the masking coordinates void SetMaskCoordinates(Vector4 coords) { m_sharedMaterial.SetVector(ShaderUtilities.ID_ClipRect, coords); } // Method used to set the masking coordinates void SetMaskCoordinates(Vector4 coords, float softX, float softY) { m_sharedMaterial.SetVector(ShaderUtilities.ID_ClipRect, coords); m_sharedMaterial.SetFloat(ShaderUtilities.ID_MaskSoftnessX, softX); m_sharedMaterial.SetFloat(ShaderUtilities.ID_MaskSoftnessY, softY); } // Enable Masking in the Shader void EnableMasking() { 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); m_isMaskingEnabled = true; UpdateMask(); } } // Enable Masking in the Shader void DisableMasking() { if (m_sharedMaterial.HasProperty(ShaderUtilities.ID_ClipRect)) { m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_SOFT); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_HARD); m_sharedMaterial.DisableKeyword(ShaderUtilities.Keyword_MASK_TEX); m_isMaskingEnabled = false; UpdateMask(); } } void UpdateMask() { //Debug.Log("UpdateMask() called."); if (!m_isMaskingEnabled) { // Release Masking Material // Re-assign Base Material return; } if (m_isMaskingEnabled && m_fontMaterial == null) { CreateMaterialInstance(); } /* if (!m_isMaskingEnabled) { //Debug.Log("Masking is not enabled."); if (m_maskingPropertyBlock != null) { m_renderer.SetPropertyBlock(null); //havePropertiesChanged = true; } return; } //else // Debug.Log("Updating Masking..."); */ // Compute Masking Coordinates & Softness //float softnessX = Mathf.Min(Mathf.Min(m_textContainer.margins.x, m_textContainer.margins.z), m_sharedMaterial.GetFloat(ShaderUtilities.ID_MaskSoftnessX)); //float softnessY = Mathf.Min(Mathf.Min(m_textContainer.margins.y, m_textContainer.margins.w), m_sharedMaterial.GetFloat(ShaderUtilities.ID_MaskSoftnessY)); //softnessX = softnessX > 0 ? softnessX : 0; //softnessY = softnessY > 0 ? softnessY : 0; //float width = (m_textContainer.width - Mathf.Max(m_textContainer.margins.x, 0) - Mathf.Max(m_textContainer.margins.z, 0)) / 2 + softnessX; //float height = (m_textContainer.height - Mathf.Max(m_textContainer.margins.y, 0) - Mathf.Max(m_textContainer.margins.w, 0)) / 2 + softnessY; //Vector2 center = new Vector2((0.5f - m_textContainer.pivot.x) * m_textContainer.width + (Mathf.Max(m_textContainer.margins.x, 0) - Mathf.Max(m_textContainer.margins.z, 0)) / 2, (0.5f - m_textContainer.pivot.y) * m_textContainer.height + (- Mathf.Max(m_textContainer.margins.y, 0) + Mathf.Max(m_textContainer.margins.w, 0)) / 2); //Vector4 mask = new Vector4(center.x, center.y, width, height); //m_fontMaterial.SetVector(ShaderUtilities.ID_ClipRect, mask); //m_fontMaterial.SetFloat(ShaderUtilities.ID_MaskSoftnessX, softnessX); //m_fontMaterial.SetFloat(ShaderUtilities.ID_MaskSoftnessY, softnessY); /* if(m_maskingPropertyBlock == null) { m_maskingPropertyBlock = new MaterialPropertyBlock(); //m_maskingPropertyBlock.AddFloat(ShaderUtilities.ID_VertexOffsetX, m_sharedMaterial.GetFloat(ShaderUtilities.ID_VertexOffsetX)); //m_maskingPropertyBlock.AddFloat(ShaderUtilities.ID_VertexOffsetY, m_sharedMaterial.GetFloat(ShaderUtilities.ID_VertexOffsetY)); //Debug.Log("Creating new MaterialPropertyBlock."); } //Debug.Log("Updating Material Property Block."); //m_maskingPropertyBlock.Clear(); m_maskingPropertyBlock.AddFloat(ShaderUtilities.ID_MaskID, m_renderer.GetInstanceID()); m_maskingPropertyBlock.AddVector(ShaderUtilities.ID_MaskCoord, mask); m_maskingPropertyBlock.AddFloat(ShaderUtilities.ID_MaskSoftnessX, softnessX); m_maskingPropertyBlock.AddFloat(ShaderUtilities.ID_MaskSoftnessY, softnessY); m_renderer.SetPropertyBlock(m_maskingPropertyBlock); */ } // Function called internally when a new material is assigned via the fontMaterial property. protected override Material GetMaterial(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_renderer == null) // m_renderer = 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(); 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_renderer == null) // m_renderer = 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++) { Texture mat_MainTex = materials[i].GetTexture(ShaderUtilities.ID_MainTex); if (i == 0) { // Only assign new material if the font atlas textures match. if ( mat_MainTex == null || mat_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 (mat_MainTex == null || mat_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) { thickness = Mathf.Clamp01(thickness); m_renderer.material.SetFloat(ShaderUtilities.ID_OutlineWidth, thickness); if (m_fontMaterial == null) m_fontMaterial = m_renderer.material; m_fontMaterial = m_renderer.material; m_sharedMaterial = m_fontMaterial; m_padding = GetPaddingForMaterial(); } // This function will create an instance of the Font Material. protected override void SetFaceColor(Color32 color) { m_renderer.material.SetColor(ShaderUtilities.ID_FaceColor, color); if (m_fontMaterial == null) m_fontMaterial = m_renderer.material; m_sharedMaterial = m_fontMaterial; } // This function will create an instance of the Font Material. protected override void SetOutlineColor(Color32 color) { m_renderer.material.SetColor(ShaderUtilities.ID_OutlineColor, color); if (m_fontMaterial == null) m_fontMaterial = m_renderer.material; //Debug.Log("Material ID:" + m_fontMaterial.GetInstanceID()); m_sharedMaterial = m_fontMaterial; } // Function used to create an instance of the material void CreateMaterialInstance() { Material mat = new Material(m_sharedMaterial); mat.shaderKeywords = m_sharedMaterial.shaderKeywords; //mat.hideFlags = HideFlags.DontSave; mat.name += " Instance"; m_fontMaterial = mat; } // Sets the Render Queue and Ztest mode protected override void SetShaderDepth() { if (m_isOverlay) { // Changing these properties results in an instance of the material //m_sharedMaterial.SetFloat(ShaderUtilities.ShaderTag_ZTestMode, 0); Material mat = m_renderer.material; //mat.renderQueue = 4000; m_sharedMaterial = mat; } else { // Should this use an instanced material? //m_sharedMaterial.SetFloat(ShaderUtilities.ShaderTag_ZTestMode, 4); Material mat = m_renderer.material; //mat.renderQueue = -1; m_sharedMaterial = mat; } } // Sets the Culling mode of the material protected override void SetCulling() { if (m_isCullingEnabled) { m_renderer.material.SetFloat("_CullMode", 2); for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { Renderer renderer = m_subTextObjects[i].renderer; if (renderer != null) { renderer.material.SetFloat(ShaderUtilities.ShaderTag_CullMode, 2); } } } else { m_renderer.material.SetFloat("_CullMode", 0); for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { Renderer renderer = m_subTextObjects[i].renderer; if (renderer != null) { renderer.material.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); } // 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. internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) { k_SetArraySizesMarker.Begin(); int spriteCount = 0; m_totalCharacterCount = 0; m_isUsingBold = false; m_isTextLayoutPhase = false; tag_NoParsing = false; m_FontStyleInternal = m_fontStyle; m_fontStyleStack.Clear(); m_FontWeightInternal = (m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold ? FontWeight.Bold : 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, ref m_materialReferences, m_materialReferenceIndexLookup); // Set allocations for the text object's TextInfo if (m_textInfo == null) m_textInfo = new TMP_TextInfo(m_InternalTextProcessingArraySize); else if (m_textInfo.characterInfo.Length < m_InternalTextProcessingArraySize) TMP_TextInfo.Resize(ref m_textInfo.characterInfo, m_InternalTextProcessingArraySize, false); m_textElementType = TMP_TextElementType.Character; // Handling for Underline special character #region Setup Underline Special Character /* GetUnderlineSpecialCharacter(m_currentFontAsset); if (m_Underline.character != null) { if (m_Underline.fontAsset.GetInstanceID() != m_currentFontAsset.GetInstanceID()) { if (TMP_Settings.matchMaterialPreset && m_currentMaterial.GetInstanceID() != m_Underline.fontAsset.material.GetInstanceID()) m_Underline.material = TMP_MaterialManager.GetFallbackMaterial(m_currentMaterial, m_Underline.fontAsset.material); else m_Underline.material = m_Underline.fontAsset.material; m_Underline.materialIndex = MaterialReference.AddMaterialReference(m_Underline.material, m_Underline.fontAsset, m_materialReferences, m_materialReferenceIndexLookup); m_materialReferences[m_Underline.materialIndex].referenceCount = 0; } } */ #endregion // Handling for Ellipsis special character #region Setup Ellipsis Special Character if (m_overflowMode == TextOverflowModes.Ellipsis) { GetEllipsisSpecialCharacter(m_currentFontAsset); if (m_Ellipsis.character != null) { if (m_Ellipsis.fontAsset.GetInstanceID() != m_currentFontAsset.GetInstanceID()) { if (TMP_Settings.matchMaterialPreset && m_currentMaterial.GetInstanceID() != m_Ellipsis.fontAsset.material.GetInstanceID()) m_Ellipsis.material = TMP_MaterialManager.GetFallbackMaterial(m_currentMaterial, m_Ellipsis.fontAsset.material); else m_Ellipsis.material = m_Ellipsis.fontAsset.material; m_Ellipsis.materialIndex = MaterialReference.AddMaterialReference(m_Ellipsis.material, m_Ellipsis.fontAsset, ref m_materialReferences, m_materialReferenceIndexLookup); m_materialReferences[m_Ellipsis.materialIndex].referenceCount = 0; } } else { m_overflowMode = TextOverflowModes.Truncate; if (!TMP_Settings.warningsDisabled) Debug.LogWarning("The character used for Ellipsis is not available in font asset [" + m_currentFontAsset.name + "] or any potential fallbacks. Switching Text Overflow mode to Truncate.", this); } } #endregion // Check if we should process Ligatures bool ligature = m_ActiveFontFeatures.Contains(OTL_FeatureTag.liga); // Clear Linked Text object if we have one. if (m_overflowMode == TextOverflowModes.Linked && m_linkedTextComponent != null && !m_isCalculatingPreferredValues) m_linkedTextComponent.text = string.Empty; // Parsing XML tags in the text for (int i = 0; i < textProcessingArray.Length && textProcessingArray[i].unicode != 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); uint unicode = textProcessingArray[i].unicode; // PARSE XML TAGS #region PARSE XML TAGS if (m_isRichText && unicode == 60) // if Char '<' { int prev_MaterialIndex = m_currentMaterialIndex; int endTagIndex; // Check if Tag is Valid if (ValidateHtmlTag(textProcessingArray, i + 1, out endTagIndex)) { int tagStartIndex = textProcessingArray[i].stringIndex; i = endTagIndex; if ((m_FontStyleInternal & 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].fontAsset = m_currentFontAsset; m_textInfo.characterInfo[m_totalCharacterCount].materialReferenceIndex = m_currentMaterialIndex; m_textInfo.characterInfo[m_totalCharacterCount].textElement = m_currentSpriteAsset.spriteCharacterTable[m_spriteIndex]; m_textInfo.characterInfo[m_totalCharacterCount].elementType = m_textElementType; m_textInfo.characterInfo[m_totalCharacterCount].index = tagStartIndex; m_textInfo.characterInfo[m_totalCharacterCount].stringLength = textProcessingArray[i].stringIndex - tagStartIndex + 1; // 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 isUsingAlternativeTypeface = false; bool isUsingFallbackOrAlternativeTypeface = false; 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_FontStyleInternal & FontStyles.UpperCase) == FontStyles.UpperCase) { // If this character is lowercase, switch to uppercase. if (char.IsLower((char)unicode)) unicode = char.ToUpper((char)unicode); } else if ((m_FontStyleInternal & FontStyles.LowerCase) == FontStyles.LowerCase) { // If this character is uppercase, switch to lowercase. if (char.IsUpper((char)unicode)) unicode = char.ToLower((char)unicode); } else if ((m_FontStyleInternal & FontStyles.SmallCaps) == FontStyles.SmallCaps) { // Only convert lowercase characters to uppercase. if (char.IsLower((char)unicode)) unicode = char.ToUpper((char)unicode); } } #endregion // Lookup the Glyph data for each character and cache it. #region LOOKUP GLYPH TMP_TextElement character = null; uint nextCharacter = i + 1 < textProcessingArray.Length ? textProcessingArray[i + 1].unicode : 0; // Check Emoji Fallback first in the event the requested unicode code point is an Emoji if (emojiFallbackSupport && ((TMP_TextParsingUtilities.IsEmojiPresentationForm(unicode) && nextCharacter != 0xFE0E) || (TMP_TextParsingUtilities.IsEmoji(unicode) && nextCharacter == 0xFE0F))) { if (TMP_Settings.emojiFallbackTextAssets != null && TMP_Settings.emojiFallbackTextAssets.Count > 0) { character = TMP_FontAssetUtilities.GetTextElementFromTextAssets(unicode, m_currentFontAsset, TMP_Settings.emojiFallbackTextAssets, true, fontStyle, fontWeight, out isUsingAlternativeTypeface); if (character != null) { // Add character to font asset lookup cache //fontAsset.AddCharacterToLookupCache(unicode, character); } } } if (character == null) character = GetTextElement(unicode, m_currentFontAsset, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); // 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); } }*/ #region MISSING CHARACTER HANDLING // Replace missing glyph by the Square (9633) glyph or possibly the Space (32) glyph. if (character == null) { DoMissingGlyphCallback((int)unicode, textProcessingArray[i].stringIndex, m_currentFontAsset); // Save the original unicode character uint srcGlyph = unicode; // Try replacing the missing glyph character by TMP Settings Missing Glyph or Square (9633) character. unicode = textProcessingArray[i].unicode = (uint)TMP_Settings.missingGlyphCharacter == 0 ? 9633 : (uint)TMP_Settings.missingGlyphCharacter; // Check for the missing glyph character in the currently assigned font asset and its fallbacks character = TMP_FontAssetUtilities.GetCharacterFromFontAsset((uint)unicode, m_currentFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); if (character == null) { // Search for the missing glyph character in the TMP Settings Fallback list. if (TMP_Settings.fallbackFontAssets != null && TMP_Settings.fallbackFontAssets.Count > 0) character = TMP_FontAssetUtilities.GetCharacterFromFontAssets(unicode, m_currentFontAsset, TMP_Settings.fallbackFontAssets, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); } if (character == null) { // Search for the missing glyph in the TMP Settings Default Font Asset. if (TMP_Settings.defaultFontAsset != null) character = TMP_FontAssetUtilities.GetCharacterFromFontAsset(unicode, TMP_Settings.defaultFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); } if (character == null) { // Use Space (32) Glyph from the currently assigned font asset. unicode = textProcessingArray[i].unicode = 32; character = TMP_FontAssetUtilities.GetCharacterFromFontAsset(unicode, m_currentFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); } if (character == null) { // Use End of Text (0x03) Glyph from the currently assigned font asset. unicode = textProcessingArray[i].unicode = 0x03; character = TMP_FontAssetUtilities.GetCharacterFromFontAsset(unicode, m_currentFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); } if (!TMP_Settings.warningsDisabled) { string formattedWarning = srcGlyph > 0xFFFF ? string.Format("The character with Unicode value \\U{0:X8} was not found in the [{1}] font asset or any potential fallbacks. It was replaced by Unicode character \\u{2:X4} in text object [{3}].", srcGlyph, m_fontAsset.name, character.unicode, this.name) : string.Format("The character with Unicode value \\u{0:X4} was not found in the [{1}] font asset or any potential fallbacks. It was replaced by Unicode character \\u{2:X4} in text object [{3}].", srcGlyph, m_fontAsset.name, character.unicode, this.name); Debug.LogWarning(formattedWarning, this); } } #endregion m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph = null; if (character.elementType == TextElementType.Character) { if (character.textAsset.instanceID != m_currentFontAsset.instanceID) { isUsingFallbackOrAlternativeTypeface = true; m_currentFontAsset = character.textAsset as TMP_FontAsset; } #region VARIATION SELECTOR if (nextCharacter >= 0xFE00 && nextCharacter <= 0xFE0F || nextCharacter >= 0xE0100 && nextCharacter <= 0xE01EF) { // Get potential variant glyph index uint variantGlyphIndex = m_currentFontAsset.GetGlyphVariantIndex((uint)unicode, nextCharacter); if (variantGlyphIndex != 0) { if (m_currentFontAsset.TryAddGlyphInternal(variantGlyphIndex, out Glyph glyph)) { m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph = glyph; } } textProcessingArray[i + 1].unicode = 0x1A; i += 1; } #endregion #region LIGATURES if (ligature && m_currentFontAsset.fontFeatureTable.m_LigatureSubstitutionRecordLookup.TryGetValue(character.glyphIndex, out List records)) { if (records == null) break; for (int j = 0; j < records.Count; j++) { LigatureSubstitutionRecord record = records[j]; int componentCount = record.componentGlyphIDs.Length; uint ligatureGlyphID = record.ligatureGlyphID; // for (int k = 1; k < componentCount; k++) { uint componentUnicode = (uint)textProcessingArray[i + k].unicode; // Special Handling for Zero Width Joiner (ZWJ) //if (componentUnicode == 0x200D) // continue; uint glyphIndex = m_currentFontAsset.GetGlyphIndex(componentUnicode); if (glyphIndex == record.componentGlyphIDs[k]) continue; ligatureGlyphID = 0; break; } if (ligatureGlyphID != 0) { if (m_currentFontAsset.TryAddGlyphInternal(ligatureGlyphID, out Glyph glyph)) { m_textInfo.characterInfo[m_totalCharacterCount].alternativeGlyph = glyph; // Update text processing array for (int c = 0; c < componentCount; c++) { if (c == 0) { textProcessingArray[i + c].length = componentCount; continue; } textProcessingArray[i + c].unicode = 0x1A; } i += componentCount - 1; break; } } } } #endregion } #endregion // Save text element data m_textInfo.characterInfo[m_totalCharacterCount].elementType = TMP_TextElementType.Character; m_textInfo.characterInfo[m_totalCharacterCount].textElement = character; m_textInfo.characterInfo[m_totalCharacterCount].isUsingAlternateTypeface = isUsingAlternativeTypeface; m_textInfo.characterInfo[m_totalCharacterCount].character = (char)unicode; m_textInfo.characterInfo[m_totalCharacterCount].index = textProcessingArray[i].stringIndex; m_textInfo.characterInfo[m_totalCharacterCount].stringLength = textProcessingArray[i].length; m_textInfo.characterInfo[m_totalCharacterCount].fontAsset = m_currentFontAsset; // Special handling if the character is a sprite. if (character.elementType == TextElementType.Sprite) { TMP_SpriteAsset spriteAssetRef = character.textAsset as TMP_SpriteAsset; m_currentMaterialIndex = MaterialReference.AddMaterialReference(spriteAssetRef.material, spriteAssetRef, ref m_materialReferences, m_materialReferenceIndexLookup); m_materialReferences[m_currentMaterialIndex].referenceCount += 1; m_textInfo.characterInfo[m_totalCharacterCount].elementType = TMP_TextElementType.Sprite; 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; } if (isUsingFallbackOrAlternativeTypeface && m_currentFontAsset.instanceID != m_fontAsset.instanceID) { // 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, ref m_materialReferences, m_materialReferenceIndexLookup); } // Handle Multi Atlas Texture support if (character != null && character.glyph.atlasIndex > 0) { m_currentMaterial = TMP_MaterialManager.GetFallbackMaterial(m_currentFontAsset, m_currentMaterial, character.glyph.atlasIndex); m_currentMaterialIndex = MaterialReference.AddMaterialReference(m_currentMaterial, m_currentFontAsset, ref m_materialReferences, m_materialReferenceIndexLookup); isUsingFallbackOrAlternativeTypeface = true; } if (!char.IsWhiteSpace((char)unicode) && unicode != 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, ref 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 = isUsingFallbackOrAlternativeTypeface; // Restore previous font asset and material if fallback font was used. if (isUsingFallbackOrAlternativeTypeface) { 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; k_SetArraySizesMarker.End(); 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_VertexBufferAutoSizeReduction && 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_SubMesh.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); // Check if the material has changed. if (m_subTextObjects[i].sharedMaterial == null || m_subTextObjects[i].sharedMaterial.GetInstanceID() != m_materialReferences[i].material.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 + 1)); } else if (m_VertexBufferAutoSizeReduction && referenceCount > 0 && m_textInfo.meshInfo[i].vertices.Length / 4 - referenceCount > 256) { // 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.NextPowerOfTwo(referenceCount + 1)); } // Assign material reference m_textInfo.meshInfo[i].material = m_materialReferences[i].material; } //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_textInfo.meshInfo[i].ClearUnusedVertices(0, true); //m_subTextObjects[i].gameObject.SetActive(false); } k_SetArraySizesMarker.End(); 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 /// public 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); Rect rect = m_rectTransform.rect; m_marginWidth = rect.width - m_margin.x - m_margin.z; m_marginHeight = rect.height - m_margin.y - m_margin.w; // Cache current RectTransform width and pivot referenced in OnRectTransformDimensionsChange() to get around potential rounding error in the reported width of the RectTransform. m_PreviousRectTransformSize = rect.size; m_PreviousPivotPosition = m_rectTransform.pivot; // Update the corners of the RectTransform m_RectTransformCorners = GetTextContainerLocalCorners(); } } /// /// /// protected override void OnDidApplyAnimationProperties() { m_havePropertiesChanged = true; isMaskUpdateRequired = true; SetVerticesDirty(); } protected override void OnTransformParentChanged() { //Debug.Log("*** OnTransformParentChanged() ***"); //ComputeMarginSize(); SetVerticesDirty(); SetLayoutDirty(); } protected override void OnRectTransformDimensionsChange() { //Debug.Log("*** OnRectTransformDimensionsChange() ***"); // Ignore changes to RectTransform SizeDelta that are very small and typically the result of rounding errors when using RectTransform in Anchor Stretch mode. if (rectTransform != null && Mathf.Abs(m_rectTransform.rect.width - m_PreviousRectTransformSize.x) < 0.0001f && Mathf.Abs(m_rectTransform.rect.height - m_PreviousRectTransformSize.y) < 0.0001f && Mathf.Abs(m_rectTransform.pivot.x - m_PreviousPivotPosition.x) < 0.0001f && Mathf.Abs(m_rectTransform.pivot.y - m_PreviousPivotPosition.y) < 0.0001f) { return; } ComputeMarginSize(); SetVerticesDirty(); SetLayoutDirty(); } /// /// Function used as a replacement for LateUpdate to check if the transform or scale of the text object has changed. /// internal override void InternalUpdate() { // We need to update the SDF scale or possibly regenerate the text object if lossy scale has changed. if (m_havePropertiesChanged == false) { float lossyScaleY = m_rectTransform.lossyScale.y; if (lossyScaleY != m_previousLossyScaleY && m_TextProcessingArray[0].unicode != 0) { float scaleDelta = lossyScaleY / m_previousLossyScaleY; // Only update SDF Scale when lossy scale has changed by more than 20% if (scaleDelta < 0.8f || scaleDelta > 1.25f) { UpdateSDFScale(scaleDelta); m_previousLossyScaleY = lossyScaleY; } } } // Added to handle legacy animation mode. if (m_isUsingLegacyAnimationComponent) { m_havePropertiesChanged = true; OnPreRenderObject(); } // Update Environment Matrix property to support changing the rotation via a script. UpdateEnvMapMatrix(); } /// /// Function called when the text needs to be updated. /// void OnPreRenderObject() { //Debug.Log("*** OnPreRenderObject() called on object [" + this.name + "] ***"); // Make sure object is active. if (!m_isAwake || (this.IsActive() == false && m_ignoreActiveState == false)) return; // 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; } if (m_havePropertiesChanged || m_isLayoutDirty) { //Debug.Log("Properties have changed!"); // Assigned Material is:" + m_sharedMaterial); // New Text is: " + m_text + "."); if (isMaskUpdateRequired) { UpdateMask(); isMaskUpdateRequired = false; } // Update mesh padding if necessary. if (checkPaddingRequired) UpdateMeshPadding(); // Reparse the text as input may have changed or been truncated. ParseInputText(); TMP_FontAsset.UpdateFontAssetsInUpdateQueue(); // 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_isTextTruncated = false; m_havePropertiesChanged = false; m_isLayoutDirty = false; m_ignoreActiveState = false; // Reset Text Auto Size iteration tracking. m_IsAutoSizePointSizeSet = false; m_AutoSizeIterationCount = 0; // Make sure state of MeshRenderer is mirrored on potential sub text objects. SetActiveSubTextObjectRenderers(m_renderer.enabled); // The GenerateTextMesh function is potentially called repeatedly when text auto size is enabled. // This is a revised implementation to remove the use of recursion which could potentially result in stack overflow issues. while (m_IsAutoSizePointSizeSet == false) { GenerateTextMesh(); m_AutoSizeIterationCount += 1; } } } /// /// This is the main function that is responsible for creating / displaying the text. /// protected virtual void GenerateTextMesh() { k_GenerateTextMarker.Begin(); // 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.characterLookupTable == null) { Debug.LogWarning("Can't Generate Mesh! No Font Asset has been assigned to Object ID: " + this.GetInstanceID()); m_IsAutoSizePointSizeSet = true; k_GenerateTextMarker.End(); return; } // Clear TextInfo if (m_textInfo != null) m_textInfo.Clear(); // Early exit if we don't have any Text to generate. if (m_TextProcessingArray == null || m_TextProcessingArray.Length == 0 || m_TextProcessingArray[0].unicode == 0) { // Clear mesh and upload changes to the mesh. ClearMesh(true); m_preferredWidth = 0; m_preferredHeight = 0; // Event indicating the text has been regenerated. TMPro_EventManager.ON_TEXT_CHANGED(this); m_IsAutoSizePointSizeSet = true; k_GenerateTextMarker.End(); 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_fontSize / m_fontAsset.m_FaceInfo.pointSize * m_fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); float currentElementScale = baseScale; float currentEmScale = m_fontSize * 0.01f * (m_isOrthographic ? 1 : 0.1f); m_fontScaleMultiplier = 1; m_currentFontSize = m_fontSize; m_sizeStack.SetDefault(m_currentFontSize); float fontSizeDelta = 0; uint charCode = 0; // Holds the character code of the currently being processed character. m_FontStyleInternal = m_fontStyle; // Set the default style. m_FontWeightInternal = (m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold ? FontWeight.Bold : m_fontWeight; m_FontWeightStack.SetDefault(m_FontWeightInternal); m_fontStyleStack.Clear(); m_lineJustification = m_HorizontalAlignment; // m_textAlignment; // Sets the line justification mode to match editor alignment. m_lineJustificationStack.SetDefault(m_lineJustification); float padding = 0; 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; 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_HighlightStateStack.SetDefault(new HighlightState(m_htmlColor, TMP_Offset.zero)); m_colorGradientPreset = null; m_colorGradientStack.SetDefault(null); m_ItalicAngle = m_currentFontAsset.italicStyle; m_ItalicAngleStack.SetDefault(m_ItalicAngle); // Clear the Style stack. //m_styleStack.Clear(); // Clear the Action stack. m_actionStack.Clear(); m_FXScale = Vector3.one; m_FXRotation = Quaternion.identity; m_lineOffset = 0; // Amount of space between lines (font line spacing + m_linespacing). m_lineHeight = TMP_Math.FLOAT_UNSET; float lineGap = m_currentFontAsset.m_FaceInfo.lineHeight - (m_currentFontAsset.m_FaceInfo.ascentLine - m_currentFontAsset.m_FaceInfo.descentLine); m_cSpacing = 0; // Amount of space added between characters as a result of the use of the tag. m_monoSpacing = 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 = m_firstVisibleCharacter; m_lastCharacterOfLine = 0; m_firstVisibleCharacterOfLine = 0; m_lastVisibleCharacterOfLine = 0; m_maxLineAscender = k_LargeNegativeFloat; m_maxLineDescender = k_LargePositiveFloat; m_lineNumber = 0; m_startOfLineAscender = 0; m_startOfLineDescender = 0; m_lineVisibleCharacterCount = 0; m_lineVisibleSpaceCount = 0; bool isStartOfNewLine = true; m_IsDrivenLineSpacing = false; m_firstOverflowCharacterIndex = -1; m_LastBaseGlyphIndex = int.MinValue; bool kerning = m_ActiveFontFeatures.Contains(OTL_FeatureTag.kern); bool markToBase = m_ActiveFontFeatures.Contains(OTL_FeatureTag.mark); bool markToMark = m_ActiveFontFeatures.Contains(OTL_FeatureTag.mkmk); m_pageNumber = 0; int pageToDisplay = Mathf.Clamp(m_pageToDisplay - 1, 0, m_textInfo.pageInfo.Length - 1); m_textInfo.ClearPageInfo(); Vector4 margins = m_margin; float marginWidth = m_marginWidth > 0 ? m_marginWidth : 0; float marginHeight = m_marginHeight > 0 ? m_marginHeight : 0; m_marginLeft = 0; m_marginRight = 0; m_width = -1; float widthOfTextArea = 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_maxTextAscender = 0; m_ElementDescender = 0; m_PageAscender = 0; float maxVisibleDescender = 0; bool isMaxVisibleDescenderSet = false; m_isNewPage = false; // Initialize struct to track states of word wrapping bool isFirstWordOfLine = true; m_isNonBreakingSpace = false; bool ignoreNonBreakingSpace = false; int lastSoftLineBreak = 0; CharacterSubstitution characterToSubstitute = new CharacterSubstitution(-1, 0); bool isSoftHyphenIgnored = false; // Save character and line state before we begin layout. SaveWordWrappingState(ref m_SavedWordWrapState, -1, -1); SaveWordWrappingState(ref m_SavedLineState, -1, -1); SaveWordWrappingState(ref m_SavedEllipsisState, -1, -1); SaveWordWrappingState(ref m_SavedLastValidState, -1, -1); SaveWordWrappingState(ref m_SavedSoftLineBreakState, -1, -1); m_EllipsisInsertionCandidateStack.Clear(); // Safety Tracker int restoreCount = 0; k_GenerateTextPhaseIMarker.Begin(); // Parse through Character buffer to read HTML tags and begin creating mesh. for (int i = 0; i < m_TextProcessingArray.Length && m_TextProcessingArray[i].unicode != 0; i++) { charCode = m_TextProcessingArray[i].unicode; if (restoreCount > 5) { Debug.LogError("Line breaking recursion max threshold hit... Character [" + charCode + "] index: " + i); characterToSubstitute.index = m_characterCount; characterToSubstitute.unicode = 0x03; } // Skip characters that have been substituted. if (charCode == 0x1A) continue; // Parse Rich Text Tag #region Parse Rich Text Tag if (m_isRichText && charCode == '<') { k_ParseMarkupTextMarker.Begin(); m_isTextLayoutPhase = true; m_textElementType = TMP_TextElementType.Character; int endTagIndex; // Check if Tag is valid. If valid, skip to the end of the validated tag. if (ValidateHtmlTag(m_TextProcessingArray, i + 1, out endTagIndex)) { i = endTagIndex; // Continue to next character or handle the sprite element if (m_textElementType == TMP_TextElementType.Character) { k_ParseMarkupTextMarker.End(); continue; } } k_ParseMarkupTextMarker.End(); } else { m_textElementType = m_textInfo.characterInfo[m_characterCount].elementType; m_currentMaterialIndex = m_textInfo.characterInfo[m_characterCount].materialReferenceIndex; m_currentFontAsset = m_textInfo.characterInfo[m_characterCount].fontAsset; } #endregion End Parse Rich Text Tag int previousMaterialIndex = m_currentMaterialIndex; bool isUsingAltTypeface = m_textInfo.characterInfo[m_characterCount].isUsingAlternateTypeface; m_isTextLayoutPhase = false; // Handle potential character substitutions #region Character Substitutions bool isInjectedCharacter = false; if (characterToSubstitute.index == m_characterCount) { charCode = characterToSubstitute.unicode; m_textElementType = TMP_TextElementType.Character; isInjectedCharacter = true; switch (charCode) { case 0x03: m_textInfo.characterInfo[m_characterCount].textElement = m_currentFontAsset.characterLookupTable[0x03]; m_isTextTruncated = true; break; case 0x2D: // break; case 0x2026: m_textInfo.characterInfo[m_characterCount].textElement = m_Ellipsis.character; m_textInfo.characterInfo[m_characterCount].elementType = TMP_TextElementType.Character; m_textInfo.characterInfo[m_characterCount].fontAsset = m_Ellipsis.fontAsset; m_textInfo.characterInfo[m_characterCount].material = m_Ellipsis.material; m_textInfo.characterInfo[m_characterCount].materialReferenceIndex = m_Ellipsis.materialIndex; // Need to increase reference count in the event the primary mesh has no characters. m_materialReferences[m_Underline.materialIndex].referenceCount += 1; // Indicates the source parsing data has been modified. m_isTextTruncated = true; // End Of Text characterToSubstitute.index = m_characterCount + 1; characterToSubstitute.unicode = 0x03; break; } } #endregion // When using Linked text, mark character as ignored and skip to next character. #region Linked Text if (m_characterCount < m_firstVisibleCharacter && charCode != 0x03) { m_textInfo.characterInfo[m_characterCount].isVisible = false; m_textInfo.characterInfo[m_characterCount].character = (char)0x200B; m_textInfo.characterInfo[m_characterCount].lineNumber = 0; m_characterCount += 1; continue; } #endregion // Handle Font Styles like LowerCase, UpperCase and SmallCaps. #region Handling of LowerCase, UpperCase and SmallCaps Font Styles float smallCapsMultiplier = 1.0f; if (m_textElementType == TMP_TextElementType.Character) { if ((m_FontStyleInternal & 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_FontStyleInternal & 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_FontStyleInternal & FontStyles.SmallCaps) == FontStyles.SmallCaps) { if (char.IsLower((char)charCode)) { smallCapsMultiplier = 0.8f; charCode = char.ToUpper((char)charCode); } } } #endregion // Look up Character Data from Dictionary and cache it. #region Look up Character Data k_CharacterLookupMarker.Begin(); float baselineOffset = 0; float elementAscentLine = 0; float elementDescentLine = 0; 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. TMP_SpriteCharacter sprite = (TMP_SpriteCharacter)textInfo.characterInfo[m_characterCount].textElement; m_currentSpriteAsset = sprite.textAsset as TMP_SpriteAsset; m_spriteIndex = (int)sprite.glyphIndex; if (sprite == null) { k_CharacterLookupMarker.End(); continue; } // Sprites are assigned in the E000 Private Area + sprite Index if (charCode == '<') charCode = 57344 + (uint)m_spriteIndex; else m_spriteColor = s_colorWhite; float fontScale = (m_currentFontSize / m_currentFontAsset.faceInfo.pointSize * m_currentFontAsset.faceInfo.scale * (m_isOrthographic ? 1 : 0.1f)); // The sprite scale calculations are based on the font asset assigned to the text object. if (m_currentSpriteAsset.m_FaceInfo.pointSize > 0) { float spriteScale = m_currentFontSize / m_currentSpriteAsset.m_FaceInfo.pointSize * m_currentSpriteAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); currentElementScale = sprite.m_Scale * sprite.m_Glyph.scale * spriteScale; elementAscentLine = m_currentSpriteAsset.m_FaceInfo.ascentLine; baselineOffset = m_currentSpriteAsset.m_FaceInfo.baseline * fontScale * m_fontScaleMultiplier * m_currentSpriteAsset.m_FaceInfo.scale; elementDescentLine = m_currentSpriteAsset.m_FaceInfo.descentLine; } else { float spriteScale = m_currentFontSize / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); currentElementScale = m_currentFontAsset.m_FaceInfo.ascentLine / sprite.m_Glyph.metrics.height * sprite.m_Scale * sprite.m_Glyph.scale * spriteScale; float scaleDelta = spriteScale / currentElementScale; elementAscentLine = m_currentFontAsset.m_FaceInfo.ascentLine * scaleDelta; baselineOffset = m_currentFontAsset.m_FaceInfo.baseline * fontScale * m_fontScaleMultiplier * m_currentFontAsset.m_FaceInfo.scale; elementDescentLine = m_currentFontAsset.m_FaceInfo.descentLine * scaleDelta; } m_cached_TextElement = sprite; m_textInfo.characterInfo[m_characterCount].elementType = TMP_TextElementType.Sprite; m_textInfo.characterInfo[m_characterCount].scale = currentElementScale; m_textInfo.characterInfo[m_characterCount].fontAsset = m_currentFontAsset; m_textInfo.characterInfo[m_characterCount].materialReferenceIndex = m_currentMaterialIndex; m_currentMaterialIndex = previousMaterialIndex; padding = 0; } else if (m_textElementType == TMP_TextElementType.Character) { m_cached_TextElement = m_textInfo.characterInfo[m_characterCount].textElement; if (m_cached_TextElement == null) { k_CharacterLookupMarker.End(); 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; // Special handling if replaced character was a line feed where in this case we have to use the scale of the previous character. float adjustedScale; if (isInjectedCharacter && m_TextProcessingArray[i].unicode == 0x0A && m_characterCount != m_firstCharacterOfLine) adjustedScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize * smallCapsMultiplier / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); else adjustedScale = m_currentFontSize * smallCapsMultiplier / m_currentFontAsset.m_FaceInfo.pointSize * m_currentFontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); // Special handling for injected Ellipsis if (isInjectedCharacter && charCode == 0x2026) { elementAscentLine = 0; elementDescentLine = 0; } else { elementAscentLine = m_currentFontAsset.m_FaceInfo.ascentLine; elementDescentLine = m_currentFontAsset.m_FaceInfo.descentLine; } currentElementScale = adjustedScale * m_fontScaleMultiplier * m_cached_TextElement.m_Scale * m_cached_TextElement.m_Glyph.scale; baselineOffset = m_currentFontAsset.m_FaceInfo.baseline * adjustedScale * m_fontScaleMultiplier * m_currentFontAsset.m_FaceInfo.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; } k_CharacterLookupMarker.End(); #endregion // Handle Soft Hyphen #region Handle Soft Hyphen float currentElementUnmodifiedScale = currentElementScale; if (charCode == 0xAD || charCode == 0x03) 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].highlightState = m_HighlightState; m_textInfo.characterInfo[m_characterCount].style = m_FontStyleInternal; // Cache glyph metrics Glyph altGlyph = m_textInfo.characterInfo[m_characterCount].alternativeGlyph; GlyphMetrics currentGlyphMetrics = altGlyph == null ? m_cached_TextElement.m_Glyph.metrics : altGlyph.metrics; // Optimization to avoid calling this more than once per character. bool isWhiteSpace = charCode <= 0xFFFF && char.IsWhiteSpace((char)charCode); // Handle Kerning if Enabled. #region Handle Kerning GlyphValueRecord glyphAdjustments = new GlyphValueRecord(); float characterSpacingAdjustment = m_characterSpacing; if (kerning && m_textElementType == TMP_TextElementType.Character) { k_HandleGPOSFeaturesMarker.Begin(); GlyphPairAdjustmentRecord adjustmentPair; uint baseGlyphIndex = m_cached_TextElement.m_GlyphIndex; if (m_characterCount < totalCharacterCount - 1 && m_textInfo.characterInfo[m_characterCount + 1].elementType == TMP_TextElementType.Character) { uint nextGlyphIndex = m_textInfo.characterInfo[m_characterCount + 1].textElement.m_GlyphIndex; uint key = nextGlyphIndex << 16 | baseGlyphIndex; if (m_currentFontAsset.m_FontFeatureTable.m_GlyphPairAdjustmentRecordLookup.TryGetValue(key, out adjustmentPair)) { glyphAdjustments = adjustmentPair.firstAdjustmentRecord.glyphValueRecord; characterSpacingAdjustment = (adjustmentPair.featureLookupFlags & UnityEngine.TextCore.LowLevel.FontFeatureLookupFlags.IgnoreSpacingAdjustments) == UnityEngine.TextCore.LowLevel.FontFeatureLookupFlags.IgnoreSpacingAdjustments ? 0 : characterSpacingAdjustment; } } if (m_characterCount >= 1) { uint previousGlyphIndex = m_textInfo.characterInfo[m_characterCount - 1].textElement.m_GlyphIndex; uint key = baseGlyphIndex << 16 | previousGlyphIndex; if (textInfo.characterInfo[m_characterCount - 1].elementType == TMP_TextElementType.Character && m_currentFontAsset.m_FontFeatureTable.m_GlyphPairAdjustmentRecordLookup.TryGetValue(key, out adjustmentPair)) { glyphAdjustments += adjustmentPair.secondAdjustmentRecord.glyphValueRecord; characterSpacingAdjustment = (adjustmentPair.featureLookupFlags & UnityEngine.TextCore.LowLevel.FontFeatureLookupFlags.IgnoreSpacingAdjustments) == UnityEngine.TextCore.LowLevel.FontFeatureLookupFlags.IgnoreSpacingAdjustments ? 0 : characterSpacingAdjustment; } } k_HandleGPOSFeaturesMarker.End(); } m_textInfo.characterInfo[m_characterCount].adjustedHorizontalAdvance = glyphAdjustments.xAdvance; #endregion // Handle Diacritical Marks #region Handle Diacritical Marks bool isBaseGlyph = TMP_TextParsingUtilities.IsBaseGlyph(charCode); if (isBaseGlyph) m_LastBaseGlyphIndex = m_characterCount; if (m_characterCount > 0 && !isBaseGlyph) { // Check for potential Mark-to-Base lookup if previous glyph was a base glyph if (markToBase && m_LastBaseGlyphIndex != int.MinValue && m_LastBaseGlyphIndex == m_characterCount - 1) { Glyph baseGlyph = m_textInfo.characterInfo[m_LastBaseGlyphIndex].textElement.glyph; uint baseGlyphIndex = baseGlyph.index; uint markGlyphIndex = m_cached_TextElement.glyphIndex; uint key = markGlyphIndex << 16 | baseGlyphIndex; if (m_currentFontAsset.fontFeatureTable.m_MarkToBaseAdjustmentRecordLookup.TryGetValue(key, out MarkToBaseAdjustmentRecord glyphAdjustmentRecord)) { float advanceOffset = (m_textInfo.characterInfo[m_LastBaseGlyphIndex].origin - m_xAdvance) / currentElementScale; glyphAdjustments.xPlacement = advanceOffset + glyphAdjustmentRecord.baseGlyphAnchorPoint.xCoordinate - glyphAdjustmentRecord.markPositionAdjustment.xPositionAdjustment; glyphAdjustments.yPlacement = glyphAdjustmentRecord.baseGlyphAnchorPoint.yCoordinate - glyphAdjustmentRecord.markPositionAdjustment.yPositionAdjustment; characterSpacingAdjustment = 0; } } else { // Iterate from previous glyph to last base glyph checking for any potential Mark-to-Mark lookups to apply. Otherwise check for potential Mark-to-Base lookup between the current glyph and last base glyph bool wasLookupApplied = false; // Check for any potential Mark-to-Mark lookups if (markToMark) { for (int characterLookupIndex = m_characterCount - 1; characterLookupIndex >= 0 && characterLookupIndex != m_LastBaseGlyphIndex; characterLookupIndex--) { // Handle any potential Mark-to-Mark lookup Glyph baseMarkGlyph = m_textInfo.characterInfo[characterLookupIndex].textElement.glyph; uint baseGlyphIndex = baseMarkGlyph.index; uint combiningMarkGlyphIndex = m_cached_TextElement.glyphIndex; uint key = combiningMarkGlyphIndex << 16 | baseGlyphIndex; if (m_currentFontAsset.fontFeatureTable.m_MarkToMarkAdjustmentRecordLookup.TryGetValue(key, out MarkToMarkAdjustmentRecord glyphAdjustmentRecord)) { float baseMarkOrigin = (m_textInfo.characterInfo[characterLookupIndex].origin - m_xAdvance) / currentElementScale; float currentBaseline = baselineOffset - m_lineOffset + m_baselineOffset; float baseMarkBaseline = (m_textInfo.characterInfo[characterLookupIndex].baseLine - currentBaseline) / currentElementScale; glyphAdjustments.xPlacement = baseMarkOrigin + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.xCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.xPositionAdjustment; glyphAdjustments.yPlacement = baseMarkBaseline + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.yCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.yPositionAdjustment; characterSpacingAdjustment = 0; wasLookupApplied = true; break; } } } // If no Mark-to-Mark lookups were applied, check for potential Mark-to-Base lookup. if (markToBase && m_LastBaseGlyphIndex != int.MinValue && !wasLookupApplied) { // Handle lookup for Mark-to-Base Glyph baseGlyph = m_textInfo.characterInfo[m_LastBaseGlyphIndex].textElement.glyph; uint baseGlyphIndex = baseGlyph.index; uint markGlyphIndex = m_cached_TextElement.glyphIndex; uint key = markGlyphIndex << 16 | baseGlyphIndex; if (m_currentFontAsset.fontFeatureTable.m_MarkToBaseAdjustmentRecordLookup.TryGetValue(key, out MarkToBaseAdjustmentRecord glyphAdjustmentRecord)) { float advanceOffset = (m_textInfo.characterInfo[m_LastBaseGlyphIndex].origin - m_xAdvance) / currentElementScale; glyphAdjustments.xPlacement = advanceOffset + glyphAdjustmentRecord.baseGlyphAnchorPoint.xCoordinate - glyphAdjustmentRecord.markPositionAdjustment.xPositionAdjustment; glyphAdjustments.yPlacement = glyphAdjustmentRecord.baseGlyphAnchorPoint.yCoordinate - glyphAdjustmentRecord.markPositionAdjustment.yPositionAdjustment; characterSpacingAdjustment = 0; } } } } // Adjust relevant text metrics elementAscentLine += glyphAdjustments.yPlacement; elementDescentLine += glyphAdjustments.yPlacement; #endregion // Initial Implementation for RTL support. #region Handle Right-to-Left if (m_isRightToLeft) { m_xAdvance -= currentGlyphMetrics.horizontalAdvance * (1 - m_charWidthAdjDelta) * currentElementScale; if (isWhiteSpace || charCode == 0x200B) m_xAdvance -= m_wordSpacing * currentEmScale; } #endregion // Handle Mono Spacing #region Handle Mono Spacing float monoAdvance = 0; if (m_monoSpacing != 0) { if (m_duoSpace && (charCode == '.' || charCode == ':' || charCode == ',')) monoAdvance = (m_monoSpacing / 4 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); else monoAdvance = (m_monoSpacing / 2 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); m_xAdvance += monoAdvance; } #endregion // Set Padding based on selected font style #region Handle Style Padding float boldSpacingAdjustment; float style_padding; if (m_textElementType == TMP_TextElementType.Character && !isUsingAltTypeface && ((m_FontStyleInternal & FontStyles.Bold) == FontStyles.Bold)) // Checks for any combination of Bold Style. { if (m_currentMaterial != null && 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; boldSpacingAdjustment = m_currentFontAsset.boldSpacing; } else { if (m_currentMaterial != null && m_currentMaterial.HasProperty(ShaderUtilities.ID_GradientScale) && m_currentMaterial.HasProperty(ShaderUtilities.ID_ScaleRatio_A)) { 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; boldSpacingAdjustment = 0; } #endregion Handle Style Padding // Determine the position of the vertices of the Character or Sprite. #region Calculate Vertices Position k_CalculateVerticesPositionMarker.Begin(); Vector3 top_left; top_left.x = m_xAdvance + ((currentGlyphMetrics.horizontalBearingX * m_FXScale.x - padding - style_padding + glyphAdjustments.xPlacement) * currentElementScale * (1 - m_charWidthAdjDelta)); top_left.y = baselineOffset + (currentGlyphMetrics.horizontalBearingY + 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 - ((currentGlyphMetrics.height + padding * 2) * currentElementScale); bottom_left.z = 0; Vector3 top_right; top_right.x = bottom_left.x + ((currentGlyphMetrics.width * m_FXScale.x + 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; k_CalculateVerticesPositionMarker.End(); #endregion // Check if we need to Shear the rectangles for Italic styles #region Handle Italic & Shearing if (m_textElementType == TMP_TextElementType.Character && !isUsingAltTypeface && ((m_FontStyleInternal & 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_ItalicAngle * 0.01f; float midPoint = ((m_currentFontAsset.m_FaceInfo.capLine - (m_currentFontAsset.m_FaceInfo.baseline + m_baselineOffset)) / 2) * m_fontScaleMultiplier * m_currentFontAsset.m_FaceInfo.scale; Vector3 topShear = new Vector3(shear_value * ((currentGlyphMetrics.horizontalBearingY + padding + style_padding - midPoint) * currentElementScale), 0, 0); Vector3 bottomShear = new Vector3(shear_value * (((currentGlyphMetrics.horizontalBearingY - currentGlyphMetrics.height - padding - style_padding - midPoint)) * currentElementScale), 0, 0); top_left += topShear; bottom_left += bottomShear; top_right += topShear; bottom_right += bottomShear; } #endregion Handle Italics & Shearing // Handle Character FX Rotation #region Handle Character FX Rotation if (m_FXRotation != Quaternion.identity) { Matrix4x4 rotationMatrix = Matrix4x4.Rotate(m_FXRotation); Vector3 positionOffset = (top_right + bottom_left) / 2; top_left = rotationMatrix.MultiplyPoint3x4(top_left - positionOffset) + positionOffset; bottom_left = rotationMatrix.MultiplyPoint3x4(bottom_left - positionOffset) + positionOffset; top_right = rotationMatrix.MultiplyPoint3x4(top_right - positionOffset) + positionOffset; bottom_right = rotationMatrix.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 + glyphAdjustments.xPlacement * currentElementScale; m_textInfo.characterInfo[m_characterCount].baseLine = (baselineOffset - m_lineOffset + m_baselineOffset) + glyphAdjustments.yPlacement * currentElementScale; m_textInfo.characterInfo[m_characterCount].aspectRatio = (top_right.x - bottom_left.x) / (top_left.y - bottom_left.y); // Compute text metrics #region Compute Ascender & Descender values k_ComputeTextMetricsMarker.Begin(); // Element Ascender in line space float elementAscender = m_textElementType == TMP_TextElementType.Character ? elementAscentLine * currentElementScale / smallCapsMultiplier + m_baselineOffset : elementAscentLine * currentElementScale + m_baselineOffset; // Element Descender in line space float elementDescender = m_textElementType == TMP_TextElementType.Character ? elementDescentLine * currentElementScale / smallCapsMultiplier + m_baselineOffset : elementDescentLine * currentElementScale + m_baselineOffset; float adjustedAscender = elementAscender; float adjustedDescender = elementDescender; // Max line ascender and descender in line space bool isFirstCharacterOfLine = m_characterCount == m_firstCharacterOfLine; if (isFirstCharacterOfLine || isWhiteSpace == false) { // Special handling for Superscript and Subscript where we use the unadjusted line ascender and descender if (m_baselineOffset != 0) { adjustedAscender = Mathf.Max((elementAscender - m_baselineOffset) / m_fontScaleMultiplier, adjustedAscender); adjustedDescender = Mathf.Min((elementDescender - m_baselineOffset) / m_fontScaleMultiplier, adjustedDescender); } m_maxLineAscender = Mathf.Max(adjustedAscender, m_maxLineAscender); m_maxLineDescender = Mathf.Min(adjustedDescender, m_maxLineDescender); } // Element Ascender and Descender in object space if (isFirstCharacterOfLine || isWhiteSpace == false) { m_textInfo.characterInfo[m_characterCount].adjustedAscender = adjustedAscender; m_textInfo.characterInfo[m_characterCount].adjustedDescender = adjustedDescender; m_ElementAscender = m_textInfo.characterInfo[m_characterCount].ascender = elementAscender - m_lineOffset; m_ElementDescender = m_textInfo.characterInfo[m_characterCount].descender = elementDescender - m_lineOffset; } else { m_textInfo.characterInfo[m_characterCount].adjustedAscender = m_maxLineAscender; m_textInfo.characterInfo[m_characterCount].adjustedDescender = m_maxLineDescender; m_ElementAscender = m_textInfo.characterInfo[m_characterCount].ascender = m_maxLineAscender - m_lineOffset; m_ElementDescender = m_textInfo.characterInfo[m_characterCount].descender = m_maxLineDescender - m_lineOffset; } // Max text object ascender and cap height if (m_lineNumber == 0 || m_isNewPage) { if (isFirstCharacterOfLine || isWhiteSpace == false) { m_maxTextAscender = m_maxLineAscender; m_maxCapHeight = Mathf.Max(m_maxCapHeight, m_currentFontAsset.m_FaceInfo.capLine * currentElementScale / smallCapsMultiplier); } } // Page ascender if (m_lineOffset == 0) { if (isFirstCharacterOfLine || isWhiteSpace == false) m_PageAscender = m_PageAscender > elementAscender ? m_PageAscender : elementAscender; } k_ComputeTextMetricsMarker.End(); #endregion // Set Characters to not visible by default. m_textInfo.characterInfo[m_characterCount].isVisible = false; bool isJustifiedOrFlush = (m_lineJustification & HorizontalAlignmentOptions.Flush) == HorizontalAlignmentOptions.Flush || (m_lineJustification & HorizontalAlignmentOptions.Justified) == HorizontalAlignmentOptions.Justified; // Setup Mesh for visible text elements. ie. not a SPACE / LINEFEED / CARRIAGE RETURN. #region Handle Visible Characters if (charCode == 9 || ((m_TextWrappingMode == TextWrappingModes.PreserveWhitespace || m_TextWrappingMode == TextWrappingModes.PreserveWhitespaceNoWrap) && (isWhiteSpace || charCode == 0x200B)) || (isWhiteSpace == false && charCode != 0x200B && charCode != 0xAD && charCode != 0x03) || (charCode == 0xAD && isSoftHyphenIgnored == false) || m_textElementType == TMP_TextElementType.Sprite) { k_HandleVisibleCharacterMarker.Begin(); 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 float marginLeft = m_marginLeft; float marginRight = m_marginRight; // Injected characters do not override margins if (isInjectedCharacter) { marginLeft = m_textInfo.lineInfo[m_lineNumber].marginLeft; marginRight = m_textInfo.lineInfo[m_lineNumber].marginRight; } widthOfTextArea = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - marginLeft - marginRight, m_width) : marginWidth + 0.0001f - marginLeft - marginRight; // Calculate the line breaking width of the text. float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? currentGlyphMetrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * (charCode == 0xAD ? currentElementUnmodifiedScale : currentElementScale); float textHeight = m_maxTextAscender - (m_maxLineDescender - m_lineOffset) + (m_lineOffset > 0 && m_IsDrivenLineSpacing == false ? m_maxLineAscender - m_startOfLineAscender : 0); int testedCharacterCount = m_characterCount; // Handling of current line Vertical Bounds #region Current Line Vertical Bounds Check if (textHeight > marginHeight + 0.0001f) { k_HandleVerticalLineBreakingMarker.Begin(); // Set isTextOverflowing and firstOverflowCharacterIndex if (m_firstOverflowCharacterIndex == -1) m_firstOverflowCharacterIndex = m_characterCount; // Check if Auto-Size is enabled if (m_enableAutoSizing) { // Handle Line spacing adjustments #region Line Spacing Adjustments if (m_lineSpacingDelta > m_lineSpacingMax && m_lineOffset > 0 && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { float adjustmentDelta = (marginHeight - textHeight) / m_lineNumber; m_lineSpacingDelta = Mathf.Max(m_lineSpacingDelta + adjustmentDelta / baseScale, m_lineSpacingMax); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Line Spacing. Delta of [" + m_lineSpacingDelta.ToString("f3") + "]."); k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion // Handle Text Auto-sizing resulting from text exceeding vertical bounds. #region Text Auto-Sizing (Text greater than vertical bounds) if (m_fontSize > m_fontSizeMin && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { m_maxFontSize = m_fontSize; float sizeDelta = Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize -= sizeDelta; m_fontSize = Mathf.Max((int)(m_fontSize * 20 + 0.5f) / 20f, m_fontSizeMin); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Point Size from [" + m_maxFontSize.ToString("f3") + "] to [" + m_fontSize.ToString("f3") + "] with delta of [" + sizeDelta.ToString("f3") + "]."); k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion Text Auto-Sizing } // Handle Vertical Overflow on current line switch (m_overflowMode) { case TextOverflowModes.Overflow: case TextOverflowModes.ScrollRect: case TextOverflowModes.Masking: // Nothing happens as vertical bounds are ignored in this mode. break; case TextOverflowModes.Truncate: i = RestoreWordWrappingState(ref m_SavedLastValidState); characterToSubstitute.index = testedCharacterCount; characterToSubstitute.unicode = 0x03; k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Ellipsis: if (m_EllipsisInsertionCandidateStack.Count == 0) { i = -1; m_characterCount = 0; characterToSubstitute.index = 0; characterToSubstitute.unicode = 0x03; m_firstCharacterOfLine = 0; k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } var ellipsisState = m_EllipsisInsertionCandidateStack.Pop(); i = RestoreWordWrappingState(ref ellipsisState); i -= 1; m_characterCount -= 1; characterToSubstitute.index = m_characterCount; characterToSubstitute.unicode = 0x2026; restoreCount += 1; k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Linked: i = RestoreWordWrappingState(ref m_SavedLastValidState); if (m_linkedTextComponent != null) { m_linkedTextComponent.text = text; m_linkedTextComponent.m_inputSource = m_inputSource; m_linkedTextComponent.firstVisibleCharacter = m_characterCount; m_linkedTextComponent.ForceMeshUpdate(); m_isTextTruncated = true; } // Truncate remaining text characterToSubstitute.index = testedCharacterCount; characterToSubstitute.unicode = 0x03; k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Page: // End layout of text if first character / page doesn't fit. if (i < 0 || testedCharacterCount == 0) { i = -1; m_characterCount = 0; characterToSubstitute.index = 0; characterToSubstitute.unicode = 0x03; k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } else if (m_maxLineAscender - m_maxLineDescender > marginHeight + 0.0001f) { // Current line exceeds the height of the text container // as such we stop on the previous line. i = RestoreWordWrappingState(ref m_SavedLineState); characterToSubstitute.index = testedCharacterCount; characterToSubstitute.unicode = 0x03; k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } // Go back to previous line and re-layout i = RestoreWordWrappingState(ref m_SavedLineState); m_isNewPage = true; m_firstCharacterOfLine = m_characterCount; m_maxLineAscender = k_LargeNegativeFloat; m_maxLineDescender = k_LargePositiveFloat; m_startOfLineAscender = 0; m_xAdvance = 0 + tag_Indent; m_lineOffset = 0; m_maxTextAscender = 0; m_PageAscender = 0; m_lineNumber += 1; m_pageNumber += 1; // Should consider saving page data here k_HandleVerticalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } k_HandleVerticalLineBreakingMarker.End(); } #endregion // Handling of Horizontal Bounds #region Current Line Horizontal Bounds Check if (isBaseGlyph && textWidth > widthOfTextArea * (isJustifiedOrFlush ? 1.05f : 1.0f)) { k_HandleHorizontalLineBreakingMarker.Begin(); // Handle Line Breaking (if still possible) if (m_TextWrappingMode != TextWrappingModes.NoWrap && m_TextWrappingMode != TextWrappingModes.PreserveWhitespaceNoWrap && m_characterCount != m_firstCharacterOfLine) { // Restore state to previous safe line breaking i = RestoreWordWrappingState(ref m_SavedWordWrapState); // Compute potential new line offset in the event a line break is needed. float lineOffsetDelta = 0; if (m_lineHeight == TMP_Math.FLOAT_UNSET) { float ascender = m_textInfo.characterInfo[m_characterCount].adjustedAscender; lineOffsetDelta = (m_lineOffset > 0 && m_IsDrivenLineSpacing == false ? m_maxLineAscender - m_startOfLineAscender : 0) - m_maxLineDescender + ascender + (lineGap + m_lineSpacingDelta) * baseScale + m_lineSpacing * currentEmScale; } else { lineOffsetDelta = m_lineHeight + m_lineSpacing * currentEmScale; m_IsDrivenLineSpacing = true; } // Calculate new text height float newTextHeight = m_maxTextAscender + lineOffsetDelta + m_lineOffset - m_textInfo.characterInfo[m_characterCount].adjustedDescender; // Replace Soft Hyphen by Hyphen Minus 0x2D #region Handle Soft Hyphenation if (m_textInfo.characterInfo[m_characterCount - 1].character == 0xAD && isSoftHyphenIgnored == false) { // Only inject Hyphen Minus if new line is possible if (m_overflowMode == TextOverflowModes.Overflow || newTextHeight < marginHeight + 0.0001f) { characterToSubstitute.index = m_characterCount - 1; characterToSubstitute.unicode = 0x2D; i -= 1; m_characterCount -= 1; k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } } isSoftHyphenIgnored = false; // Ignore Soft Hyphen to prevent it from wrapping if (m_textInfo.characterInfo[m_characterCount].character == 0xAD) { isSoftHyphenIgnored = true; k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } #endregion // Adjust character spacing before breaking up word if auto size is enabled if (m_enableAutoSizing && isFirstWordOfLine) { // Handle Character Width Adjustments #region Character Width Adjustments if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100 && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { float adjustedTextWidth = textWidth; // Determine full width of the text if (m_charWidthAdjDelta > 0) adjustedTextWidth /= 1f - m_charWidthAdjDelta; float adjustmentDelta = textWidth - (widthOfTextArea - 0.0001f) * (isJustifiedOrFlush ? 1.05f : 1.0f); m_charWidthAdjDelta += adjustmentDelta / adjustedTextWidth; m_charWidthAdjDelta = Mathf.Min(m_charWidthAdjDelta, m_charWidthMaxAdj / 100); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Character Width by " + (m_charWidthAdjDelta * 100) + "%"); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion // Handle Text Auto-sizing resulting from text exceeding vertical bounds. #region Text Auto-Sizing (Text greater than vertical bounds) if (m_fontSize > m_fontSizeMin && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { m_maxFontSize = m_fontSize; float sizeDelta = Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize -= sizeDelta; m_fontSize = Mathf.Max((int)(m_fontSize * 20 + 0.5f) / 20f, m_fontSizeMin); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Point Size from [" + m_maxFontSize.ToString("f3") + "] to [" + m_fontSize.ToString("f3") + "] with delta of [" + sizeDelta.ToString("f3") + "]."); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion Text Auto-Sizing } // Special handling if first word of line and non breaking space int savedSoftLineBreakingSpace = m_SavedSoftLineBreakState.previous_WordBreak; if (isFirstWordOfLine && savedSoftLineBreakingSpace != -1) { if (savedSoftLineBreakingSpace != lastSoftLineBreak) { i = RestoreWordWrappingState(ref m_SavedSoftLineBreakState); lastSoftLineBreak = savedSoftLineBreakingSpace; // check if soft hyphen if (m_textInfo.characterInfo[m_characterCount - 1].character == 0xAD) { characterToSubstitute.index = m_characterCount - 1; characterToSubstitute.unicode = 0x2D; i -= 1; m_characterCount -= 1; k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } } } // Determine if new line of text would exceed the vertical bounds of text container if (newTextHeight > marginHeight + 0.0001f) { k_HandleVerticalLineBreakingMarker.Begin(); // Set isTextOverflowing and firstOverflowCharacterIndex if (m_firstOverflowCharacterIndex == -1) m_firstOverflowCharacterIndex = m_characterCount; // Check if Auto-Size is enabled if (m_enableAutoSizing) { // Handle Line spacing adjustments #region Line Spacing Adjustments if (m_lineSpacingDelta > m_lineSpacingMax && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { float adjustmentDelta = (marginHeight - newTextHeight) / (m_lineNumber + 1); m_lineSpacingDelta = Mathf.Max(m_lineSpacingDelta + adjustmentDelta / baseScale, m_lineSpacingMax); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Line Spacing. Delta of [" + m_lineSpacingDelta.ToString("f3") + "]."); k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion // Handle Character Width Adjustments #region Character Width Adjustments if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100 && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { float adjustedTextWidth = textWidth; // Determine full width of the text if (m_charWidthAdjDelta > 0) adjustedTextWidth /= 1f - m_charWidthAdjDelta; float adjustmentDelta = textWidth - (widthOfTextArea - 0.0001f) * (isJustifiedOrFlush ? 1.05f : 1.0f); m_charWidthAdjDelta += adjustmentDelta / adjustedTextWidth; m_charWidthAdjDelta = Mathf.Min(m_charWidthAdjDelta, m_charWidthMaxAdj / 100); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Character Width by " + (m_charWidthAdjDelta * 100) + "%"); k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion // Handle Text Auto-sizing resulting from text exceeding vertical bounds. #region Text Auto-Sizing (Text greater than vertical bounds) if (m_fontSize > m_fontSizeMin && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { m_maxFontSize = m_fontSize; float sizeDelta = Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize -= sizeDelta; m_fontSize = Mathf.Max((int)(m_fontSize * 20 + 0.5f) / 20f, m_fontSizeMin); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Point Size from [" + m_maxFontSize.ToString("f3") + "] to [" + m_fontSize.ToString("f3") + "] with delta of [" + sizeDelta.ToString("f3") + "]."); k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion Text Auto-Sizing } // Check Text Overflow Modes switch (m_overflowMode) { case TextOverflowModes.Overflow: case TextOverflowModes.ScrollRect: case TextOverflowModes.Masking: InsertNewLine(i, baseScale, currentElementScale, currentEmScale, boldSpacingAdjustment, characterSpacingAdjustment, widthOfTextArea, lineGap, ref isMaxVisibleDescenderSet, ref maxVisibleDescender); isStartOfNewLine = true; isFirstWordOfLine = true; k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Truncate: i = RestoreWordWrappingState(ref m_SavedLastValidState); characterToSubstitute.index = testedCharacterCount; characterToSubstitute.unicode = 0x03; k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Ellipsis: if (m_EllipsisInsertionCandidateStack.Count == 0) { i = -1; m_characterCount = 0; characterToSubstitute.index = 0; characterToSubstitute.unicode = 0x03; m_firstCharacterOfLine = 0; k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } var ellipsisState = m_EllipsisInsertionCandidateStack.Pop(); i = RestoreWordWrappingState(ref ellipsisState); i -= 1; m_characterCount -= 1; characterToSubstitute.index = m_characterCount; characterToSubstitute.unicode = 0x2026; restoreCount += 1; k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Linked: if (m_linkedTextComponent != null) { m_linkedTextComponent.text = text; m_linkedTextComponent.m_inputSource = m_inputSource; m_linkedTextComponent.firstVisibleCharacter = m_characterCount; m_linkedTextComponent.ForceMeshUpdate(); m_isTextTruncated = true; } // Truncate remaining text characterToSubstitute.index = m_characterCount; characterToSubstitute.unicode = 0x03; k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Page: // Add new page m_isNewPage = true; InsertNewLine(i, baseScale, currentElementScale, currentEmScale, boldSpacingAdjustment, characterSpacingAdjustment, widthOfTextArea, lineGap, ref isMaxVisibleDescenderSet, ref maxVisibleDescender); m_startOfLineAscender = 0; m_lineOffset = 0; m_maxTextAscender = 0; m_PageAscender = 0; m_pageNumber += 1; isStartOfNewLine = true; isFirstWordOfLine = true; k_HandleVerticalLineBreakingMarker.End(); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } } else { //if (m_enableAutoSizing && isFirstWordOfLine) //{ // // Handle Character Width Adjustments // #region Character Width Adjustments // if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100 && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) // { // //m_AutoSizeIterationCount = 0; // float adjustedTextWidth = textWidth; // // Determine full width of the text // if (m_charWidthAdjDelta > 0) // adjustedTextWidth /= 1f - m_charWidthAdjDelta; // float adjustmentDelta = textWidth - (widthOfTextArea - 0.0001f) * (isJustifiedOrFlush ? 1.05f : 1.0f); // m_charWidthAdjDelta += adjustmentDelta / adjustedTextWidth; // m_charWidthAdjDelta = Mathf.Min(m_charWidthAdjDelta, m_charWidthMaxAdj / 100); // //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Character Width by " + (m_charWidthAdjDelta * 100) + "%"); // GenerateTextMesh(); // return; // } // #endregion //} // New line of text does not exceed vertical bounds of text container InsertNewLine(i, baseScale, currentElementScale, currentEmScale, boldSpacingAdjustment, characterSpacingAdjustment, widthOfTextArea, lineGap, ref isMaxVisibleDescenderSet, ref maxVisibleDescender); isStartOfNewLine = true; isFirstWordOfLine = true; k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } } else { if (m_enableAutoSizing && m_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { // Handle Character Width Adjustments #region Character Width Adjustments if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100) { float adjustedTextWidth = textWidth; // Determine full width of the text if (m_charWidthAdjDelta > 0) adjustedTextWidth /= 1f - m_charWidthAdjDelta; float adjustmentDelta = textWidth - (widthOfTextArea - 0.0001f) * (isJustifiedOrFlush ? 1.05f : 1.0f); m_charWidthAdjDelta += adjustmentDelta / adjustedTextWidth; m_charWidthAdjDelta = Mathf.Min(m_charWidthAdjDelta, m_charWidthMaxAdj / 100); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Character Width by " + (m_charWidthAdjDelta * 100) + "%"); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion // Handle Text Auto-sizing resulting from text exceeding horizontal bounds. #region Text Exceeds Horizontal Bounds - Reducing Point Size if (m_fontSize > m_fontSizeMin) { // Reset character width adjustment delta //m_charWidthAdjDelta = 0; // Adjust Point Size m_maxFontSize = m_fontSize; float sizeDelta = Mathf.Max((m_fontSize - m_minFontSize) / 2, 0.05f); m_fontSize -= sizeDelta; m_fontSize = Mathf.Max((int)(m_fontSize * 20 + 0.5f) / 20f, m_fontSizeMin); //Debug.Log("[" + m_AutoSizeIterationCount + "] Reducing Point Size from [" + m_maxFontSize.ToString("f3") + "] to [" + m_fontSize.ToString("f3") + "] with delta of [" + sizeDelta.ToString("f3") + "]."); k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion } // Check Text Overflow Modes switch (m_overflowMode) { case TextOverflowModes.Overflow: case TextOverflowModes.ScrollRect: case TextOverflowModes.Masking: // Nothing happens as horizontal bounds are ignored in this mode. break; case TextOverflowModes.Truncate: i = RestoreWordWrappingState(ref m_SavedWordWrapState); characterToSubstitute.index = testedCharacterCount; characterToSubstitute.unicode = 0x03; k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Ellipsis: if (m_EllipsisInsertionCandidateStack.Count == 0) { i = -1; m_characterCount = 0; characterToSubstitute.index = 0; characterToSubstitute.unicode = 0x03; m_firstCharacterOfLine = 0; k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } var ellipsisState = m_EllipsisInsertionCandidateStack.Pop(); i = RestoreWordWrappingState(ref ellipsisState); i -= 1; m_characterCount -= 1; characterToSubstitute.index = m_characterCount; characterToSubstitute.unicode = 0x2026; restoreCount += 1; k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; case TextOverflowModes.Linked: i = RestoreWordWrappingState(ref m_SavedWordWrapState); if (m_linkedTextComponent != null) { m_linkedTextComponent.text = text; m_linkedTextComponent.m_inputSource = m_inputSource; m_linkedTextComponent.firstVisibleCharacter = m_characterCount; m_linkedTextComponent.ForceMeshUpdate(); m_isTextTruncated = true; } // Truncate text the overflows the vertical bounds characterToSubstitute.index = m_characterCount; characterToSubstitute.unicode = 0x03; k_HandleHorizontalLineBreakingMarker.End(); k_HandleVisibleCharacterMarker.End(); continue; } } k_HandleHorizontalLineBreakingMarker.End(); } #endregion // Special handling of characters that are not ignored at the end of a line. if (isWhiteSpace) { m_textInfo.characterInfo[m_characterCount].isVisible = false; m_lastVisibleCharacterOfLine = m_characterCount; m_lineVisibleSpaceCount = m_textInfo.lineInfo[m_lineNumber].spaceCount += 1; m_textInfo.lineInfo[m_lineNumber].marginLeft = marginLeft; m_textInfo.lineInfo[m_lineNumber].marginRight = marginRight; m_textInfo.spaceCount += 1; if (charCode == 0xA0) m_textInfo.lineInfo[m_lineNumber].controlCharacterCount += 1; } else if (charCode == 0xAD) { m_textInfo.characterInfo[m_characterCount].isVisible = false; } else { // Determine Vertex Color Color32 vertexColor; if (m_overrideHtmlColors) vertexColor = m_fontColor32; else vertexColor = m_htmlColor; k_SaveGlyphVertexDataMarker.Begin(); // 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); } k_SaveGlyphVertexDataMarker.End(); if (isStartOfNewLine) { isStartOfNewLine = false; m_firstVisibleCharacterOfLine = m_characterCount; } m_lineVisibleCharacterCount += 1; m_lastVisibleCharacterOfLine = m_characterCount; m_textInfo.lineInfo[m_lineNumber].marginLeft = marginLeft; m_textInfo.lineInfo[m_lineNumber].marginRight = marginRight; } k_HandleVisibleCharacterMarker.End(); } else { k_HandleWhiteSpacesMarker.Begin(); // Special handling for text overflow linked mode #region Check Vertical Bounds if (m_overflowMode == TextOverflowModes.Linked && (charCode == 10 || charCode == 11)) { float textHeight = m_maxTextAscender - (m_maxLineDescender - m_lineOffset) + (m_lineOffset > 0 && m_IsDrivenLineSpacing == false ? m_maxLineAscender - m_startOfLineAscender : 0); int testedCharacterCount = m_characterCount; if (textHeight > marginHeight + 0.0001f) { // Set isTextOverflowing and firstOverflowCharacterIndex if (m_firstOverflowCharacterIndex == -1) m_firstOverflowCharacterIndex = m_characterCount; i = RestoreWordWrappingState(ref m_SavedLastValidState); if (m_linkedTextComponent != null) { m_linkedTextComponent.text = text; m_linkedTextComponent.m_inputSource = m_inputSource; m_linkedTextComponent.firstVisibleCharacter = m_characterCount; m_linkedTextComponent.ForceMeshUpdate(); m_isTextTruncated = true; } // Truncate remaining text characterToSubstitute.index = testedCharacterCount; characterToSubstitute.unicode = 0x03; k_HandleWhiteSpacesMarker.End(); continue; } } #endregion // Track # of spaces per line which is used for line justification. if ((charCode == 10 || charCode == 11 || charCode == 0xA0 || charCode == 0x2007 || charCode == 0x2028 || charCode == 0x2029 || char.IsSeparator((char)charCode)) && charCode != 0xAD && charCode != 0x200B && charCode != 0x2060) { m_textInfo.lineInfo[m_lineNumber].spaceCount += 1; m_textInfo.spaceCount += 1; } // Special handling for control characters like if (charCode == 0xA0) m_textInfo.lineInfo[m_lineNumber].controlCharacterCount += 1; k_HandleWhiteSpacesMarker.End(); } #endregion Handle Visible Characters // Tracking of potential insertion positions for Ellipsis character #region Track Potential Insertion Location for Ellipsis if (m_overflowMode == TextOverflowModes.Ellipsis && (isInjectedCharacter == false || charCode == 0x2D)) { float fontScale = m_currentFontSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); float scale = fontScale * m_fontScaleMultiplier * m_Ellipsis.character.m_Scale * m_Ellipsis.character.m_Glyph.scale; float marginLeft = m_marginLeft; float marginRight = m_marginRight; // Use the scale and margins of the previous character if Line Feed (LF) is not the first character of a line. if (charCode == 0x0A && m_characterCount != m_firstCharacterOfLine) { fontScale = m_textInfo.characterInfo[m_characterCount - 1].pointSize / m_Ellipsis.fontAsset.m_FaceInfo.pointSize * m_Ellipsis.fontAsset.m_FaceInfo.scale * (m_isOrthographic ? 1 : 0.1f); scale = fontScale * m_fontScaleMultiplier * m_Ellipsis.character.m_Scale * m_Ellipsis.character.m_Glyph.scale; marginLeft = m_textInfo.lineInfo[m_lineNumber].marginLeft; marginRight = m_textInfo.lineInfo[m_lineNumber].marginRight; } float textHeight = m_maxTextAscender - (m_maxLineDescender - m_lineOffset) + (m_lineOffset > 0 && m_IsDrivenLineSpacing == false ? m_maxLineAscender - m_startOfLineAscender : 0); float textWidth = Mathf.Abs(m_xAdvance) + (!m_isRightToLeft ? m_Ellipsis.character.m_Glyph.metrics.horizontalAdvance : 0) * (1 - m_charWidthAdjDelta) * scale; float widthOfTextAreaForEllipsis = m_width != -1 ? Mathf.Min(marginWidth + 0.0001f - marginLeft - marginRight, m_width) : marginWidth + 0.0001f - marginLeft - marginRight; if (textWidth < widthOfTextAreaForEllipsis * (isJustifiedOrFlush ? 1.05f : 1.0f) && textHeight < marginHeight + 0.0001f) { SaveWordWrappingState(ref m_SavedEllipsisState, i, m_characterCount); m_EllipsisInsertionCandidateStack.Push(m_SavedEllipsisState); } } #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 != 11 && charCode != 13 && isInjectedCharacter == false /* && charCode != 8230 */ || m_textInfo.lineInfo[m_lineNumber].characterCount == 1) m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; #endregion Store Character Data // Handle xAdvance & Tabulation Stops. Tab stops at every 25% of Font Size. #region XAdvance, Tabulation & Stops k_ComputeCharacterAdvanceMarker.Begin(); if (charCode == 9) { float tabSize = m_currentFontAsset.m_FaceInfo.tabWidth * m_currentFontAsset.tabSize * currentElementScale; // Adjust horizontal tab depending on RTL if (m_isRightToLeft) { float tabs = Mathf.Floor(m_xAdvance / tabSize) * tabSize; m_xAdvance = tabs < m_xAdvance ? tabs : m_xAdvance - tabSize; } else { float tabs = Mathf.Ceil(m_xAdvance / tabSize) * tabSize; m_xAdvance = tabs > m_xAdvance ? tabs : m_xAdvance + tabSize; } } else if (m_monoSpacing != 0) { float monoAdjustment; if (m_duoSpace && (charCode == '.' || charCode == ':' || charCode == ',')) monoAdjustment = m_monoSpacing / 2 - monoAdvance; else monoAdjustment = m_monoSpacing - monoAdvance; m_xAdvance += (monoAdjustment + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta); if (isWhiteSpace || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentEmScale; } else if (m_isRightToLeft) { m_xAdvance -= ((glyphAdjustments.xAdvance * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta)); if (isWhiteSpace || charCode == 0x200B) m_xAdvance -= m_wordSpacing * currentEmScale; } else { m_xAdvance += ((currentGlyphMetrics.horizontalAdvance * m_FXScale.x + glyphAdjustments.xAdvance) * currentElementScale + (m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta); if (isWhiteSpace || charCode == 0x200B) m_xAdvance += m_wordSpacing * currentEmScale; } // Store xAdvance information m_textInfo.characterInfo[m_characterCount].xAdvance = m_xAdvance; k_ComputeCharacterAdvanceMarker.End(); #endregion Tabulation & Stops // Handle Carriage Return #region Carriage Return if (charCode == 13) { k_HandleCarriageReturnMarker.Begin(); m_xAdvance = 0 + tag_Indent; k_HandleCarriageReturnMarker.End(); } #endregion Carriage Return // Tracking of text overflow page mode #region Save PageInfo k_SavePageInfoMarker.Begin(); if (m_overflowMode == TextOverflowModes.Page && charCode != 10 && charCode != 11 && charCode != 13 && charCode != 0x2028 && charCode != 0x2029) { // 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 = m_PageAscender; m_textInfo.pageInfo[m_pageNumber].descender = m_ElementDescender < m_textInfo.pageInfo[m_pageNumber].descender ? m_ElementDescender : m_textInfo.pageInfo[m_pageNumber].descender; if (m_isNewPage) { m_isNewPage = false; m_textInfo.pageInfo[m_pageNumber].firstCharacterIndex = m_characterCount; } // Last index m_textInfo.pageInfo[m_pageNumber].lastCharacterIndex = m_characterCount; } k_SavePageInfoMarker.End(); #endregion Save PageInfo // Handle Line Spacing Adjustments + Word Wrapping & special case for last line. #region Check for Line Feed and Last Character if (charCode == 10 || charCode == 11 || charCode == 0x03 || charCode == 0x2028 || charCode == 0x2029 || (charCode == 0x2D && isInjectedCharacter) || m_characterCount == totalCharacterCount - 1) { k_HandleLineTerminationMarker.Begin(); // Adjust current line spacing (if necessary) before inserting new line float baselineAdjustmentDelta = m_maxLineAscender - m_startOfLineAscender; if (m_lineOffset > 0 && Math.Abs(baselineAdjustmentDelta) > 0.01f && m_IsDrivenLineSpacing == false && !m_isNewPage) { //Debug.Log("Line Feed - Adjusting Line Spacing on line #" + m_lineNumber); AdjustLineOffset(m_firstCharacterOfLine, m_characterCount, baselineAdjustmentDelta); m_ElementDescender -= baselineAdjustmentDelta; m_lineOffset += baselineAdjustmentDelta; // Adjust saved ellipsis state only if we are adjusting the same line number if (m_SavedEllipsisState.lineNumber == m_lineNumber) { m_SavedEllipsisState = m_EllipsisInsertionCandidateStack.Pop(); m_SavedEllipsisState.startOfLineAscender += baselineAdjustmentDelta; m_SavedEllipsisState.lineOffset += baselineAdjustmentDelta; m_EllipsisInsertionCandidateStack.Push(m_SavedEllipsisState); } } 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_ElementDescender = m_ElementDescender < lineDescender ? m_ElementDescender : lineDescender; if (!isMaxVisibleDescenderSet) maxVisibleDescender = m_ElementDescender; 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].visibleSpaceCount = (m_textInfo.lineInfo[m_lineNumber].lastVisibleCharacterIndex + 1) - 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 = widthOfTextArea; if (m_textInfo.lineInfo[m_lineNumber].characterCount == 1) m_textInfo.lineInfo[m_lineNumber].alignment = m_lineJustification; float maxAdvanceOffset = ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment + boldSpacingAdjustment) * currentEmScale + m_cSpacing) * (1 - m_charWidthAdjDelta); if (m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].isVisible) m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastVisibleCharacterOfLine].xAdvance + (m_isRightToLeft ? maxAdvanceOffset : - maxAdvanceOffset); else m_textInfo.lineInfo[m_lineNumber].maxAdvance = m_textInfo.characterInfo[m_lastCharacterOfLine].xAdvance + (m_isRightToLeft ? maxAdvanceOffset : - maxAdvanceOffset); 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; // Add new line if not last line or character. if (charCode == 10 || charCode == 11 || (charCode == 0x2D && isInjectedCharacter) || charCode == 0x2028 || charCode == 0x2029) { // Store the state of the line before starting on the new line. SaveWordWrappingState(ref m_SavedLineState, i, m_characterCount); m_lineNumber += 1; isStartOfNewLine = true; ignoreNonBreakingSpace = false; isFirstWordOfLine = true; m_firstCharacterOfLine = m_characterCount + 1; m_lineVisibleCharacterCount = 0; m_lineVisibleSpaceCount = 0; // Check to make sure Array is large enough to hold a new line. if (m_lineNumber >= m_textInfo.lineInfo.Length) ResizeLineExtents(m_lineNumber); float lastVisibleAscender = m_textInfo.characterInfo[m_characterCount].adjustedAscender; // Apply Line Spacing with special handling for VT char(11) if (m_lineHeight == TMP_Math.FLOAT_UNSET) { float lineOffsetDelta = 0 - m_maxLineDescender + lastVisibleAscender + (lineGap + m_lineSpacingDelta) * baseScale + (m_lineSpacing + (charCode == 10 || charCode == 0x2029 ? m_paragraphSpacing : 0)) * currentEmScale; m_lineOffset += lineOffsetDelta; m_IsDrivenLineSpacing = false; } else { m_lineOffset += m_lineHeight + (m_lineSpacing + (charCode == 10 || charCode == 0x2029 ? m_paragraphSpacing : 0)) * currentEmScale; m_IsDrivenLineSpacing = true; } m_maxLineAscender = k_LargeNegativeFloat; m_maxLineDescender = k_LargePositiveFloat; m_startOfLineAscender = lastVisibleAscender; m_xAdvance = 0 + tag_LineIndent + tag_Indent; SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); SaveWordWrappingState(ref m_SavedLastValidState, i, m_characterCount); m_characterCount += 1; k_HandleLineTerminationMarker.End(); continue; } // If End of Text if (charCode == 0x03) i = m_TextProcessingArray.Length; k_HandleLineTerminationMarker.End(); } #endregion Check for Linefeed or Last Character // Track extents of the text #region Track Text Extents k_SaveTextExtentMarker.Begin(); // 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)); } k_SaveTextExtentMarker.End(); #endregion Track Text Extents // Save State of Mesh Creation for handling of Word Wrapping #region Save Word Wrapping State if ((m_TextWrappingMode != TextWrappingModes.NoWrap && m_TextWrappingMode != TextWrappingModes.PreserveWhitespaceNoWrap) || m_overflowMode == TextOverflowModes.Truncate || m_overflowMode == TextOverflowModes.Ellipsis || m_overflowMode == TextOverflowModes.Linked) { k_SaveProcessingStatesMarker.Begin(); bool shouldSaveHardLineBreak = false; bool shouldSaveSoftLineBreak = false; if ((isWhiteSpace || charCode == 0x200B || charCode == 0x2D || charCode == 0xAD) && (!m_isNonBreakingSpace || ignoreNonBreakingSpace) && charCode != 0xA0 && charCode != 0x2007 && charCode != 0x2011 && charCode != 0x202F && charCode != 0x2060) { // Ignore Hyphen (0x2D) when preceded by a whitespace if ((charCode == 0x2D && m_characterCount > 0 && char.IsWhiteSpace(m_textInfo.characterInfo[m_characterCount - 1].character)) == false) { isFirstWordOfLine = false; shouldSaveHardLineBreak = true; // Reset soft line breaking point since we now have a valid hard break point. m_SavedSoftLineBreakState.previous_WordBreak = -1; } } // Handling for East Asian scripts else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul(charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK(charCode))) { bool isCurrentLeadingCharacter = TMP_Settings.linebreakingRules.leadingCharacters.Contains(charCode); bool isNextFollowingCharacter = m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.Contains(m_textInfo.characterInfo[m_characterCount + 1].character); if (isCurrentLeadingCharacter == false) { if (isNextFollowingCharacter == false) { isFirstWordOfLine = false; shouldSaveHardLineBreak = true; } if (isFirstWordOfLine) { // Special handling for non-breaking space and soft line breaks if (isWhiteSpace) shouldSaveSoftLineBreak = true; shouldSaveHardLineBreak = true; } } else { if (isFirstWordOfLine && isFirstCharacterOfLine) { // Special handling for non-breaking space and soft line breaks if (isWhiteSpace) shouldSaveSoftLineBreak = true; shouldSaveHardLineBreak = true; } } } // Special handling for Latin characters followed by a CJK character. else if (m_isNonBreakingSpace == false && m_characterCount + 1 < totalCharacterCount && TMP_TextParsingUtilities.IsCJK(m_textInfo.characterInfo[m_characterCount + 1].character)) { shouldSaveHardLineBreak = true; } else if (isFirstWordOfLine) { // Special handling for non-breaking space and soft line breaks if (isWhiteSpace && charCode != 0xA0 || (charCode == 0xAD && isSoftHyphenIgnored == false)) shouldSaveSoftLineBreak = true; shouldSaveHardLineBreak = true; } // Save potential Hard lines break if (shouldSaveHardLineBreak) SaveWordWrappingState(ref m_SavedWordWrapState, i, m_characterCount); // Save potential Soft line break if (shouldSaveSoftLineBreak) SaveWordWrappingState(ref m_SavedSoftLineBreakState, i, m_characterCount); k_SaveProcessingStatesMarker.End(); } #endregion Save Word Wrapping State // Consider only saving state on base glyphs SaveWordWrappingState(ref m_SavedLastValidState, i, m_characterCount); 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_AutoSizeIterationCount < m_AutoSizeMaxIterationCount) { // Reset character width adjustment delta if (m_charWidthAdjDelta < m_charWidthMaxAdj / 100) m_charWidthAdjDelta = 0; m_minFontSize = m_fontSize; float sizeDelta = Mathf.Max((m_maxFontSize - m_fontSize) / 2, 0.05f); m_fontSize += sizeDelta; m_fontSize = Mathf.Min((int)(m_fontSize * 20 + 0.5f) / 20f, m_fontSizeMax); //Debug.Log("[" + m_AutoSizeIterationCount + "] Increasing Point Size from [" + m_minFontSize.ToString("f3") + "] to [" + m_fontSize.ToString("f3") + "] with delta of [" + sizeDelta.ToString("f3") + "]."); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } #endregion End Auto-sizing Check m_IsAutoSizePointSizeSet = true; if (m_AutoSizeIterationCount >= m_AutoSizeMaxIterationCount) Debug.Log("Auto Size Iteration Count: " + m_AutoSizeIterationCount + ". Final Point Size: " + m_fontSize); // If there are no visible characters or only character is End of Text (0x03)... no need to continue if (m_characterCount == 0 || (m_characterCount == 1 && charCode == 0x03)) { ClearMesh(true); // Event indicating the text has been regenerated. TMPro_EventManager.ON_TEXT_CHANGED(this); k_GenerateTextPhaseIMarker.End(); k_GenerateTextMarker.End(); return; } // End Sampling of Phase I k_GenerateTextPhaseIMarker.End(); // *** PHASE II of Text Generation *** k_GenerateTextPhaseIIMarker.Begin(); int last_vert_index = m_materialReferences[m_Underline.materialIndex].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 Vector3 anchorOffset = Vector3.zero; Vector3[] corners = m_RectTransformCorners; // GetTextContainerLocalCorners(); // Handle Vertical Text Alignment switch (m_VerticalAlignment) { // Top Vertically case VerticalAlignmentOptions.Top: if (m_overflowMode != TextOverflowModes.Page) anchorOffset = corners[1] + new Vector3(0 + margins.x, 0 - m_maxTextAscender - 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 VerticalAlignmentOptions.Middle: if (m_overflowMode != TextOverflowModes.Page) anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_maxTextAscender + 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 VerticalAlignmentOptions.Bottom: 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 VerticalAlignmentOptions.Baseline: anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0, 0); break; // Midline Vertically case VerticalAlignmentOptions.Geometry: 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 VerticalAlignmentOptions.Capline: anchorOffset = (corners[0] + corners[1]) / 2 + new Vector3(0 + margins.x, 0 - (m_maxCapHeight - margins.y - margins.w) / 2, 0); break; } #endregion // Initialization for Second Pass Vector3 justificationOffset = Vector3.zero; Vector3 offset = Vector3.zero; // int vert_index_X4 = 0; // int 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. float lossyScale = m_previousLossyScaleY = this.transform.lossyScale.y; Color32 underlineColor = Color.white; Color32 strikethroughColor = Color.white; HighlightState highlightState = new HighlightState(new Color32(255, 255, 0, 64), TMP_Offset.zero); float xScale = 0; float xScaleMax = 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 unicode = characterInfos[i].character; bool isWhiteSpace = char.IsWhiteSpace(unicode); int currentLine = characterInfos[i].lineNumber; TMP_LineInfo lineInfo = m_textInfo.lineInfo[currentLine]; lineCount = currentLine + 1; HorizontalAlignmentOptions lineAlignment = lineInfo.alignment; // Process Line Justification #region Handle Line Justification switch (lineAlignment) { case HorizontalAlignmentOptions.Left: if (!m_isRightToLeft) justificationOffset = new Vector3(0 + lineInfo.marginLeft, 0, 0); else justificationOffset = new Vector3(0 - lineInfo.maxAdvance, 0, 0); break; case HorizontalAlignmentOptions.Center: justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width / 2 - lineInfo.maxAdvance / 2, 0, 0); break; case HorizontalAlignmentOptions.Geometry: justificationOffset = new Vector3(lineInfo.marginLeft + lineInfo.width / 2 - (lineInfo.lineExtents.min.x + lineInfo.lineExtents.max.x) / 2, 0, 0); break; case HorizontalAlignmentOptions.Right: 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 HorizontalAlignmentOptions.Justified: case HorizontalAlignmentOptions.Flush: // Skip Zero Width Characters and spaces outside of the margins. if (i > lineInfo.lastVisibleCharacterIndex || unicode == 0x0A || unicode == 0xAD || unicode == 0x200B || unicode == 0x2060 || unicode == 0x03) break; char lastCharOfCurrentLine = characterInfos[lineInfo.lastCharacterIndex].character; bool isFlush = (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(unicode)) isFirstSeperator = true; else isFirstSeperator = false; } else { float gap = !m_isRightToLeft ? lineInfo.width - lineInfo.maxAdvance : lineInfo.width + lineInfo.maxAdvance; int visibleCount = lineInfo.visibleCharacterCount - 1 + lineInfo.controlCharacterCount; int spaces = lineInfo.spaceCount - lineInfo.controlCharacterCount; if (isFirstSeperator) { spaces -= 1; visibleCount += 1; } float ratio = spaces > 0 ? m_wordWrappingRatios : 1; if (spaces < 1) spaces = 1; if (unicode != 0xA0 && (unicode == 9 || char.IsSeparator(unicode))) { 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; } #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 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; } #endregion // Pack UV's so that we can pass Xscale needed for Shader to maintain 1:1 ratio. #region Pack Scale into UV2 xScale = characterInfos[i].scale * Mathf.Abs(lossyScale) * (1 - m_charWidthAdjDelta); if (!characterInfos[i].isUsingAlternateTypeface && (characterInfos[i].style & FontStyles.Bold) == FontStyles.Bold) xScale *= -1; // Set SDF Scale characterInfos[i].vertex_BL.uv.w = xScale; characterInfos[i].vertex_TL.uv.w = xScale; characterInfos[i].vertex_TR.uv.w = xScale; characterInfos[i].vertex_BR.uv.w = xScale; #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 (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; } #endregion if (QualitySettings.activeColorSpace == ColorSpace.Linear) m_ConvertToLinearSpace = true; else m_ConvertToLinearSpace = false; // Fill Vertex Buffers for the various types of element if (elementType == TMP_TextElementType.Character) { FillCharacterVertexBuffers(i); } else if (elementType == TMP_TextElementType.Sprite) { FillSpriteVertexBuffers(i); } } #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 (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].maxAdvance += offset.x; 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].maxAdvance += offset.x; m_textInfo.lineInfo[currentLine].lineExtents.min = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[currentLine].firstCharacterIndex].bottomLeft.x, m_textInfo.lineInfo[currentLine].descender); m_textInfo.lineInfo[currentLine].lineExtents.max = new Vector2(m_textInfo.characterInfo[m_textInfo.lineInfo[currentLine].lastVisibleCharacterIndex].topRight.x, m_textInfo.lineInfo[currentLine].ascender); } } #endregion // Track Word Count per line and for the object #region Track Word Count if (char.IsLetterOrDigit(unicode) || unicode == 0x2D || unicode == 0xAD || unicode == 0x2010 || unicode == 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(unicode) || isWhiteSpace || unicode == 0x200B || i == m_characterCount - 1)) { if (i > 0 && i < characterInfos.Length - 1 && i < m_characterCount && (unicode == 39 || unicode == 8217) && char.IsLetterOrDigit(characterInfos[i - 1].character) && char.IsLetterOrDigit(characterInfos[i + 1].character)) { } else { wordLastChar = i == m_characterCount - 1 && char.IsLetterOrDigit(unicode) ? 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; } } #endregion // Setup & Handle Underline #region Underline // NOTE: Need to figure out how underline will be handled with multiple fonts and which font will be used for the underline. bool isUnderline = (m_textInfo.characterInfo[i].style & FontStyles.Underline) == FontStyles.Underline; if (isUnderline) { bool isUnderlineVisible = true; int currentPage = m_textInfo.characterInfo[i].pageNumber; m_textInfo.characterInfo[i].underlineVertexIndex = last_vert_index; 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 (!isWhiteSpace && unicode != 0x200B) { underlineMaxScale = Mathf.Max(underlineMaxScale, m_textInfo.characterInfo[i].scale); xScaleMax = Mathf.Max(xScaleMax, Mathf.Abs(xScale)); underlineBaseLine = Mathf.Min(currentPage == lastPage ? underlineBaseLine : k_LargePositiveFloat, m_textInfo.characterInfo[i].baseLine + font.m_FaceInfo.underlineOffset * underlineMaxScale); lastPage = currentPage; // Need to track pages to ensure we reset baseline for the new pages. } if (beginUnderline == false && isUnderlineVisible == true && i <= lineInfo.lastVisibleCharacterIndex && unicode != 10 && unicode != 11 && unicode != 13) { if (i == lineInfo.lastVisibleCharacterIndex && char.IsSeparator(unicode)) { } else { beginUnderline = true; underlineStartScale = m_textInfo.characterInfo[i].scale; if (underlineMaxScale == 0) { underlineMaxScale = underlineStartScale; xScaleMax = xScale; } 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, xScaleMax, underlineColor); underlineMaxScale = 0; xScaleMax = 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 (isWhiteSpace || unicode == 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, xScaleMax, underlineColor); underlineMaxScale = 0; xScaleMax = 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, xScaleMax, underlineColor); underlineMaxScale = 0; xScaleMax = 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, xScaleMax, underlineColor); underlineMaxScale = 0; xScaleMax = 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, xScaleMax, underlineColor); underlineMaxScale = 0; xScaleMax = 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.m_FaceInfo.strikethroughOffset; if (isStrikethrough) { bool isStrikeThroughVisible = true; m_textInfo.characterInfo[i].strikethroughVertexIndex = last_vert_index; 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 && unicode != 10 && unicode != 11 && unicode != 13) { if (i == lineInfo.lastVisibleCharacterIndex && char.IsSeparator(unicode)) { } 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 (isWhiteSpace || unicode == 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 && unicode != 10 && unicode != 11 && unicode != 13) { if (i == lineInfo.lastVisibleCharacterIndex && char.IsSeparator(unicode)) { } else { beginHighlight = true; highlight_start = k_LargePositiveVector2; highlight_end = k_LargeNegativeVector2; highlightState = m_textInfo.characterInfo[i].highlightState; } } if (beginHighlight) { TMP_CharacterInfo currentCharacter = m_textInfo.characterInfo[i]; HighlightState currentState = currentCharacter.highlightState; bool isColorTransition = false; // Handle Highlight color changes if (highlightState != currentState) { // Adjust previous highlight section to prevent a gaps between sections. if (isWhiteSpace) highlight_end.x = (highlight_end.x - highlightState.padding.right + currentCharacter.origin) / 2; else highlight_end.x = (highlight_end.x - highlightState.padding.right + currentCharacter.bottomLeft.x) / 2; highlight_start.y = Mathf.Min(highlight_start.y, currentCharacter.descender); highlight_end.y = Mathf.Max(highlight_end.y, currentCharacter.ascender); DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightState.color); beginHighlight = true; highlight_start = new Vector2(highlight_end.x, currentCharacter.descender - currentState.padding.bottom); if (isWhiteSpace) highlight_end = new Vector2(currentCharacter.xAdvance + currentState.padding.right, currentCharacter.ascender + currentState.padding.top); else highlight_end = new Vector2(currentCharacter.topRight.x + currentState.padding.right, currentCharacter.ascender + currentState.padding.top); highlightState = currentState; isColorTransition = true; } if (!isColorTransition) { if (isWhiteSpace) { // Use the Min / Max of glyph metrics if white space. highlight_start.x = Mathf.Min(highlight_start.x, currentCharacter.origin - highlightState.padding.left); highlight_end.x = Mathf.Max(highlight_end.x, currentCharacter.xAdvance + highlightState.padding.right); } else { // Use the Min / Max of character bounds highlight_start.x = Mathf.Min(highlight_start.x, currentCharacter.bottomLeft.x - highlightState.padding.left); highlight_end.x = Mathf.Max(highlight_end.x, currentCharacter.topRight.x + highlightState.padding.right); } highlight_start.y = Mathf.Min(highlight_start.y, currentCharacter.descender - highlightState.padding.bottom); highlight_end.y = Mathf.Max(highlight_end.y, currentCharacter.ascender + highlightState.padding.top); } } // End Highlight if text only contains one character. if (beginHighlight && m_characterCount == 1) { beginHighlight = false; DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightState.color); } else if (beginHighlight && (i == lineInfo.lastCharacterIndex || i >= lineInfo.lastVisibleCharacterIndex)) { beginHighlight = false; DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightState.color); } else if (beginHighlight && !isHighlightVisible) { beginHighlight = false; DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightState.color); } } else { // End Highlight if (beginHighlight == true) { beginHighlight = false; DrawTextHighlight(highlight_start, highlight_end, ref last_vert_index, highlightState.color); } } #endregion lastLine = currentLine; } #endregion // Set vertex count for Underline geometry m_textInfo.meshInfo[m_Underline.materialIndex].vertexCount = last_vert_index; // 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; // End Sampling of Phase II k_GenerateTextPhaseIIMarker.End(); // Phase III - Update Mesh Vertex Data k_GenerateTextPhaseIIIMarker.Begin(); if (m_renderMode == TextRenderFlags.Render && IsActive()) { // Event to allow users to modify the content of the text info before the text is rendered. OnPreRenderText?.Invoke(m_textInfo); // 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.SetUVs(0, 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.RecalculteBounds. 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)); 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.vertices = m_textInfo.meshInfo[i].vertices; m_subTextObjects[i].mesh.SetUVs(0, 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(); // Update the collider on the sub text object //m_subTextObjects[i].UpdateColliders(m_textInfo.meshInfo[i].vertexCount); } } // Event indicating the text has been regenerated. TMPro_EventManager.ON_TEXT_CHANGED(this); //Debug.Log("***** Done rendering text object ID " + GetInstanceID() + ". *****"); // Clear allocations no longer necessary given the text object is static // if (true) // { // m_isInputParsingRequired = true; // m_textInfo.ClearAllData(); // } // End Sampling k_GenerateTextPhaseIIIMarker.End(); k_GenerateTextMarker.End(); } /// /// 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 disable the renderers. /// void SetMeshFilters(bool state) { // Parent text object if (m_meshFilter != null) { if (state) m_meshFilter.sharedMesh = m_mesh; else m_meshFilter.sharedMesh = null; } for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { if (m_subTextObjects[i].meshFilter != null) { if (state) m_subTextObjects[i].meshFilter.sharedMesh = m_subTextObjects[i].mesh; else m_subTextObjects[i].meshFilter.sharedMesh = null; } } } /// /// 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; } } protected void SetActiveSubTextObjectRenderers(bool state) { for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) { Renderer subMeshRenderer = m_subTextObjects[i].renderer; if (subMeshRenderer != null && subMeshRenderer.enabled != state) subMeshRenderer.enabled = state; } } /// /// Destroy Sub Mesh Objects /// protected override void DestroySubMeshObjects() { for (int i = 1; i < m_subTextObjects.Length && m_subTextObjects[i] != null; i++) DestroyImmediate(m_subTextObjects[i]); } /// /// /// internal void UpdateSubMeshSortingLayerID(int id) { for (int i = 1; i < m_subTextObjects.Length; i++) { TMP_SubMesh subMesh = m_subTextObjects[i]; if (subMesh != null && subMesh.renderer != null) { subMesh.renderer.sortingLayerID = id; } } } /// /// /// internal void UpdateSubMeshSortingOrder(int order) { for (int i = 1; i < m_subTextObjects.Length; i++) { TMP_SubMesh subMesh = m_subTextObjects[i]; if (subMesh != null && subMesh.renderer != null) { subMesh.renderer.sortingOrder = order; } } } /// /// 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); } /// /// Method to Update Scale in UV2 /// //void UpdateSDFScale(float lossyScale) //{ // // TODO: Resolve - Underline / Strikethrough segments not getting their SDF Scale adjusted. // //Debug.Log("*** UpdateSDFScale() ***"); // // 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 = lossyScale * 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.meshInfo.Length; i++) // { // if (i == 0) // m_mesh.uv2 = m_textInfo.meshInfo[0].uvs2; // else // m_subTextObjects[i].mesh.uv2 = m_textInfo.meshInfo[i].uvs2; // } //} /// /// Method to update the SDF Scale in UV2. /// /// void UpdateSDFScale(float scaleDelta) { if (scaleDelta == 0 || scaleDelta == float.PositiveInfinity || scaleDelta == float.NegativeInfinity) { m_havePropertiesChanged = true; OnPreRenderObject(); return; } for (int materialIndex = 0; materialIndex < m_textInfo.materialCount; materialIndex++) { TMP_MeshInfo meshInfo = m_textInfo.meshInfo[materialIndex]; for (int i = 0; i < meshInfo.uvs0.Length; i++) { meshInfo.uvs0[i].w *= Mathf.Abs(scaleDelta); } } // Push the updated uv0 scale information to the meshes. for (int i = 0; i < m_textInfo.meshInfo.Length; i++) { if (i == 0) m_mesh.SetUVs(0, m_textInfo.meshInfo[0].uvs0); else m_subTextObjects[i].mesh.SetUVs(0, m_textInfo.meshInfo[i].uvs0); } } #endregion } }