RubyTextMeshProUGUI.cs 5.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138
  1. using System;
  2. using System.Collections.Generic;
  3. using System.Linq;
  4. using System.Text;
  5. using System.Text.RegularExpressions;
  6. using System.Threading.Tasks;
  7. using TMPro;
  8. using UnityEngine;
  9. namespace BeatLyrics
  10. {
  11. //SOURCE: https://github.com/jp-netsis/RubyTextMeshPro/blob/5dbc919f2842ec1fe8e07b792afb01da853c5ea2/Assets/RubyTextMeshPro/Source/RubyTextMeshProUGUI.cs
  12. public class RubyTextMeshProUGUI : TextMeshProUGUI
  13. {
  14. protected enum RubyShowType
  15. {
  16. RUBY_ALIGNMENT,
  17. BASE_ALIGNMENT
  18. }
  19. // ruby tag <亜=あ>
  20. private static readonly Regex RubyRegex = new Regex(@"<(?<val>[\s\S]+?)=(?<ruby>[\s\S]+?)>");
  21. [Tooltip("v offset ruby. (em, px, %).")]
  22. [SerializeField] private string rubyVerticalOffset = "1em";
  23. [Tooltip("ruby scale. (1=100%)")]
  24. [SerializeField] private float rubyScale = 0.7f;
  25. [Tooltip("ruby show type.")]
  26. [SerializeField] private RubyShowType rubyShowType = RubyShowType.RUBY_ALIGNMENT;
  27. [Tooltip("all v compensation ruby.")]
  28. [SerializeField] private bool allVCompensationRuby = false;
  29. [Tooltip("all ruby v compensation. (em, px, %).")]
  30. [SerializeField] private string allVCompensationRubyLineHeight = "1.945em";
  31. [SerializeField]
  32. [TextArea(5, 10)]
  33. private string m_uneditedText;
  34. public string UnditedText
  35. {
  36. set { m_uneditedText = value; SetTextCustom(m_uneditedText); }
  37. }
  38. private void SetTextCustom(string value)
  39. {
  40. text = ReplaceRubyTags(value);
  41. // SetLayoutDirty called
  42. if (m_layoutAlreadyDirty)
  43. {
  44. // changes to the text object properties need to be applied immediately.
  45. ForceMeshUpdate();
  46. }
  47. }
  48. /// <summary>
  49. /// replace ruby tags.
  50. /// </summary>
  51. /// <param name="str"></param>
  52. /// <returns>relpaced str</returns>
  53. private string ReplaceRubyTags(string str)
  54. {
  55. if (string.IsNullOrEmpty(str)) return str;
  56. var hiddenSpaceW = GetPreferredValues("\u00A0").x * (m_isOrthographic ? 1 : 10f);
  57. // Replace <ruby> tags text layout.
  58. var matches = RubyRegex.Matches(str);
  59. foreach (Match match in matches)
  60. {
  61. if (match.Groups.Count != 3) continue;
  62. var fullMatch = match.Groups[0].ToString();
  63. var rubyText = match.Groups["ruby"].ToString();
  64. var baseText = match.Groups["val"].ToString();
  65. var rubyTextW = GetPreferredValues(rubyText).x * (m_isOrthographic ? 1 : 10f) * rubyScale;
  66. var baseTextW = GetPreferredValues(baseText).x * (m_isOrthographic ? 1 : 10f);
  67. var dir = isRightToLeftText ? 1 : -1;
  68. var rubyTextOffset = dir * (baseTextW / 2f + rubyTextW / 2f);
  69. var compensationOffset = -dir * ((baseTextW - rubyTextW) / 2f);
  70. var replace = CreateReplaceValue(baseText, rubyText, rubyTextOffset, compensationOffset, hiddenSpaceW);
  71. str = str.Replace(fullMatch, replace);
  72. }
  73. if (allVCompensationRuby)
  74. {
  75. // warning! bad Know-how
  76. // line-height tag is down the next line start.
  77. // now line can't change, corresponding by putting a hidden ruby
  78. var dir = isRightToLeftText ? 1 : -1;
  79. // Get hidden ruby width
  80. var spaceTextWidth = hiddenSpaceW * rubyScale;
  81. var compensationOffset = dir * spaceTextWidth;
  82. str = $"<line-height={allVCompensationRubyLineHeight}><voffset={rubyVerticalOffset}><size={rubyScale * 100f}%>\u00A0</size></voffset><space={compensationOffset}>" + str;
  83. }
  84. return str;
  85. }
  86. private string CreateReplaceValue(string baseText, string rubyText, float rubyTextOffset, float compensationOffset, float hiddenSpaceW)
  87. {
  88. var replace = string.Empty;
  89. switch (rubyShowType)
  90. {
  91. case RubyShowType.RUBY_ALIGNMENT:
  92. if (compensationOffset < 0)
  93. {
  94. replace = $"<nobr><space={-compensationOffset}>{baseText}<space={rubyTextOffset}><voffset={rubyVerticalOffset}><size={rubyScale * 100f}%>{rubyText}</size></voffset></nobr>";
  95. }
  96. else
  97. {
  98. var n = Mathf.CeilToInt(compensationOffset / hiddenSpaceW);
  99. var hiddenSpaceSize = compensationOffset / hiddenSpaceW / n;
  100. string hiddenSpaces = new string(' ', n);
  101. replace = $"<nobr>{baseText}<space={rubyTextOffset}><voffset={rubyVerticalOffset}><size={rubyScale * 100f}%>{rubyText}</size></voffset><size={hiddenSpaceSize * 100f}%>{hiddenSpaces}</size></nobr>";
  102. }
  103. break;
  104. case RubyShowType.BASE_ALIGNMENT:
  105. replace = $"<nobr>{baseText}<space={rubyTextOffset}><voffset={rubyVerticalOffset}><size={rubyScale * 100f}%>{rubyText}</size></voffset><space={compensationOffset}></nobr>";
  106. break;
  107. }
  108. return replace;
  109. }
  110. #if UNITY_EDITOR
  111. protected override void OnValidate()
  112. {
  113. base.OnValidate();
  114. SetTextCustom(m_uneditedText);
  115. }
  116. #endif
  117. }
  118. }