From 62d989c9618788674f4551c84b1b582abcf70940 Mon Sep 17 00:00:00 2001 From: Not Officer Date: Sat, 10 Oct 2020 13:46:43 +0200 Subject: [PATCH] added arabic icon drawing support --- FModel/Creator/Bases/BaseUIData.cs | 4 +- FModel/Creator/Bases/BaseUserOption.cs | 2 +- FModel/Creator/Texts/Helper.cs | 84 ++++++++++++++---- FModel/Creator/Texts/Text.cs | 88 ++++++++++++++----- FModel/FModel.csproj | 1 + FModel/Grabber/Paks/PaksGrabber.cs | 7 +- .../ViewModels/ImageBox/ImageBoxViewModel.cs | 4 +- 7 files changed, 145 insertions(+), 45 deletions(-) diff --git a/FModel/Creator/Bases/BaseUIData.cs b/FModel/Creator/Bases/BaseUIData.cs index 2c93f0d1..79e5affe 100644 --- a/FModel/Creator/Bases/BaseUIData.cs +++ b/FModel/Creator/Bases/BaseUIData.cs @@ -45,7 +45,7 @@ namespace FModel.Creator.Bases if (Description.Equals(DisplayName)) Description = string.Empty; if (!string.IsNullOrEmpty(Description)) { - Height += (int)descriptionPaint.TextSize * Helper.SplitLines(Description, descriptionPaint, Width - Margin).Length; + Height += (int)descriptionPaint.TextSize * Helper.SplitLines(Description, descriptionPaint, Width - Margin).Count; Height += (int)descriptionPaint.TextSize; } } @@ -78,7 +78,7 @@ namespace FModel.Creator.Bases s.Description = Text.GetTextPropertyBase(aDescription) ?? ""; if (!string.IsNullOrEmpty(Description)) { - s.Height += (int)descriptionPaint.TextSize * Helper.SplitLines(s.Description, descriptionPaint, Width - Margin).Length; + s.Height += (int)descriptionPaint.TextSize * Helper.SplitLines(s.Description, descriptionPaint, Width - Margin).Count; s.Height += (int)descriptionPaint.TextSize * 3; } } diff --git a/FModel/Creator/Bases/BaseUserOption.cs b/FModel/Creator/Bases/BaseUserOption.cs index d5156d15..977f6851 100644 --- a/FModel/Creator/Bases/BaseUserOption.cs +++ b/FModel/Creator/Bases/BaseUserOption.cs @@ -40,7 +40,7 @@ namespace FModel.Creator.Bases OptionDescription = Text.GetTextPropertyBase(optionDescription); if (!string.IsNullOrEmpty(OptionDescription)) { - Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Length; + Height += (int)descriptionPaint.TextSize * Helper.SplitLines(OptionDescription, descriptionPaint, Width - Margin).Count; Height += (int)descriptionPaint.TextSize; } } diff --git a/FModel/Creator/Texts/Helper.cs b/FModel/Creator/Texts/Helper.cs index cf3da354..3d513483 100644 --- a/FModel/Creator/Texts/Helper.cs +++ b/FModel/Creator/Texts/Helper.cs @@ -4,6 +4,10 @@ using System; using System.Collections.Generic; using System.Text; +using FModel.Properties; + +using SkiaSharp.HarfBuzz; + namespace FModel.Creator.Texts { static class Helper @@ -19,33 +23,56 @@ namespace FModel.Creator.Texts public static void DrawCenteredMultilineText(SKCanvas canvas, string text, int maxLineCount, int size, int margin, ETextSide side, SKRect area, SKPaint paint) { float lineHeight = paint.TextSize * 1.2f; - Line[] lines = SplitLines(text, paint, area.Width - margin); + List lines = SplitLines(text, paint, area.Width - margin); if (lines == null) return; - if (lines.Length <= maxLineCount) - maxLineCount = lines.Length; + if (lines.Count <= maxLineCount) + maxLineCount = lines.Count; float height = maxLineCount * lineHeight; float y = area.MidY - height / 2; + SKShaper shaper = (ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic ? new SKShaper(paint.Typeface) : null; + for (int i = 0; i < maxLineCount; i++) { + Line line = lines[i]; + y += lineHeight; float x = side switch { - ETextSide.Center => area.MidX - lines[i].Width / 2, - ETextSide.Right => size - margin - lines[i].Width, + ETextSide.Center => area.MidX - line.Width / 2, + ETextSide.Right => size - margin - line.Width, ETextSide.Left => margin, - _ => area.MidX - lines[i].Width / 2 + _ => area.MidX - line.Width / 2 }; - canvas.DrawText(lines[i].Value.TrimEnd(), x, y, paint); + + string lineText = line.Value.TrimEnd(); + + if (shaper == null) + { + canvas.DrawText(lineText, x, y, paint); + } + else + { + if (side == ETextSide.Center) + { + SKShaper.Result shapedText = shaper.Shape(lineText, paint); + float shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f; + canvas.DrawShapedText(shaper, lineText, (area.Width - shapedTextWidth) / 2f, y, paint); + } + else + { + canvas.DrawShapedText(shaper, lineText, x, y, paint); + } + } } } public static void DrawMultilineText(SKCanvas canvas, string text, int size, int margin, ETextSide side, SKRect area, SKPaint paint, out int yPos) { float lineHeight = paint.TextSize * 1.2f; - Line[] lines = SplitLines(text, paint, area.Width); + List lines = SplitLines(text, paint, area.Width); if (lines == null) { yPos = (int)area.Top; @@ -53,22 +80,45 @@ namespace FModel.Creator.Texts } float y = area.Top; - for (int i = 0; i < lines.Length; i++) + SKShaper shaper = (ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic ? new SKShaper(paint.Typeface) : null; + + for (int i = 0; i < lines.Count; i++) { + var line = lines[i]; float x = side switch { - ETextSide.Center => area.MidX - lines[i].Width / 2, - ETextSide.Right => size - margin - lines[i].Width, + ETextSide.Center => area.MidX - line.Width / 2, + ETextSide.Right => size - margin - line.Width, ETextSide.Left => area.Left, - _ => area.MidX - lines[i].Width / 2 + _ => area.MidX - line.Width / 2 }; - canvas.DrawText(lines[i].Value.TrimEnd(), x, y, paint); + + string lineText = line.Value.TrimEnd(); + + if (shaper == null) + { + canvas.DrawText(lineText, x, y, paint); + } + else + { + if (side == ETextSide.Center) + { + SKShaper.Result shapedText = shaper.Shape(lineText, paint); + float shapedTextWidth = shapedText.Points[^1].X + paint.TextSize / 2f; + canvas.DrawShapedText(shaper, lineText, (area.Width - shapedTextWidth) / 2f, y, paint); + } + else + { + canvas.DrawShapedText(shaper, lineText, x, y, paint); + } + } + y += lineHeight; } - yPos = (int)area.Top + ((int)lineHeight * lines.Length); + yPos = (int)area.Top + ((int)lineHeight * lines.Count); } - public static Line[] SplitLines(string text, SKPaint paint, float maxWidth) + public static List SplitLines(string text, SKPaint paint, float maxWidth) { if (string.IsNullOrEmpty(text)) return null; @@ -84,7 +134,7 @@ namespace FModel.Creator.Texts float width = 0; var lineResult = new StringBuilder(); - string[] words = lines[i].Split(' ', StringSplitOptions.None); + string[] words = lines[i].Split(' '); foreach (var word in words) { float wordWidth = paint.MeasureText(word); @@ -105,7 +155,7 @@ namespace FModel.Creator.Texts } ret.Add(new Line { Value = lineResult.ToString(), Width = width }); } - return ret.ToArray(); + return ret; } } } diff --git a/FModel/Creator/Texts/Text.cs b/FModel/Creator/Texts/Text.cs index a017b5ee..ee565909 100644 --- a/FModel/Creator/Texts/Text.cs +++ b/FModel/Creator/Texts/Text.cs @@ -1,10 +1,16 @@ -using FModel.Creator.Bases; +using System; +using System.Collections.Generic; + +using FModel.Creator.Bases; +using FModel.Properties; + using PakReader.Pak; using PakReader.Parsers.Class; using PakReader.Parsers.Objects; using PakReader.Parsers.PropertyTagData; + using SkiaSharp; -using System.Collections.Generic; +using SkiaSharp.HarfBuzz; namespace FModel.Creator.Texts { @@ -17,7 +23,7 @@ namespace FModel.Creator.Texts public static string GetTextPropertyBase(TextProperty t) { - if (t.Value is FText text) + if (t.Value is { } text) if (text.Text is FTextHistory.None n) return n.CultureInvariantString; else if (text.Text is FTextHistory.Base b) @@ -51,7 +57,7 @@ namespace FModel.Creator.Texts public static (string, string, string) GetTextPropertyBases(TextProperty t) { - if (t.Value is FText text && text.Text is FTextHistory.Base b) + if (t.Value is { } text && text.Text is FTextHistory.Base b) return (b.Namespace, b.Key, b.SourceString); return (string.Empty, string.Empty, string.Empty); } @@ -62,7 +68,8 @@ namespace FModel.Creator.Texts { if (o1.TryGetValue("Value", out var c) && c is FloatProperty value && value.Value != -1) // old way return $"MaxStackSize : {value.Value}"; - else if ( + + if ( o1.TryGetValue("Curve", out var c1) && c1 is StructProperty curve && curve.Value is UObject o2 && o2.TryGetValue("CurveTable", out var c2) && c2 is ObjectProperty curveTable && o2.TryGetValue("RowName", out var c3) && c3 is NameProperty rowName) // new way @@ -75,7 +82,7 @@ namespace FModel.Creator.Texts { if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount && maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys && - keys.Value.Length > 0 && (keys.Value[0] as StructProperty).Value is FSimpleCurveKey amount && + keys.Value.Length > 0 && (keys.Value[0] as StructProperty)?.Value is FSimpleCurveKey amount && amount.KeyValue != -1) { return $"MaxStackSize : {amount.KeyValue}"; @@ -104,7 +111,7 @@ namespace FModel.Creator.Texts { if (table.TryGetValue(rowName.Value.String, out var v1) && v1 is UObject maxStackAmount && maxStackAmount.TryGetValue("Keys", out var v2) && v2 is ArrayProperty keys && - keys.Value.Length > 0 && (keys.Value[0] as StructProperty).Value is FSimpleCurveKey amount && + keys.Value.Length > 0 && (keys.Value[0] as StructProperty)?.Value is FSimpleCurveKey amount && amount.KeyValue != -1) { return $"{amount.KeyValue} Xp"; @@ -118,14 +125,14 @@ namespace FModel.Creator.Texts public static void DrawBackground(SKCanvas c, IBase icon) { - switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) + switch ((EIconDesign)Settings.Default.AssetsIconDesign) { case EIconDesign.Flat: { var pathBottom = new SKPath { FillType = SKPathFillType.EvenOdd }; pathBottom.MoveTo(icon.Margin, icon.Height - icon.Margin); - pathBottom.LineTo(icon.Margin, icon.Height - icon.Margin - (icon.Height / 17 * 2.5f)); - pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin - (icon.Height / 17 * 4.5f)); + pathBottom.LineTo(icon.Margin, icon.Height - icon.Margin - icon.Height / 17 * 2.5f); + pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin - icon.Height / 17 * 4.5f); pathBottom.LineTo(icon.Width - icon.Margin, icon.Height - icon.Margin); pathBottom.Close(); c.DrawPath(pathBottom, new SKPaint @@ -158,12 +165,12 @@ namespace FModel.Creator.Texts SKTextAlign side = SKTextAlign.Center; int x = icon.Width / 2; int y = _STARTER_TEXT_POSITION + _NAME_TEXT_SIZE; - switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) + switch ((EIconDesign)Settings.Default.AssetsIconDesign) { case EIconDesign.Mini: { _NAME_TEXT_SIZE = 47; - text = text.ToUpper(); + text = text.ToUpperInvariant(); break; } case EIconDesign.Flat: @@ -182,16 +189,41 @@ namespace FModel.Creator.Texts Typeface = TypeFaces.DisplayNameTypeface, TextSize = _NAME_TEXT_SIZE, Color = SKColors.White, - TextAlign = side, + TextAlign = side }; - // resize if too long - while (namePaint.MeasureText(text) > (icon.Width - (icon.Margin * 2))) + if ((ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic) { - namePaint.TextSize = _NAME_TEXT_SIZE -= 2; - } + SKShaper shaper = new SKShaper(namePaint.Typeface); + float shapedTextWidth; - c.DrawText(text, x, y, namePaint); + while (true) + { + SKShaper.Result shapedText = shaper.Shape(text, namePaint); + shapedTextWidth = shapedText.Points[^1].X + namePaint.TextSize / 2f; + + if (shapedTextWidth > icon.Width - icon.Margin * 2) + { + namePaint.TextSize = _NAME_TEXT_SIZE -= 1; + } + else + { + break; + } + } + + c.DrawShapedText(shaper, text, (icon.Width - shapedTextWidth) / 2f, y, namePaint); + } + else + { + // resize if too long + while (namePaint.MeasureText(text) > icon.Width - icon.Margin * 2) + { + namePaint.TextSize = _NAME_TEXT_SIZE -= 1; + } + + c.DrawText(text, x, y, namePaint); + } } public static void DrawDescription(SKCanvas c, IBase icon) @@ -200,7 +232,7 @@ namespace FModel.Creator.Texts _BOTTOM_TEXT_SIZE = 15; string text = icon.Description; ETextSide side = ETextSide.Center; - switch ((EIconDesign)Properties.Settings.Default.AssetsIconDesign) + switch ((EIconDesign)Settings.Default.AssetsIconDesign) { case EIconDesign.Mini: { @@ -243,7 +275,23 @@ namespace FModel.Creator.Texts TextAlign = side == ETextSide.Left ? SKTextAlign.Left : SKTextAlign.Right, }; - c.DrawText(text, side == ETextSide.Left ? icon.Margin * 2.5f : icon.Size - (icon.Margin * 2.5f), icon.Size - (icon.Margin * 2.5f), shortDescriptionPaint); + if (side == ETextSide.Left) + { + if ((ELanguage)Settings.Default.AssetsLanguage == ELanguage.Arabic) + { + shortDescriptionPaint.TextSize -= 4f; + SKShaper shaper = new SKShaper(shortDescriptionPaint.Typeface); + c.DrawShapedText(shaper, text, icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f - shortDescriptionPaint.TextSize * .5f /* ¯\_(ツ)_/¯ */, shortDescriptionPaint); + } + else + { + c.DrawText(text, icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f, shortDescriptionPaint); + } + } + else + { + c.DrawText(text, icon.Size - icon.Margin * 2.5f, icon.Size - icon.Margin * 2.5f, shortDescriptionPaint); + } } } } diff --git a/FModel/FModel.csproj b/FModel/FModel.csproj index 20923824..34dec3f6 100644 --- a/FModel/FModel.csproj +++ b/FModel/FModel.csproj @@ -144,6 +144,7 @@ + diff --git a/FModel/Grabber/Paks/PaksGrabber.cs b/FModel/Grabber/Paks/PaksGrabber.cs index 8d1dff8d..01616e3e 100644 --- a/FModel/Grabber/Paks/PaksGrabber.cs +++ b/FModel/Grabber/Paks/PaksGrabber.cs @@ -18,7 +18,7 @@ namespace FModel.Grabber.Paks { static class PaksGrabber { - private static readonly Regex _pakFileRegex = new Regex(@"^FortniteGame/Content/Paks/.+\.pak$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase); + private static readonly Regex _pakFileRegex = new Regex(@"^FortniteGame/Content/Paks/.+\.pak$", RegexOptions.Compiled | RegexOptions.Singleline | RegexOptions.IgnoreCase | RegexOptions.CultureInvariant); public static async Task PopulateMenu() { @@ -41,9 +41,10 @@ namespace FModel.Grabber.Paks } // Add Pak Files - if (Properties.Settings.Default.PakPath.EndsWith(".manifest") && await ManifestGrabber.TryGetLatestManifestInfo().ConfigureAwait(false) is ManifestInfo manifestInfo) + if (Properties.Settings.Default.PakPath.EndsWith(".manifest")) { - var manifestData = await manifestInfo.DownloadManifestDataAsync(); + ManifestInfo manifestInfo = await ManifestGrabber.TryGetLatestManifestInfo().ConfigureAwait(false); + byte[] manifestData = await manifestInfo.DownloadManifestDataAsync().ConfigureAwait(false); Manifest manifest = new Manifest(manifestData, new ManifestOptions { ChunkBaseUri = new Uri("http://download.epicgames.com/Builds/Fortnite/CloudDir/ChunksV3/", UriKind.Absolute), diff --git a/FModel/ViewModels/ImageBox/ImageBoxViewModel.cs b/FModel/ViewModels/ImageBox/ImageBoxViewModel.cs index 840d1b0e..ed62301b 100644 --- a/FModel/ViewModels/ImageBox/ImageBoxViewModel.cs +++ b/FModel/ViewModels/ImageBox/ImageBoxViewModel.cs @@ -65,8 +65,8 @@ namespace FModel.ViewModels.ImageBox { Title = vm.Name, WindowStartupLocation = WindowStartupLocation.CenterScreen, - Width = vm.Image.Width, - Height = vm.Image.Height + Width = vm.Image.Width + 16, + Height = vm.Image.Height + 39 }; win.SetValue(TextOptions.TextFormattingModeProperty, TextFormattingMode.Display); if (vm.Image.Height > 1000)