using System; using System.Collections.Generic; namespace UnityEngine.Localization.Pseudo { /// /// Expands the size of the input string to show where there may not be enough space for languages that result in longer strings, and either wrap awkwardly or truncate. /// [Serializable] public class Expander : IPseudoLocalizationMethod { /// /// Where to insert the padding characters. /// public enum InsertLocation { /// /// At the beginning of the input string. /// Start, /// /// At the end of the input string. /// End, /// /// Split between the beginning and end of the input string. /// Both } /// /// A rule used to determine how much the string length should be increased by. /// [Serializable] public struct ExpansionRule : IComparable { [SerializeField] int m_MinCharacters; [SerializeField] int m_MaxCharacters; [SerializeField] float m_ExpansionAmount; /// /// The minimum characters. The evaluated string length must be equal or greater than this value. /// public int MinCharacters { get => m_MinCharacters; set => m_MinCharacters = Mathf.Max(0, value); } /// /// The maximum characters. The evaluated string length must be less than this value. /// public int MaxCharacters { get => m_MaxCharacters; set => m_MaxCharacters = Mathf.Max(0, value); } /// /// The amount to increase the string length by as a ratio where 0 is no expansion and 1.0 is 100%(double length). /// It varies per language but 0.3 is a good value when using English as the source language. /// public float ExpansionAmount { get => m_ExpansionAmount; set => m_ExpansionAmount = Mathf.Max(0, value); } /// /// Create a new Expansion Rule instance. /// /// /// /// public ExpansionRule(int minCharacters, int maxCharacters, float expansion) { m_MinCharacters = Mathf.Max(0, minCharacters); m_MaxCharacters = Mathf.Max(0, maxCharacters); m_ExpansionAmount = Mathf.Max(0, expansion); } internal bool InRange(int length) { // We only check less than(not equal) for the max value so we can specify ranges like 0-10, 10-20 etc. return length >= MinCharacters && length < MaxCharacters; } /// /// Used for sorting the expansion rules. Rules are sorted by the value. /// /// /// public int CompareTo(ExpansionRule other) => MinCharacters.CompareTo(other.MinCharacters); } [SerializeField] List m_ExpansionRules = new List { // Default values based on IBM `Guidelines to design global solutions`. new ExpansionRule(0, 10, 2), new ExpansionRule(10, 20, 1), new ExpansionRule(20, 30, 0.8f), new ExpansionRule(30, 50, 0.6f), new ExpansionRule(50, 70, 0.7f), new ExpansionRule(70, int.MaxValue, 0.3f) }; [SerializeField] InsertLocation m_Location = InsertLocation.End; [SerializeField] int m_MinimumStringLength = 1; [SerializeField] List m_PaddingCharacters = new List(); /// /// Rules based on string length, that determine the amount to append onto the input string as a ratio of its length. /// For example, 0.3 would add an extra 30% onto the length. /// Note: Negative values are ignored. /// When the newly calculated length is not whole then the next largest whole number will be used. /// Rules can also be added using and . /// public List ExpansionRules => m_ExpansionRules; /// /// The location where the padding characters will be added to the input string. /// public InsertLocation Location { get => m_Location; set => m_Location = value; } /// /// The characters to randomly pick from when padding the length. /// public List PaddingCharacters => m_PaddingCharacters; /// /// The minimum length strings should be before evaluating the . /// For example if the value was 10, then all strings under 10 in length would be increased to 10 before the expansion rules were applied. /// By default this value is 1. /// public int MinimumStringLength { get => m_MinimumStringLength; set => m_MinimumStringLength = Mathf.Max(0, value); } /// /// Creates an instance with default padding characters. /// public Expander() { AddCharacterRange('!', '~'); } /// /// Creates an instance with a single padding character. /// /// The character to use for padding. public Expander(char paddingCharacter) { PaddingCharacters.Add(paddingCharacter); } /// /// Creates an instance with a range of padding characters from start to end. /// /// The character at the start of the range. /// The last character in the range. public Expander(char start, char end) { AddCharacterRange(start, end); } /// /// Adds all characters between start and end to the list of padding characters. /// /// Character to start with. /// Last character to add. public void AddCharacterRange(char start, char end) { for (var i = start; i < end; ++i) { PaddingCharacters.Add(i); } } /// /// Sets a single expansion rule that will be applied to all strings. /// /// public void SetConstantExpansion(float expansion) { if (m_ExpansionRules != null) m_ExpansionRules.Clear(); AddExpansionRule(0, int.MaxValue, expansion); } /// /// Adds an expansion rule to /// /// The minimum characters. The evaluated string length must be equal or greater than this value. /// The maximum characters. The evaluated string length must be less than this value. /// The amount to increase the string length by as a ratio where 0 is no expansion and 1.0 is 100%(double length). It varies per language but 0.3 is a good value when using English. public void AddExpansionRule(int minCharacters, int maxCharacters, float expansion) { if (m_ExpansionRules == null) m_ExpansionRules = new List(); m_ExpansionRules.Add(new ExpansionRule(minCharacters, maxCharacters, expansion)); } internal float GetExpansionForLength(int length) { foreach (var item in ExpansionRules) { if (item.InRange(length)) return item.ExpansionAmount; } return 0; } /// /// Pad the string with random characters to increase its length. /// /// public void Transform(Message message) { var messageLength = message.Length; int stringLength = Mathf.Max(messageLength, MinimumStringLength); var paddingAmount = Mathf.CeilToInt(GetExpansionForLength(stringLength) * stringLength); if (paddingAmount > 0) { // Add the extra length which may have resulted due to the minimum string length. paddingAmount += stringLength - messageLength; var padding = new char[paddingAmount]; Random.InitState(GetRandomSeed(message.Original)); for (int i = 0; i < paddingAmount; ++i) { padding[i] = PaddingCharacters[Random.Range(0, PaddingCharacters.Count)]; } AddPaddingToMessage(message, padding); } } void AddPaddingToMessage(Message message, char[] padding) { MessageFragment start = null; MessageFragment end = null; string paddingString = new string(padding); if (Location == InsertLocation.Start) start = message.CreateTextFragment(paddingString); else if (Location == InsertLocation.End) end = message.CreateTextFragment(paddingString); else // Both { int splitPoint = Mathf.FloorToInt(padding.Length * 0.5f); start = message.CreateTextFragment(paddingString, 0, splitPoint); end = message.CreateTextFragment(paddingString, splitPoint, padding.Length - 1); } if (start != null) message.Fragments.Insert(0, start); if (end != null) message.Fragments.Add(end); } int GetRandomSeed(string input) => input.GetHashCode(); } }