| 123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212 |
- using System.Text;
- using TMPro;
- using UnityEngine;
- using UnityEngine.UI;
- namespace AppUI.Localization
- {
- /// <summary>
- /// 按 UI 可用宽度在文案中插入零宽空格 <c>\u200B</c>,配合 TMP / Legacy Text 自动换行。
- /// </summary>
- static class AppUILocalizationTextBreak
- {
- const char ZeroWidthSpace = '\u200B';
- public static float ResolveMaxWidth(RectTransform rect, float maxWidth)
- {
- if (maxWidth > 0f)
- return maxWidth;
- if (rect == null)
- return 0f;
- float width = rect.rect.width;
- if (width > 0f)
- return width;
- return LayoutUtility.GetPreferredWidth(rect);
- }
- public static float ResolveMaxHeight(RectTransform rect, float maxHeight)
- {
- if (maxHeight > 0f)
- return maxHeight;
- if (rect == null)
- return 0f;
- float height = rect.rect.height;
- if (height > 0f)
- return height;
- return LayoutUtility.GetPreferredHeight(rect);
- }
- public static string InsertWidthBreaks(string text, TMP_Text tmpText, float maxWidth, float maxHeight = 0f)
- {
- if (string.IsNullOrEmpty(text) || tmpText == null)
- return text;
- maxWidth = ResolveMaxWidth(tmpText.rectTransform, maxWidth);
- if (maxWidth <= 0f)
- return text;
- if (FitsSingleLine(text, tmpText, maxWidth))
- return text;
- var output = new StringBuilder(text.Length + 16);
- InsertWidthBreaksInto(text, output, maxWidth, line => tmpText.GetPreferredValues(line, 0, 0).x);
- string result = output.ToString();
- maxHeight = ResolveMaxHeight(tmpText.rectTransform, maxHeight);
- if (maxHeight > 0f && tmpText.GetPreferredValues(result, maxWidth, 0).y > maxHeight)
- return result;
- return result;
- }
- public static string InsertWidthBreaks(string text, Text legacyText, float maxWidth, float maxHeight = 0f)
- {
- if (string.IsNullOrEmpty(text) || legacyText == null)
- return text;
- maxWidth = ResolveMaxWidth(legacyText.rectTransform, maxWidth);
- if (maxWidth <= 0f)
- return text;
- float Measure(string line)
- {
- var settings = legacyText.GetGenerationSettings(new Vector2(maxWidth, 0f));
- settings.generateOutOfBounds = true;
- return new TextGenerator().GetPreferredWidth(line, settings) / legacyText.pixelsPerUnit;
- }
- if (Measure(text) <= maxWidth)
- return text;
- var output = new StringBuilder(text.Length + 16);
- InsertWidthBreaksInto(text, output, maxWidth, Measure);
- string result = output.ToString();
- maxHeight = ResolveMaxHeight(legacyText.rectTransform, maxHeight);
- if (maxHeight > 0f)
- {
- var settings = legacyText.GetGenerationSettings(new Vector2(maxWidth, maxHeight));
- settings.generateOutOfBounds = true;
- float height = new TextGenerator().GetPreferredHeight(result, settings) / legacyText.pixelsPerUnit;
- if (height > maxHeight)
- return result;
- }
- return result;
- }
- static bool FitsSingleLine(string text, TMP_Text tmpText, float maxWidth)
- {
- if (text.IndexOf('\n') >= 0)
- return false;
- Vector2 preferred = tmpText.GetPreferredValues(text, maxWidth, 0);
- if (preferred.x <= maxWidth)
- return true;
- return tmpText.GetPreferredValues(text, 0, 0).x <= maxWidth;
- }
- static void InsertWidthBreaksInto(string source, StringBuilder output, float maxWidth, System.Func<string, float> measureWidth)
- {
- for (int i = 0; i < source.Length; i++)
- {
- char c = source[i];
- if (c == '<')
- {
- int end = source.IndexOf('>', i);
- if (end > i)
- {
- output.Append(source, i, end - i + 1);
- i = end;
- continue;
- }
- }
- if (c == '\n')
- {
- output.Append(c);
- continue;
- }
- output.Append(c);
- while (GetVisibleLineLength(output) > 1 && measureWidth(GetCurrentLine(output)) > maxWidth)
- output.Insert(FindLastVisibleIndex(output), ZeroWidthSpace);
- }
- }
- static int FindLineStart(StringBuilder sb)
- {
- for (int i = sb.Length - 1; i >= 0; i--)
- {
- if (sb[i] == ZeroWidthSpace || sb[i] == '\n')
- return i + 1;
- }
- return 0;
- }
- static string GetCurrentLine(StringBuilder sb)
- {
- int start = FindLineStart(sb);
- return sb.ToString(start, sb.Length - start);
- }
- static int GetVisibleLineLength(StringBuilder sb)
- {
- string line = GetCurrentLine(sb);
- int count = 0;
- for (int i = 0; i < line.Length; i++)
- {
- if (line[i] == '<')
- {
- int end = line.IndexOf('>', i);
- if (end > i)
- {
- i = end;
- continue;
- }
- }
- count++;
- }
- return count;
- }
- static int FindLastVisibleIndex(StringBuilder sb)
- {
- int lineStart = FindLineStart(sb);
- for (int i = sb.Length - 1; i >= lineStart; i--)
- {
- if (sb[i] == ZeroWidthSpace || sb[i] == '\n')
- continue;
- if (sb[i] == '>')
- {
- int open = i;
- while (open >= lineStart && sb[open] != '<')
- open--;
- if (open >= lineStart)
- {
- i = open;
- continue;
- }
- }
- if (sb[i] == '<')
- continue;
- return i;
- }
- return sb.Length - 1;
- }
- }
- }
|