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();
}
}