diff --git a/CHANGELOG.md b/CHANGELOG.md index 75599c7..3cc4a57 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,26 @@ # Changelog These are the release notes for the TextMesh Pro UPM package which was first introduced with Unity 2018.1. Please see the following link for the Release Notes for prior versions of TextMesh Pro. http://digitalnativestudios.com/forum/index.php?topic=1363.0 +## [3.2.0-pre.4] - 2022-12-16 +### Changes +- Fixed italic horizontal displacement of punctuation. (TMPB-133) +- Updated Input Field to not restore the original text when the X in the HoloLens keyboard is pressed. +- Fixed MissingReferenceException when changing scenes. See case [TMPB-120](https://issuetracker.unity3d.com/issues/missingreferenceexception-thrown-when-changing-scenes) and forum [thread](https://forum.unity.com/threads/tmpro-submeshui-error-when-changing-scenes.1129724/) for more info. +- Fixed Undo operation not correctly undoing some newly created TMP objects. Case #1400391 +- Fixed glyphs not being drawn in the various font asset inspector tables although present in the glyph table until unselecting and re-selecting the font asset. +- Fixed incorrect line breaking when using a mixture of Latin and CJK text. +- Fixed potential NullReferenceException when creating font asset with multiple atlas textures. See [forum post](https://forum.unity.com/threads/how-can-i-submit-bugs-or-issues-for-tmp.628687/#post-8186100) for details. +- Fixed potential issue when using the <mark> tag in conjunction with Ellipsis. +- Added new "Get Font Features" option in the Generation Settings of Font Assets to provide control over fetching of font feature data. +- Fix potential duplication of Ligature data when using Dynamic Font Assets. +- Added new "duospace" attribute to the <mspace=value> markup tag where the spacing of the '.', ':' and ',' characters will be half of the value. This was added as an option when displaying numerical values using this markup tag. +- With the addition of support for new OpenType Layout features such as Ligatures, Mark-to-Base and Mark-to-Mark, we have made the following changes: + - The "Kerning" option in the Extra Settings of the text component inspector has been replaced by a new popup menu to independently control these features which are "kern", "liga", "mark" and "mkmk" + - The "enableKerning" property has been deprecated and replaced by the "fontFeatures" property which is a list that contains which features are enabled on the text component. +- Fixed two potential NullReferenceException related to missing material references or materials on SubMesh text objects. See [forum post](https://forum.unity.com/threads/tmpro-submeshui-error-when-changing-scenes.1129724/#post-8462771) for details. +- Fixed for text alignment mode Justified and Flush blending not working correctly when using <NBSP> in the text. +- Fixed external keyboard on iOS/tvOS and Android when Hide Soft Keyboard is used + ## [3.2.0-pre.3] - 2022-03-07 ### Changes - Fixed a potential IndexOutOfRangeException when trying to select any portions of text in an Input Field whose child text component is using Overflow mode Truncate or Ellipsis where due to RectTransform width and / or height restrictions have results in the text being fully truncated. Case #1361032 diff --git a/Documentation~/ColorEmojis.md b/Documentation~/ColorEmojis.md new file mode 100644 index 0000000..49afbf4 --- /dev/null +++ b/Documentation~/ColorEmojis.md @@ -0,0 +1,56 @@ +# Color emojis + +You can include color glyphs and emojis in text. To do so, import a font file that has color emojis in it and set it as the fallback emojis text assets. + +![](images/NativeEmojiExample.png) + +## Set up color emojis + +Create a color font asset and add it to the TMP Settings Fallback. Note this is the same as the Project Settings/TextMeshPro/Settings as depicted in the image. + +1. In your project, import a font file that has color emojis in it. +2. Right click in the `Asset` folder, and then select **Create > TextMeshPro > FontAsset > Color**. This ensures that you create the font asset with the right shader (Sprite) and the right atlas rendering mode (Color). +3. Open the TMP Settings asset or alternatively via the **Edit > ProjectSettings > TextMesh Pro > Settings**. +4. Add the emoji font asset to the **Fallback Emoji Text Assets** section. + +![](images/FallbackEmoji.png) + +Alternatively, assigning a color font asset to the text object will work fine provided that the [Emoji Fallback Support](ColorEmojis) option in the Extra Settings of the text component is disabled. + +## Include emojis in text + +To include emojis in text, do the following: + +- Include emojis in text through their Unicode. For example, enter `\U00001f60` to represent a smile. +- Use OS Virtual Keyboard. +- Copy the emojis from an external Text Editing tool and paste them in your text field. + +To find more information about the Unicode Emojis Standard, see this [link](http://unicode.org/Public/emoji/14.0/). + +## Control Emoji Fallback Search + +The "Emoji Fallback Support" option controls where we search for characters defined in the Unicode Standards as Emojis. + +When this option is enabled (default), the "Fallback Emoji Text Assets" list will be search first for any characters defined as Emojis. + +When this option is disabled, the Primary font asset assigned to the text component will be searched first. + +Basically, this option overrides the character search to prioritize searching thru the "Fallback Emoji Text Assets" list first when the character is an emoji. + +This option is also useful when a font contains black-and-white emojis as it allows the user to control if the emojis contained in the primary will be used or those from the "Fallback Emoji Text Assets" list. + +To update the `Emoji Fallback Support`: +1. Select the **Text (TMP)** field in the hierarchy. +2. In the Inspector window, under the `Extra Settings` foldout of the **Text (TMP)** field, select the **Emoji Fallback Support** toggle. + +![](images/EmojiFallbackSupport.png) + +## Limitations + +The color emojis feature has the following limitations: + +- It doesn't support some OpenType font features, such as chain context and single substitution. +- It doesn't support Apple fonts that use the AAT format. It's a predecessor to OpenType. +- It doesn't support SVG color glyphs. +- Dynamic OS FontAsset has limited support on some iOS devices. The `Apple Color Emoji` font file found on OSX and several iOS devices works fine. However, the `Apple Color Emoji-160px` found on newer iOS devices is not support as the emoji's are encoded in JPEG format which is not supported by FreeType. +- Prior to Unity 2023.1, adding a UTF-32 through the inspector sends an error. The emojis won't display in the inspector. \ No newline at end of file diff --git a/Documentation~/FontAssets.md b/Documentation~/FontAssets.md index 6f12d0f..248bab5 100644 --- a/Documentation~/FontAssets.md +++ b/Documentation~/FontAssets.md @@ -24,3 +24,46 @@ Font Assets can have the following types of font atlas: * **Smooth/Hinted Smooth:** This type of atlas is an antialiased bitmap texture. A Hinted smooth atlas aligns glyph pixels with texture pixels to produce a smoother result.

Smooth atlases work well for static text that is viewed head on, in situations where there is a good correspondence between texture pixels and screen pixels. Transforming text generated from a smooth atlas blurs the text edges. * **Raster/Raster Hinted:** Raster atlases are un-smoothed bitmap textures. They almost always produce text with jagged, pixellated edges. The Hinted rater atlases align glyph pixels with texture pixels to produce a smoother result. + +## Get Font Features + +This option determines if OpenType font features should be retrieved from the source font file as new characters and glyphs are added to the font asset. Disabling this option will prevent extracting font features. + +To update the Get Font Features option on a FontAsset: +1. Select the FontAsset +2. In the FontAsset inspector, navigate to the Generation Settings section. +3. Select **Get Font Features**. + +![](images/GetFontFeatures.png) + +## Reset +The `Reset` context menu option clears all tables which includes the Character and Glyph tables along with all font features tables such as the Ligature, Glyph Adjustment, Mark to Base, Mark to Mark tables. This option also clears the font asset's atlas texture and resets it back to size zero. + +To reset a FontAsset: +1. Select the FontAsset +2. Expand the top right menu in the FontAsset Inspector. +3. Select **Reset**. + +![](images/ResetMenuItem.png) + +## Clear Dynamic Data +The `Clear Dynamic Data` context menu option clears the character and glyph tables as well as the font asset's atlas texture which is also resized back to size zero. This option preserves all font feature table data such as Ligatures, Glyph Adjustment, Mark to Base, Mark to Mark, etc. + +To clear a FontAsset: +1. Select the FontAsset +2. Expand the top right menu in the FontAsset Inspector. +3. Select **Clear Dynamic Data**. + +![](images/ClearDynamicDataMenuItem.png) + +This preserves the custom ligatures, kernings, and diacritical marks you added to the font asset when clearing the atlas. + +## Clear Dynamic Data on Build +The "Clear Dynamic Data on Build" performs the same function as the "Clear Dynamic Data" context menu option but performs this operation when creating a build as well as when closing the Editor. + +To update the Clear Dynamic Data on Build option: +1. Select the FontAsset +2. In the FontAsset inspector, navigate to the Generation Settings section. +3. Select **Clear Dynamic Data on Build**. + +![](images/ClearDynamicDataOnBuild.png) diff --git a/Documentation~/TMPObjects.md b/Documentation~/TMPObjects.md index f898aff..1351e6d 100644 --- a/Documentation~/TMPObjects.md +++ b/Documentation~/TMPObjects.md @@ -35,3 +35,20 @@ TextMesh Pro 3D text objects are nearly identical to their UI counterparts, but 1. From the menu, choose **GameObject > 3D GameObject > TextMesh Pro - Text**. 1. In the **TextMesh Pro** Inspector, enter your text. 1. Adjust the [3D text properties](TMPObject3DText.md) as needed. + +## OpenType FontFeature +Font Features define the typographic capabilities of a font asset. These features define potential substitutions or positional adjustments of glyphs. The following are the currently supported Font Features: + +- **Ligatures:** Defines the substitution of multiple glyphs by a single glyph, such as 'fi' or 'ffl'. This features is identified as "liga". + +- **Kerning:** Defines positional adjustments between two glyphs relative to each other. This features is identified as "kern". + +- **Diacritical Marks:** Defines positional adjustments between Base glyphs and Mark glyphs. + + The Mark-to-Base feature "mark" defines the positional adjustments of Mark glyphs relative to Base glyphs. + + The Mark-to-Mark feature "mkmk" defines the positional adjustments of Mark glyphs relative to Base Mark glyphs. + +You can enable or disable the Font Features through the Font Features field in the Text component's Extra Settings section shown in the image below: + +![](images/FontFeatures.png) \ No newline at end of file diff --git a/Documentation~/TableOfContents.md b/Documentation~/TableOfContents.md index dcdf510..d7f32e0 100644 --- a/Documentation~/TableOfContents.md +++ b/Documentation~/TableOfContents.md @@ -9,6 +9,7 @@ * [Signed Distance Fields](FontAssetsSDF) * [Dynamic Fonts](FontAssetsDynamicFonts) * [The Fallback Chain](FontAssetsFallback) + * [Color emojis](ColorEmojis) * [Rich Text Tags](RichText) * [Supported Tags](RichTextSupportedTags) * [](RichTextAlignment) diff --git a/Documentation~/images/ClearDynamicDataMenuItem.png b/Documentation~/images/ClearDynamicDataMenuItem.png new file mode 100644 index 0000000..f37cc41 Binary files /dev/null and b/Documentation~/images/ClearDynamicDataMenuItem.png differ diff --git a/Documentation~/images/ClearDynamicDataOnBuild.png b/Documentation~/images/ClearDynamicDataOnBuild.png new file mode 100644 index 0000000..63871b3 Binary files /dev/null and b/Documentation~/images/ClearDynamicDataOnBuild.png differ diff --git a/Documentation~/images/EmojiFallbackSupport.png b/Documentation~/images/EmojiFallbackSupport.png new file mode 100644 index 0000000..103532f Binary files /dev/null and b/Documentation~/images/EmojiFallbackSupport.png differ diff --git a/Documentation~/images/FallbackEmoji.png b/Documentation~/images/FallbackEmoji.png new file mode 100644 index 0000000..cd0b57c Binary files /dev/null and b/Documentation~/images/FallbackEmoji.png differ diff --git a/Documentation~/images/FontFeatures.png b/Documentation~/images/FontFeatures.png new file mode 100644 index 0000000..1833c33 Binary files /dev/null and b/Documentation~/images/FontFeatures.png differ diff --git a/Documentation~/images/GetFontFeatures.png b/Documentation~/images/GetFontFeatures.png new file mode 100644 index 0000000..02869be Binary files /dev/null and b/Documentation~/images/GetFontFeatures.png differ diff --git a/Documentation~/images/NativeEmojiExample.png b/Documentation~/images/NativeEmojiExample.png new file mode 100644 index 0000000..2edeb8a Binary files /dev/null and b/Documentation~/images/NativeEmojiExample.png differ diff --git a/Documentation~/images/ResetMenuItem.png b/Documentation~/images/ResetMenuItem.png new file mode 100644 index 0000000..28f4ef3 Binary files /dev/null and b/Documentation~/images/ResetMenuItem.png differ diff --git a/LICENSE.md b/LICENSE.md index cf9f27d..a4a9161 100644 --- a/LICENSE.md +++ b/LICENSE.md @@ -1,4 +1,4 @@ -TextMesh Pro copyright © 2021 Unity Technologies ApS +TextMesh Pro copyright © 2022 Unity Technologies ApS Licensed under the Unity Companion License for Unity-dependent projects--see [Unity Companion License](http://www.unity3d.com/legal/licenses/Unity_Companion_License). diff --git a/Package Resources/TMP Examples & Extras.unitypackage b/Package Resources/TMP Examples & Extras.unitypackage index adec320..f1f6048 100644 Binary files a/Package Resources/TMP Examples & Extras.unitypackage and b/Package Resources/TMP Examples & Extras.unitypackage differ diff --git a/Scripts/Editor/PropertyDrawers/GlyphPairAdjustmentRecordPropertyDrawer.cs b/Scripts/Editor/PropertyDrawers/GlyphPairAdjustmentRecordPropertyDrawer.cs index c92e957..1458ccd 100644 --- a/Scripts/Editor/PropertyDrawers/GlyphPairAdjustmentRecordPropertyDrawer.cs +++ b/Scripts/Editor/PropertyDrawers/GlyphPairAdjustmentRecordPropertyDrawer.cs @@ -35,8 +35,9 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty prop_FontFeatureLookupFlags = property.FindPropertyRelative("m_FeatureLookupFlags"); + // Refresh glyph proxy lookup dictionary if needed if (TMP_PropertyDrawerUtilities.s_RefreshGlyphProxyLookup) - TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject, m_GlyphLookupDictionary); + TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject); position.yMin += 2; @@ -317,7 +318,7 @@ uint GetUnicodeCharacter (string source) return unicode; } - void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) + void DrawGlyph(uint glyphIndex, Rect glyphDrawPosition, SerializedProperty property) { // Get a reference to the serialized object which can either be a TMP_FontAsset or FontAsset. SerializedObject so = property.serializedObject; @@ -325,10 +326,7 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) return; if (m_GlyphLookupDictionary == null) - { - m_GlyphLookupDictionary = new Dictionary(); - TMP_PropertyDrawerUtilities.PopulateGlyphProxyLookupDictionary(so, m_GlyphLookupDictionary); - } + m_GlyphLookupDictionary = TMP_PropertyDrawerUtilities.GetGlyphProxyLookupDictionary(so); // Try getting a reference to the glyph for the given glyph index. if (!m_GlyphLookupDictionary.TryGetValue(glyphIndex, out GlyphProxy glyph)) @@ -342,9 +340,6 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) if (TMP_PropertyDrawerUtilities.TryGetMaterial(so, atlasTexture, out mat) == false) return; - // Draw glyph from atlas texture. - Rect glyphDrawPosition = position; - int padding = so.FindProperty("m_AtlasPadding").intValue; GlyphRect glyphRect = glyph.glyphRect; int glyphOriginX = glyphRect.x - padding; diff --git a/Scripts/Editor/PropertyDrawers/LigatureSubstitutionRecordPropertyDrawer.cs b/Scripts/Editor/PropertyDrawers/LigatureSubstitutionRecordPropertyDrawer.cs index 6d0187a..c2d3821 100644 --- a/Scripts/Editor/PropertyDrawers/LigatureSubstitutionRecordPropertyDrawer.cs +++ b/Scripts/Editor/PropertyDrawers/LigatureSubstitutionRecordPropertyDrawer.cs @@ -7,10 +7,8 @@ namespace TMPro.EditorUtilities { [CustomPropertyDrawer(typeof(LigatureSubstitutionRecord))] - public class LigatureSubstitutionRecordPropertyDrawer : PropertyDrawer + class LigatureSubstitutionRecordPropertyDrawer : PropertyDrawer { - //private bool isEditingEnabled; - private bool isSelectable; private Dictionary m_GlyphLookupDictionary; public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) @@ -19,21 +17,12 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten int ComponentGlyphIDCount = prop_ComponentGlyphIDs.arraySize; SerializedProperty prop_LigatureGlyphID = property.FindPropertyRelative("m_LigatureGlyphID"); - // Refresh glyph proxy lookup dictionary if needed. + // Refresh glyph proxy lookup dictionary if needed if (TMP_PropertyDrawerUtilities.s_RefreshGlyphProxyLookup) - TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject, m_GlyphLookupDictionary); + TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject); Rect rect = position; - - //isEditingEnabled = GUI.enabled; - isSelectable = label.text == "Selectable"; - - if (isSelectable) - GUILayoutUtility.GetRect(position.width, 100); - else - GUILayoutUtility.GetRect(position.width, 80); - - //GUIStyle style = new GUIStyle(EditorStyles.label) {richText = true, alignment = TextAnchor.UpperCenter}; + GUILayoutUtility.GetRect(position.width, 100); EditorGUIUtility.labelWidth = 115; EditorGUI.BeginChangeCheck(); @@ -80,7 +69,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten } } - void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) + void DrawGlyph(uint glyphIndex, Rect glyphDrawPosition, SerializedProperty property) { // Get a reference to the serialized object which can either be a TMP_FontAsset or FontAsset. SerializedObject so = property.serializedObject; @@ -88,10 +77,7 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) return; if (m_GlyphLookupDictionary == null) - { - m_GlyphLookupDictionary = new Dictionary(); - TMP_PropertyDrawerUtilities.PopulateGlyphProxyLookupDictionary(so, m_GlyphLookupDictionary); - } + m_GlyphLookupDictionary = TMP_PropertyDrawerUtilities.GetGlyphProxyLookupDictionary(so); // Try getting a reference to the glyph for the given glyph index. if (!m_GlyphLookupDictionary.TryGetValue(glyphIndex, out GlyphProxy glyph)) @@ -105,9 +91,6 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) if (TMP_PropertyDrawerUtilities.TryGetMaterial(so, atlasTexture, out mat) == false) return; - // Draw glyph from atlas texture. - Rect glyphDrawPosition = position; - int padding = so.FindProperty("m_AtlasPadding").intValue; GlyphRect glyphRect = glyph.glyphRect; int glyphOriginX = glyphRect.x - padding; diff --git a/Scripts/Editor/PropertyDrawers/TMP_CharacterPropertyDrawer.cs b/Scripts/Editor/PropertyDrawers/TMP_CharacterPropertyDrawer.cs index 46b112f..7034ffa 100644 --- a/Scripts/Editor/PropertyDrawers/TMP_CharacterPropertyDrawer.cs +++ b/Scripts/Editor/PropertyDrawers/TMP_CharacterPropertyDrawer.cs @@ -19,8 +19,9 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty prop_GlyphIndex = property.FindPropertyRelative("m_GlyphIndex"); SerializedProperty prop_Scale = property.FindPropertyRelative("m_Scale"); + // Refresh glyph proxy lookup dictionary if needed if (TMP_PropertyDrawerUtilities.s_RefreshGlyphProxyLookup) - TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject, m_GlyphLookupDictionary); + TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject); GUIStyle style = new GUIStyle(EditorStyles.label); style.richText = true; @@ -154,7 +155,7 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent return 58; } - void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) + void DrawGlyph(uint glyphIndex, Rect glyphDrawPosition, SerializedProperty property) { // Get a reference to the serialized object which can either be a TMP_FontAsset or FontAsset. SerializedObject so = property.serializedObject; @@ -162,10 +163,7 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) return; if (m_GlyphLookupDictionary == null) - { - m_GlyphLookupDictionary = new Dictionary(); - TMP_PropertyDrawerUtilities.PopulateGlyphProxyLookupDictionary(so, m_GlyphLookupDictionary); - } + m_GlyphLookupDictionary = TMP_PropertyDrawerUtilities.GetGlyphProxyLookupDictionary(so); // Try getting a reference to the glyph for the given glyph index. if (!m_GlyphLookupDictionary.TryGetValue(glyphIndex, out GlyphProxy glyph)) @@ -179,9 +177,6 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) if (TMP_PropertyDrawerUtilities.TryGetMaterial(so, atlasTexture, out mat) == false) return; - // Draw glyph from atlas texture. - Rect glyphDrawPosition = position; - int padding = so.FindProperty("m_AtlasPadding").intValue; GlyphRect glyphRect = glyph.glyphRect; int glyphOriginX = glyphRect.x - padding; diff --git a/Scripts/Editor/PropertyDrawers/TMP_GlyphPropertyDrawer.cs b/Scripts/Editor/PropertyDrawers/TMP_GlyphPropertyDrawer.cs index 97a4107..69fb0d0 100644 --- a/Scripts/Editor/PropertyDrawers/TMP_GlyphPropertyDrawer.cs +++ b/Scripts/Editor/PropertyDrawers/TMP_GlyphPropertyDrawer.cs @@ -14,8 +14,6 @@ public class TMP_GlyphPropertyDrawer : PropertyDrawer private static readonly GUIContent k_AtlasIndexLabel = new GUIContent("Atlas Index:", "The index of the atlas texture that contains this glyph."); private static readonly GUIContent k_ClassTypeLabel = new GUIContent("Class Type:", "The class definition type of this glyph."); - private Dictionary m_GlyphLookupDictionary; - public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { SerializedProperty prop_GlyphIndex = property.FindPropertyRelative("m_Index"); @@ -25,9 +23,6 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty prop_AtlasIndex = property.FindPropertyRelative("m_AtlasIndex"); SerializedProperty prop_ClassDefinitionType = property.FindPropertyRelative("m_ClassDefinitionType"); - if (TMP_PropertyDrawerUtilities.s_RefreshGlyphProxyLookup) - TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject, m_GlyphLookupDictionary); - GUIStyle style = new GUIStyle(EditorStyles.label); style.richText = true; @@ -55,7 +50,7 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten EditorGUI.PropertyField(new Rect(rect.x + 190, rect.y + 65, minWidth, 18), prop_ClassDefinitionType, k_ClassTypeLabel); } - DrawGlyph((uint)prop_GlyphIndex.intValue, new Rect(position.x, position.y + 2, 64, 80), property); + DrawGlyph(new Rect(position.x, position.y + 2, 64, 80), property); } public override float GetPropertyHeight(SerializedProperty property, GUIContent label) @@ -63,36 +58,24 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent return 130f; } - void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) + void DrawGlyph(Rect glyphDrawPosition, SerializedProperty property) { // Get a reference to the serialized object which can either be a TMP_FontAsset or FontAsset. SerializedObject so = property.serializedObject; if (so == null) return; - if (m_GlyphLookupDictionary == null) - { - m_GlyphLookupDictionary = new Dictionary(); - TMP_PropertyDrawerUtilities.PopulateGlyphProxyLookupDictionary(so, m_GlyphLookupDictionary); - } - - // Try getting a reference to the glyph for the given glyph index. - if (!m_GlyphLookupDictionary.TryGetValue(glyphIndex, out GlyphProxy glyph)) - return; - Texture2D atlasTexture; - if (TMP_PropertyDrawerUtilities.TryGetAtlasTextureFromSerializedObject(so, glyph.atlasIndex, out atlasTexture) == false) + int atlasIndex = property.FindPropertyRelative("m_AtlasIndex").intValue; + int padding = so.FindProperty("m_AtlasPadding").intValue; + if (TMP_PropertyDrawerUtilities.TryGetAtlasTextureFromSerializedObject(so, atlasIndex, out atlasTexture) == false) return; Material mat; if (TMP_PropertyDrawerUtilities.TryGetMaterial(so, atlasTexture, out mat) == false) return; - // Draw glyph from atlas texture. - Rect glyphDrawPosition = position; - - int padding = so.FindProperty("m_AtlasPadding").intValue; - GlyphRect glyphRect = glyph.glyphRect; + GlyphRect glyphRect = TMP_PropertyDrawerUtilities.GetGlyphRectFromGlyphSerializedProperty(property); int glyphOriginX = glyphRect.x - padding; int glyphOriginY = glyphRect.y - padding; int glyphWidth = glyphRect.width + padding * 2; diff --git a/Scripts/Editor/PropertyDrawers/TMP_MarkToBaseAdjustmentRecordPropertyDrawer.cs b/Scripts/Editor/PropertyDrawers/TMP_MarkToBaseAdjustmentRecordPropertyDrawer.cs index 6efbb21..25a29ac 100644 --- a/Scripts/Editor/PropertyDrawers/TMP_MarkToBaseAdjustmentRecordPropertyDrawer.cs +++ b/Scripts/Editor/PropertyDrawers/TMP_MarkToBaseAdjustmentRecordPropertyDrawer.cs @@ -27,6 +27,10 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty prop_MarkGlyphID = property.FindPropertyRelative("m_MarkGlyphID"); SerializedProperty prop_MarkAdjustmentRecord = property.FindPropertyRelative("m_MarkPositionAdjustment"); + // Refresh glyph proxy lookup dictionary if needed + if (TMP_PropertyDrawerUtilities.s_RefreshGlyphProxyLookup) + TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject); + position.yMin += 2; float width = position.width / 2; @@ -180,7 +184,7 @@ uint GetUnicodeCharacter (string source) return unicode; } - void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) + void DrawGlyph(uint glyphIndex, Rect glyphDrawPosition, SerializedProperty property) { // Get a reference to the serialized object which can either be a TMP_FontAsset or FontAsset. SerializedObject so = property.serializedObject; @@ -188,10 +192,7 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) return; if (m_GlyphLookupDictionary == null) - { - m_GlyphLookupDictionary = new Dictionary(); - TMP_PropertyDrawerUtilities.PopulateGlyphProxyLookupDictionary(so, m_GlyphLookupDictionary); - } + m_GlyphLookupDictionary = TMP_PropertyDrawerUtilities.GetGlyphProxyLookupDictionary(so); // Try getting a reference to the glyph for the given glyph index. if (!m_GlyphLookupDictionary.TryGetValue(glyphIndex, out GlyphProxy glyph)) @@ -205,9 +206,6 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) if (TMP_PropertyDrawerUtilities.TryGetMaterial(so, atlasTexture, out mat) == false) return; - // Draw glyph from atlas texture. - Rect glyphDrawPosition = position; - int padding = so.FindProperty("m_AtlasPadding").intValue; GlyphRect glyphRect = glyph.glyphRect; int glyphOriginX = glyphRect.x - padding; diff --git a/Scripts/Editor/PropertyDrawers/TMP_MarkToMarkAdjustmentRecordPropertyDrawer.cs b/Scripts/Editor/PropertyDrawers/TMP_MarkToMarkAdjustmentRecordPropertyDrawer.cs index 42724f4..12d5573 100644 --- a/Scripts/Editor/PropertyDrawers/TMP_MarkToMarkAdjustmentRecordPropertyDrawer.cs +++ b/Scripts/Editor/PropertyDrawers/TMP_MarkToMarkAdjustmentRecordPropertyDrawer.cs @@ -28,6 +28,10 @@ public override void OnGUI(Rect position, SerializedProperty property, GUIConten SerializedProperty prop_MarkGlyphID = property.FindPropertyRelative("m_CombiningMarkGlyphID"); SerializedProperty prop_MarkAdjustmentRecord = property.FindPropertyRelative("m_CombiningMarkPositionAdjustment"); + // Refresh glyph proxy lookup dictionary if needed + if (TMP_PropertyDrawerUtilities.s_RefreshGlyphProxyLookup) + TMP_PropertyDrawerUtilities.RefreshGlyphProxyLookup(property.serializedObject); + position.yMin += 2; float width = position.width / 2; @@ -181,7 +185,7 @@ uint GetUnicodeCharacter (string source) return unicode; } - void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) + void DrawGlyph(uint glyphIndex, Rect glyphDrawPosition, SerializedProperty property) { // Get a reference to the serialized object which can either be a TMP_FontAsset or FontAsset. SerializedObject so = property.serializedObject; @@ -189,10 +193,7 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) return; if (m_GlyphLookupDictionary == null) - { - m_GlyphLookupDictionary = new Dictionary(); - TMP_PropertyDrawerUtilities.PopulateGlyphProxyLookupDictionary(so, m_GlyphLookupDictionary); - } + m_GlyphLookupDictionary = TMP_PropertyDrawerUtilities.GetGlyphProxyLookupDictionary(so); // Try getting a reference to the glyph for the given glyph index. if (!m_GlyphLookupDictionary.TryGetValue(glyphIndex, out GlyphProxy glyph)) @@ -206,9 +207,6 @@ void DrawGlyph(uint glyphIndex, Rect position, SerializedProperty property) if (TMP_PropertyDrawerUtilities.TryGetMaterial(so, atlasTexture, out mat) == false) return; - // Draw glyph from atlas texture. - Rect glyphDrawPosition = position; - int padding = so.FindProperty("m_AtlasPadding").intValue; GlyphRect glyphRect = glyph.glyphRect; int glyphOriginX = glyphRect.x - padding; diff --git a/Scripts/Editor/PropertyDrawers/TMP_PropertyDrawerUtilities.cs b/Scripts/Editor/PropertyDrawers/TMP_PropertyDrawerUtilities.cs index 3f27928..5f8c9e9 100644 --- a/Scripts/Editor/PropertyDrawers/TMP_PropertyDrawerUtilities.cs +++ b/Scripts/Editor/PropertyDrawers/TMP_PropertyDrawerUtilities.cs @@ -18,24 +18,44 @@ internal struct GlyphProxy internal static class TMP_PropertyDrawerUtilities { internal static bool s_RefreshGlyphProxyLookup; + private static Dictionary> s_GlyphProxyLookups = new Dictionary>(); - internal static void RefreshGlyphProxyLookup(SerializedObject so, Dictionary glyphLookup) + internal static void ClearGlyphProxyLookups() { - if (glyphLookup != null) - { - glyphLookup.Clear(); - PopulateGlyphProxyLookupDictionary(so, glyphLookup); - } + s_GlyphProxyLookups.Clear(); + } + + internal static void RefreshGlyphProxyLookup(SerializedObject so) + { + if (!s_GlyphProxyLookups.ContainsKey(so)) + return; + + Dictionary glyphProxyLookup = s_GlyphProxyLookups[so]; + + glyphProxyLookup.Clear(); + PopulateGlyphProxyLookupDictionary(so, glyphProxyLookup); s_RefreshGlyphProxyLookup = false; } + internal static Dictionary GetGlyphProxyLookupDictionary(SerializedObject so) + { + if (s_GlyphProxyLookups.ContainsKey(so)) + return s_GlyphProxyLookups[so]; + + Dictionary glyphProxyLookup = new Dictionary(); + PopulateGlyphProxyLookupDictionary(so, glyphProxyLookup); + s_GlyphProxyLookups.Add(so, glyphProxyLookup); + + return glyphProxyLookup; + } + /// /// /// /// /// - internal static void PopulateGlyphProxyLookupDictionary(SerializedObject so, Dictionary lookupDictionary) + static void PopulateGlyphProxyLookupDictionary(SerializedObject so, Dictionary lookupDictionary) { if (lookupDictionary == null) return; @@ -52,21 +72,31 @@ internal static void PopulateGlyphProxyLookupDictionary(SerializedObject so, Dic } } - internal static void PopulateSpriteGlyphProxyLookupDictionary(SerializedObject so, Dictionary lookupDictionary) + internal static GlyphRect GetGlyphRectFromGlyphSerializedProperty(SerializedProperty property) { - if (lookupDictionary == null) - return; + SerializedProperty glyphRectProp = property.FindPropertyRelative("m_GlyphRect"); - // Get reference to serialized property for the glyph table - SerializedProperty glyphTable = so.FindProperty("m_SpriteGlyphTable"); + GlyphRect glyphRect = new GlyphRect(); + glyphRect.x = glyphRectProp.FindPropertyRelative("m_X").intValue; + glyphRect.y = glyphRectProp.FindPropertyRelative("m_Y").intValue; + glyphRect.width = glyphRectProp.FindPropertyRelative("m_Width").intValue; + glyphRect.height = glyphRectProp.FindPropertyRelative("m_Height").intValue; - for (int i = 0; i < glyphTable.arraySize; i++) - { - SerializedProperty glyphProperty = glyphTable.GetArrayElementAtIndex(i); - GlyphProxy proxy = GetGlyphProxyFromSerializedProperty(glyphProperty); + return glyphRect; + } - lookupDictionary.Add(proxy.index, proxy); - } + internal static GlyphMetrics GetGlyphMetricsFromGlyphSerializedProperty(SerializedProperty property) + { + SerializedProperty glyphMetricsProperty = property.FindPropertyRelative("m_Metrics"); + + GlyphMetrics glyphMetrics = new GlyphMetrics(); + glyphMetrics.horizontalBearingX = glyphMetricsProperty.FindPropertyRelative("m_HorizontalBearingX").floatValue; + glyphMetrics.horizontalBearingY = glyphMetricsProperty.FindPropertyRelative("m_HorizontalBearingY").floatValue; + glyphMetrics.horizontalAdvance = glyphMetricsProperty.FindPropertyRelative("m_HorizontalAdvance").floatValue; + glyphMetrics.width = glyphMetricsProperty.FindPropertyRelative("m_Width").floatValue; + glyphMetrics.height = glyphMetricsProperty.FindPropertyRelative("m_Height").floatValue; + + return glyphMetrics; } /// @@ -74,25 +104,13 @@ internal static void PopulateSpriteGlyphProxyLookupDictionary(SerializedObject s /// /// /// - static GlyphProxy GetGlyphProxyFromSerializedProperty(SerializedProperty property) + internal static GlyphProxy GetGlyphProxyFromSerializedProperty(SerializedProperty property) { GlyphProxy proxy = new GlyphProxy(); proxy.index = (uint)property.FindPropertyRelative("m_Index").intValue; - - SerializedProperty glyphRectProperty = property.FindPropertyRelative("m_GlyphRect"); - proxy.glyphRect = new GlyphRect(); - proxy.glyphRect.x = glyphRectProperty.FindPropertyRelative("m_X").intValue; - proxy.glyphRect.y = glyphRectProperty.FindPropertyRelative("m_Y").intValue; - proxy.glyphRect.width = glyphRectProperty.FindPropertyRelative("m_Width").intValue; - proxy.glyphRect.height = glyphRectProperty.FindPropertyRelative("m_Height").intValue; - - SerializedProperty glyphMetricsProperty = property.FindPropertyRelative("m_Metrics"); - proxy.metrics = new GlyphMetrics(); - proxy.metrics.horizontalBearingX = glyphMetricsProperty.FindPropertyRelative("m_HorizontalBearingX").floatValue; - proxy.metrics.horizontalBearingY = glyphMetricsProperty.FindPropertyRelative("m_HorizontalBearingY").floatValue; - proxy.metrics.horizontalAdvance = glyphMetricsProperty.FindPropertyRelative("m_HorizontalAdvance").floatValue; - proxy.metrics.width = glyphMetricsProperty.FindPropertyRelative("m_Width").floatValue; - proxy.metrics.height = glyphMetricsProperty.FindPropertyRelative("m_Height").floatValue; + proxy.glyphRect = GetGlyphRectFromGlyphSerializedProperty(property); + proxy.metrics = GetGlyphMetricsFromGlyphSerializedProperty(property); + proxy.atlasIndex = property.FindPropertyRelative("m_AtlasIndex").intValue; return proxy; } @@ -142,6 +160,7 @@ internal static bool TryGetMaterial(SerializedObject serializedObject, Texture2D return false; mat.mainTexture = texture; + mat.color = Color.white; } else { diff --git a/Scripts/Editor/PropertyDrawers/TMP_SpriteCharacterPropertyDrawer.cs b/Scripts/Editor/PropertyDrawers/TMP_SpriteCharacterPropertyDrawer.cs index 3f42437..1d144a2 100644 --- a/Scripts/Editor/PropertyDrawers/TMP_SpriteCharacterPropertyDrawer.cs +++ b/Scripts/Editor/PropertyDrawers/TMP_SpriteCharacterPropertyDrawer.cs @@ -16,7 +16,6 @@ public class TMP_SpriteCharacterPropertyDrawer : PropertyDrawer public override void OnGUI(Rect position, SerializedProperty property, GUIContent label) { SerializedProperty prop_SpriteName = property.FindPropertyRelative("m_Name"); - //SerializedProperty prop_SpriteNameHashCode = property.FindPropertyRelative("m_HashCode"); SerializedProperty prop_SpriteUnicode = property.FindPropertyRelative("m_Unicode"); SerializedProperty prop_SpriteGlyphIndex = property.FindPropertyRelative("m_GlyphIndex"); SerializedProperty prop_SpriteScale = property.FindPropertyRelative("m_Scale"); @@ -168,10 +167,7 @@ public override float GetPropertyHeight(SerializedProperty property, GUIContent void DrawSpriteGlyph(uint glyphIndex, Rect position, SerializedProperty property) { if (m_GlyphLookupDictionary == null) - { - m_GlyphLookupDictionary = new Dictionary(); - TMP_PropertyDrawerUtilities.PopulateSpriteGlyphProxyLookupDictionary(property.serializedObject, m_GlyphLookupDictionary); - } + m_GlyphLookupDictionary = TMP_PropertyDrawerUtilities.GetGlyphProxyLookupDictionary(property.serializedObject); // Try getting a reference to the glyph for the given glyph index. if (!m_GlyphLookupDictionary.TryGetValue(glyphIndex, out GlyphProxy glyph)) diff --git a/Scripts/Editor/PropertyDrawers/TMP_SpriteGlyphPropertyDrawer.cs b/Scripts/Editor/PropertyDrawers/TMP_SpriteGlyphPropertyDrawer.cs index ddfa810..566b93b 100644 --- a/Scripts/Editor/PropertyDrawers/TMP_SpriteGlyphPropertyDrawer.cs +++ b/Scripts/Editor/PropertyDrawers/TMP_SpriteGlyphPropertyDrawer.cs @@ -61,27 +61,21 @@ void DrawGlyph(Rect position, SerializedProperty property) Vector2 spriteTexPosition = new Vector2(position.x, position.y); Vector2 spriteSize = new Vector2(65, 65); + GlyphRect glyphRect = TMP_PropertyDrawerUtilities.GetGlyphRectFromGlyphSerializedProperty(property); - SerializedProperty prop_GlyphRect = property.FindPropertyRelative("m_GlyphRect"); - - int spriteImageX = prop_GlyphRect.FindPropertyRelative("m_X").intValue; - int spriteImageY = prop_GlyphRect.FindPropertyRelative("m_Y").intValue; - int spriteImageWidth = prop_GlyphRect.FindPropertyRelative("m_Width").intValue; - int spriteImageHeight = prop_GlyphRect.FindPropertyRelative("m_Height").intValue; - - if (spriteImageWidth >= spriteImageHeight) + if (glyphRect.width >= glyphRect.height) { - spriteSize.y = spriteImageHeight * spriteSize.x / spriteImageWidth; + spriteSize.y = glyphRect.height * spriteSize.x / glyphRect.width; spriteTexPosition.y += (spriteSize.x - spriteSize.y) / 2; } else { - spriteSize.x = spriteImageWidth * spriteSize.y / spriteImageHeight; + spriteSize.x = glyphRect.width * spriteSize.y / glyphRect.height; spriteTexPosition.x += (spriteSize.y - spriteSize.x) / 2; } // Compute the normalized texture coordinates - Rect texCoords = new Rect((float)spriteImageX / tex.width, (float)spriteImageY / tex.height, (float)spriteImageWidth / tex.width, (float)spriteImageHeight / tex.height); + Rect texCoords = new Rect((float)glyphRect.x / tex.width, (float)glyphRect.y / tex.height, (float)glyphRect.width / tex.width, (float)glyphRect.height / tex.height); GUI.DrawTextureWithTexCoords(new Rect(spriteTexPosition.x + 5, spriteTexPosition.y + 32f, spriteSize.x, spriteSize.y), tex, texCoords, true); } diff --git a/Scripts/Editor/TMP_BaseEditorPanel.cs b/Scripts/Editor/TMP_BaseEditorPanel.cs index 186a30b..5caea52 100644 --- a/Scripts/Editor/TMP_BaseEditorPanel.cs +++ b/Scripts/Editor/TMP_BaseEditorPanel.cs @@ -2,6 +2,7 @@ using UnityEditor; using System.Collections.Generic; using System.Linq; +using UnityEngine.TextCore.LowLevel; namespace TMPro.EditorUtilities @@ -65,7 +66,7 @@ public abstract class TMP_BaseEditorPanel : Editor static readonly GUIContent k_VerticalMappingLabel = new GUIContent("Vertical Mapping", "Vertical UV mapping when using a shader with a texture face option."); static readonly GUIContent k_LineOffsetLabel = new GUIContent("Line Offset", "Adds an horizontal offset to each successive line. Used for slanted texturing."); - static readonly GUIContent k_KerningLabel = new GUIContent("Kerning", "Enables character specific spacing between pairs of characters."); + static readonly GUIContent k_FontFeaturesLabel = new GUIContent("Font Features", "Font features available for the primary font asset assigned to the text component."); static readonly GUIContent k_PaddingLabel = new GUIContent("Extra Padding", "Adds some padding between the characters and the edge of the text mesh. Can reduce graphical errors when displaying small text."); static readonly GUIContent k_LeftLabel = new GUIContent("Left"); @@ -148,7 +149,7 @@ protected struct Foldout protected SerializedProperty m_LinkedTextComponentProp; protected SerializedProperty m_ParentLinkedTextComponentProp; - protected SerializedProperty m_EnableKerningProp; + protected SerializedProperty m_FontFeaturesActiveProp; protected SerializedProperty m_IsRichTextProp; @@ -183,6 +184,8 @@ protected struct Foldout protected Vector3[] m_RectCorners = new Vector3[4]; protected Vector3[] m_HandlePoints = new Vector3[4]; + private static readonly string[] k_FontFeatures = new string[] { "kern", "liga", "mark", "mkmk" }; + protected virtual void OnEnable() { m_TextProp = serializedObject.FindProperty("m_text"); @@ -229,7 +232,7 @@ protected virtual void OnEnable() m_LinkedTextComponentProp = serializedObject.FindProperty("m_linkedTextComponent"); m_ParentLinkedTextComponentProp = serializedObject.FindProperty("parentLinkedComponent"); - m_EnableKerningProp = serializedObject.FindProperty("m_enableKerning"); + m_FontFeaturesActiveProp = serializedObject.FindProperty("m_ActiveFontFeatures"); m_EnableExtraPaddingProp = serializedObject.FindProperty("m_enableExtraPadding"); m_IsRichTextProp = serializedObject.FindProperty("m_isRichText"); @@ -272,6 +275,25 @@ protected virtual void OnEnable() // Get Styles from Style Sheet if (TMP_Settings.instance != null) m_StyleNames = GetStyleNames(); + + // Get list of font features for the primary font asset assigned to the text component + // FontEngine.LoadFontFace(m_TextComponent.font.SourceFont_EditorRef); + // OTL_Table gposTable = UnityEngine.TextCore.LowLevel.FontEngine.GetOpenTypeLayoutTable(OTL_TableType.GPOS); + // OTL_Table gsubTable = UnityEngine.TextCore.LowLevel.FontEngine.GetOpenTypeLayoutTable(OTL_TableType.GSUB); + // + // HashSet fontFeatures = new HashSet(); + // + // foreach (OTL_Feature feature in gposTable.features) + // { + // fontFeatures.Add(feature.tag); + // } + // + // foreach (OTL_Feature feature in gsubTable.features) + // { + // fontFeatures.Add(feature.tag); + // } + // + // m_FontFeatures = fontFeatures.OrderBy(item => item).ToArray(); // Register to receive events when style sheets are modified. TMPro_EventManager.TEXT_STYLE_PROPERTY_EVENT.Add(ON_TEXT_STYLE_CHANGED); @@ -1236,15 +1258,50 @@ protected void DrawTextureMapping() EditorGUILayout.Space(); } - protected void DrawKerning() + protected void DrawFontFeatures() { - // KERNING + int srcMask = 0; + + int featureCount = m_FontFeaturesActiveProp.arraySize; + for (int i = 0; i < featureCount; i++) + { + SerializedProperty activeFeatureProperty = m_FontFeaturesActiveProp.GetArrayElementAtIndex(i); + + for (int j = 0; j < k_FontFeatures.Length; j++) + { + if (activeFeatureProperty.intValue == k_FontFeatures[j].TagToInt()) + { + srcMask |= 0x1 << j; + break; + } + } + } + EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(m_EnableKerningProp, k_KerningLabel); + + int mask = EditorGUILayout.MaskField(k_FontFeaturesLabel, srcMask, k_FontFeatures); + if (EditorGUI.EndChangeCheck()) { + m_FontFeaturesActiveProp.ClearArray(); + + int writeIndex = 0; + + for (int i = 0; i < k_FontFeatures.Length; i++) + { + int bit = 0x1 << i; + if ((mask & bit) == bit) + { + m_FontFeaturesActiveProp.InsertArrayElementAtIndex(writeIndex); + SerializedProperty newFeature = m_FontFeaturesActiveProp.GetArrayElementAtIndex(writeIndex); + newFeature.intValue = k_FontFeatures[i].TagToInt(); + + writeIndex += 1; + } + } + m_HavePropertiesChanged = true; - } + } } protected void DrawPadding() diff --git a/Scripts/Editor/TMP_EditorPanel.cs b/Scripts/Editor/TMP_EditorPanel.cs index 1fd6f8f..45265c6 100644 --- a/Scripts/Editor/TMP_EditorPanel.cs +++ b/Scripts/Editor/TMP_EditorPanel.cs @@ -69,7 +69,6 @@ protected override void DrawExtraSettings() if (Foldout.extraSettings) { - //EditorGUI.indentLevel += 1; DrawMargins(); @@ -91,13 +90,10 @@ protected override void DrawExtraSettings() DrawStyleSheet(); - //DrawVolumetricSetup(); - - DrawKerning(); + DrawFontFeatures(); DrawPadding(); - - //EditorGUI.indentLevel -= 1; + } } diff --git a/Scripts/Editor/TMP_EditorPanelUI.cs b/Scripts/Editor/TMP_EditorPanelUI.cs index 01af90a..2b22a03 100644 --- a/Scripts/Editor/TMP_EditorPanelUI.cs +++ b/Scripts/Editor/TMP_EditorPanelUI.cs @@ -31,7 +31,6 @@ protected override void DrawExtraSettings() GUI.Label(rect, (Foldout.extraSettings ? k_UiStateLabel[0] : k_UiStateLabel[1]), TMP_UIStyleManager.rightLabel); if (Foldout.extraSettings) { - //EditorGUI.indentLevel += 1; DrawMargins(); @@ -52,12 +51,11 @@ protected override void DrawExtraSettings() DrawSpriteAsset(); DrawStyleSheet(); - - DrawKerning(); + + DrawFontFeatures(); DrawPadding(); - //EditorGUI.indentLevel -= 1; } } diff --git a/Scripts/Editor/TMP_FontAssetEditor.cs b/Scripts/Editor/TMP_FontAssetEditor.cs index 69dfc59..8b9f3e6 100644 --- a/Scripts/Editor/TMP_FontAssetEditor.cs +++ b/Scripts/Editor/TMP_FontAssetEditor.cs @@ -136,6 +136,7 @@ internal static Material internalRGBABitmapMaterial private static string[] s_UiStateLabel = new string[] { "(Click to collapse) ", "(Click to expand) " }; + public static readonly GUIContent getFontFeaturesLabel = new GUIContent("Get Font Features", "Determines if OpenType font features should be retrieved from the source font file as new characters and glyphs are added to the font asset."); private GUIContent[] m_AtlasResolutionLabels = { new GUIContent("8"), new GUIContent("16"), new GUIContent("32"), new GUIContent("64"), new GUIContent("128"), new GUIContent("256"), new GUIContent("512"), new GUIContent("1024"), new GUIContent("2048"), new GUIContent("4096"), new GUIContent("8192") }; private int[] m_AtlasResolutions = { 8, 16, 32, 64, 128, 256, 512, 1024, 2048, 4096, 8192 }; @@ -196,6 +197,7 @@ enum RecordSelectionType { CharacterRecord, GlyphRecord, LigatureSubstitutionRec private string m_MarkToMarkTableSearchPattern; private List m_MarkToMarkTableSearchList; + private HashSet m_GlyphsToAdd; private bool m_isSearchDirty; @@ -213,6 +215,7 @@ enum RecordSelectionType { CharacterRecord, GlyphRecord, LigatureSubstitutionRec private SerializedProperty m_AtlasHeight_prop; private SerializedProperty m_IsMultiAtlasTexturesEnabled_prop; private SerializedProperty m_ClearDynamicDataOnBuild_prop; + private SerializedProperty m_GetFontFeatures_prop; private SerializedProperty fontWeights_prop; @@ -276,6 +279,7 @@ public void OnEnable() m_AtlasHeight_prop = serializedObject.FindProperty("m_AtlasHeight"); m_IsMultiAtlasTexturesEnabled_prop = serializedObject.FindProperty("m_IsMultiAtlasTexturesEnabled"); m_ClearDynamicDataOnBuild_prop = serializedObject.FindProperty("m_ClearDynamicDataOnBuild"); + m_GetFontFeatures_prop = serializedObject.FindProperty("m_GetFontFeatures"); fontWeights_prop = serializedObject.FindProperty("m_FontWeightTable"); @@ -327,6 +331,9 @@ public void OnEnable() // Sort Font Asset Tables m_fontAsset.SortAllTables(); + + // Clear glyph proxy lookups + TMP_PropertyDrawerUtilities.ClearGlyphProxyLookups(); } private ReorderableList PrepareReorderableList(SerializedProperty property, string label) @@ -546,7 +553,6 @@ public override void OnInspectorGUI() EditorGUILayout.IntPopup(m_AtlasWidth_prop, m_AtlasResolutionLabels, m_AtlasResolutions, new GUIContent("Atlas Width")); EditorGUILayout.IntPopup(m_AtlasHeight_prop, m_AtlasResolutionLabels, m_AtlasResolutions, new GUIContent("Atlas Height")); EditorGUILayout.PropertyField(m_IsMultiAtlasTexturesEnabled_prop, new GUIContent("Multi Atlas Textures", "Determines if the font asset will store glyphs in multiple atlas textures.")); - EditorGUILayout.PropertyField(m_ClearDynamicDataOnBuild_prop, new GUIContent("Clear Dynamic Data On Build", "Clears all dynamic data restoring the font asset back to its default creation and empty state.")); if (EditorGUI.EndChangeCheck()) { if (m_AtlasPadding_prop.intValue < 0) @@ -558,6 +564,8 @@ public override void OnInspectorGUI() m_MaterialPresetsRequireUpdate = true; m_DisplayDestructiveChangeWarning = true; } + EditorGUILayout.PropertyField(m_ClearDynamicDataOnBuild_prop, new GUIContent("Clear Dynamic Data On Build", "Clears all dynamic data restoring the font asset back to its default creation and empty state.")); + EditorGUILayout.PropertyField(m_GetFontFeatures_prop, getFontFeaturesLabel); EditorGUILayout.Space(); @@ -991,9 +999,9 @@ public override void OnInspectorGUI() GUIStyle glyphPanelStyle = new GUIStyle(EditorStyles.helpBox); - int glyphCount = m_fontAsset.glyphTable.Count; + int glyphRecordCount = m_fontAsset.glyphTable.Count; - if (GUI.Button(rect, new GUIContent("Glyph Table [" + glyphCount + "]" + (rect.width > 275 ? " Glyphs" : ""), "List of glyphs contained in this font asset."), TMP_UIStyleManager.sectionHeader)) + if (GUI.Button(rect, new GUIContent("Glyph Table [" + glyphRecordCount + "]" + (rect.width > 275 ? " Glyphs" : ""), "List of glyphs contained in this font asset."), TMP_UIStyleManager.sectionHeader)) UI_PanelState.glyphTablePanel = !UI_PanelState.glyphTablePanel; GUI.Label(rect, (UI_PanelState.glyphTablePanel ? "" : s_UiStateLabel[1]), TMP_UIStyleManager.rightLabel); @@ -1177,6 +1185,8 @@ public override void OnInspectorGUI() } } + //DisplayAddRemoveButtons(m_GlyphTable_prop, m_SelectedGlyphRecord, glyphRecordCount); + DisplayPageNavigation(ref m_CurrentGlyphPage, arraySize, itemsPerPage); EditorGUILayout.Space(); @@ -1266,7 +1276,7 @@ public override void OnInspectorGUI() using (new EditorGUI.DisabledScope(i != m_SelectedLigatureRecord)) { EditorGUI.BeginChangeCheck(); - EditorGUILayout.PropertyField(ligaturePropertyRecord, new GUIContent("Selectable")); + EditorGUILayout.PropertyField(ligaturePropertyRecord); if (EditorGUI.EndChangeCheck()) { @@ -2114,7 +2124,7 @@ void DisplayAddRemoveButtons(SerializedProperty property, int selectedRecord, in rect.width /= 6; // Add Style - rect.x = rect.width * 4 + 15; + rect.x = rect.width * 4 + 17; if (GUI.Button(rect, "+")) { int index = selectedRecord == -1 ? 0 : selectedRecord; @@ -2297,6 +2307,38 @@ void RemoveCharacterFromList(int index) m_fontAsset.ReadFontAssetDefinition(); } + void AddNewGlyphsFromProperty(SerializedProperty property) + { + if (m_GlyphsToAdd == null) + m_GlyphsToAdd = new HashSet(); + else + m_GlyphsToAdd.Clear(); + + string propertyType = property.type; + + switch (propertyType) + { + case "LigatureSubstitutionRecord": + int componentCount = property.FindPropertyRelative("m_ComponentGlyphIDs").arraySize; + for (int i = 0; i < componentCount; i++) + { + uint glyphIndex = (uint)property.FindPropertyRelative("m_ComponentGlyphIDs").GetArrayElementAtIndex(i).intValue; + m_GlyphsToAdd.Add(glyphIndex); + } + + m_GlyphsToAdd.Add((uint)property.FindPropertyRelative("m_LigatureGlyphID").intValue); + + foreach (uint glyphIndex in m_GlyphsToAdd) + { + if (glyphIndex != 0) + m_fontAsset.AddGlyphInternal(glyphIndex); + } + + break; + } + + } + // Check if any of the Style elements were clicked on. private bool DoSelectionCheck(Rect selectionArea) @@ -2321,38 +2363,11 @@ private bool DoSelectionCheck(Rect selectionArea) private void UpdateLigatureSubstitutionRecordLookup(SerializedProperty property) { serializedObject.ApplyModifiedProperties(); + AddNewGlyphsFromProperty(property); m_fontAsset.InitializeLigatureSubstitutionLookupDictionary(); isAssetDirty = true; - - /*LigatureSubstitutionRecord record = GetLigatureSubstitutionRecord(property); - - uint firstComponentGlyphIndex = record.componentGlyphIDs[0]; - - // Lookup dictionary entry and update it - if (m_FontFeatureTable.m_LigatureSubstitutionRecordLookup.ContainsKey(firstComponentGlyphIndex)) - m_FontFeatureTable.m_LigatureSubstitutionRecordLookup[firstComponentGlyphIndex] = record;*/ } - /* - LigatureSubstitutionRecord GetLigatureSubstitutionRecord(SerializedProperty property) - { - LigatureSubstitutionRecord record = new LigatureSubstitutionRecord(); - - // Get Component Glyph IDs - SerializedProperty componentGlyphIDProperty = property.FindPropertyRelative("m_ComponentGlyphIDs"); - uint[] glyphIndexes = new uint[componentGlyphIDProperty.arraySize]; - for (int i = 0; i < glyphIndexes.Length; i++) - { - glyphIndexes[i] = (uint)componentGlyphIDProperty.GetArrayElementAtIndex(i).intValue; - } - record.componentGlyphIDs = glyphIndexes; - - // Get Ligature Glyph ID - record.ligatureGlyphID = (uint)property.FindPropertyRelative("m_LigatureGlyphID").intValue; - - return record; - }*/ - void SetPropertyHolderGlyphIndexes() { uint firstCharacterUnicode = (uint)m_FirstCharacterUnicode_prop.intValue; @@ -2821,12 +2836,16 @@ void DrawMarkToBasePreview(int selectedRecord, Rect rect) DrawAnchorPoint(baseAnchorPosition, Color.green); - Glyph markGlyph = m_fontAsset.glyphLookupTable[markGlyphIndex]; + // Draw Mark + if (m_fontAsset.glyphLookupTable.ContainsKey(markGlyphIndex)) + { + Glyph markGlyph = m_fontAsset.glyphLookupTable[markGlyphIndex]; - Rect markGlyphPosition = new Rect(baseAnchorPosition.x + (markGlyph.metrics.horizontalBearingX - adjustmentRecord.markPositionAdjustment.xPositionAdjustment) * scale, baseAnchorPosition.y + (adjustmentRecord.markPositionAdjustment.yPositionAdjustment - markGlyph.metrics.horizontalBearingY) * scale, markGlyph.metrics.width, markGlyph.metrics.height); + Rect markGlyphPosition = new Rect(baseAnchorPosition.x + (markGlyph.metrics.horizontalBearingX - adjustmentRecord.markPositionAdjustment.xPositionAdjustment) * scale, baseAnchorPosition.y + (adjustmentRecord.markPositionAdjustment.yPositionAdjustment - markGlyph.metrics.horizontalBearingY) * scale, markGlyph.metrics.width, markGlyph.metrics.height); - // Draw Mark Origin - DrawGlyph(markGlyph, markGlyphPosition, scale); + // Draw Mark Origin + DrawGlyph(markGlyph, markGlyphPosition, scale); + } } void DrawMarkToMarkPreview(int selectedRecord, Rect rect) @@ -2869,11 +2888,15 @@ void DrawMarkToMarkPreview(int selectedRecord, Rect rect) DrawAnchorPoint(baseAnchorPosition, Color.green); - Glyph markGlyph = m_fontAsset.glyphLookupTable[markGlyphIndex]; + // Draw Mark Glyph + if (m_fontAsset.glyphLookupTable.ContainsKey(markGlyphIndex)) + { + Glyph markGlyph = m_fontAsset.glyphLookupTable[markGlyphIndex]; - Rect markGlyphPosition = new Rect(baseAnchorPosition.x + (markGlyph.metrics.horizontalBearingX - adjustmentRecord.combiningMarkPositionAdjustment.xPositionAdjustment) * scale, baseAnchorPosition.y + (adjustmentRecord.combiningMarkPositionAdjustment.yPositionAdjustment - markGlyph.metrics.horizontalBearingY) * scale, markGlyph.metrics.width, markGlyph.metrics.height); + Rect markGlyphPosition = new Rect(baseAnchorPosition.x + (markGlyph.metrics.horizontalBearingX - adjustmentRecord.combiningMarkPositionAdjustment.xPositionAdjustment) * scale, baseAnchorPosition.y + (adjustmentRecord.combiningMarkPositionAdjustment.yPositionAdjustment - markGlyph.metrics.horizontalBearingY) * scale, markGlyph.metrics.width, markGlyph.metrics.height); - DrawGlyph(markGlyph, markGlyphPosition, scale); + DrawGlyph(markGlyph, markGlyphPosition, scale); + } } void DrawBaseline(Vector2 position, float width, Color color) diff --git a/Scripts/Editor/TMP_FontAsset_CreationMenu.cs b/Scripts/Editor/TMP_FontAsset_CreationMenu.cs index 2df77d7..4119f3b 100644 --- a/Scripts/Editor/TMP_FontAsset_CreationMenu.cs +++ b/Scripts/Editor/TMP_FontAsset_CreationMenu.cs @@ -181,7 +181,7 @@ static void CreateFontAssetFromSelectedObject(Object target, GlyphRenderMode ren { case GlyphRenderMode.SMOOTH: fontAsset.atlasRenderMode = GlyphRenderMode.SMOOTH; - texture = new Texture2D(0, 0, TextureFormat.Alpha8, false); + texture = new Texture2D(1, 1, TextureFormat.Alpha8, false); shader = Shader.Find("TextMeshPro/Bitmap"); packingModifier = 0; mat = new Material(shader); @@ -189,7 +189,7 @@ static void CreateFontAssetFromSelectedObject(Object target, GlyphRenderMode ren #if TEXTCORE_FONT_ENGINE_1_5_OR_NEWER case GlyphRenderMode.COLOR: fontAsset.atlasRenderMode = GlyphRenderMode.COLOR; - texture = new Texture2D(0, 0, TextureFormat.RGBA32, false); + texture = new Texture2D(1, 1, TextureFormat.RGBA32, false); shader = Shader.Find("TextMeshPro/Sprite"); packingModifier = 0; mat = new Material(shader); @@ -198,7 +198,7 @@ static void CreateFontAssetFromSelectedObject(Object target, GlyphRenderMode ren case GlyphRenderMode.SDFAA: default: fontAsset.atlasRenderMode = GlyphRenderMode.SDFAA; - texture = new Texture2D(0, 0, TextureFormat.Alpha8, false); + texture = new Texture2D(1, 1, TextureFormat.Alpha8, false); shader = Shader.Find("TextMeshPro/Distance Field"); packingModifier = 1; mat = new Material(shader); diff --git a/Scripts/Editor/TMP_InputFieldEditor.cs b/Scripts/Editor/TMP_InputFieldEditor.cs index d585640..6f57224 100644 --- a/Scripts/Editor/TMP_InputFieldEditor.cs +++ b/Scripts/Editor/TMP_InputFieldEditor.cs @@ -268,7 +268,7 @@ public override void OnInspectorGUI() GUI.enabled = true; EditorGUI.indentLevel--; - EditorGUILayout.PropertyField(m_RestoreOriginalTextOnEscape, new GUIContent("Restore On ESC Key", "Should the original text be restored when pressing ESC?")); + EditorGUILayout.PropertyField(m_RestoreOriginalTextOnEscape, new GUIContent("Restore On ESC Key", "Should the original text be restored when pressing ESC? (Property not applicable for HoloLens)")); EditorGUILayout.PropertyField(m_ShouldActivateOnSelect, new GUIContent("Should Activate On Select", "Determines if the Input Field will be activated when selected.")); EditorGUILayout.PropertyField(m_HideMobileKeyboard, new GUIContent("Hide Soft Keyboard", "Controls the visibility of the mobile virtual keyboard.")); diff --git a/Scripts/Editor/TMP_SettingsEditor.cs b/Scripts/Editor/TMP_SettingsEditor.cs index 1d35430..e657b22 100644 --- a/Scripts/Editor/TMP_SettingsEditor.cs +++ b/Scripts/Editor/TMP_SettingsEditor.cs @@ -38,12 +38,13 @@ internal class Styles public static readonly GUIContent textWrappingModeLabel = new GUIContent("Text Wrapping Mode"); public static readonly GUIContent kerningLabel = new GUIContent("Kerning"); + public static readonly GUIContent fontFeaturesLabel = new GUIContent("Font Features", "Font features that should be set by default on the text component."); public static readonly GUIContent extraPaddingLabel = new GUIContent("Extra Padding"); public static readonly GUIContent tintAllSpritesLabel = new GUIContent("Tint All Sprites"); public static readonly GUIContent parseEscapeCharactersLabel = new GUIContent("Parse Escape Sequence"); public static readonly GUIContent dynamicFontSystemSettingsLabel = new GUIContent("Dynamic Font System Settings"); - public static readonly GUIContent getFontFeaturesAtRuntime = new GUIContent("Get Font Features at Runtime", "Determines if Glyph Adjustment Data will be retrieved from font files at runtime when new characters and glyphs are added to font assets."); + public static readonly GUIContent getFontFeaturesAtRuntime = new GUIContent("Get Font Features at Runtime", "Determines if OpenType font features should be retrieved from source font files as new characters and glyphs are added to font assets."); public static readonly GUIContent dynamicAtlasTextureGroup = new GUIContent("Dynamic Atlas Texture Group"); public static readonly GUIContent missingGlyphLabel = new GUIContent("Missing Character Unicode", "The character to be displayed when the requested character is not found in any font asset or fallbacks."); @@ -96,7 +97,7 @@ internal class Styles SerializedProperty m_PropMatchMaterialPreset; SerializedProperty m_PropHideSubTextObjects; SerializedProperty m_PropTextWrappingMode; - SerializedProperty m_PropKerning; + SerializedProperty m_PropFontFeatures; SerializedProperty m_PropExtraPadding; SerializedProperty m_PropTintAllSprites; SerializedProperty m_PropParseEscapeCharacters; @@ -114,6 +115,8 @@ internal class Styles private const string k_UndoRedo = "UndoRedoPerformed"; private bool m_IsFallbackGlyphCacheDirty; + + private static readonly string[] k_FontFeatures = new string[] { "kern", "liga", "mark", "mkmk" }; public void OnEnable() { @@ -187,7 +190,8 @@ public void OnEnable() m_PropHideSubTextObjects = serializedObject.FindProperty("m_HideSubTextObjects"); m_PropTextWrappingMode = serializedObject.FindProperty("m_TextWrappingMode"); - m_PropKerning = serializedObject.FindProperty("m_enableKerning"); + + m_PropFontFeatures = serializedObject.FindProperty("m_ActiveFontFeatures"); m_PropExtraPadding = serializedObject.FindProperty("m_enableExtraPadding"); m_PropTintAllSprites = serializedObject.FindProperty("m_enableTintAllSprites"); m_PropParseEscapeCharacters = serializedObject.FindProperty("m_enableParseEscapeCharacters"); @@ -297,7 +301,8 @@ public override void OnInspectorGUI() EditorGUIUtility.fieldWidth = fieldWidth; EditorGUILayout.PropertyField(m_PropTextWrappingMode, Styles.textWrappingModeLabel); - EditorGUILayout.PropertyField(m_PropKerning, Styles.kerningLabel); + + DrawFontFeatures(); EditorGUILayout.PropertyField(m_PropExtraPadding, Styles.extraPaddingLabel); EditorGUILayout.PropertyField(m_PropTintAllSprites, Styles.tintAllSpritesLabel); @@ -393,6 +398,50 @@ public override void OnInspectorGUI() TMPro_EventManager.ON_TMP_SETTINGS_CHANGED(); } } + + void DrawFontFeatures() + { + int srcMask = 0; + + int featureCount = m_PropFontFeatures.arraySize; + for (int i = 0; i < featureCount; i++) + { + SerializedProperty activeFeatureProperty = m_PropFontFeatures.GetArrayElementAtIndex(i); + + for (int j = 0; j < k_FontFeatures.Length; j++) + { + if (activeFeatureProperty.intValue == k_FontFeatures[j].TagToInt()) + { + srcMask |= 0x1 << j; + break; + } + } + } + + EditorGUI.BeginChangeCheck(); + + int mask = EditorGUILayout.MaskField(Styles.fontFeaturesLabel, srcMask, k_FontFeatures); + + if (EditorGUI.EndChangeCheck()) + { + m_PropFontFeatures.ClearArray(); + + int writeIndex = 0; + + for (int i = 0; i < k_FontFeatures.Length; i++) + { + int bit = 0x1 << i; + if ((mask & bit) == bit) + { + m_PropFontFeatures.InsertArrayElementAtIndex(writeIndex); + SerializedProperty newFeature = m_PropFontFeatures.GetArrayElementAtIndex(writeIndex); + newFeature.intValue = k_FontFeatures[i].TagToInt(); + + writeIndex += 1; + } + } + } + } } #if UNITY_2018_3_OR_NEWER diff --git a/Scripts/Editor/TMP_SpriteAssetEditor.cs b/Scripts/Editor/TMP_SpriteAssetEditor.cs index ef4ab00..3656e45 100644 --- a/Scripts/Editor/TMP_SpriteAssetEditor.cs +++ b/Scripts/Editor/TMP_SpriteAssetEditor.cs @@ -75,7 +75,7 @@ public void OnEnable() m_spriteAtlas_prop = serializedObject.FindProperty("spriteSheet"); m_material_prop = serializedObject.FindProperty("m_Material"); m_SpriteCharacterTableProperty = serializedObject.FindProperty("m_SpriteCharacterTable"); - m_SpriteGlyphTableProperty = serializedObject.FindProperty("m_SpriteGlyphTable"); + m_SpriteGlyphTableProperty = serializedObject.FindProperty("m_GlyphTable"); // Fallback TMP Sprite Asset list m_fallbackSpriteAssetList = new ReorderableList(serializedObject, serializedObject.FindProperty("fallbackSpriteAssets"), true, true, true, true); @@ -91,6 +91,9 @@ public void OnEnable() { EditorGUI.LabelField(rect, new GUIContent("Fallback Sprite Asset List", "Select the Sprite Assets that will be searched and used as fallback when a given sprite is missing from this sprite asset.")); }; + + // Clear glyph proxy lookups + TMP_PropertyDrawerUtilities.ClearGlyphProxyLookups(); } diff --git a/Scripts/Editor/TMPro_CreateObjectMenu.cs b/Scripts/Editor/TMPro_CreateObjectMenu.cs index 9525565..8aa5b4f 100644 --- a/Scripts/Editor/TMPro_CreateObjectMenu.cs +++ b/Scripts/Editor/TMPro_CreateObjectMenu.cs @@ -34,7 +34,7 @@ static void CreateTextMeshProObjectPerform(MenuCommand command) Preset[] presets = Preset.GetDefaultPresetsForObject(textComponent); if (presets == null || presets.Length == 0) - { + { textComponent.text = "Sample text"; textComponent.alignment = TextAlignmentOptions.TopLeft; } @@ -43,7 +43,7 @@ static void CreateTextMeshProObjectPerform(MenuCommand command) textComponent.renderer.sortingLayerID = textComponent._SortingLayerID; textComponent.renderer.sortingOrder = textComponent._SortingOrder; } - #else + #else if (Preset.GetDefaultForObject(textComponent) == null) { textComponent.text = "Sample text"; @@ -151,7 +151,6 @@ public static void AddButton(MenuCommand menuCommand) } - [MenuItem("GameObject/UI/Input Field - TextMeshPro", false, 2037)] static void AddTextMeshProInputField(MenuCommand menuCommand) { diff --git a/Scripts/Runtime/FontFeatureCommon.cs b/Scripts/Runtime/FontFeatureCommon.cs new file mode 100644 index 0000000..810e06f --- /dev/null +++ b/Scripts/Runtime/FontFeatureCommon.cs @@ -0,0 +1,15 @@ + + +namespace UnityEngine.TextCore +{ + /// + /// Enumeration of currently supported OpenType Layout features. + /// + public enum OTL_FeatureTag : uint + { + kern = 'k' << 24 | 'e' << 16 | 'r' << 8 | 'n', + liga = 'l' << 24 | 'i' << 16 | 'g' << 8 | 'a', + mark = 'm' << 24 | 'a' << 16 | 'r' << 8 | 'k', + mkmk = 'm' << 24 | 'k' << 16 | 'm' << 8 | 'k', + } +} diff --git a/Scripts/Runtime/FontFeatureCommon.cs.meta b/Scripts/Runtime/FontFeatureCommon.cs.meta new file mode 100644 index 0000000..7ce018b --- /dev/null +++ b/Scripts/Runtime/FontFeatureCommon.cs.meta @@ -0,0 +1,11 @@ +fileFormatVersion: 2 +guid: 326e2b1b31fc06d4fa884c803b27b46a +MonoImporter: + externalObjects: {} + serializedVersion: 2 + defaultReferences: [] + executionOrder: 0 + icon: {instanceID: 0} + userData: + assetBundleName: + assetBundleVariant: diff --git a/Scripts/Runtime/FontFeatureCommonGSUB.cs b/Scripts/Runtime/FontFeatureCommonGSUB.cs index 5ea2543..a7618cf 100644 --- a/Scripts/Runtime/FontFeatureCommonGSUB.cs +++ b/Scripts/Runtime/FontFeatureCommonGSUB.cs @@ -1,6 +1,7 @@ using System; using UnityEngine; +#pragma warning disable CS0660, CS0661 namespace TMPro { @@ -74,5 +75,33 @@ public struct LigatureSubstitutionRecord [SerializeField] private uint m_LigatureGlyphID; + + // ============================================= + // Operator overrides + // ============================================= + + public static bool operator==(LigatureSubstitutionRecord lhs, LigatureSubstitutionRecord rhs) + { + if (lhs.ligatureGlyphID != rhs.m_LigatureGlyphID) + return false; + + int lhsComponentCount = lhs.m_ComponentGlyphIDs.Length; + + if (lhsComponentCount != rhs.m_ComponentGlyphIDs.Length) + return false; + + for (int i = 0; i < lhsComponentCount; i++) + { + if (lhs.m_ComponentGlyphIDs[i] != rhs.m_ComponentGlyphIDs[i]) + return false; + } + + return true; + } + + public static bool operator!=(LigatureSubstitutionRecord lhs, LigatureSubstitutionRecord rhs) + { + return !(lhs == rhs); + } } } diff --git a/Scripts/Runtime/TMP_DefaultControls.cs b/Scripts/Runtime/TMP_DefaultControls.cs index 9f9e435..88b91a0 100644 --- a/Scripts/Runtime/TMP_DefaultControls.cs +++ b/Scripts/Runtime/TMP_DefaultControls.cs @@ -37,10 +37,17 @@ public struct Resources private static GameObject CreateUIElementRoot(string name, Vector2 size) { - GameObject child = new GameObject(name); - RectTransform rectTransform = child.AddComponent(); + GameObject root; + + #if UNITY_EDITOR + root = ObjectFactory.CreateGameObject(name); + #else + root = new GameObject(name); + #endif + + RectTransform rectTransform = root.AddComponent(); rectTransform.sizeDelta = size; - return child; + return root; } static GameObject CreateUIObject(string name, GameObject parent) diff --git a/Scripts/Runtime/TMP_FontAsset.cs b/Scripts/Runtime/TMP_FontAsset.cs index 2be57b9..2487d2a 100644 --- a/Scripts/Runtime/TMP_FontAsset.cs +++ b/Scripts/Runtime/TMP_FontAsset.cs @@ -261,6 +261,17 @@ public bool isMultiAtlasTexturesEnabled [SerializeField] private bool m_IsMultiAtlasTexturesEnabled; + /// + /// Determines if OpenType font features should be retrieved from the source font file as new characters and glyphs are added dynamically to the font asset. + /// + public bool getFontFeatures + { + get { return m_GetFontFeatures; } + set { m_GetFontFeatures = value; } + } + [SerializeField] + private bool m_GetFontFeatures = true; + /// /// Determines if dynamic font asset data should be cleared before builds. /// @@ -590,7 +601,7 @@ static TMP_FontAsset CreateFontAssetInstance(Font font, int atlasPadding, GlyphR #else TextureFormat texFormat = TextureFormat.Alpha8; #endif - Texture2D texture = new Texture2D(0, 0, texFormat, false); + Texture2D texture = new Texture2D(1, 1, texFormat, false); fontAsset.atlasTextures[0] = texture; fontAsset.isMultiAtlasTexturesEnabled = enableMultiAtlasSupport; @@ -1810,7 +1821,7 @@ public bool TryAddCharacters(uint[] unicodes, out uint[] missingUnicodes, bool i } // Resize the Atlas Texture to the appropriate size - if (m_AtlasTextures[m_AtlasTextureIndex].width == 0 || m_AtlasTextures[m_AtlasTextureIndex].height == 0) + if (m_AtlasTextures[m_AtlasTextureIndex].width <= 1 || m_AtlasTextures[m_AtlasTextureIndex].height <= 1) { #if UNITY_2021_2_OR_NEWER m_AtlasTextures[m_AtlasTextureIndex].Reinitialize(m_AtlasWidth, m_AtlasHeight); @@ -2034,7 +2045,7 @@ public bool TryAddCharacters(string characters, out string missingCharacters, bo } // Resize the Atlas Texture to the appropriate size - if (m_AtlasTextures[m_AtlasTextureIndex].width == 0 || m_AtlasTextures[m_AtlasTextureIndex].height == 0) + if (m_AtlasTextures[m_AtlasTextureIndex].width <= 1 || m_AtlasTextures[m_AtlasTextureIndex].height <= 1) { #if UNITY_2021_2_OR_NEWER m_AtlasTextures[m_AtlasTextureIndex].Reinitialize(m_AtlasWidth, m_AtlasHeight); @@ -2164,7 +2175,7 @@ internal bool TryAddCharacter_Internal(uint unicode) } // Resize the Atlas Texture to the appropriate size - if (m_AtlasTextures[m_AtlasTextureIndex].width == 0 || m_AtlasTextures[m_AtlasTextureIndex].height == 0) + if (m_AtlasTextures[m_AtlasTextureIndex].width <= 1 || m_AtlasTextures[m_AtlasTextureIndex].height <= 1 { //Debug.Log("Setting initial size of atlas texture used by font asset [" + this.name + "]."); m_AtlasTextures[m_AtlasTextureIndex].Resize(m_AtlasWidth, m_AtlasHeight); @@ -2213,7 +2224,7 @@ internal TMP_Character AddCharacter_Internal(uint unicode, Glyph glyph) uint glyphIndex = glyph.index; // Resize the Atlas Texture to the appropriate size - if (m_AtlasTextures[m_AtlasTextureIndex].width == 0 || m_AtlasTextures[m_AtlasTextureIndex].height == 0) + if (m_AtlasTextures[m_AtlasTextureIndex].width <= 1 || m_AtlasTextures[m_AtlasTextureIndex].height <= 1) { //Debug.Log("Setting initial size of atlas texture used by font asset [" + this.name + "]."); m_AtlasTextures[m_AtlasTextureIndex].Resize(m_AtlasWidth, m_AtlasHeight); @@ -2265,6 +2276,12 @@ internal TMP_Character AddCharacter_Internal(uint unicode, Glyph glyph) } */ + internal bool AddGlyphInternal(uint glyphIndex) + { + Glyph glyph; + return TryAddGlyphInternal(glyphIndex, out glyph); + } + /// /// /// @@ -2285,6 +2302,13 @@ internal bool TryAddGlyphInternal(uint glyphIndex, out Glyph glyph) k_TryAddGlyphMarker.End(); return true; } + + // Return if font asset is static + if (m_AtlasPopulationMode == AtlasPopulationMode.Static) + { + k_TryAddGlyphMarker.End(); + return false; + } // Load font face. if (LoadFontFace() != FontEngineError.Success) @@ -2302,7 +2326,7 @@ internal bool TryAddGlyphInternal(uint glyphIndex, out Glyph glyph) } // Resize the Atlas Texture to the appropriate size - if (m_AtlasTextures[m_AtlasTextureIndex].width == 0 || m_AtlasTextures[m_AtlasTextureIndex].height == 0) + if (m_AtlasTextures[m_AtlasTextureIndex].width <= 1 || m_AtlasTextures[m_AtlasTextureIndex].height <= 1) { #if UNITY_2021_2_OR_NEWER m_AtlasTextures[m_AtlasTextureIndex].Reinitialize(m_AtlasWidth, m_AtlasHeight); @@ -2334,7 +2358,7 @@ internal bool TryAddGlyphInternal(uint glyphIndex, out Glyph glyph) m_GlyphIndexList.Add(glyphIndex); m_GlyphIndexListNewlyAdded.Add(glyphIndex); - if (TMP_Settings.getFontFeaturesAtRuntime) + if (m_GetFontFeatures && TMP_Settings.getFontFeaturesAtRuntime) { UpdateGSUBFontFeaturesForNewGlyphIndex(glyphIndex); RegisterFontAssetForFontFeatureUpdate(this); @@ -2376,7 +2400,7 @@ internal bool TryAddGlyphInternal(uint glyphIndex, out Glyph glyph) m_GlyphIndexList.Add(glyphIndex); m_GlyphIndexListNewlyAdded.Add(glyphIndex); - if (TMP_Settings.getFontFeaturesAtRuntime) + if (m_GetFontFeatures && TMP_Settings.getFontFeaturesAtRuntime) { UpdateGSUBFontFeaturesForNewGlyphIndex(glyphIndex); RegisterFontAssetForFontFeatureUpdate(this); @@ -2509,7 +2533,7 @@ internal bool TryAddCharacterInternal(uint unicode, out TMP_Character character) } // Resize the Atlas Texture to the appropriate size - if (m_AtlasTextures[m_AtlasTextureIndex].width == 0 || m_AtlasTextures[m_AtlasTextureIndex].height == 0) + if (m_AtlasTextures[m_AtlasTextureIndex].width <= 1 || m_AtlasTextures[m_AtlasTextureIndex].height <= 1) { #if UNITY_2021_2_OR_NEWER m_AtlasTextures[m_AtlasTextureIndex].Reinitialize(m_AtlasWidth, m_AtlasHeight); @@ -2541,7 +2565,7 @@ internal bool TryAddCharacterInternal(uint unicode, out TMP_Character character) m_GlyphIndexList.Add(glyphIndex); m_GlyphIndexListNewlyAdded.Add(glyphIndex); - if (TMP_Settings.getFontFeaturesAtRuntime) + if (m_GetFontFeatures && TMP_Settings.getFontFeaturesAtRuntime) { UpdateGSUBFontFeaturesForNewGlyphIndex(glyphIndex); RegisterFontAssetForFontFeatureUpdate(this); @@ -2583,7 +2607,7 @@ internal bool TryAddCharacterInternal(uint unicode, out TMP_Character character) m_GlyphIndexList.Add(glyphIndex); m_GlyphIndexListNewlyAdded.Add(glyphIndex); - if (TMP_Settings.getFontFeaturesAtRuntime) + if (m_GetFontFeatures && TMP_Settings.getFontFeaturesAtRuntime) { UpdateGSUBFontFeaturesForNewGlyphIndex(glyphIndex); RegisterFontAssetForFontFeatureUpdate(this); @@ -2691,7 +2715,7 @@ internal bool TryGetCharacter_and_QueueRenderToTexture(uint unicode, out TMP_Cha m_GlyphIndexList.Add(glyphIndex); m_GlyphIndexListNewlyAdded.Add(glyphIndex); - if (TMP_Settings.getFontFeaturesAtRuntime) + if (m_GetFontFeatures && TMP_Settings.getFontFeaturesAtRuntime) { UpdateGSUBFontFeaturesForNewGlyphIndex(glyphIndex); RegisterFontAssetForFontFeatureUpdate(this); @@ -2728,7 +2752,7 @@ internal void TryAddGlyphsToAtlasTextures() return; // Resize the Atlas Texture to the appropriate size - if (m_AtlasTextures[m_AtlasTextureIndex].width == 0 || m_AtlasTextures[m_AtlasTextureIndex].height == 0) + if (m_AtlasTextures[m_AtlasTextureIndex].width <= 1 || m_AtlasTextures[m_AtlasTextureIndex].height <= 1) { //Debug.Log("Setting initial size of atlas texture used by font asset [" + this.name + "]."); m_AtlasTextures[m_AtlasTextureIndex].Resize(m_AtlasWidth, m_AtlasHeight); @@ -2856,7 +2880,7 @@ void SetupNewAtlasTexture() #if UNITY_EDITOR // Add new texture as sub asset to font asset Texture2D tex = m_AtlasTextures[m_AtlasTextureIndex]; - tex.name = m_AtlasTexture.name + " " + m_AtlasTextureIndex; + tex.name = m_AtlasTextures[0].name + " " + m_AtlasTextureIndex; OnFontAssetTextureChanged?.Invoke(tex, this); #endif @@ -2879,7 +2903,7 @@ internal void UpdateAtlasTexture() //FontEngine.RenderGlyphsToTexture(m_GlyphsPacked, m_AtlasPadding, GlyphRenderMode.SDFAA, m_AtlasTextures[m_AtlasTextureIndex]); // Resize the Atlas Texture to the appropriate size - if (m_AtlasTextures[m_AtlasTextureIndex].width == 0 || m_AtlasTextures[m_AtlasTextureIndex].height == 0) + if (m_AtlasTextures[m_AtlasTextureIndex].width <= 1 || m_AtlasTextures[m_AtlasTextureIndex].height <= 1) { #if UNITY_2021_2_OR_NEWER m_AtlasTextures[m_AtlasTextureIndex].Reinitialize(m_AtlasWidth, m_AtlasHeight); @@ -3041,17 +3065,24 @@ void AddLigatureSubstitutionRecords(UnityEngine.TextCore.LowLevel.LigatureSubsti return; uint firstComponentGlyphIndex = record.componentGlyphIDs[0]; + + LigatureSubstitutionRecord newRecord = new LigatureSubstitutionRecord { componentGlyphIDs = record.componentGlyphIDs, ligatureGlyphID = record.ligatureGlyphID }; - LigatureSubstitutionRecord newRecord = new LigatureSubstitutionRecord() { componentGlyphIDs = record.componentGlyphIDs, ligatureGlyphID = record.ligatureGlyphID }; - - // Add new record to lookup - if (!m_FontFeatureTable.m_LigatureSubstitutionRecordLookup.ContainsKey(firstComponentGlyphIndex)) + // Check if we already have a record for this new Ligature + if (m_FontFeatureTable.m_LigatureSubstitutionRecordLookup.TryGetValue(firstComponentGlyphIndex, out List existingRecords)) { - m_FontFeatureTable.m_LigatureSubstitutionRecordLookup.Add(firstComponentGlyphIndex, new List() { newRecord }); + foreach (LigatureSubstitutionRecord ligature in existingRecords) + { + if (newRecord == ligature) + return; + } + + // Add new record to lookup + m_FontFeatureTable.m_LigatureSubstitutionRecordLookup[firstComponentGlyphIndex].Add(newRecord); } else { - m_FontFeatureTable.m_LigatureSubstitutionRecordLookup[firstComponentGlyphIndex].Add(newRecord); + m_FontFeatureTable.m_LigatureSubstitutionRecordLookup.Add(firstComponentGlyphIndex, new List { newRecord }); } m_FontFeatureTable.m_LigatureSubstitutionRecords.Add(newRecord); @@ -3210,6 +3241,9 @@ void AddMarkToBaseAdjustmentRecords(UnityEngine.TextCore.LowLevel.MarkToBaseAdju for (int i = 0; i < records.Length; i++) { UnityEngine.TextCore.LowLevel.MarkToBaseAdjustmentRecord record = records[i]; + + if (records[i].baseGlyphID == 0 || records[i].markGlyphID == 0) + return; uint key = record.markGlyphID << 16 | record.baseGlyphID; @@ -3238,6 +3272,9 @@ void AddMarkToMarkAdjustmentRecords(UnityEngine.TextCore.LowLevel.MarkToMarkAdju for (int i = 0; i < records.Length; i++) { UnityEngine.TextCore.LowLevel.MarkToMarkAdjustmentRecord record = records[i]; + + if (records[i].baseMarkGlyphID == 0 || records[i].combiningMarkGlyphID == 0) + return; uint key = record.combiningMarkGlyphID << 16 | record.baseMarkGlyphID; @@ -3304,7 +3341,7 @@ internal void UpdateFontAssetData() // Add existing glyphs and characters back in the font asset (if any) if (unicodeCharacters.Length > 0) - TryAddCharacters(unicodeCharacters, true); + TryAddCharacters(unicodeCharacters, m_GetFontFeatures && TMP_Settings.getFontFeaturesAtRuntime); k_UpdateFontAssetDataMarker.End(); } @@ -3480,7 +3517,7 @@ internal void ClearAtlasTextures(bool setAtlasSizeToZero = false) if (setAtlasSizeToZero) { #if UNITY_2021_2_OR_NEWER - texture.Reinitialize(0, 0, texFormat, false); + texture.Reinitialize(1, 1, texFormat, false); #else texture.Resize(0, 0, texFormat, false); #endif diff --git a/Scripts/Runtime/TMP_InputField.cs b/Scripts/Runtime/TMP_InputField.cs index 3818f09..7df46fa 100644 --- a/Scripts/Runtime/TMP_InputField.cs +++ b/Scripts/Runtime/TMP_InputField.cs @@ -19,6 +19,7 @@ namespace TMPro /// Editable text input field. /// [AddComponentMenu("UI/TextMeshPro - Input Field", 11)] + [HelpURL("https://docs.unity3d.com/Packages/com.unity.textmeshpro@3.2")] public class TMP_InputField : Selectable, IUpdateSelectedHandler, IBeginDragHandler, @@ -93,6 +94,12 @@ public class TouchScreenKeyboardEvent : UnityEvent { protected TouchScreenKeyboard m_SoftKeyboard; static private readonly char[] kSeparators = { ' ', '.', ',', '\t', '\r', '\n' }; + #if UNITY_ANDROID + static private bool s_IsQuestDeviceEvaluated = false; + #endif // if UNITY_ANDROID + + static private bool s_IsQuestDevice = false; + #region Exposed properties /// /// Text Text used to display the input's value. @@ -328,8 +335,11 @@ public class TouchScreenKeyboardEvent : UnityEvent { private float m_KeyDownStartTime; private float m_DoubleClickDelay = 0.5f; + private bool m_IsApplePlatform = false; + // Doesn't include dot and @ on purpose! See usage for details. const string kEmailSpecialCharacters = "!#$%&'*+-/=?^_`{|}~"; + const string kOculusQuestDeviceModel = "Oculus Quest"; private BaseInput inputSystem { @@ -510,9 +520,10 @@ private bool isKeyboardUsingEvents() switch (Application.platform) { case RuntimePlatform.Android: - return m_TouchKeyboardAllowsInPlaceEditing; + return InPlaceEditing() && m_HideSoftKeyboard; case RuntimePlatform.IPhonePlayer: case RuntimePlatform.tvOS: + return m_HideSoftKeyboard; #if UNITY_2020_2_OR_NEWER case RuntimePlatform.PS4: #if !(UNITY_2020_2_1 || UNITY_2020_2_2) @@ -972,6 +983,9 @@ int ClampArrayIndex(int index) private bool m_IsStringPositionDirty; private bool m_IsCaretPositionDirty; private bool m_forceRectTransformAdjustment; + + // Primary to track when an user presses on the X to close the keyboard in the HoloLens + private bool m_IsKeyboardBeingClosedInHoloLens = false; /// /// Get: Returns the focus position as thats the position that moves around even during selection. @@ -1099,6 +1113,22 @@ protected override void OnValidate() } #endif // if UNITY_EDITOR + #if UNITY_ANDROID + protected override void Awake() + { + base.Awake(); + + if (s_IsQuestDeviceEvaluated) + return; + + // Used for Oculus Quest 1 and 2 software keyboard regression. + // TouchScreenKeyboard.isInPlaceEditingAllowed is always returning true in these devices and would prevent the software keyboard from showing up if that value was used. + s_IsQuestDevice = SystemInfo.deviceModel == kOculusQuestDeviceModel; + s_IsQuestDeviceEvaluated = true; + } + #endif // if UNITY_ANDROID + + protected override void OnEnable() { //Debug.Log("*** OnEnable() *** - " + this.name); @@ -1108,6 +1138,8 @@ protected override void OnEnable() if (m_Text == null) m_Text = string.Empty; + m_IsApplePlatform = SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX || SystemInfo.operatingSystem.Contains("iOS") || SystemInfo.operatingSystem.Contains("tvOS"); + // Check if Input Field is driven by any layout components ILayoutController layoutController = GetComponent(); @@ -1468,7 +1500,7 @@ private bool InPlaceEditing() if (m_TouchKeyboardAllowsInPlaceEditing) return true; - if (Application.platform == RuntimePlatform.WSAPlayerX86 || Application.platform == RuntimePlatform.WSAPlayerX64 || Application.platform == RuntimePlatform.WSAPlayerARM) + if (isUWP()) return !TouchScreenKeyboard.isSupported; if (TouchScreenKeyboard.isSupported && shouldHideSoftKeyboard) @@ -1484,7 +1516,7 @@ private bool InPlaceEditing() private bool InPlaceEditingChanged() { #if UNITY_2019_1_OR_NEWER - return m_TouchKeyboardAllowsInPlaceEditing != TouchScreenKeyboard.isInPlaceEditingAllowed; + return !s_IsQuestDevice && m_TouchKeyboardAllowsInPlaceEditing != TouchScreenKeyboard.isInPlaceEditingAllowed; #else return false; #endif @@ -1499,6 +1531,9 @@ private bool TouchScreenKeyboardShouldBeUsed() { #if UNITY_2019_1_OR_NEWER case RuntimePlatform.Android: + if (s_IsQuestDevice) + return TouchScreenKeyboard.isSupported; + return !TouchScreenKeyboard.isInPlaceEditingAllowed; #endif default: @@ -1506,6 +1541,21 @@ private bool TouchScreenKeyboardShouldBeUsed() } } + void UpdateKeyboardStringPosition() + { + // On iOS/tvOS we only update SoftKeyboard selection when we know that it might have changed by touch/pointer interactions with InputField + // Setting the TouchScreenKeyboard selection here instead of LateUpdate so that we wouldn't override + // TouchScreenKeyboard selection when it's changed with cmd+a/ctrl+a/arrow/etc. in the TouchScreenKeyboard + // This is only applicable for iOS/tvOS as we have instance of TouchScreenKeyboard even when external keyboard is connected + if (m_HideMobileInput && m_SoftKeyboard != null && m_SoftKeyboard.canSetSelection && + (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.tvOS)) + { + var selectionStart = Mathf.Min(caretSelectPositionInternal, caretPositionInternal); + var selectionLength = Mathf.Abs(caretSelectPositionInternal - caretPositionInternal); + m_SoftKeyboard.selection = new RangeInt(selectionStart, selectionLength); + } + } + void UpdateStringPositionFromKeyboard() { // TODO: Might want to add null check here. @@ -1682,7 +1732,11 @@ protected virtual void LateUpdate() // Special handling for UWP - Hololens which does not support Canceled status if (m_LastKeyCode != KeyCode.Return && status == TouchScreenKeyboard.Status.Done && isUWP()) + { status = TouchScreenKeyboard.Status.Canceled; + // The HoloLen's X button will not be acting as an ESC Key (TMBP-98) + m_IsKeyboardBeingClosedInHoloLens = true; + } switch (status) { @@ -1757,7 +1811,17 @@ protected virtual void LateUpdate() SendOnValueChangedAndUpdateLabel(); } } - else if (m_HideMobileInput && Application.platform == RuntimePlatform.Android) + // On iOS/tvOS we always have TouchScreenKeyboard instance even when using external keyboard + // so we keep track of the caret position there + else if (m_HideMobileInput && m_SoftKeyboard != null && m_SoftKeyboard.canSetSelection && + Application.platform != RuntimePlatform.IPhonePlayer && Application.platform != RuntimePlatform.tvOS) + { + var selectionStart = Mathf.Min(caretSelectPositionInternal, caretPositionInternal); + var selectionLength = Mathf.Abs(caretSelectPositionInternal - caretPositionInternal); + m_SoftKeyboard.selection = new RangeInt(selectionStart, selectionLength); + } + else if (m_HideMobileInput && Application.platform == RuntimePlatform.Android || + m_SoftKeyboard.canSetSelection && (Application.platform == RuntimePlatform.IPhonePlayer || Application.platform == RuntimePlatform.tvOS)) { UpdateStringPositionFromKeyboard(); } @@ -1840,6 +1904,7 @@ public virtual void OnDrag(PointerEventData eventData) if (m_DragPositionOutOfBounds && m_DragCoroutine == null) m_DragCoroutine = StartCoroutine(MouseDragOutsideRect(eventData)); + UpdateKeyboardStringPosition(); eventData.Use(); #if TMP_DEBUG_MODE @@ -2027,6 +2092,7 @@ public override void OnPointerDown(PointerEventData eventData) } UpdateLabel(); + UpdateKeyboardStringPosition(); eventData.Use(); #if TMP_DEBUG_MODE @@ -2043,7 +2109,7 @@ protected enum EditState protected EditState KeyPressed(Event evt) { var currentEventModifiers = evt.modifiers; - bool ctrl = SystemInfo.operatingSystemFamily == OperatingSystemFamily.MacOSX ? (currentEventModifiers & EventModifiers.Command) != 0 : (currentEventModifiers & EventModifiers.Control) != 0; + bool ctrl = m_IsApplePlatform ? (currentEventModifiers & EventModifiers.Command) != 0 : (currentEventModifiers & EventModifiers.Control) != 0; bool shift = (currentEventModifiers & EventModifiers.Shift) != 0; bool alt = (currentEventModifiers & EventModifiers.Alt) != 0; bool ctrlOnly = ctrl && !alt && !shift; @@ -3660,7 +3726,7 @@ private void UpdateGeometry() #endif // No need to draw a cursor on mobile as its handled by the devices keyboard with the exception of UWP. - if (InPlaceEditing() == false && (Application.platform != RuntimePlatform.WSAPlayerX86 && Application.platform != RuntimePlatform.WSAPlayerX64 && Application.platform != RuntimePlatform.WSAPlayerARM)) + if (InPlaceEditing() == false && isUWP() == false) return; if (m_CachedInputRenderer == null) @@ -4230,7 +4296,7 @@ private void ActivateInputFieldInternal() // Cache the value of isInPlaceEditingAllowed, because on UWP this involves calling into native code // The value only needs to be updated once when the TouchKeyboard is opened. #if UNITY_2019_1_OR_NEWER - m_TouchKeyboardAllowsInPlaceEditing = TouchScreenKeyboard.isInPlaceEditingAllowed; + m_TouchKeyboardAllowsInPlaceEditing = !s_IsQuestDevice && TouchScreenKeyboard.isInPlaceEditingAllowed; #endif if (TouchScreenKeyboardShouldBeUsed() && shouldHideSoftKeyboard == false) @@ -4250,7 +4316,7 @@ private void ActivateInputFieldInternal() // Opening the soft keyboard sets its selection to the end of the text. // As such, we set the selection to match the Input Field's internal selection. - if (m_SoftKeyboard != null) + if (m_SoftKeyboard != null && m_SoftKeyboard.canSetSelection) { int length = stringPositionInternal < stringSelectPositionInternal ? stringSelectPositionInternal - stringPositionInternal : stringPositionInternal - stringSelectPositionInternal; m_SoftKeyboard.selection = new RangeInt(stringPositionInternal < stringSelectPositionInternal ? stringPositionInternal : stringSelectPositionInternal, length); @@ -4327,7 +4393,7 @@ public void DeactivateInputField(bool clearSelection = false) if (m_TextComponent != null && IsInteractable()) { - if (m_WasCanceled && m_RestoreOriginalTextOnEscape) + if (m_WasCanceled && m_RestoreOriginalTextOnEscape && !m_IsKeyboardBeingClosedInHoloLens) text = m_OriginalText; if (m_SoftKeyboard != null) @@ -4350,6 +4416,8 @@ public void DeactivateInputField(bool clearSelection = false) if (inputSystem != null) inputSystem.imeCompositionMode = IMECompositionMode.Auto; + + m_IsKeyboardBeingClosedInHoloLens = false; } MarkGeometryAsDirty(); diff --git a/Scripts/Runtime/TMP_RichTextTagsCommon.cs b/Scripts/Runtime/TMP_RichTextTagsCommon.cs index 4844623..7b4fb1d 100644 --- a/Scripts/Runtime/TMP_RichTextTagsCommon.cs +++ b/Scripts/Runtime/TMP_RichTextTagsCommon.cs @@ -115,6 +115,7 @@ internal enum MarkupTag PADDING = -2144568463, FAMILYNAME = 704251153, STYLENAME = -1207081936, + DUOSPACE = 582810522, // to enable this mode // Named Colors RED = 91635, diff --git a/Scripts/Runtime/TMP_Settings.cs b/Scripts/Runtime/TMP_Settings.cs index 9060676..8928a5c 100644 --- a/Scripts/Runtime/TMP_Settings.cs +++ b/Scripts/Runtime/TMP_Settings.cs @@ -1,6 +1,6 @@ using UnityEngine; using UnityEngine.Serialization; -using System.Collections; +using UnityEngine.TextCore; using System.Collections.Generic; @@ -44,13 +44,30 @@ public static TextWrappingModes textWrappingMode /// /// Controls if Kerning is enabled on newly created text objects by default. /// + [System.Obsolete("The \"enableKerning\" property has been deprecated. Use the \"fontFeatures\" property to control what features are enabled by default on newly created text components.")] public static bool enableKerning { - get { return instance.m_enableKerning; } + get + { + if (instance.m_ActiveFontFeatures != null) + return instance.m_ActiveFontFeatures.Contains(OTL_FeatureTag.kern); + + return instance.m_enableKerning; + } } [SerializeField] private bool m_enableKerning; + /// + /// Controls which font features are enabled by default on newly created text objects. + /// + public static List fontFeatures + { + get { return instance.m_ActiveFontFeatures; } + } + [SerializeField] + private List m_ActiveFontFeatures = new List { 0 }; + /// /// Controls if Extra Padding is enabled on newly created text objects by default. /// @@ -433,6 +450,15 @@ public static TMP_Settings instance TMP_PackageResourceImporterWindow.ShowPackageImporterWindow(); } #endif + + // Convert use of the "enableKerning" property to the new "fontFeature" property. + if (s_Instance != null && s_Instance.m_ActiveFontFeatures.Count == 1 && s_Instance.m_ActiveFontFeatures[0] == 0) + { + s_Instance.m_ActiveFontFeatures.Clear(); + + if (s_Instance.m_enableKerning) + s_Instance.m_ActiveFontFeatures.Add(OTL_FeatureTag.kern); + } } return s_Instance; diff --git a/Scripts/Runtime/TMP_SpriteAsset.cs b/Scripts/Runtime/TMP_SpriteAsset.cs index 4132a7a..65d4956 100644 --- a/Scripts/Runtime/TMP_SpriteAsset.cs +++ b/Scripts/Runtime/TMP_SpriteAsset.cs @@ -2,6 +2,7 @@ using UnityEngine.TextCore; using System.Collections.Generic; using System.Linq; +using UnityEngine.Serialization; namespace TMPro @@ -51,11 +52,12 @@ public Dictionary spriteCharacterLookupTable public List spriteGlyphTable { - get { return m_SpriteGlyphTable; } - internal set { m_SpriteGlyphTable = value; } + get { return m_GlyphTable; } + internal set { m_GlyphTable = value; } } + [FormerlySerializedAs("m_SpriteGlyphTable")] [SerializeField] - private List m_SpriteGlyphTable = new List(); + private List m_GlyphTable = new List(); internal Dictionary m_SpriteGlyphLookup; @@ -126,9 +128,9 @@ public void UpdateLookupTables() m_SpriteGlyphLookup.Clear(); // Initialize SpriteGlyphLookup - for (int i = 0; i < m_SpriteGlyphTable.Count; i++) + for (int i = 0; i < m_GlyphTable.Count; i++) { - TMP_SpriteGlyph spriteGlyph = m_SpriteGlyphTable[i]; + TMP_SpriteGlyph spriteGlyph = m_GlyphTable[i]; uint glyphIndex = spriteGlyph.index; if (m_GlyphIndexLookup.ContainsKey(glyphIndex) == false) @@ -485,9 +487,9 @@ private static TMP_SpriteAsset SearchForSpriteByHashCodeInternal(TMP_SpriteAsset /// public void SortGlyphTable() { - if (m_SpriteGlyphTable == null || m_SpriteGlyphTable.Count == 0) return; + if (m_GlyphTable == null || m_GlyphTable.Count == 0) return; - m_SpriteGlyphTable = m_SpriteGlyphTable.OrderBy(item => item.index).ToList(); + m_GlyphTable = m_GlyphTable.OrderBy(item => item.index).ToList(); } /// @@ -520,7 +522,7 @@ private void UpgradeSpriteAsset() // Convert legacy glyph and character tables to new format m_SpriteCharacterTable.Clear(); - m_SpriteGlyphTable.Clear(); + m_GlyphTable.Clear(); for (int i = 0; i < spriteInfoList.Count; i++) { @@ -535,7 +537,7 @@ private void UpgradeSpriteAsset() spriteGlyph.scale = 1.0f; spriteGlyph.atlasIndex = 0; - m_SpriteGlyphTable.Add(spriteGlyph); + m_GlyphTable.Add(spriteGlyph); TMP_SpriteCharacter spriteCharacter = new TMP_SpriteCharacter(); spriteCharacter.glyph = spriteGlyph; diff --git a/Scripts/Runtime/TMP_SubMesh.cs b/Scripts/Runtime/TMP_SubMesh.cs index f213ce1..13a8b1f 100644 --- a/Scripts/Runtime/TMP_SubMesh.cs +++ b/Scripts/Runtime/TMP_SubMesh.cs @@ -234,17 +234,19 @@ public TMP_Text textComponent public static TMP_SubMesh AddSubTextObject(TextMeshPro textComponent, MaterialReference materialReference) { - GameObject go = new GameObject("TMP SubMesh [" + materialReference.material.name + "]", typeof(TMP_SubMesh)); + GameObject go = new GameObject(); go.hideFlags = TMP_Settings.hideSubTextObjects ? HideFlags.HideAndDontSave : HideFlags.DontSave; - - TMP_SubMesh subMesh = go.GetComponent(); - go.transform.SetParent(textComponent.transform, false); go.transform.localPosition = Vector3.zero; go.transform.localRotation = Quaternion.identity; go.transform.localScale = Vector3.one; go.layer = textComponent.gameObject.layer; + + #if UNITY_EDITOR + go.name = materialReference.material == null ? "TMP SubMesh" : "TMP SubMesh [" + materialReference.material.name + "]"; + #endif + TMP_SubMesh subMesh = go.AddComponent(); subMesh.m_TextComponent = textComponent; subMesh.m_fontAsset = materialReference.fontAsset; subMesh.m_spriteAsset = materialReference.spriteAsset; @@ -599,7 +601,7 @@ protected void UpdateMaterial() m_renderer.sharedMaterial = m_sharedMaterial; // Special handling to keep the Culling of the material in sync with parent text object - if (m_sharedMaterial.HasProperty(ShaderUtilities.ShaderTag_CullMode)) + if (m_sharedMaterial.HasProperty(ShaderUtilities.ShaderTag_CullMode) && textComponent.fontSharedMaterial != null) { float cullMode = textComponent.fontSharedMaterial.GetFloat(ShaderUtilities.ShaderTag_CullMode); m_sharedMaterial.SetFloat(ShaderUtilities.ShaderTag_CullMode, cullMode); diff --git a/Scripts/Runtime/TMP_SubMeshUI.cs b/Scripts/Runtime/TMP_SubMeshUI.cs index 36ce900..10c7c96 100644 --- a/Scripts/Runtime/TMP_SubMeshUI.cs +++ b/Scripts/Runtime/TMP_SubMeshUI.cs @@ -212,14 +212,18 @@ public TMP_Text textComponent /// public static TMP_SubMeshUI AddSubTextObject(TextMeshProUGUI textComponent, MaterialReference materialReference) { - GameObject go = new GameObject("TMP UI SubObject [" + materialReference.material.name + "]", typeof(RectTransform)); + GameObject go = new GameObject(); go.hideFlags = TMP_Settings.hideSubTextObjects ? HideFlags.HideAndDontSave : HideFlags.DontSave; go.transform.SetParent(textComponent.transform, false); go.transform.SetAsFirstSibling(); go.layer = textComponent.gameObject.layer; + + #if UNITY_EDITOR + go.name = materialReference.material == null ? "TMP SubMesh" : "TMP SubMesh [" + materialReference.material.name + "]"; + #endif - RectTransform rectTransform = go.GetComponent(); + RectTransform rectTransform = go.AddComponent(); rectTransform.anchorMin = Vector2.zero; rectTransform.anchorMax = Vector2.one; rectTransform.sizeDelta = Vector2.zero; @@ -229,10 +233,7 @@ public static TMP_SubMeshUI AddSubTextObject(TextMeshProUGUI textComponent, Mate layoutElement.ignoreLayout = true; TMP_SubMeshUI subMesh = go.AddComponent(); - - //subMesh.canvasRenderer = subMesh.canvasRenderer; subMesh.m_TextComponent = textComponent; - subMesh.m_materialReferenceIndex = materialReference.index; subMesh.m_fontAsset = materialReference.fontAsset; subMesh.m_spriteAsset = materialReference.spriteAsset; @@ -705,7 +706,7 @@ protected override void UpdateMaterial() //if (canvasRenderer == null) m_canvasRenderer = this.canvasRenderer; // Special handling to keep the Culling of the material in sync with parent text object - if (m_sharedMaterial.HasProperty(ShaderUtilities.ShaderTag_CullMode)) + if (m_sharedMaterial.HasProperty(ShaderUtilities.ShaderTag_CullMode) && textComponent.fontSharedMaterial != null) { float cullMode = textComponent.fontSharedMaterial.GetFloat(ShaderUtilities.ShaderTag_CullMode); m_sharedMaterial.SetFloat(ShaderUtilities.ShaderTag_CullMode, cullMode); diff --git a/Scripts/Runtime/TMP_Text.cs b/Scripts/Runtime/TMP_Text.cs index 62f92ee..ec3515f 100644 --- a/Scripts/Runtime/TMP_Text.cs +++ b/Scripts/Runtime/TMP_Text.cs @@ -651,6 +651,7 @@ public float characterSpacing protected float m_characterSpacing = 0; protected float m_cSpacing = 0; protected float m_monoSpacing = 0; + protected bool m_duoSpace; /// /// The amount of additional spacing between words. @@ -731,7 +732,7 @@ public TextWrappingModes textWrappingMode [Obsolete("The enabledWordWrapping property is now obsolete. Please use the textWrappingMode property instead.")] public bool enableWordWrapping { - get { return m_TextWrappingMode != 0; } + get { return m_TextWrappingMode == TextWrappingModes.Normal || textWrappingMode == TextWrappingModes.PreserveWhitespace; } set { TextWrappingModes mode = (TextWrappingModes)(value ? 1 : 0); @@ -857,15 +858,56 @@ public TMP_Text linkedTextComponent /// /// Determines if kerning is enabled or disabled. /// + [Obsolete("The \"enableKerning\" property has been deprecated. Use the \"fontFeatures\" property to control what features are enabled on the text component.")] public bool enableKerning { - get { return m_enableKerning; } - set { if (m_enableKerning == value) return; m_havePropertiesChanged = true; m_enableKerning = value; SetVerticesDirty(); SetLayoutDirty(); } + get { return m_ActiveFontFeatures.Contains(OTL_FeatureTag.kern); } + set + { + if (m_ActiveFontFeatures.Contains(OTL_FeatureTag.kern)) + { + if (value) + return; + + m_ActiveFontFeatures.Remove(OTL_FeatureTag.kern); + m_enableKerning = false; + } + else + { + if (!value) + return; + + m_ActiveFontFeatures.Add(OTL_FeatureTag.kern); + m_enableKerning = true; + + } + + m_havePropertiesChanged = true; + SetVerticesDirty(); + SetLayoutDirty(); + } } [SerializeField] protected bool m_enableKerning; protected int m_LastBaseGlyphIndex; + /// + /// List of OpenType font features that are enabled. + /// + public List fontFeatures + { + get { return m_ActiveFontFeatures; } + set + { + if (value == null) + return; + + m_havePropertiesChanged = true; m_ActiveFontFeatures = value; SetVerticesDirty(); SetLayoutDirty(); + } + } + [SerializeField] + protected List m_ActiveFontFeatures = new List { 0 }; + /// /// Adds extra padding around each character. This may be necessary when the displayed text is very small to prevent clipping. /// @@ -1631,7 +1673,7 @@ public SpecialCharacter(TMP_Character character, int materialIndex) /// Method which derived classes need to override to load Font Assets. /// protected virtual void LoadFontAsset() { } - + /// /// Function called internally when a new shared material is assigned via the fontSharedMaterial property. /// @@ -4635,9 +4677,9 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m } } // Handling for East Asian scripts - else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul((uint)charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK((uint)charCode))) + else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul(charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK(charCode))) { - bool isCurrentLeadingCharacter = TMP_Settings.linebreakingRules.leadingCharacters.Contains((uint)charCode); + bool isCurrentLeadingCharacter = TMP_Settings.linebreakingRules.leadingCharacters.Contains(charCode); bool isNextFollowingCharacter = m_characterCount < totalCharacterCount - 1 && TMP_Settings.linebreakingRules.followingCharacters.Contains(m_internalCharacterInfo[m_characterCount + 1].character); if (isCurrentLeadingCharacter == false) @@ -4669,6 +4711,11 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m } } } + // 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 @@ -5402,10 +5449,10 @@ protected virtual void SaveSpriteVertexInfo(Color32 vertexColor) /// /// /// - protected virtual void FillCharacterVertexBuffers(int i, int index_X4) + protected virtual void FillCharacterVertexBuffers(int i) { int materialIndex = m_textInfo.characterInfo[i].materialReferenceIndex; - index_X4 = m_textInfo.meshInfo[materialIndex].vertexCount; + int index_X4 = m_textInfo.meshInfo[materialIndex].vertexCount; // Check to make sure our current mesh buffer allocations can hold these new Quads. if (index_X4 >= m_textInfo.meshInfo[materialIndex].vertices.Length) @@ -5453,10 +5500,10 @@ protected virtual void FillCharacterVertexBuffers(int i, int index_X4) } - protected virtual void FillCharacterVertexBuffers(int i, int index_X4, bool isVolumetric) + protected virtual void FillCharacterVertexBuffers(int i, bool isVolumetric) { int materialIndex = m_textInfo.characterInfo[i].materialReferenceIndex; - index_X4 = m_textInfo.meshInfo[materialIndex].vertexCount; + int index_X4 = m_textInfo.meshInfo[materialIndex].vertexCount; // Check to make sure our current mesh buffer allocations can hold these new Quads. if (index_X4 >= m_textInfo.meshInfo[materialIndex].vertices.Length) @@ -5541,10 +5588,10 @@ protected virtual void FillCharacterVertexBuffers(int i, int index_X4, bool isVo /// /// /// - protected virtual void FillSpriteVertexBuffers(int i, int index_X4) + protected virtual void FillSpriteVertexBuffers(int i) { int materialIndex = m_textInfo.characterInfo[i].materialReferenceIndex; - index_X4 = m_textInfo.meshInfo[materialIndex].vertexCount; + int index_X4 = m_textInfo.meshInfo[materialIndex].vertexCount; // Check to make sure our current mesh buffer allocations can hold these new Quads. if (index_X4 >= m_textInfo.meshInfo[materialIndex].vertices.Length) @@ -5809,20 +5856,20 @@ protected virtual void DrawTextHighlight(Vector3 start, Vector3 end, ref int ind Vector2 uvTexelSize = new Vector2(1.0f / atlasWidth, 1.0f / atlasHeight); // UVs for the Quad - uvs0[0 + index] = uvGlyphCenter - uvTexelSize; // BL - uvs0[1 + index] = uvGlyphCenter + new Vector2(-uvTexelSize.x, uvTexelSize.y); // TL - uvs0[2 + index] = uvGlyphCenter + uvTexelSize; // TR - uvs0[3 + index] = uvGlyphCenter + new Vector2(uvTexelSize.x, -uvTexelSize.y); // BR + uvs0[index + 0] = uvGlyphCenter - uvTexelSize; // BL + uvs0[index + 1] = uvGlyphCenter + new Vector2(-uvTexelSize.x, uvTexelSize.y); // TL + uvs0[index + 2] = uvGlyphCenter + uvTexelSize; // TR + uvs0[index + 3] = uvGlyphCenter + new Vector2(uvTexelSize.x, -uvTexelSize.y); // BR #endregion // HIGHLIGHT UV2 #region HANDLE UV2 - SDF SCALE Vector2[] uvs2 = m_textInfo.meshInfo[underlineMaterialIndex].uvs2; Vector2 customUV = new Vector2(0, 1); - uvs2[0 + index] = customUV; - uvs2[1 + index] = customUV; - uvs2[2 + index] = customUV; - uvs2[3 + index] = customUV; + uvs2[index + 0] = customUV; + uvs2[index + 1] = customUV; + uvs2[index + 2] = customUV; + uvs2[index + 3] = customUV; #endregion // HIGHLIGHT VERTEX COLORS @@ -5831,10 +5878,10 @@ protected virtual void DrawTextHighlight(Vector3 start, Vector3 end, ref int ind highlightColor.a = m_fontColor32.a < highlightColor.a ? m_fontColor32.a : highlightColor.a; Color32[] colors32 = m_textInfo.meshInfo[underlineMaterialIndex].colors32; - colors32[0 + index] = highlightColor; - colors32[1 + index] = highlightColor; - colors32[2 + index] = highlightColor; - colors32[3 + index] = highlightColor; + colors32[index + 0] = highlightColor; + colors32[index + 1] = highlightColor; + colors32[index + 2] = highlightColor; + colors32[index + 3] = highlightColor; #endregion index += 4; @@ -5870,7 +5917,9 @@ protected void LoadDefaultSettings() } m_TextWrappingMode = TMP_Settings.textWrappingMode; - m_enableKerning = TMP_Settings.enableKerning; + + m_ActiveFontFeatures = new List(TMP_Settings.fontFeatures); + m_enableExtraPadding = TMP_Settings.enableExtraPadding; m_tintAllSprites = TMP_Settings.enableTintAllSprites; m_parseCtrlCharacters = TMP_Settings.enableParseEscapeCharacters; @@ -5881,10 +5930,20 @@ protected void LoadDefaultSettings() raycastTarget = TMP_Settings.enableRaycastTarget; m_IsTextObjectScaleStatic = TMP_Settings.isTextObjectScaleStatic; } - else if ((int)m_textAlignment < 0xFF) + else { // Convert Legacy TextAlignmentOptions enumerations from Unity 5.2 / 5.3. - m_textAlignment = TMP_Compatibility.ConvertTextAlignmentEnumValues(m_textAlignment); + if ((int)m_textAlignment < 0xFF) + m_textAlignment = TMP_Compatibility.ConvertTextAlignmentEnumValues(m_textAlignment); + + // Convert use of the "enableKerning" property to the new "fontFeature" property. + if (m_ActiveFontFeatures.Count == 1 && m_ActiveFontFeatures[0] == 0) + { + m_ActiveFontFeatures.Clear(); + + if (m_enableKerning) + m_ActiveFontFeatures.Add(OTL_FeatureTag.kern); + } } // Convert text alignment to independent horizontal and vertical alignment properties @@ -7734,7 +7793,7 @@ internal bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out // Reject tag if value is invalid. if (value == Int16.MinValue) return false; - switch (tagUnitType) + switch (m_xmlAttribute[0].unitType) { case TagUnitType.Pixels: m_monoSpacing = value * (m_isOrthographic ? 1 : 0.1f); @@ -7745,9 +7804,15 @@ internal bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out case TagUnitType.Percentage: return false; } + + // Check for potential DuoSpace attribute. + if (m_xmlAttribute[1].nameHashCode == (int)MarkupTag.DUOSPACE) + m_duoSpace = ConvertToFloat(m_htmlTag, m_xmlAttribute[1].valueStartIndex, m_xmlAttribute[1].valueLength) != 0; + return true; case MarkupTag.SLASH_MONOSPACE: m_monoSpacing = 0; + m_duoSpace = false; return true; case MarkupTag.CLASS: return false; diff --git a/Scripts/Runtime/TMPro_ExtensionMethods.cs b/Scripts/Runtime/TMPro_ExtensionMethods.cs index 7478453..4f89c16 100644 --- a/Scripts/Runtime/TMPro_ExtensionMethods.cs +++ b/Scripts/Runtime/TMPro_ExtensionMethods.cs @@ -7,7 +7,21 @@ namespace TMPro { public static class TMPro_ExtensionMethods { - + + /// + /// Converts a string of 4 ascii characters to an int. + /// + /// String comprised of 4 ascii characters. + /// The integer value for the string. + internal static int TagToInt(this string s) + { + if (string.IsNullOrEmpty(s)) + return 0; + + return s[0] << 24 | s[1] << 16 | s[2] << 8 | s[3]; + } + + public static int[] ToIntArray(this string text) { int[] intArray = new int[text.Length]; diff --git a/Scripts/Runtime/TMPro_Private.cs b/Scripts/Runtime/TMPro_Private.cs index 2c4c60d..0ab627b 100644 --- a/Scripts/Runtime/TMPro_Private.cs +++ b/Scripts/Runtime/TMPro_Private.cs @@ -487,7 +487,6 @@ protected override void LoadFontAsset() m_sharedMaterial = m_fontAsset.material; m_sharedMaterial.SetFloat("_CullMode", 0); - //m_sharedMaterial.SetFloat(ShaderUtilities.ShaderTag_ZTestMode, 4); m_renderer.receiveShadows = false; m_renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; @@ -505,16 +504,6 @@ protected override void LoadFontAsset() else m_sharedMaterial = m_fontAsset.material; } - - //m_sharedMaterial.SetFloat(ShaderUtilities.ShaderTag_ZTestMode, 4); - - // Check if we are using the SDF Surface Shader - /*if (m_sharedMaterial.FindPass("ShadowCaster") == -1) - { - m_renderer.receiveShadows = false; - m_renderer.shadowCastingMode = UnityEngine.Rendering.ShadowCastingMode.Off; - }*/ - } m_padding = GetPaddingForMaterial(); @@ -1027,6 +1016,8 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) } #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) @@ -1219,7 +1210,7 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) #endregion #region LIGATURES - if (m_currentFontAsset.fontFeatureTable.m_LigatureSubstitutionRecordLookup.TryGetValue(character.glyphIndex, out List records)) + if (ligature && m_currentFontAsset.fontFeatureTable.m_LigatureSubstitutionRecordLookup.TryGetValue(character.glyphIndex, out List records)) { if (records == null) break; @@ -1777,6 +1768,10 @@ protected virtual void GenerateTextMesh() 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(); @@ -1907,6 +1902,9 @@ protected virtual void GenerateTextMesh() 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; @@ -2094,7 +2092,7 @@ protected virtual void GenerateTextMesh() #region Handle Kerning GlyphValueRecord glyphAdjustments = new GlyphValueRecord(); float characterSpacingAdjustment = m_characterSpacing; - if (m_enableKerning) + if (kerning) { k_HandleGPOSFeaturesMarker.Begin(); @@ -2134,7 +2132,7 @@ protected virtual void GenerateTextMesh() // Handle Diacritical Marks #region Handle Diacritical Marks - bool isBaseGlyph = TMP_TextParsingUtilities.IsBaseGlyph((uint)charCode); + bool isBaseGlyph = TMP_TextParsingUtilities.IsBaseGlyph(charCode); if (isBaseGlyph) m_LastBaseGlyphIndex = m_characterCount; @@ -2142,7 +2140,7 @@ protected virtual void GenerateTextMesh() if (m_characterCount > 0 && !isBaseGlyph) { // Check for potential Mark-to-Base lookup if previous glyph was a base glyph - if (m_LastBaseGlyphIndex != int.MinValue && m_LastBaseGlyphIndex == m_characterCount - 1) + if (markToBase && m_LastBaseGlyphIndex != int.MinValue && m_LastBaseGlyphIndex == m_characterCount - 1) { Glyph baseGlyph = m_textInfo.characterInfo[m_LastBaseGlyphIndex].textElement.glyph; uint baseGlyphIndex = baseGlyph.index; @@ -2165,31 +2163,34 @@ protected virtual void GenerateTextMesh() bool wasLookupApplied = false; // Check for any potential Mark-to-Mark lookups - for (int characterLookupIndex = m_characterCount - 1; characterLookupIndex >= 0 && characterLookupIndex != m_LastBaseGlyphIndex; characterLookupIndex--) + if (markToMark) { - // 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)) + for (int characterLookupIndex = m_characterCount - 1; characterLookupIndex >= 0 && characterLookupIndex != m_LastBaseGlyphIndex; characterLookupIndex--) { - 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; + // 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; - glyphAdjustments.xPlacement = baseMarkOrigin + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.xCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.xPositionAdjustment; - glyphAdjustments.yPlacement = baseMarkBaseline + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.yCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.yPositionAdjustment; + 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; - characterSpacingAdjustment = 0; - wasLookupApplied = true; - break; + 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 (m_LastBaseGlyphIndex != int.MinValue && !wasLookupApplied) + if (markToBase && m_LastBaseGlyphIndex != int.MinValue && !wasLookupApplied) { // Handle lookup for Mark-to-Base Glyph baseGlyph = m_textInfo.characterInfo[m_LastBaseGlyphIndex].textElement.glyph; @@ -2233,7 +2234,11 @@ protected virtual void GenerateTextMesh() float monoAdvance = 0; if (m_monoSpacing != 0) { - monoAdvance = (m_monoSpacing / 2 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); + 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 @@ -2311,15 +2316,14 @@ protected virtual void GenerateTextMesh() { // 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; - Vector3 topShear = new Vector3(shear_value * ((currentGlyphMetrics.horizontalBearingY + padding + style_padding) * currentElementScale), 0, 0); - Vector3 bottomShear = new Vector3(shear_value * (((currentGlyphMetrics.horizontalBearingY - currentGlyphMetrics.height - padding - style_padding)) * currentElementScale), 0, 0); - - Vector3 shearAdjustment = new Vector3((topShear.x - bottomShear.x) / 2, 0, 0); - - top_left = top_left + topShear - shearAdjustment; - bottom_left = bottom_left + bottomShear - shearAdjustment; - top_right = top_right + topShear - shearAdjustment; - bottom_right = bottom_right + bottomShear - shearAdjustment; + 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 @@ -3116,6 +3120,9 @@ protected virtual void GenerateTextMesh() 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) { @@ -3264,7 +3271,13 @@ protected virtual void GenerateTextMesh() } else if (m_monoSpacing != 0) { - m_xAdvance += (m_monoSpacing - monoAdvance + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta); + 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; @@ -3374,7 +3387,7 @@ protected virtual void GenerateTextMesh() 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_lineVisibleSpaceCount; + 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); @@ -3494,9 +3507,9 @@ protected virtual void GenerateTextMesh() } } // Handling for East Asian scripts - else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul((uint)charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK((uint)charCode))) + else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul(charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK(charCode))) { - bool isCurrentLeadingCharacter = TMP_Settings.linebreakingRules.leadingCharacters.Contains((uint)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) @@ -3528,6 +3541,11 @@ protected virtual void GenerateTextMesh() } } } + // 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 @@ -3656,8 +3674,8 @@ protected virtual void GenerateTextMesh() // Initialization for Second Pass Vector3 justificationOffset = Vector3.zero; Vector3 offset = Vector3.zero; - int vert_index_X4 = 0; - int sprite_index_X4 = 0; + // int vert_index_X4 = 0; + // int sprite_index_X4 = 0; int wordCount = 0; int lineCount = 0; @@ -3971,11 +3989,11 @@ protected virtual void GenerateTextMesh() // Fill Vertex Buffers for the various types of element if (elementType == TMP_TextElementType.Character) { - FillCharacterVertexBuffers(i, vert_index_X4); + FillCharacterVertexBuffers(i); } else if (elementType == TMP_TextElementType.Sprite) { - FillSpriteVertexBuffers(i, sprite_index_X4); + FillSpriteVertexBuffers(i); } } #endregion diff --git a/Scripts/Runtime/TMPro_UGUI_Private.cs b/Scripts/Runtime/TMPro_UGUI_Private.cs index f20c961..6644791 100644 --- a/Scripts/Runtime/TMPro_UGUI_Private.cs +++ b/Scripts/Runtime/TMPro_UGUI_Private.cs @@ -1090,6 +1090,8 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) } #endregion + // Check if we should process Ligatures + bool ligature = m_ActiveFontFeatures.Contains(OTL_FeatureTag.liga); // Clear Linked Text object content if we have any. if (m_overflowMode == TextOverflowModes.Linked && m_linkedTextComponent != null && !m_isCalculatingPreferredValues) @@ -1192,7 +1194,7 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) // Lookup the Glyph data for each character and cache it. #region LOOKUP GLYPH - TMP_TextElement character = GetTextElement((uint)unicode, m_currentFontAsset, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); + TMP_TextElement 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. @@ -1229,28 +1231,28 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) { // 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((uint)unicode, m_currentFontAsset, TMP_Settings.fallbackFontAssets, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); + 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((uint)unicode, TMP_Settings.defaultFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); + 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((uint)unicode, m_currentFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); + 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((uint)unicode, m_currentFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); + character = TMP_FontAssetUtilities.GetCharacterFromFontAsset(unicode, m_currentFontAsset, true, m_FontStyleInternal, m_FontWeightInternal, out isUsingAlternativeTypeface); } if (!TMP_Settings.warningsDisabled) @@ -1294,7 +1296,7 @@ internal override int SetArraySizes(TextProcessingElement[] textProcessingArray) #endregion #region LIGATURES - if (m_currentFontAsset.fontFeatureTable.m_LigatureSubstitutionRecordLookup.TryGetValue(character.glyphIndex, out List records)) + if (ligature && m_currentFontAsset.fontFeatureTable.m_LigatureSubstitutionRecordLookup.TryGetValue(character.glyphIndex, out List records)) { if (records == null) break; @@ -1890,6 +1892,10 @@ protected virtual void GenerateTextMesh() 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(); @@ -2020,6 +2026,9 @@ protected virtual void GenerateTextMesh() 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; @@ -2207,7 +2216,7 @@ protected virtual void GenerateTextMesh() #region Handle Kerning GlyphValueRecord glyphAdjustments = new GlyphValueRecord(); float characterSpacingAdjustment = m_characterSpacing; - if (m_enableKerning) + if (kerning) { k_HandleGPOSFeaturesMarker.Begin(); @@ -2247,7 +2256,7 @@ protected virtual void GenerateTextMesh() // Handle Diacritical Marks #region Handle Diacritical Marks - bool isBaseGlyph = TMP_TextParsingUtilities.IsBaseGlyph((uint)charCode); + bool isBaseGlyph = TMP_TextParsingUtilities.IsBaseGlyph(charCode); if (isBaseGlyph) m_LastBaseGlyphIndex = m_characterCount; @@ -2255,7 +2264,7 @@ protected virtual void GenerateTextMesh() if (m_characterCount > 0 && !isBaseGlyph) { // Check for potential Mark-to-Base lookup if previous glyph was a base glyph - if (m_LastBaseGlyphIndex != int.MinValue && m_LastBaseGlyphIndex == m_characterCount - 1) + if (markToBase && m_LastBaseGlyphIndex != int.MinValue && m_LastBaseGlyphIndex == m_characterCount - 1) { Glyph baseGlyph = m_textInfo.characterInfo[m_LastBaseGlyphIndex].textElement.glyph; uint baseGlyphIndex = baseGlyph.index; @@ -2278,31 +2287,34 @@ protected virtual void GenerateTextMesh() bool wasLookupApplied = false; // Check for any potential Mark-to-Mark lookups - for (int characterLookupIndex = m_characterCount - 1; characterLookupIndex >= 0 && characterLookupIndex != m_LastBaseGlyphIndex; characterLookupIndex--) + if (markToMark) { - // 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)) + for (int characterLookupIndex = m_characterCount - 1; characterLookupIndex >= 0 && characterLookupIndex != m_LastBaseGlyphIndex; characterLookupIndex--) { - 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; + // 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; + glyphAdjustments.xPlacement = baseMarkOrigin + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.xCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.xPositionAdjustment; + glyphAdjustments.yPlacement = baseMarkBaseline + glyphAdjustmentRecord.baseMarkGlyphAnchorPoint.yCoordinate - glyphAdjustmentRecord.combiningMarkPositionAdjustment.yPositionAdjustment; - characterSpacingAdjustment = 0; - wasLookupApplied = true; - break; + characterSpacingAdjustment = 0; + wasLookupApplied = true; + break; + } } } // If no Mark-to-Mark lookups were applied, check for potential Mark-to-Base lookup. - if (m_LastBaseGlyphIndex != int.MinValue && !wasLookupApplied) + if (markToBase && m_LastBaseGlyphIndex != int.MinValue && !wasLookupApplied) { // Handle lookup for Mark-to-Base Glyph baseGlyph = m_textInfo.characterInfo[m_LastBaseGlyphIndex].textElement.glyph; @@ -2346,7 +2358,11 @@ protected virtual void GenerateTextMesh() float monoAdvance = 0; if (m_monoSpacing != 0) { - monoAdvance = (m_monoSpacing / 2 - (currentGlyphMetrics.width / 2 + currentGlyphMetrics.horizontalBearingX) * currentElementScale) * (1 - m_charWidthAdjDelta); + 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 @@ -2424,15 +2440,14 @@ protected virtual void GenerateTextMesh() { // 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; - Vector3 topShear = new Vector3(shear_value * ((currentGlyphMetrics.horizontalBearingY + padding + style_padding) * currentElementScale), 0, 0); - Vector3 bottomShear = new Vector3(shear_value * (((currentGlyphMetrics.horizontalBearingY - currentGlyphMetrics.height - padding - style_padding)) * currentElementScale), 0, 0); - - Vector3 shearAdjustment = new Vector3((topShear.x - bottomShear.x) / 2, 0, 0); - - top_left = top_left + topShear - shearAdjustment; - bottom_left = bottom_left + bottomShear - shearAdjustment; - top_right = top_right + topShear - shearAdjustment; - bottom_right = bottom_right + bottomShear - shearAdjustment; + 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 @@ -3229,6 +3244,9 @@ protected virtual void GenerateTextMesh() 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) { @@ -3377,7 +3395,13 @@ protected virtual void GenerateTextMesh() } else if (m_monoSpacing != 0) { - m_xAdvance += (m_monoSpacing - monoAdvance + ((m_currentFontAsset.normalSpacingOffset + characterSpacingAdjustment) * currentEmScale) + m_cSpacing) * (1 - m_charWidthAdjDelta); + 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; @@ -3487,7 +3511,7 @@ protected virtual void GenerateTextMesh() 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_lineVisibleSpaceCount; + 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); @@ -3607,9 +3631,9 @@ protected virtual void GenerateTextMesh() } } // Handling for East Asian scripts - else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul((uint)charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK((uint)charCode))) + else if (m_isNonBreakingSpace == false && (TMP_TextParsingUtilities.IsHangul(charCode) && TMP_Settings.useModernHangulLineBreakingRules == false || TMP_TextParsingUtilities.IsCJK(charCode))) { - bool isCurrentLeadingCharacter = TMP_Settings.linebreakingRules.leadingCharacters.Contains((uint)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) @@ -3641,6 +3665,11 @@ protected virtual void GenerateTextMesh() } } } + // 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 @@ -3769,8 +3798,8 @@ protected virtual void GenerateTextMesh() // Initialization for Second Pass Vector3 justificationOffset = Vector3.zero; Vector3 offset = Vector3.zero; - int vert_index_X4 = 0; - int sprite_index_X4 = 0; + // int vert_index_X4 = 0; + // int sprite_index_X4 = 0; int wordCount = 0; int lineCount = 0; @@ -4097,11 +4126,11 @@ protected virtual void GenerateTextMesh() // Fill Vertex Buffers for the various types of element if (elementType == TMP_TextElementType.Character) { - FillCharacterVertexBuffers(i, vert_index_X4); + FillCharacterVertexBuffers(i); } else if (elementType == TMP_TextElementType.Sprite) { - FillSpriteVertexBuffers(i, sprite_index_X4); + FillSpriteVertexBuffers(i); } } #endregion diff --git a/Tests/Editor/FontEngineTests.cs b/Tests/Editor/FontEngineTests.cs index 554725d..8d8710c 100644 --- a/Tests/Editor/FontEngineTests.cs +++ b/Tests/Editor/FontEngineTests.cs @@ -159,6 +159,8 @@ public void GetMarkToMarkAdjustmentRecords(string fontFileGUID, int recordCount) Assert.AreEqual(recordCount, records.Length); } + // GetOpenTypeFontFeatureList throws NotImplementedException with new FontEngine changes to support FontFeature (TEXTCORE_FONT_ENGINE_1_5_OR_NEWER) + /* [TestCase("e3265ab4bf004d28a9537516768c1c75", 0)] [TestCase("4beb055f07aaff244873dec698d0363e", 0)] [TestCase("24007ea0bd4d6b2418f4caf1b06e2cb4", 324)] @@ -170,13 +172,13 @@ public void GetFontFeatureList(string fontFileGUID, int recordCount) if (FontEngine.LoadFontFace(filePath) != FontEngineError.Success) return; - UnityEngine.TextCore.LowLevel.OpenTypeFeature[] fontFeatureList = FontEngine.GetOpenTypeFontFeatureList(); + OpenTypeFeature[] fontFeatureList = FontEngine.GetOpenTypeFontFeatureList(); if (fontFeatureList == null) return; Assert.AreEqual(recordCount, fontFeatureList.Length); - } + } */ #endif } } diff --git a/Tests/Editor/TMP_EditorTests.cs b/Tests/Editor/TMP_EditorTests.cs index f34f4d2..8ef6ce0 100644 --- a/Tests/Editor/TMP_EditorTests.cs +++ b/Tests/Editor/TMP_EditorTests.cs @@ -271,7 +271,7 @@ public void MarkupTag_Cspace(string sourceText, float origin1, float advance1, f [Test] [TestCase("ABC", -34.0965118f, -32.0f, -31.1279068f, -29.0f, -28.1593018f, -26.0f)] - [TestCase("ABC", -33.7965126f, -31.3999996f, -30.2279072f, -27.7999992f, -26.6593018f, -24.1999989f)] + [TestCase("ABC", -33.7965126f, -31.3999996f, -30.2279072f, -27.7999992f, -26.6593018f, -24.2000008f)] public void MarkupTag_Mspace(string sourceText, float origin1, float advance1, float origin2, float advance2, float origin3, float advance3) { m_TextComponent.text = sourceText; diff --git a/package.json b/package.json index a112cc1..5f9699a 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.textmeshpro", "displayName": "TextMeshPro", - "version": "3.2.0-pre.3", + "version": "3.2.0-pre.4", "unity": "2020.3", "description": "TextMeshPro is the ultimate text solution for Unity. It's the perfect replacement for Unity's UI Text and the legacy Text Mesh.\n\nPowerful and easy to use, TextMeshPro (also known as TMP) uses Advanced Text Rendering techniques along with a set of custom shaders; delivering substantial visual quality improvements while giving users incredible flexibility when it comes to text styling and texturing.\n\nTextMeshPro provides Improved Control over text formatting and layout with features like character, word, line and paragraph spacing, kerning, justified text, Links, over 30 Rich Text Tags available, support for Multi Font & Sprites, Custom Styles and more.\n\nGreat performance. Since the geometry created by TextMeshPro uses two triangles per character just like Unity's text components, this improved visual quality and flexibility comes at no additional performance cost.\n\n\n\nUPGRADE NOTE\n--------------------\nThis latest release of the TMP package includes updated TMP Essential Resources and TMP Examples & Extras. Be sure to update those via the \"Window - TextMeshPro - Import...\" menu options.", "keywords": [ @@ -18,12 +18,13 @@ "repository": { "url": "https://github.cds.internal.unity3d.com/unity/com.unity.textmeshpro.git", "type": "git", - "revision": "77f8fb64afbae57702de803d5375832a8e21c89d" + "revision": "26fadfb632ffe5cb14bab5019ef8097eece1e62a" }, - "upm": { - "changelog": "### Changes,- Fixed a potential IndexOutOfRangeException when trying to select any portions of text in an Input Field whose child text component is using Overflow mode Truncate or Ellipsis where due to RectTransform width and / or height restrictions have results in the text being fully truncated. Case #1361032 ,- Fixed incorrect character sequencing in the Input Field when using Japanese IME in UWP builds. Case #1374755,- Fixed several potential IndexOutOfRangeException in the Input Field when text is fully truncated when using Text Overflow mode Ellipsis or Truncate.,- Fixed Input Field incorrect caretPosition and / or stringPosition properties when updating these via scripting. Case #1334622,- Fixed incorrect event handling on UWP and Hololens 2. Case #1357718 and Case #1351006,- Fixed Input Field OnSelect event potentially firing twice on some mobile devices including Microsoft Surfaces.,- Fixed Input Field incorrect caret insertion position when text contains a Carriage Return <CR> at the end of a line. Case #1362068,- Added support for <CR> markup tag.,- Added value range check for the text component margins located in the text component extra settings. Case #1365554,- Added new overload to the TMP_FontAsset.CreateFontAsset() to enable creating font asset instances using a file path.,- Added new TMP_ResourceManager.RemoveFontAsset() function to remove a font asset from the TMP Resource Manager. This new function would typically be used prior to unloading bundles and / or resources.,- Warnings about potentially missing character 0x5F used to display underline or strikethrough will only be displayed when using those features when Display Warning is enabled in the TMP Settings.,- Fixed incorrect handling of '-' at the end of a line when using Text Overflow Page mode. Case #1382173,- Fixed incorrect line breaking when a hyphen '-' is preceded by a space. Case #1391990,- When using Text Wrapping Mode - Preserve White Space or Preserve White Space No Wrap, the <ZWSP> character will now contribute to the preferred height when it is the first and only character on a new line.,- Added ClearFontAssetGlyphCache() function to the TMP_ResourcesManager to enable clearing the font asset fallback glyph cache.,- Added new <ZWJ> markup tag.,- Added ability to define a list of fallback text assets in the TMP Settings to be used for Emojis.,- Added new property \"emojiFallbackSupport\" to text components which is also exposed in the extra settings section of the text component inspector. When enabled, this will prioritize searching the Emoji Fallback Text Assets before the primary font asset for characters defined as Emoji in Unicode 14.0. See [forum post](https://forum.unity.com/threads/fallback-to-emojis-before-fonts.1186174/) for details.,- Fixed duplicated character when using custom validator in conjunction with soft keyboard. Case #1387772,- The <noparse> markup tag will now also affect inline markup tags like <br>, <shy>, etc.,- Fixed newly created StyleSheets having two Normal styles.,- Added new Double Pass SDF shader and example scene to the TMP Examples & Extras to showcase this new shader.,- Added new context menu option to manually \"clear dynamic data\" of a dynamic font asset. This performs the same clearing of data as the \"Clear Dynamic Data on Build\" option when manually triggered.,- Added new <a href=\"url\"> markup tag which can be used as follows: \"See <a href=\"http://url...\">this link</a> for details.\". This new markup tag is similar to the <link> tag but does not use an ID.,- Added new \"A\" style to the Default Style Sheet referenced in the TMP Settings. This \"A\" style defines the styling of the new <a> markup tag.,- The TMP_Text.parseCtrlCharacters property which controls the interpretation and parsing of escape characters in the text component's Text Input box in the inspector, will now apply to all methods of setting the text on the component. Case #1400008,- Fixed potential Null Reference Exception when editing the name of a sprite character. Case #1396591,- Fixed issue where TMP_FontAsset.HasCharacters would return false when called prior to font asset initialization. Case #1394817,- Minor TMP_InputField UI improvement where \"Hide Mobile Input\" property will be disabled when \"Hide Soft Keyboard\" property is disabled. Case #1388243" + "_upm": { + "changelog": "### Changes\n- Fixed italic horizontal displacement of punctuation. (TMPB-133)\n- Updated Input Field to not restore the original text when the X in the HoloLens keyboard is pressed.\n- Fixed MissingReferenceException when changing scenes. See case [TMPB-120](https://issuetracker.unity3d.com/issues/missingreferenceexception-thrown-when-changing-scenes) and forum [thread](https://forum.unity.com/threads/tmpro-submeshui-error-when-changing-scenes.1129724/) for more info.\n- Fixed Undo operation not correctly undoing some newly created TMP objects. Case #1400391\n- Fixed glyphs not being drawn in the various font asset inspector tables although present in the glyph table until unselecting and re-selecting the font asset.\n- Fixed incorrect line breaking when using a mixture of Latin and CJK text.\n- Fixed potential NullReferenceException when creating font asset with multiple atlas textures. See [forum post](https://forum.unity.com/threads/how-can-i-submit-bugs-or-issues-for-tmp.628687/#post-8186100) for details.\n- Fixed potential issue when using the <mark> tag in conjunction with Ellipsis.\n- Added new \"Get Font Features\" option in the Generation Settings of Font Assets to provide control over fetching of font feature data.\n- Fix potential duplication of Ligature data when using Dynamic Font Assets.\n- Added new \"duospace\" attribute to the <mspace=value> markup tag where the spacing of the '.', ':' and ',' characters will be half of the value. This was added as an option when displaying numerical values using this markup tag.\n- With the addition of support for new OpenType Layout features such as Ligatures, Mark-to-Base and Mark-to-Mark, we have made the following changes:\n - The \"Kerning\" option in the Extra Settings of the text component inspector has been replaced by a new popup menu to independently control these features which are \"kern\", \"liga\", \"mark\" and \"mkmk\"\n - The \"enableKerning\" property has been deprecated and replaced by the \"fontFeatures\" property which is a list that contains which features are enabled on the text component.\n- Fixed two potential NullReferenceException related to missing material references or materials on SubMesh text objects. See [forum post](https://forum.unity.com/threads/tmpro-submeshui-error-when-changing-scenes.1129724/#post-8462771) for details.\n- Fixed for text alignment mode Justified and Flush blending not working correctly when using <NBSP> in the text.\n- Fixed external keyboard on iOS/tvOS and Android when Hide Soft Keyboard is used" }, "upmCi": { - "footprint": "2ba82b05f323a5e2c9fa3a2aaa232f683f237b67" - } + "footprint": "ec3068141184eaa4c855a462f5ea357e961ad27a" + }, + "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.textmeshpro@3.2/manual/index.html" }