diff --git a/CHANGELOG.md b/CHANGELOG.md index d9cb794..99a5ab1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,6 +1,18 @@ # 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.7] - 2023-12-17 +### Changes +- Fixed TMP_InputField line limit behavior to mean unlimited when the value is set to zero or negative (UUM-57192) +- Fixed custom validator ignores the returned character from the validate function (UUM-42147) +- Fixed editing a textfield on mobile and then submitting throws an exception (UUM-37282) +- Addressed issue surrounding dropdown not closing correctly in certain situations(UUM-33691) +- Ensure Sprites can be reordered within a SpriteAsset. (UUM-49349) +- Added missing grey and lightblue tags (UUM-54820) +- Fix underline when use at end of text. (UUM-55135) +- Add support for Visions OS keyboard. + ## [3.2.0-pre.6] - 2023-09-25 ### Changes - Fix TextMeshPro component does not perform linear color conversion when the VertexColorAlwaysGammaSpace option is enabled. Case #UUM-36113 diff --git a/Scripts/Editor/TMP_BaseEditorPanel.cs b/Scripts/Editor/TMP_BaseEditorPanel.cs index 5caea52..5881c77 100644 --- a/Scripts/Editor/TMP_BaseEditorPanel.cs +++ b/Scripts/Editor/TMP_BaseEditorPanel.cs @@ -1132,13 +1132,13 @@ protected void DrawMargins() { // Value range check on margins to make sure they are not excessive. Vector4 margins = m_MarginProp.vector4Value; - Vector2 textContainerSize = m_RectTransform.sizeDelta; + Rect textContainerSize = m_RectTransform.rect; - margins.x = Mathf.Clamp(margins.x, -textContainerSize.x, textContainerSize.x); - margins.z = Mathf.Clamp(margins.z, -textContainerSize.x, textContainerSize.x); + margins.x = Mathf.Clamp(margins.x, -textContainerSize.width, textContainerSize.width); + margins.z = Mathf.Clamp(margins.z, -textContainerSize.width, textContainerSize.width); - margins.y = Mathf.Clamp(margins.y, -textContainerSize.y, textContainerSize.y); - margins.w = Mathf.Clamp(margins.w, -textContainerSize.y, textContainerSize.y); + margins.y = Mathf.Clamp(margins.y, -textContainerSize.height, textContainerSize.height); + margins.w = Mathf.Clamp(margins.w, -textContainerSize.height, textContainerSize.height); m_MarginProp.vector4Value = margins; diff --git a/Scripts/Editor/TMP_EditorResourceManager.cs b/Scripts/Editor/TMP_EditorResourceManager.cs index a06de2f..7a205b7 100644 --- a/Scripts/Editor/TMP_EditorResourceManager.cs +++ b/Scripts/Editor/TMP_EditorResourceManager.cs @@ -103,16 +103,13 @@ void OnPreRenderCanvases() } #if UNITY_2023_3_OR_NEWER - void OnEndOfFrame(ScriptableRenderContext renderContext, List cameras) - { - DoPostRenderUpdates(); - } + void OnEndOfFrame(ScriptableRenderContext renderContext, List cameras) #else - void OnEndOfFrame(ScriptableRenderContext renderContext, Camera[] cameras) + void OnEndOfFrame(ScriptableRenderContext renderContext, Camera[] cameras) + #endif { DoPostRenderUpdates(); } - #endif /// /// Register resource for re-import. diff --git a/Scripts/Editor/TMP_FontAssetEditor.cs b/Scripts/Editor/TMP_FontAssetEditor.cs index f2cdfe0..6dafc8f 100644 --- a/Scripts/Editor/TMP_FontAssetEditor.cs +++ b/Scripts/Editor/TMP_FontAssetEditor.cs @@ -312,12 +312,11 @@ public void OnEnable() // Create serialized object to allow us to use a serialized property of an empty kerning pair. m_SerializedPropertyHolder = CreateInstance(); m_SerializedPropertyHolder.fontAsset = m_fontAsset; - using (SerializedObject internalSerializedObject = new SerializedObject(m_SerializedPropertyHolder)) - { - m_FirstCharacterUnicode_prop = internalSerializedObject.FindProperty("firstCharacter"); - m_SecondCharacterUnicode_prop = internalSerializedObject.FindProperty("secondCharacter"); - m_EmptyGlyphPairAdjustmentRecord_prop = internalSerializedObject.FindProperty("glyphPairAdjustmentRecord"); - } + SerializedObject internalSerializedObject = new SerializedObject(m_SerializedPropertyHolder); + m_FirstCharacterUnicode_prop = internalSerializedObject.FindProperty("firstCharacter"); + m_SecondCharacterUnicode_prop = internalSerializedObject.FindProperty("secondCharacter"); + m_EmptyGlyphPairAdjustmentRecord_prop = internalSerializedObject.FindProperty("glyphPairAdjustmentRecord"); + m_materialPresets = TMP_EditorUtility.FindMaterialReferences(m_fontAsset); m_GlyphSearchList = new List(); diff --git a/Scripts/Editor/TMP_FontAsset_CreationMenu.cs b/Scripts/Editor/TMP_FontAsset_CreationMenu.cs index 4119f3b..7a76a3a 100644 --- a/Scripts/Editor/TMP_FontAsset_CreationMenu.cs +++ b/Scripts/Editor/TMP_FontAsset_CreationMenu.cs @@ -12,7 +12,7 @@ namespace TMPro { static class TMP_FontAsset_CreationMenu { - [MenuItem("Assets/Create/TextMeshPro/FontAsset/Font Asset Variant", false, 200)] + [MenuItem("Assets/Create/TextMeshPro/Font Asset/Font Asset Variant", false, 200)] static void CreateFontAssetVariant() { Object target = Selection.activeObject; @@ -56,21 +56,21 @@ static void CreateFontAssetVariant() AssetDatabase.SaveAssets(); } - [MenuItem("Assets/Create/TextMeshPro/FontAsset/SDF #%F12", false, 100)] + [MenuItem("Assets/Create/TextMeshPro/Font Asset/SDF #%F12", false, 100)] //[MenuItem("Assets/Create/TextMeshPro/Font Asset", false, 100)] static void CreateFontAssetSDF() { CreateFontAsset(GlyphRenderMode.SDFAA); } - [MenuItem("Assets/Create/TextMeshPro/FontAsset/Bitmap", false, 105)] + [MenuItem("Assets/Create/TextMeshPro/Font Asset/Bitmap", false, 105)] static void CreateFontAssetBitmap() { CreateFontAsset(GlyphRenderMode.SMOOTH); } #if TEXTCORE_FONT_ENGINE_1_5_OR_NEWER - [MenuItem("Assets/Create/TextMeshPro/FontAsset/Color", false, 110)] + [MenuItem("Assets/Create/TextMeshPro/Font Asset/Color", false, 110)] static void CreateFontAssetColor() { CreateFontAsset(GlyphRenderMode.COLOR); diff --git a/Scripts/Editor/TMP_PackageUtilities.cs b/Scripts/Editor/TMP_PackageUtilities.cs index 938bd61..4649996 100644 --- a/Scripts/Editor/TMP_PackageUtilities.cs +++ b/Scripts/Editor/TMP_PackageUtilities.cs @@ -1,4 +1,4 @@ -using UnityEngine; +using UnityEngine; using UnityEngine.SceneManagement; using UnityEditor; using System; @@ -10,6 +10,11 @@ using System.Threading; using TMPro.EditorUtilities; +#if UNITY_2023_3_OR_NEWER +using PhysicsMaterialAsset = UnityEngine.PhysicsMaterial; +#else +using PhysicsMaterialAsset = UnityEngine.PhysicMaterial; +#endif namespace TMPro { @@ -618,7 +623,7 @@ static void ShowConverterWindow() typeof(LightingDataAsset), typeof(Mesh), typeof(MonoScript), - typeof(PhysicMaterial), + typeof(PhysicsMaterialAsset), typeof(PhysicsMaterial2D), typeof(RenderTexture), typeof(Shader), diff --git a/Scripts/Runtime/TMP_FontAsset.cs b/Scripts/Runtime/TMP_FontAsset.cs index a9b29a8..2358dd9 100644 --- a/Scripts/Runtime/TMP_FontAsset.cs +++ b/Scripts/Runtime/TMP_FontAsset.cs @@ -2884,7 +2884,7 @@ void SetupNewAtlasTexture() #if UNITY_EDITOR // Add new texture as sub asset to font asset Texture2D tex = m_AtlasTextures[m_AtlasTextureIndex]; - tex.name = m_AtlasTextures[0].name + " " + m_AtlasTextureIndex; + tex.name = atlasTexture.name + " " + m_AtlasTextureIndex; OnFontAssetTextureChanged?.Invoke(tex, this); #endif diff --git a/Scripts/Runtime/TMP_InputField.cs b/Scripts/Runtime/TMP_InputField.cs index 1887d8d..5af6186 100644 --- a/Scripts/Runtime/TMP_InputField.cs +++ b/Scripts/Runtime/TMP_InputField.cs @@ -452,6 +452,9 @@ public bool shouldHideSoftKeyboard case RuntimePlatform.Android: case RuntimePlatform.IPhonePlayer: case RuntimePlatform.tvOS: + #if UNITY_2022_3_OR_NEWER + case RuntimePlatform.VisionOS: + #endif case RuntimePlatform.WSAPlayerX86: case RuntimePlatform.WSAPlayerX64: case RuntimePlatform.WSAPlayerARM: @@ -482,6 +485,9 @@ public bool shouldHideSoftKeyboard case RuntimePlatform.Android: case RuntimePlatform.IPhonePlayer: case RuntimePlatform.tvOS: + #if UNITY_2022_3_OR_NEWER + case RuntimePlatform.VisionOS: + #endif case RuntimePlatform.WSAPlayerX86: case RuntimePlatform.WSAPlayerX64: case RuntimePlatform.WSAPlayerARM: @@ -522,6 +528,9 @@ private bool isKeyboardUsingEvents() return InPlaceEditing() && m_HideSoftKeyboard; case RuntimePlatform.IPhonePlayer: case RuntimePlatform.tvOS: + #if UNITY_2022_3_OR_NEWER + case RuntimePlatform.VisionOS: + #endif return m_HideSoftKeyboard; #if UNITY_2020_2_OR_NEWER case RuntimePlatform.PS4: @@ -1768,6 +1777,7 @@ protected virtual void LateUpdate() for (int i = 0; i < val.Length; ++i) { char c = val[i]; + bool hasValidateUpdatedText = false; if (c == '\r' || c == 3) c = '\n'; @@ -1775,7 +1785,11 @@ protected virtual void LateUpdate() if (onValidateInput != null) c = onValidateInput(m_Text, m_Text.Length, c); else if (characterValidation != CharacterValidation.None) + { + string textBeforeValidate = m_Text; c = Validate(m_Text, m_Text.Length, c); + hasValidateUpdatedText = textBeforeValidate != m_Text; + } if (lineType == LineType.MultiLineSubmit && c == '\n') { @@ -1787,7 +1801,8 @@ protected virtual void LateUpdate() } // In the case of a Custom Validator, the user is expected to modify the m_Text where as such we do not append c. - if (c != 0 && characterValidation != CharacterValidation.CustomValidator) + // However we will append c if the user did not modify the m_Text (UUM-42147) + if (c != 0 && (characterValidation != CharacterValidation.CustomValidator || !hasValidateUpdatedText)) m_Text += c; } @@ -2236,7 +2251,7 @@ protected EditState KeyPressed(Event evt) { TMP_TextInfo textInfo = m_TextComponent.textInfo; - if (textInfo != null && textInfo.lineCount >= m_LineLimit) + if (m_LineLimit > 0 && textInfo != null && textInfo.lineCount >= m_LineLimit) { m_ReleaseSelection = true; return EditState.Finish; @@ -4445,7 +4460,7 @@ public virtual void OnSubmit(BaseEventData eventData) SendOnSubmit(); DeactivateInputField(); - eventData.Use(); + eventData?.Use(); } public virtual void OnCancel(BaseEventData eventData) diff --git a/Scripts/Runtime/TMP_RichTextTagsCommon.cs b/Scripts/Runtime/TMP_RichTextTagsCommon.cs index 7b4fb1d..50ddebb 100644 --- a/Scripts/Runtime/TMP_RichTextTagsCommon.cs +++ b/Scripts/Runtime/TMP_RichTextTagsCommon.cs @@ -126,6 +126,8 @@ internal enum MarkupTag BLACK = 81074727, WHITE = 105680263, PURPLE = -1250222130, + GREY = 2638345, + LIGHTBLUE= 341063360, // Unicode Characters BR = 2256, //
Line Feed (LF) \u000A diff --git a/Scripts/Runtime/TMP_Text.cs b/Scripts/Runtime/TMP_Text.cs index 459c932..b04960e 100644 --- a/Scripts/Runtime/TMP_Text.cs +++ b/Scripts/Runtime/TMP_Text.cs @@ -4077,10 +4077,10 @@ protected virtual Vector2 CalculatePreferredValues(ref float fontSize, Vector2 m if (m_textElementType == TMP_TextElementType.Sprite) { // If a sprite is used as a fallback then get a reference to it and set the color to white. - m_currentSpriteAsset = m_textInfo.characterInfo[m_characterCount].textElement.textAsset as TMP_SpriteAsset; - m_spriteIndex = (int)m_textInfo.characterInfo[m_characterCount].textElement.glyphIndex; + TMP_SpriteCharacter sprite = (TMP_SpriteCharacter)m_textInfo.characterInfo[m_characterCount].textElement; + m_currentSpriteAsset = sprite.textAsset as TMP_SpriteAsset; + m_spriteIndex = (int)sprite.glyphIndex; - TMP_SpriteCharacter sprite = m_currentSpriteAsset.spriteCharacterTable[m_spriteIndex]; if (sprite == null) continue; // Sprites are assigned in the E000 Private Area + sprite Index @@ -7669,7 +7669,7 @@ internal bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out m_htmlColor = Color.red; m_colorStack.Add(m_htmlColor); return true; - case -992792864: // + case (int)MarkupTag.LIGHTBLUE: // m_htmlColor = new Color32(173, 216, 230, 255); m_colorStack.Add(m_htmlColor); return true; @@ -7677,7 +7677,7 @@ internal bool ValidateHtmlTag(TextProcessingElement[] chars, int startIndex, out m_htmlColor = Color.blue; m_colorStack.Add(m_htmlColor); return true; - case 3680713: // + case (int)MarkupTag.GREY: // m_htmlColor = new Color32(128, 128, 128, 255); m_colorStack.Add(m_htmlColor); return true; diff --git a/Scripts/Runtime/TextMeshPro.cs b/Scripts/Runtime/TextMeshPro.cs index 160aa95..e132d96 100644 --- a/Scripts/Runtime/TextMeshPro.cs +++ b/Scripts/Runtime/TextMeshPro.cs @@ -4905,7 +4905,7 @@ protected virtual void GenerateTextMesh() #endregion // Set vertex count for Underline geometry - //m_textInfo.meshInfo[m_Underline.materialIndex].vertexCount = last_vert_index; + m_textInfo.meshInfo[m_Underline.materialIndex].vertexCount = last_vert_index; // METRICS ABOUT THE TEXT OBJECT m_textInfo.characterCount = m_characterCount; diff --git a/Scripts/Runtime/TextMeshProUGUI.cs b/Scripts/Runtime/TextMeshProUGUI.cs index 4a89022..d82c4ba 100644 --- a/Scripts/Runtime/TextMeshProUGUI.cs +++ b/Scripts/Runtime/TextMeshProUGUI.cs @@ -5271,7 +5271,7 @@ protected virtual void GenerateTextMesh() #endregion // Set vertex count for Underline geometry - //m_textInfo.meshInfo[m_Underline.materialIndex].vertexCount = last_vert_index; + m_textInfo.meshInfo[m_Underline.materialIndex].vertexCount = last_vert_index; // METRICS ABOUT THE TEXT OBJECT m_textInfo.characterCount = m_characterCount; diff --git a/Tests/Editor/TMP_EditorTests.cs b/Tests/Editor/TMP_EditorTests.cs index 8ef6ce0..132a62d 100644 --- a/Tests/Editor/TMP_EditorTests.cs +++ b/Tests/Editor/TMP_EditorTests.cs @@ -363,6 +363,27 @@ public void MarkupTag_Indent(string sourceText, float origin1, float advance1, f Assert.AreEqual(advance3, m_TextComponent.textInfo.characterInfo[2].xAdvance); } +#if TMP_TEST_RESOURCES + const string k_SpriteAssetPath = "Assets/TextMesh Pro/Resources/Sprite Assets/MixedIndexTest.asset"; + [Test] + public void SpriteAssetIndexAreValidAfterReordering() + { + var spriteAsset = AssetDatabase.LoadAssetAtPath(k_SpriteAssetPath); + if (spriteAsset == null) + { + Debug.LogError("Failed to load Sprite Asset at path: " + k_SpriteAssetPath); + return; + } + + string text = $""; + m_TextComponent.spriteAsset = spriteAsset; + m_TextComponent.text = text; + m_TextComponent.ForceMeshUpdate(); + + Assert.AreEqual(203, m_TextComponent.textInfo.characterInfo[0].textElement.glyphIndex, $"Mismatch between sprite index. Expected 203 but was {m_TextComponent.textInfo.characterInfo[0].textElement.glyphIndex}"); + } +#endif + // Add tests that check position of individual characters in a complex block of text. // These test also use the data contained inside the TMP_TextInfo class. diff --git a/Tests/Runtime/TMP_RuntimeTests.cs b/Tests/Runtime/TMP_RuntimeTests.cs index 86fe19f..ef20ef6 100644 --- a/Tests/Runtime/TMP_RuntimeTests.cs +++ b/Tests/Runtime/TMP_RuntimeTests.cs @@ -194,8 +194,16 @@ public static IEnumerable TestCases_MultiLineNewline_OnLastLine_WhenPr yield return new object[] { 6, 6 }; } - [Test, TestCaseSource("TestCases_MultiLineNewline_OnLastLine_WhenPressedEnter_Caret_ShouldNotGoto_NextLine")] - public void MultiLineNewline_OnLastLine_WhenPressedEnter_Caret_ShouldNotGoto_NextLine(int lineLimit, int expectedLineCount) + [Test, TestCaseSource(nameof(TestCases_MultiLineNewline_OnLastLine_WhenPressedEnter_Caret_ShouldNotGoto_NextLine))] + public void MultiLineNewline_OnLastLine_WhenPressedEnter_Caret_ShouldNotGoto_NextLine(int lineLimit, + int expectedLineCount) + { + MultiLineNewline_LineLimit_ExpectedLineCount_Logic(lineLimit, lineLimit, 3); + Assert.AreEqual(m_TextComponent.textInfo.lineCount, expectedLineCount); + } + + private void MultiLineNewline_LineLimit_ExpectedLineCount_Logic(int lineLimitValue, int lineLimitApplied, + int extraKeyDownEventCount) { GameObject cameraObject = new GameObject("Camera Object", typeof(Camera)); GameObject canvasObject = new GameObject("Canvas Object", typeof(Canvas), typeof(GraphicRaycaster)); @@ -207,7 +215,7 @@ public void MultiLineNewline_OnLastLine_WhenPressedEnter_Caret_ShouldNotGoto_Nex m_InputField.targetGraphic = inputObject.GetComponent(); m_InputField.textComponent = m_TextComponent; m_InputField.lineType = TMP_InputField.LineType.MultiLineNewline; - m_InputField.lineLimit = lineLimit; + m_InputField.lineLimit = lineLimitValue; GameObject eventGameObject = new GameObject("Event Object", typeof(EventSystem), typeof(StandaloneInputModule)); Event enterKeyDownEvent = new Event { type = EventType.KeyDown, keyCode = KeyCode.KeypadEnter, modifiers = EventModifiers.None, character = '\n' }; @@ -215,12 +223,12 @@ public void MultiLineNewline_OnLastLine_WhenPressedEnter_Caret_ShouldNotGoto_Nex m_InputField.text = "POTUS"; EventSystem.current.SetSelectedGameObject(inputObject); m_InputField.ActivateInputField(); - int count = 0; - while (count < lineLimit + 3) + int count = lineLimitApplied + extraKeyDownEventCount; + while (count > 0) { m_InputField.ProcessEvent(enterKeyDownEvent); m_InputField.ForceLabelUpdate(); - count++; + count--; } m_InputField.textComponent.ForceMeshUpdate(); @@ -231,8 +239,21 @@ public void MultiLineNewline_OnLastLine_WhenPressedEnter_Caret_ShouldNotGoto_Nex GameObject.Destroy(inputObject); GameObject.Destroy(canvasObject); GameObject.Destroy(cameraObject); + } - Assert.AreEqual(m_TextComponent.textInfo.lineCount, expectedLineCount); + public static IEnumerable TestCases_MultiLineNewLine_NegativeOrZeroLineLimit_AddsNewLine() + { + yield return new object[] { 0, 0, 1 }; + yield return new object[] { 0, 0, 4 }; + yield return new object[] { -1, 0, 2 }; + } + + [Test, TestCaseSource(nameof(TestCases_MultiLineNewLine_NegativeOrZeroLineLimit_AddsNewLine))] + public void MultiLineNewLine_NegativeOrZeroLineLimit_AddsNewLine(int lineLimitValue, int lineLimitApplied, + int extraKeyDownEventCount) + { + MultiLineNewline_LineLimit_ExpectedLineCount_Logic(lineLimitValue, lineLimitApplied, extraKeyDownEventCount); + Assert.AreEqual(m_TextComponent.textInfo.lineCount, extraKeyDownEventCount + 1); } //[OneTimeTearDown] diff --git a/package.json b/package.json index c2a1329..f5e6954 100644 --- a/package.json +++ b/package.json @@ -1,7 +1,7 @@ { "name": "com.unity.textmeshpro", "displayName": "TextMeshPro", - "version": "3.2.0-pre.6", + "version": "3.2.0-pre.7", "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": [ @@ -16,15 +16,15 @@ "com.unity.ugui": "1.0.0" }, "_upm": { - "changelog": "### Changes\n- Fix TextMeshPro component does not perform linear color conversion when the VertexColorAlwaysGammaSpace option is enabled. Case #UUM-36113\n- Addressed issue surrounding dropdown not closing correctly in certain situations. Case #UUM-33691\n- Fixed Multi Line Newline input field from not accepting any new line past the set line limit. Case #UUM-42585" + "changelog": "### Changes\n- Fixed TMP_InputField line limit behavior to mean unlimited when the value is set to zero or negative (UUM-57192)\n- Fixed custom validator ignores the returned character from the validate function (UUM-42147)\n- Fixed editing a textfield on mobile and then submitting throws an exception (UUM-37282)\n- Addressed issue surrounding dropdown not closing correctly in certain situations(UUM-33691)\n- Ensure Sprites can be reordered within a SpriteAsset. (UUM-49349)\n- Added missing grey and lightblue tags (UUM-54820)\n- Fix underline when use at end of text. (UUM-55135)\n- Add support for Visions OS keyboard." }, "upmCi": { - "footprint": "a8e38db0379d59354c856907ccc8155d9ebb6986" + "footprint": "82ac48d5f236f037b6ad155ca24628fd9496be86" }, "documentationUrl": "https://docs.unity3d.com/Packages/com.unity.textmeshpro@3.2/manual/index.html", "repository": { "url": "https://github.cds.internal.unity3d.com/unity/unity.git", "type": "git", - "revision": "9e6fd1eba5042538628bbff3a2c33ce1af0b3e98" + "revision": "d3461b7a060288ff97eb1b0df540169a5f496964" } }