Browse Source

commit: ruby support and lyric new from blank

HOME 3 years ago
parent
commit
8fc4e72879

+ 0 - 8
BeatLyrics.Common/BeatLyrics.Common.csproj

@@ -1,8 +0,0 @@
-<Project Sdk="Microsoft.NET.Sdk">
-
-  <PropertyGroup>
-    <TargetFramework>net472</TargetFramework>  
-  </PropertyGroup>
-
-
-</Project>

+ 16 - 0
BeatLyrics.Shared/BeatLyrics.Shared.projitems

@@ -0,0 +1,16 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup>
+    <MSBuildAllProjects>$(MSBuildAllProjects);$(MSBuildThisFileFullPath)</MSBuildAllProjects>
+    <HasSharedItems>true</HasSharedItems>
+    <SharedGUID>be605f1f-f452-404c-98b0-364bcb14f3c0</SharedGUID>
+  </PropertyGroup>
+  <PropertyGroup Label="Configuration">
+    <Import_RootNamespace>BeatLyrics.Shared</Import_RootNamespace>
+  </PropertyGroup>
+  <ItemGroup>
+    <Compile Include="$(MSBuildThisFileDirectory)DirProvider.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Models\LyricDetail.cs" />
+    <Compile Include="$(MSBuildThisFileDirectory)Models\LyricFile.cs" />
+  </ItemGroup>
+</Project>

+ 13 - 0
BeatLyrics.Shared/BeatLyrics.Shared.shproj

@@ -0,0 +1,13 @@
+<?xml version="1.0" encoding="utf-8"?>
+<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
+  <PropertyGroup Label="Globals">
+    <ProjectGuid>be605f1f-f452-404c-98b0-364bcb14f3c0</ProjectGuid>
+    <MinimumVisualStudioVersion>14.0</MinimumVisualStudioVersion>
+  </PropertyGroup>
+  <Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.Default.props" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.Common.props" />
+  <PropertyGroup />
+  <Import Project="BeatLyrics.Shared.projitems" Label="Shared" />
+  <Import Project="$(MSBuildExtensionsPath32)\Microsoft\VisualStudio\v$(VisualStudioVersion)\CodeSharing\Microsoft.CodeSharing.CSharp.targets" />
+</Project>

BeatLyrics.Common/DirProvider.cs → BeatLyrics.Shared/DirProvider.cs


BeatLyrics.Common/Models/LyricDetail.cs → BeatLyrics.Shared/Models/LyricDetail.cs


BeatLyrics.Common/Models/LyricFile.cs → BeatLyrics.Shared/Models/LyricFile.cs


+ 1 - 6
BeatLyrics.Tool/BeatLyrics.Tool.csproj

@@ -209,18 +209,13 @@
     <None Include="App.config" />
   </ItemGroup>
   <ItemGroup>
-    <ProjectReference Include="..\BeatLyrics.Common\BeatLyrics.Common.csproj">
-      <Project>{f97f6bcd-49fb-444d-96cc-f96a39b10bad}</Project>
-      <Name>BeatLyrics.Common</Name>
-    </ProjectReference>
-  </ItemGroup>
-  <ItemGroup>
     <Content Include="RefLibs\ChineseConverter.dll" />
     <Content Include="RefLibs\JpnKanaConversion.dll" />
     <Content Include="RefLibs\JpnKanaConvHelper.dll" />
     <None Include="Resources\loading.gif" />
   </ItemGroup>
   <ItemGroup />
+  <Import Project="..\BeatLyrics.Shared\BeatLyrics.Shared.projitems" Label="Shared" />
   <Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
   <PropertyGroup>
     <PostBuildEvent>

+ 62 - 0
BeatLyrics.Tool/Dialogs/ContextDialog.cs

@@ -6,6 +6,68 @@ namespace BeatLyrics.Tool.Dialogs
 {
     internal class ContextDialog
     {
+        public static void ShowInput(Point screenPoint, string title, string orig, Action<DialogResult, string> ret)
+        {
+            var form = new Form
+            {
+                ControlBox = false,
+                FormBorderStyle = FormBorderStyle.FixedSingle,
+                StartPosition = FormStartPosition.Manual,
+                Location = screenPoint,
+                ShowInTaskbar = false,
+                Text = title,
+                Width = 320
+            };
+
+            var textBox = new TextBox
+            {
+                Left = 0,
+                Top = 0,
+            };
+
+            form.Controls.Add(textBox);
+
+            var closed = false;
+            textBox.PreviewKeyDown += delegate (object sender, PreviewKeyDownEventArgs args)
+            {
+                if (closed) return;
+                if (args.KeyCode == Keys.Escape)
+                {
+                    form.DialogResult = DialogResult.Cancel;
+                    form.Close();
+                }
+
+                if (args.KeyCode == Keys.Enter)
+                {
+                    form.DialogResult = DialogResult.OK;
+                    form.Close();
+                }
+            };
+            textBox.LostFocus += delegate
+            {
+                if (closed) return;
+                form.DialogResult = DialogResult.Cancel;
+                form.Close();
+            };
+
+            form.FormClosed += delegate { closed = true; ret(form.DialogResult, textBox.Text); };
+
+            form.Show();
+            form.ClientSize = new Size(form.ClientSize.Width, textBox.Height);
+            textBox.Text = orig;
+            textBox.Width = form.ClientSize.Width;
+            textBox.SelectAll();
+            var tw = TextRenderer.MeasureText(orig, textBox.Font).Width;
+            if (tw > textBox.Width)
+            {
+                textBox.Width = tw;
+                form.ClientSize = new Size(form.ClientSize.Height, tw);
+            }
+
+            form.Left -= form.Width / 2;
+            form.Top -= form.Height / 2;
+        }
+
         public static void PopTextBox(Point screenPoint, string orig, Action<DialogResult, string> ret)
         {
             var form = new Form

+ 21 - 7
BeatLyrics.Tool/Dialogs/TimelineEditDialog.cs

@@ -1,21 +1,20 @@
 using BeatLyrics.Common;
 using BeatLyrics.Tool.Models;
+using BeatLyrics.Tool.RefLibs;
 using BeatLyrics.Tool.UserControls;
 using BeatLyrics.Tool.Utils;
 using Newtonsoft.Json;
 using System;
+using System.Collections.Generic;
 using System.IO;
 using System.Linq;
 using System.Threading.Tasks;
 using System.Windows.Forms;
-using BeatLyrics.Tool.RefLibs;
 
 namespace BeatLyrics.Tool.Dialogs
 {
     internal partial class TimelineEditForm : BaseForm
     {
-        //TODO: range select control for Spectrum visualize
-
         private readonly LevelInfo _levelInfo;
         private readonly string _filename;
 
@@ -37,14 +36,28 @@ namespace BeatLyrics.Tool.Dialogs
             PlayPosTrackBar.Maximum = (int)OggAudioPlayer.Length.TotalMilliseconds;
 
             _filePath = Path.Combine(DirProvider.GetLyricDir(_levelInfo.Hash), _filename);
-            var json = File.ReadAllText(_filePath);
-            var lli = JsonConvert.DeserializeObject<LyricFileExt>(json);
+
+            LyricFileExt lli;
+            if (File.Exists(_filePath))
+            {
+                var json = File.ReadAllText(_filePath);
+                lli = JsonConvert.DeserializeObject<LyricFileExt>(json);
+            }
+            else
+            {
+                Directory.CreateDirectory(DirProvider.GetLyricDir(_levelInfo.Hash));
+                lli = new LyricFileExt
+                {
+                    Main = new List<LyricDetailExt>(),
+                    Subtitle = new List<LyricDetailExt>(),
+                };
+            }
 
             EditorControl1.Items = lli.Main.ToList();
             if (lli.Subtitle != null) EditorControl2.Items = lli.Subtitle.ToList();
 
-            SaWindoeTypeComboBox.DataSource = Enum.GetValues(typeof(RefLibs.DSP.Window.Type));
-            SaWindoeTypeComboBox.SelectedItem = RefLibs.DSP.Window.Type.Rectangular;
+            SaWindoeTypeComboBox.DataSource = Enum.GetValues(typeof(DSP.Window.Type));
+            SaWindoeTypeComboBox.SelectedItem = DSP.Window.Type.Rectangular;
 
             Task.Factory.StartNew(() =>
             {
@@ -188,6 +201,7 @@ namespace BeatLyrics.Tool.Dialogs
                 Main = EditorControl1.Items.OrderBy(p => p.TimeMs).ToList(),
                 Subtitle = EditorControl2.Items.OrderBy(p => p.TimeMs).ToList(),
             });
+
             File.WriteAllText(_filePath, json);
             Close();
         }

+ 30 - 17
BeatLyrics.Tool/MainForm.Designer.cs

@@ -46,11 +46,13 @@
             this.DirExploreHereLinkLabel = new System.Windows.Forms.LinkLabel();
             this.EditButton = new System.Windows.Forms.Button();
             this.SetButton = new System.Windows.Forms.Button();
+            this.NewButton = new System.Windows.Forms.Button();
             this.OnlineButton = new System.Windows.Forms.Button();
             this.HashTextBox = new System.Windows.Forms.TextBox();
             this.LevelAuthorTextBox = new System.Windows.Forms.TextBox();
             this.SongAuthorTextBox = new System.Windows.Forms.TextBox();
             this.SubNameTextBox = new System.Windows.Forms.TextBox();
+            this.DirTimeLabel = new System.Windows.Forms.Label();
             this.SongNameTextBox = new System.Windows.Forms.TextBox();
             this.DirTextBox = new System.Windows.Forms.TextBox();
             this.CoverPictureBox = new System.Windows.Forms.PictureBox();
@@ -65,7 +67,6 @@
             this.SetOnlyRadioButton = new System.Windows.Forms.RadioButton();
             this.OtherRadioButton = new System.Windows.Forms.RadioButton();
             this.SearchTextBox = new System.Windows.Forms.TextBox();
-            this.DirTimeLabel = new System.Windows.Forms.Label();
             label1 = new System.Windows.Forms.Label();
             label2 = new System.Windows.Forms.Label();
             label3 = new System.Windows.Forms.Label();
@@ -158,6 +159,7 @@
             this.splitContainer1.Panel2.Controls.Add(this.DirExploreHereLinkLabel);
             this.splitContainer1.Panel2.Controls.Add(this.EditButton);
             this.splitContainer1.Panel2.Controls.Add(this.SetButton);
+            this.splitContainer1.Panel2.Controls.Add(this.NewButton);
             this.splitContainer1.Panel2.Controls.Add(this.OnlineButton);
             this.splitContainer1.Panel2.Controls.Add(label6);
             this.splitContainer1.Panel2.Controls.Add(label5);
@@ -192,7 +194,7 @@
             this.MainObjectListView.Size = new System.Drawing.Size(805, 681);
             this.MainObjectListView.SmallImageList = this.MainImageList;
             this.MainObjectListView.TabIndex = 2;
-            this.MainObjectListView.TileSize = new System.Drawing.Size(192, 56);
+            this.MainObjectListView.TileSize = new System.Drawing.Size(192, 96);
             this.MainObjectListView.UseCompatibleStateImageBehavior = false;
             this.MainObjectListView.View = System.Windows.Forms.View.Tile;
             this.MainObjectListView.SelectedIndexChanged += new System.EventHandler(this.MainObjectListView_SelectedIndexChanged);
@@ -200,13 +202,13 @@
             // MainImageList
             // 
             this.MainImageList.ColorDepth = System.Windows.Forms.ColorDepth.Depth32Bit;
-            this.MainImageList.ImageSize = new System.Drawing.Size(48, 48);
+            this.MainImageList.ImageSize = new System.Drawing.Size(64, 64);
             this.MainImageList.TransparentColor = System.Drawing.Color.Transparent;
             // 
             // VerifyCheckBox
             // 
             this.VerifyCheckBox.AutoSize = true;
-            this.VerifyCheckBox.Location = new System.Drawing.Point(166, 512);
+            this.VerifyCheckBox.Location = new System.Drawing.Point(201, 512);
             this.VerifyCheckBox.Name = "VerifyCheckBox";
             this.VerifyCheckBox.Size = new System.Drawing.Size(60, 16);
             this.VerifyCheckBox.TabIndex = 8;
@@ -273,9 +275,9 @@
             // 
             // EditButton
             // 
-            this.EditButton.Location = new System.Drawing.Point(67, 508);
+            this.EditButton.Location = new System.Drawing.Point(121, 508);
             this.EditButton.Name = "EditButton";
-            this.EditButton.Size = new System.Drawing.Size(43, 23);
+            this.EditButton.Size = new System.Drawing.Size(37, 23);
             this.EditButton.TabIndex = 5;
             this.EditButton.Text = "Edit";
             this.EditButton.UseVisualStyleBackColor = true;
@@ -283,14 +285,24 @@
             // 
             // SetButton
             // 
-            this.SetButton.Location = new System.Drawing.Point(116, 508);
+            this.SetButton.Location = new System.Drawing.Point(164, 508);
             this.SetButton.Name = "SetButton";
-            this.SetButton.Size = new System.Drawing.Size(44, 23);
+            this.SetButton.Size = new System.Drawing.Size(31, 23);
             this.SetButton.TabIndex = 5;
             this.SetButton.Text = "Set";
             this.SetButton.UseVisualStyleBackColor = true;
             this.SetButton.Click += new System.EventHandler(this.SetButton_Click);
             // 
+            // NewButton
+            // 
+            this.NewButton.Location = new System.Drawing.Point(67, 508);
+            this.NewButton.Name = "NewButton";
+            this.NewButton.Size = new System.Drawing.Size(48, 23);
+            this.NewButton.TabIndex = 5;
+            this.NewButton.Text = "&New";
+            this.NewButton.UseVisualStyleBackColor = true;
+            this.NewButton.Click += new System.EventHandler(this.NewButton_Click);
+            // 
             // OnlineButton
             // 
             this.OnlineButton.Location = new System.Drawing.Point(6, 508);
@@ -333,6 +345,15 @@
             this.SubNameTextBox.Size = new System.Drawing.Size(259, 21);
             this.SubNameTextBox.TabIndex = 2;
             // 
+            // DirTimeLabel
+            // 
+            this.DirTimeLabel.AutoSize = true;
+            this.DirTimeLabel.Location = new System.Drawing.Point(35, 271);
+            this.DirTimeLabel.Name = "DirTimeLabel";
+            this.DirTimeLabel.Size = new System.Drawing.Size(119, 12);
+            this.DirTimeLabel.TabIndex = 3;
+            this.DirTimeLabel.Text = "0000-00-00 00:00:00";
+            // 
             // SongNameTextBox
             // 
             this.SongNameTextBox.Location = new System.Drawing.Point(6, 325);
@@ -485,15 +506,6 @@
             this.SearchTextBox.TabIndex = 0;
             this.SearchTextBox.TextChanged += new System.EventHandler(this.SearchTextBox_TextChanged);
             // 
-            // DirTimeLabel
-            // 
-            this.DirTimeLabel.AutoSize = true;
-            this.DirTimeLabel.Location = new System.Drawing.Point(35, 271);
-            this.DirTimeLabel.Name = "DirTimeLabel";
-            this.DirTimeLabel.Size = new System.Drawing.Size(119, 12);
-            this.DirTimeLabel.TabIndex = 3;
-            this.DirTimeLabel.Text = "0000-00-00 00:00:00";
-            // 
             // MainForm
             // 
             this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 12F);
@@ -557,5 +569,6 @@
         private System.Windows.Forms.RadioButton Others2RadioButton;
         private System.Windows.Forms.Panel panel2;
         private System.Windows.Forms.Label DirTimeLabel;
+        private System.Windows.Forms.Button NewButton;
     }
 }

+ 9 - 1
BeatLyrics.Tool/MainForm.cs

@@ -153,7 +153,6 @@ namespace BeatLyrics.Tool
                     .ToArray();
             }
 
-
             MainObjectListView.SetObjects(items);
 
             if (MainObjectListView.Items.Count == 1) MainObjectListView.SelectedIndex = 0;
@@ -247,6 +246,15 @@ namespace BeatLyrics.Tool
             ctx.Show(MousePosition);
         }
 
+        private void NewButton_Click(object sender, EventArgs e)
+        {
+            var nfo = (LevelInfo)MainObjectListView.SelectedObject;
+            ContextDialog.ShowInput(MousePosition, "Create New Lyric", nfo.SongName + ".json", (dr, input) =>
+            {
+                if (dr == DialogResult.OK) new TimelineEditForm(nfo, input).ShowDialog(this);
+            });
+        }
+
         private void SetButton_Click(object sender, EventArgs e)
         {
             var selectedObject = LocalLyricObjectListView.SelectedObject;

+ 36 - 0
BeatLyrics.Tool/Models/TextTuple.cs

@@ -8,6 +8,42 @@
 
         public override string ToString()
         {
+            return ToRubyTmpString();
+        }
+
+        public string ToRubyTmpString()
+        {
+            var left = 0;
+
+            var regexKata = new System.Text.RegularExpressions.Regex("^[\\p{IsKatakana}]+$");
+            if (regexKata.IsMatch(Text)) return Text;
+
+            if (Ruby == Text) return Text;
+
+            if (false == string.IsNullOrWhiteSpace(Ruby))
+            {
+                for (int i = 0; i < Ruby.Length; i++)
+                {
+                    if (Text[Text.Length - 1 - i] == Ruby[Ruby.Length - 1 - i]) continue;
+                    left = i;
+                    break;
+                }
+
+                var textLeft = Text.Substring(0, Text.Length - left);
+                var textRight = Text.Substring(Text.Length - left);
+
+                var midRuby = Ruby.Substring(0, Ruby.Length - left);
+
+                return $"<ruby={midRuby}>{textLeft}</ruby>{textRight}";
+            }
+
+            if (Text == Ruby || string.IsNullOrWhiteSpace(Ruby)) return Text;
+
+            return $"<ruby={Ruby}>{Text}</ruby>";
+        }
+
+        public string ToBraceString()
+        {
             var left = 0;
 
             var regexKata = new System.Text.RegularExpressions.Regex("^[\\p{IsKatakana}]+$");

+ 8 - 4
BeatLyrics.Tool/UserControls/TextArrangeUserControl.cs

@@ -14,6 +14,8 @@ namespace BeatLyrics.Tool.UserControls
     internal partial class TextArrangeUserControl : UserControl
     {
         private static readonly Regex _reg = new Regex("([(\\(]\\p{IsHiragana}+[\\))])", RegexOptions.Compiled);
+        private static readonly Regex _reg2 = new Regex("(<ruby=\\p{IsHiragana}+>)", RegexOptions.Compiled);
+        private static readonly Regex _reg3 = new Regex("(</ruby>)", RegexOptions.Compiled);
 
         private LyricFileExt _file;
 
@@ -124,8 +126,8 @@ namespace BeatLyrics.Tool.UserControls
             PosLabel.Text = timeSpan.FormatToTotalMinuteAndSecondsAndMs();
 
             var milliseconds = (int)timeSpan.TotalMilliseconds;
-            PlayPosTrackBar.Value = milliseconds > PlayPosTrackBar.Maximum 
-                ? PlayPosTrackBar.Maximum 
+            PlayPosTrackBar.Value = milliseconds > PlayPosTrackBar.Maximum
+                ? PlayPosTrackBar.Maximum
                 : milliseconds;
 
             if (_file == null)
@@ -177,6 +179,8 @@ namespace BeatLyrics.Tool.UserControls
             foreach (LyricDetailExt item in LeftLyricsObjectListView.SelectedObjects)
             {
                 item.Text = _reg.Replace(item.Text, "");
+                item.Text = _reg2.Replace(item.Text, "");
+                item.Text = _reg3.Replace(item.Text, "");
                 LeftLyricsObjectListView.UpdateObject(item);
             }
         }
@@ -216,10 +220,10 @@ namespace BeatLyrics.Tool.UserControls
             foreach (var detail in _file.Subtitle)
             {
                 detail.Text = detail.Text
+                    .Trim()
                     .Trim('【', '】')
                     .Trim('(', ')')
-                    .Trim('(', ')')
-                    ;
+                    .Trim('(', ')');
             }
 
             RightLyricsObjectListView.UpdateObjects(_file.Subtitle);

+ 7 - 6
BeatLyrics.sln

@@ -7,14 +7,19 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BeatLyrics", "BeatLyrics\Be
 EndProject
 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "BeatLyrics.Tool", "BeatLyrics.Tool\BeatLyrics.Tool.csproj", "{E50656DF-85A0-4A37-8F64-618D2DCAC8D8}"
 EndProject
-Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "BeatLyrics.Common", "BeatLyrics.Common\BeatLyrics.Common.csproj", "{F97F6BCD-49FB-444D-96CC-F96A39B10BAD}"
-EndProject
 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "@", "@", "{A8D98CA3-D10A-4E8E-8FDD-1D96B61EB068}"
 	ProjectSection(SolutionItems) = preProject
 		NuGet.config = NuGet.config
 	EndProjectSection
 EndProject
+Project("{D954291E-2A0B-460D-934E-DC6B0785DB48}") = "BeatLyrics.Shared", "BeatLyrics.Shared\BeatLyrics.Shared.shproj", "{BE605F1F-F452-404C-98B0-364BCB14F3C0}"
+EndProject
 Global
+	GlobalSection(SharedMSBuildProjectFiles) = preSolution
+		BeatLyrics.Shared\BeatLyrics.Shared.projitems*{159ec5ec-9340-4cc1-9c09-4b0cca902b82}*SharedItemsImports = 5
+		BeatLyrics.Shared\BeatLyrics.Shared.projitems*{be605f1f-f452-404c-98b0-364bcb14f3c0}*SharedItemsImports = 13
+		BeatLyrics.Shared\BeatLyrics.Shared.projitems*{e50656df-85a0-4a37-8f64-618d2dcac8d8}*SharedItemsImports = 4
+	EndGlobalSection
 	GlobalSection(SolutionConfigurationPlatforms) = preSolution
 		Debug|Any CPU = Debug|Any CPU
 		Release|Any CPU = Release|Any CPU
@@ -28,10 +33,6 @@ Global
 		{E50656DF-85A0-4A37-8F64-618D2DCAC8D8}.Debug|Any CPU.Build.0 = Debug|Any CPU
 		{E50656DF-85A0-4A37-8F64-618D2DCAC8D8}.Release|Any CPU.ActiveCfg = Release|Any CPU
 		{E50656DF-85A0-4A37-8F64-618D2DCAC8D8}.Release|Any CPU.Build.0 = Release|Any CPU
-		{F97F6BCD-49FB-444D-96CC-F96A39B10BAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
-		{F97F6BCD-49FB-444D-96CC-F96A39B10BAD}.Debug|Any CPU.Build.0 = Debug|Any CPU
-		{F97F6BCD-49FB-444D-96CC-F96A39B10BAD}.Release|Any CPU.ActiveCfg = Release|Any CPU
-		{F97F6BCD-49FB-444D-96CC-F96A39B10BAD}.Release|Any CPU.Build.0 = Release|Any CPU
 	EndGlobalSection
 	GlobalSection(SolutionProperties) = preSolution
 		HideSolutionNode = FALSE

+ 4 - 12
BeatLyrics/BeatLyrics.csproj

@@ -1,4 +1,4 @@
-<Project Sdk="Microsoft.NET.Sdk">
+<Project Sdk="Microsoft.NET.Sdk">
 
   <PropertyGroup>
     <TargetFramework>net472</TargetFramework>
@@ -11,14 +11,6 @@
   </PropertyGroup>
 
   <ItemGroup>
-    <PackageReference Include="ILRepack" Version="2.0.18" />
-  </ItemGroup>
-
-  <ItemGroup>
-    <ProjectReference Include="..\BeatLyrics.Common\BeatLyrics.Common.csproj" />
-  </ItemGroup>
-
-  <ItemGroup>
     <Reference Include="Main">
       <HintPath>$(BeatSaberDir)\Beat Saber_Data\Managed\Main.dll</HintPath>
     </Reference>
@@ -67,9 +59,9 @@
   </ItemGroup>
 
   <Target Name="PostBuild" AfterTargets="PostBuildEvent">
-
-    <Exec Command="$(ilrepack) /ndebug /out:$(TargetDir)$(TargetName).Merged.dll $(TargetPath) $(TargetDir)$(TargetName).Common.dll" />
-    <Copy SourceFiles="$(TargetDir)$(TargetName).Merged.dll" DestinationFolder="$(BeatSaberDir)\Plugins" />
+    <Copy SourceFiles="$(TargetDir)$(TargetName).dll" DestinationFolder="$(BeatSaberDir)\Plugins" />
   </Target>
 
+  <Import Project="..\BeatLyrics.Shared\BeatLyrics.Shared.projitems" Label="Shared" />
+
 </Project>

+ 167 - 0
BeatLyrics/LyricsComponent.cs

@@ -0,0 +1,167 @@
+using System;
+using System.Collections;
+using System.IO;
+using System.Linq;
+using BeatLyrics.Common;
+using BeatLyrics.Common.Models;
+using Newtonsoft.Json;
+using SongCore.Utilities;
+using TMPro;
+using UnityEngine;
+
+namespace BeatLyrics
+{
+    public class LyricsComponent : MonoBehaviour
+    {
+        private static readonly Quaternion Rot = Quaternion.Euler(new Vector3(0, 0, 0));
+        private static readonly Vector3 Pos = new Vector3(0, 1.5f, 15f);
+        private static readonly float FontSize = 25;
+        private static readonly Color32 FontColor = new Color32(255, 255, 255, 96);
+        private static readonly float OutlineWidth = 0.4f;
+        private static readonly Color32 OutlineColor = new Color32(255, 0, 0, 255);
+        //TODO: OutLine
+        //TODO: Select Font
+
+        private readonly AudioTimeSyncController _audioTime;
+
+        private readonly GameObject _clockCanvas = null;
+        private readonly RubyTextMeshProUGUI _text = null;
+
+        private GameplayCoreSceneSetupData _data;
+        private LyricFile _lyricFile;
+        private string _lyricFileName;
+
+        private string _debugInfo;
+
+        public LyricsComponent()
+        {
+            _audioTime = Resources.FindObjectsOfTypeAll<AudioTimeSyncController>().FirstOrDefault();
+
+            _clockCanvas = new GameObject();
+            _clockCanvas.AddComponent<Canvas>();
+
+            _clockCanvas.name = "BeatLyrics Canvas";
+            _clockCanvas.transform.position = Pos;
+            _clockCanvas.transform.rotation = Rot;
+            _clockCanvas.transform.localScale = new Vector3(0.02f, 0.02f, 1.0f);
+
+            var textObj = new GameObject();
+            textObj.transform.SetParent(_clockCanvas.transform);
+            textObj.transform.localPosition = Vector3.zero;
+            textObj.transform.localRotation = Quaternion.identity;
+            textObj.transform.localScale = Vector3.one;
+
+            _text = textObj.AddComponent<RubyTextMeshProUGUI>();
+
+            _text.alignment = TextAlignmentOptions.Center;
+            _text.fontSize = FontSize;
+            _text.enableWordWrapping = false;
+            _text.color = FontColor;
+            _text.name = "Lyrics Text";
+            _text.text = "BeatLyrics";
+
+            //text.font=
+        }
+
+        public IEnumerator Start()
+        {
+            var sceneSetup = FindObjectOfType<GameplayCoreSceneSetup>();
+            _data = (GameplayCoreSceneSetupData)sceneSetup?.GetField("_sceneSetupData");
+
+            //HACK: NJS Cheat
+            if (_data != null) //TODO: Create New Plugin  - OR -  Load from Config
+            {
+                try
+                {
+                    _data.difficultyBeatmap.SetField("_noteJumpMovementSpeed", 17);
+                }
+                catch (Exception e)
+                {
+                    System.Diagnostics.Debug.Print(e.ToString());
+                }
+            }
+
+            try
+            {
+                LoadLrc();
+            }
+            catch (Exception e)
+            {
+                _debugInfo = "Error Load Lrc:" + e.Message;
+            }
+
+            yield break;
+        }
+
+        private void LoadLrc()
+        {
+            if (_data?.difficultyBeatmap.level.levelID != null)
+            {
+                var beatLyricsDir = DirProvider.BeatLyricsDir;
+
+                if (beatLyricsDir != null)
+                {
+                    var path = GetSetFilePath(_data.difficultyBeatmap.level.levelID);
+                    if (File.Exists(path))
+                    {
+                        var jsonFileName = File.ReadAllText(path);
+                        var jsonFilePath = Path.Combine(beatLyricsDir, _data.difficultyBeatmap.level.levelID, jsonFileName);
+                        if (File.Exists(jsonFilePath))
+                        {
+                            var json = File.ReadAllText(jsonFilePath);
+                            _lyricFile = JsonConvert.DeserializeObject<LyricFile>(json);
+                            _lyricFileName = Path.GetFileNameWithoutExtension(jsonFileName);
+                        }
+                        else
+                        {
+                            _debugInfo += "json file not found";
+                        }
+                    }
+                    else
+                    {
+                        _debugInfo += "set.txt file not found";
+                    }
+                }
+                else
+                {
+                    _debugInfo += "failure to get EnvVar";
+                }
+            }
+        }
+
+        public void Update()
+        {
+            var milliseconds = _audioTime?.songTime * 1000 ?? 0;
+
+            if (_lyricFile != null)
+            {
+                if (milliseconds < 1)
+                {
+                    _text.text = _lyricFileName;
+                }
+                else
+                {
+                    //Show LRC
+                    _text.UnditedText = _lyricFile.Main?.FirstOrDefault(p => p.TimeMs <= milliseconds && p.TimeMs + p.DurationMs >= milliseconds)?.Text
+                                 + Environment.NewLine + _lyricFile.Subtitle?.FirstOrDefault(p => p.TimeMs <= milliseconds && p.TimeMs + p.DurationMs >= milliseconds)?.Text;
+                }
+            }
+            else if (milliseconds < 1)
+            {
+                var level = _data?.difficultyBeatmap.level;
+                _text.text = "此处应有歌词,但是未能加载" +
+                             $"{Environment.NewLine}{level?.levelID}" +
+                             $"{Environment.NewLine}{_debugInfo}";
+            }
+            else
+            {
+                _text.text = "";
+            }
+        }
+
+        public static string GetSetFilePath(string levelId)
+        {
+            return Path.Combine(DirProvider.BeatLyricsDir, levelId + DirProvider.SuffixSet);
+        }
+    }
+}

+ 1 - 165
BeatLyrics/Plugin.cs

@@ -1,14 +1,4 @@
-using BeatLyrics.Common;
-using BeatLyrics.Common.Models;
-using IllusionPlugin;
-using Newtonsoft.Json;
-using SongCore.Utilities;
-using System;
-using System.Collections;
-using System.IO;
-using System.Linq;
-using TMPro;
-using UnityEngine;
+using IllusionPlugin;
 using UnityEngine.SceneManagement;
 
 namespace BeatLyrics
@@ -68,158 +58,4 @@ namespace BeatLyrics
             }
         }
     }
-
-    public class LyricsComponent : MonoBehaviour
-    {
-        private static readonly Quaternion Rot = Quaternion.Euler(new Vector3(0, 0, 0));
-        private static readonly Vector3 Pos = new Vector3(0, 1.5f, 15f);
-        private static readonly float FontSize = 25;
-        private static readonly Color32 FontColor = new Color32(255, 255, 255, 64);
-        private static readonly float OutlineWidth = 0.4f;
-        private static readonly Color32 OutlineColor = new Color32(255, 0, 0, 255);
-        //TODO: OutLine
-        //TODO: Select Font
-
-        private readonly AudioTimeSyncController _audioTime;
-
-        private readonly GameObject _clockCanvas = null;
-        private readonly TextMeshProUGUI _text = null;
-
-        private GameplayCoreSceneSetupData _data;
-        private LyricFile _lyricFile;
-        private string _lyricFileName;
-
-        private string _debugInfo;
-
-        public LyricsComponent()
-        {
-            _audioTime = Resources.FindObjectsOfTypeAll<AudioTimeSyncController>().FirstOrDefault();
-
-            _clockCanvas = new GameObject();
-            _clockCanvas.AddComponent<Canvas>();
-
-            _clockCanvas.name = "BeatLyrics Canvas";
-            _clockCanvas.transform.position = Pos;
-            _clockCanvas.transform.rotation = Rot;
-            _clockCanvas.transform.localScale = new Vector3(0.02f, 0.02f, 1.0f);
-
-            var textObj = new GameObject();
-            textObj.transform.SetParent(_clockCanvas.transform);
-            textObj.transform.localPosition = Vector3.zero;
-            textObj.transform.localRotation = Quaternion.identity;
-            textObj.transform.localScale = Vector3.one;
-
-            _text = textObj.AddComponent<TextMeshProUGUI>();
-
-            _text.alignment = TextAlignmentOptions.Center;
-            _text.fontSize = FontSize;
-            _text.enableWordWrapping = false;
-            _text.color = FontColor;
-            _text.name = "Lyrics Text";
-            _text.text = "BeatLyrics";
-
-            //text.font=
-        }
-
-        public IEnumerator Start()
-        {
-            var sceneSetup = FindObjectOfType<GameplayCoreSceneSetup>();
-            _data = (GameplayCoreSceneSetupData)sceneSetup?.GetField("_sceneSetupData");
-
-            //HACK: NJS Cheat
-            if (_data != null) //TODO: Create New Plugin  - OR -  Load from Config
-            {
-                try
-                {
-                    _data.difficultyBeatmap.SetField("_noteJumpMovementSpeed", 17);
-                }
-                catch (Exception e)
-                {
-                    System.Diagnostics.Debug.Print(e.ToString());
-                }
-            }
-
-            try
-            {
-                LoadLrc();
-            }
-            catch (Exception e)
-            {
-                _debugInfo = "Error Load Lrc:" + e.Message;
-            }
-
-            yield break;
-        }
-
-        private void LoadLrc()
-        {
-            if (_data?.difficultyBeatmap.level.levelID != null)
-            {
-                var beatLyricsDir = DirProvider.BeatLyricsDir;
-
-                if (beatLyricsDir != null)
-                {
-                    var path = GetSetFilePath(_data.difficultyBeatmap.level.levelID);
-                    if (File.Exists(path))
-                    {
-                        var jsonFileName = File.ReadAllText(path);
-                        var jsonFilePath = Path.Combine(beatLyricsDir, _data.difficultyBeatmap.level.levelID, jsonFileName);
-                        if (File.Exists(jsonFilePath))
-                        {
-                            var json = File.ReadAllText(jsonFilePath);
-                            _lyricFile = JsonConvert.DeserializeObject<LyricFile>(json);
-                            _lyricFileName = Path.GetFileNameWithoutExtension(jsonFileName);
-                        }
-                        else
-                        {
-                            _debugInfo += "json file not found";
-                        }
-                    }
-                    else
-                    {
-                        _debugInfo += "set.txt file not found";
-                    }
-                }
-                else
-                {
-                    _debugInfo += "failure to get EnvVar";
-                }
-            }
-        }
-
-        public void Update()
-        {
-            var milliseconds = _audioTime?.songTime * 1000 ?? 0;
-
-            if (_lyricFile != null)
-            {
-                if (milliseconds < 1)
-                {
-                    _text.text = _lyricFileName;
-                }
-                else
-                {
-                    //Show LRC
-                    _text.text = _lyricFile.Main?.FirstOrDefault(p => p.TimeMs <= milliseconds && p.TimeMs + p.DurationMs >= milliseconds)?.Text
-                                + Environment.NewLine + _lyricFile.Subtitle?.FirstOrDefault(p => p.TimeMs <= milliseconds && p.TimeMs + p.DurationMs >= milliseconds)?.Text;
-                }
-            }
-            else if (milliseconds < 1)
-            {
-                var level = _data?.difficultyBeatmap.level;
-                _text.text = "此处应有歌词,但是未能加载" +
-                             $"{Environment.NewLine}{level?.levelID}" +
-                             $"{Environment.NewLine}{_debugInfo}";
-            }
-            else
-            {
-                _text.text = "";
-            }
-        }
-
-        public static string GetSetFilePath(string levelId)
-        {
-            return Path.Combine(DirProvider.BeatLyricsDir, levelId + DirProvider.SuffixSet);
-        }
-    }
 }

+ 138 - 0
BeatLyrics/RubyTextMeshProUGUI.cs

@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Text.RegularExpressions;
+using System.Threading.Tasks;
+using TMPro;
+using UnityEngine;
+
+namespace BeatLyrics
+{
+    //SOURCE: https://github.com/jp-netsis/RubyTextMeshPro/blob/5dbc919f2842ec1fe8e07b792afb01da853c5ea2/Assets/RubyTextMeshPro/Source/RubyTextMeshProUGUI.cs
+    public class RubyTextMeshProUGUI : TextMeshProUGUI
+    {
+        protected enum RubyShowType
+        {
+            RUBY_ALIGNMENT,
+            BASE_ALIGNMENT
+        }
+
+        // ruby tag
+        private static readonly Regex RubyRegex = new Regex(@"<r(uby)?=(?<ruby>[\s\S]*?)>(?<val>[\s\S]*?)<\/r(uby)?>");
+
+        [Tooltip("v offset ruby. (em, px, %).")]
+        [SerializeField] private string rubyVerticalOffset = "1em";
+
+        [Tooltip("ruby scale. (1=100%)")]
+        [SerializeField] private float rubyScale = 0.5f;
+
+        [Tooltip("ruby show type.")]
+        [SerializeField] private RubyShowType rubyShowType = RubyShowType.RUBY_ALIGNMENT;
+
+        [Tooltip("all v compensation ruby.")]
+        [SerializeField] private bool allVCompensationRuby = false;
+        [Tooltip("all ruby v compensation. (em, px, %).")]
+        [SerializeField] private string allVCompensationRubyLineHeight = "1.945em";
+
+        [SerializeField]
+        [TextArea(5, 10)]
+        private string m_uneditedText;
+
+        public string UnditedText
+        {
+            set { m_uneditedText = value; SetTextCustom(m_uneditedText); }
+        }
+
+        private void SetTextCustom(string value)
+        {
+            text = ReplaceRubyTags(value);
+
+            // SetLayoutDirty called
+            if (m_layoutAlreadyDirty)
+            {
+                // changes to the text object properties need to be applied immediately.
+                ForceMeshUpdate();
+            }
+        }
+
+        /// <summary>
+        /// replace ruby tags.
+        /// </summary>
+        /// <param name="str"></param>
+        /// <returns>relpaced str</returns>
+        private string ReplaceRubyTags(string str)
+        {
+            if (string.IsNullOrEmpty(str)) return str;
+            var hiddenSpaceW = GetPreferredValues("\u00A0").x * (m_isOrthographic ? 1 : 10f);
+            // Replace <ruby> tags text layout.
+            var matches = RubyRegex.Matches(str);
+            foreach (Match match in matches)
+            {
+                if (match.Groups.Count != 5) continue;
+                var fullMatch = match.Groups[0].ToString();
+                var rubyText = match.Groups["ruby"].ToString();
+                var baseText = match.Groups["val"].ToString();
+
+                var rubyTextW = GetPreferredValues(rubyText).x * (m_isOrthographic ? 1 : 10f) * rubyScale;
+                var baseTextW = GetPreferredValues(baseText).x * (m_isOrthographic ? 1 : 10f);
+                var dir = isRightToLeftText ? 1 : -1;
+                var rubyTextOffset = dir * (baseTextW / 2f + rubyTextW / 2f);
+                var compensationOffset = -dir * ((baseTextW - rubyTextW) / 2f);
+                var replace = CreateReplaceValue(baseText, rubyText, rubyTextOffset, compensationOffset, hiddenSpaceW);
+                str = str.Replace(fullMatch, replace);
+            }
+            if (allVCompensationRuby)
+            {
+                // warning! bad Know-how
+                // line-height tag is down the next line start.
+                // now line can't change, corresponding by putting a hidden ruby
+                var dir = isRightToLeftText ? 1 : -1;
+                // Get hidden ruby width
+                var spaceTextWidth = hiddenSpaceW * rubyScale;
+                var compensationOffset = dir * spaceTextWidth;
+                str = $"<line-height={allVCompensationRubyLineHeight}><voffset={rubyVerticalOffset}><size={rubyScale * 100f}%>\u00A0</size></voffset><space={compensationOffset}>" + str;
+            }
+
+            return str;
+        }
+
+        private string CreateReplaceValue(string baseText, string rubyText, float rubyTextOffset, float compensationOffset, float hiddenSpaceW)
+        {
+            var replace = string.Empty;
+            switch (rubyShowType)
+            {
+                case RubyShowType.RUBY_ALIGNMENT:
+                    if (compensationOffset < 0)
+                    {
+                        replace = $"<nobr><space={-compensationOffset}>{baseText}<space={rubyTextOffset}><voffset={rubyVerticalOffset}><size={rubyScale * 100f}%>{rubyText}</size></voffset></nobr>";
+                    }
+                    else
+                    {
+                        var n = Mathf.CeilToInt(compensationOffset / hiddenSpaceW);
+                        var hiddenSpaceSize = compensationOffset / hiddenSpaceW / n;
+                        string hiddenSpaces = new string(' ', n);
+                        replace = $"<nobr>{baseText}<space={rubyTextOffset}><voffset={rubyVerticalOffset}><size={rubyScale * 100f}%>{rubyText}</size></voffset><size={hiddenSpaceSize * 100f}%>{hiddenSpaces}</size></nobr>";
+                    }
+                    break;
+
+                case RubyShowType.BASE_ALIGNMENT:
+                    replace = $"<nobr>{baseText}<space={rubyTextOffset}><voffset={rubyVerticalOffset}><size={rubyScale * 100f}%>{rubyText}</size></voffset><space={compensationOffset}></nobr>";
+                    break;
+            }
+
+            return replace;
+        }
+
+#if UNITY_EDITOR
+
+        protected override void OnValidate()
+        {
+            base.OnValidate();
+
+            SetTextCustom(m_uneditedText);
+        }
+
+#endif
+    }
+}