From 84be12b826645ff0d98536fe73e289a6c0a491b5 Mon Sep 17 00:00:00 2001 From: Robin Krom Date: Tue, 25 Oct 2022 21:48:40 +0200 Subject: [PATCH] Added gz support, and improved upon some additional code. --- src/Directory.Build.targets | 18 ++ .../Controls/Emoji/ChunkHelper.cs | 69 ------ .../Controls/Emoji/EmojiData.cs | 212 ++++++++++-------- src/Greenshot.Editor/Controls/Emoji/Emojis.cs | 7 - .../Drawing/Emoji/EmojiContainer.cs | 5 +- .../Drawing/Emoji/EmojiRenderer.cs | 11 +- src/Greenshot.Editor/Greenshot.Editor.csproj | 9 +- 7 files changed, 148 insertions(+), 183 deletions(-) delete mode 100644 src/Greenshot.Editor/Controls/Emoji/ChunkHelper.cs diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index f2ce406a1..68bf0563f 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -10,4 +10,22 @@ + + + + + + + + + + + + \ No newline at end of file diff --git a/src/Greenshot.Editor/Controls/Emoji/ChunkHelper.cs b/src/Greenshot.Editor/Controls/Emoji/ChunkHelper.cs deleted file mode 100644 index d4aa24002..000000000 --- a/src/Greenshot.Editor/Controls/Emoji/ChunkHelper.cs +++ /dev/null @@ -1,69 +0,0 @@ -ο»Ώ/* - * Greenshot - a free and open source screenshot tool - * Copyright (C) 2007-2021 Thomas Braun, Jens Klingen, Robin Krom - * - * For more information see: https://getgreenshot.org/ - * The Greenshot project is hosted on GitHub https://github.com/greenshot/greenshot - * - * This program is free software: you can redistribute it and/or modify - * it under the terms of the GNU General Public License as published by - * the Free Software Foundation, either version 1 of the License, or - * (at your option) any later version. - * - * This program is distributed in the hope that it will be useful, - * but WITHOUT ANY WARRANTY; without even the implied warranty of - * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the - * GNU General Public License for more details. - * - * You should have received a copy of the GNU General Public License - * along with this program. If not, see . - */ - -using System.Collections; -using System.Collections.Generic; -using System.Linq; - -namespace Greenshot.Editor.Controls.Emoji; - -/// -/// This seems to enumerate multiple IEnumerables. -/// TODO: Replace this with LINQ -/// -/// -internal sealed class ChunkHelper : IEnumerable> -{ - private readonly IEnumerable _elements; - private readonly int _size; - private bool _hasMore; - - public ChunkHelper(IEnumerable elements, int size) - { - _elements = elements; - _size = size; - } - - public IEnumerator> GetEnumerator() - { - using var enumerator = _elements.GetEnumerator(); - _hasMore = enumerator.MoveNext(); - while (_hasMore) - { - yield return GetNextBatch(enumerator).ToList(); - } - } - - private IEnumerable GetNextBatch(IEnumerator enumerator) - { - for (int i = 0; i < _size; ++i) - { - yield return enumerator.Current; - _hasMore = enumerator.MoveNext(); - if (!_hasMore) - { - yield break; - } - } - } - - IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); -} \ No newline at end of file diff --git a/src/Greenshot.Editor/Controls/Emoji/EmojiData.cs b/src/Greenshot.Editor/Controls/Emoji/EmojiData.cs index b053ab3f4..b01951026 100644 --- a/src/Greenshot.Editor/Controls/Emoji/EmojiData.cs +++ b/src/Greenshot.Editor/Controls/Emoji/EmojiData.cs @@ -11,15 +11,18 @@ // using System; -using System.Collections.Generic; using System.IO; -using System.Linq; -using System.Reflection; -using System.Text; using System.Text.RegularExpressions; using System.Xml; using System.Xml.Serialization; +#if DEBUG +using System.Collections.Generic; +using System.IO.Compression; +using System.Linq; +using System.Reflection; +using System.Text; using SixLabors.Fonts.Unicode; +#endif namespace Greenshot.Editor.Controls.Emoji { @@ -28,27 +31,17 @@ namespace Greenshot.Editor.Controls.Emoji /// public static class EmojiData { - private const string FilePath = "emojis.xml"; + private const string EmojisXmlFilePath = "emojis.xml"; + private const string EmojisTestFile = @"emoji-test.txt.gz"; +#if DEBUG + private const string Adult = "(πŸ‘¨|πŸ‘©)(🏻|🏼|🏽|🏾|🏿)?"; + private const string Child = "(πŸ‘¦|πŸ‘§|πŸ‘Ά)(🏻|🏼|🏽|🏾|🏿)?"; + private static readonly Regex MatchFamily = new($"{Adult}(\u200d{Adult})*(\u200d{Child})+"); - public static Emojis Data { get; private set; } = new Emojis(); - - public static void Load() - { - var x = new XmlSerializer(typeof(Emojis)); - - if (File.Exists(FilePath)) - { - Data = (Emojis)x.Deserialize(new XmlTextReader(FilePath)); - } - else - { - // To be removed - ParseEmojiList(); - x.Serialize(new XmlTextWriter(FilePath, Encoding.UTF8), Data); - } - } - - private static readonly List SkinToneComponents = new List + private static readonly Regex MatchGroup = new(@"^# group: (.*)", RegexOptions.Compiled); + private static readonly Regex MatchSubgroup = new(@"^# subgroup: (.*)", RegexOptions.Compiled); + private static readonly Regex MatchSequence = new(@"^([0-9a-fA-F ]+[0-9a-fA-F]).*; *([-a-z]*) *# [^ ]* (E[0-9.]* )?(.*)", RegexOptions.Compiled); + private static readonly List SkinToneComponents = new() { "🏻", // light skin tone "🏼", // medium-light skin tone @@ -57,29 +50,51 @@ namespace Greenshot.Editor.Controls.Emoji "🏿", // dark skin tone }; - private static readonly List HairStyleComponents = new List + private static readonly List HairStyleComponents = new() { "🦰", // red hair "🦱", // curly hair "🦳", // white hair "🦲", // bald }; + + private static readonly Regex MatchSkinTone = new($"({string.Join("|", SkinToneComponents)})", RegexOptions.Compiled); + private static readonly Regex MatchHairStyle = new($"({string.Join("|", HairStyleComponents)})", RegexOptions.Compiled); - private static string ToColonSyntax(string s) - => Regex.Replace(s.Trim().ToLowerInvariant(), "[^a-z0-9]+", "-"); +#endif + + public static Emojis Data { get; private set; } = new(); + + public static void Load() + { + var x = new XmlSerializer(typeof(Emojis)); + + if (File.Exists(EmojisXmlFilePath)) + { + Data = (Emojis)x.Deserialize(new XmlTextReader(EmojisXmlFilePath)); + } +#if RELEASE + else + { + throw new NotSupportedException($"Missing {EmojisXmlFilePath}, can't load "); + } +#elif DEBUG + else + { + // To be removed + ParseEmojiList(); + x.Serialize(new XmlTextWriter(EmojisXmlFilePath, Encoding.UTF8), Data); + } +#endif + } + + +#if DEBUG + private static string ToColonSyntax(string s) => Regex.Replace(s.Trim().ToLowerInvariant(), "[^a-z0-9]+", "-"); private static void ParseEmojiList() { var lookupByName = new Dictionary(); - var matchGroup = new Regex(@"^# group: (.*)"); - var matchSubgroup = new Regex(@"^# subgroup: (.*)"); - var matchSequence = new Regex(@"^([0-9a-fA-F ]+[0-9a-fA-F]).*; *([-a-z]*) *# [^ ]* (E[0-9.]* )?(.*)"); - var matchSkinTone = new Regex($"({string.Join("|", SkinToneComponents)})"); - var matchHairStyle = new Regex($"({string.Join("|", HairStyleComponents)})"); - - var adult = "(πŸ‘¨|πŸ‘©)(🏻|🏼|🏽|🏾|🏿)?"; - var child = "(πŸ‘¦|πŸ‘§|πŸ‘Ά)(🏻|🏼|🏽|🏾|🏿)?"; - var matchFamily = new Regex($"{adult}(\u200d{adult})*(\u200d{child})+"); var qualifiedLut = new Dictionary(); var allText = new List(); @@ -89,7 +104,7 @@ namespace Greenshot.Editor.Controls.Emoji foreach (var line in EmojiDescriptionLines()) { - var m = matchGroup.Match(line); + var m = MatchGroup.Match(line); if (m.Success) { currentGroup = new Emojis.Group { Name = m.Groups[1].ToString() }; @@ -97,85 +112,82 @@ namespace Greenshot.Editor.Controls.Emoji continue; } - m = matchSubgroup.Match(line); + m = MatchSubgroup.Match(line); if (m.Success) { currentSubgroup = new Emojis.Group { Name = m.Groups[1].ToString() }; - currentGroup.SubGroups.Add(currentSubgroup); + currentGroup?.SubGroups?.Add(currentSubgroup); continue; } - m = matchSequence.Match(line); - if (m.Success) + m = MatchSequence.Match(line); + if (!m.Success) { - string sequence = m.Groups[1].ToString(); - string name = m.Groups[4].ToString(); + continue; + } + string sequence = m.Groups[1].ToString(); + string name = m.Groups[4].ToString(); - string text = string.Join("", from n in sequence.Split(' ') - select char.ConvertFromUtf32(Convert.ToInt32(n, 16))); - bool has_modifier = false; + string text = string.Join("", sequence.Split(' ').Select(c => char.ConvertFromUtf32(Convert.ToInt32(c, 16)))); + bool hasModifier = false; - if (matchFamily.Match(text).Success) - { - // If this is a family emoji, no need to add it to our big matching - // regex, since the match_family regex is already included. - } - else - { - // Construct a regex to replace e.g. "🏻" with "(🏻|🏼|🏽|🏾|🏿)" in a big - // regex so that we can match all variations of this Emoji even if they are - // not in the standard. - bool hasNonfirstModifier = false; - var regexText = matchSkinTone.Replace( - matchHairStyle.Replace(text, (x) => - { - has_modifier = true; - hasNonfirstModifier |= x.Value != HairStyleComponents[0]; - return matchHairStyle.ToString(); - }), (x) => - { - has_modifier = true; - hasNonfirstModifier |= x.Value != SkinToneComponents[0]; - return matchSkinTone.ToString(); - }); - - if (!hasNonfirstModifier) + // If this is a family emoji, no need to add it to our big matching + // regex, since the match_family regex is already included. + if (!MatchFamily.Match(text).Success) + { + // Construct a regex to replace e.g. "🏻" with "(🏻|🏼|🏽|🏾|🏿)" in a big + // regex so that we can match all variations of this Emoji even if they are + // not in the standard. + bool hasNonfirstModifier = false; + var regexText = MatchSkinTone.Replace( + MatchHairStyle.Replace(text, (x) => { - allText.Add(has_modifier ? regexText : text); - } - } + hasModifier = true; + hasNonfirstModifier |= x.Value != HairStyleComponents[0]; + return MatchHairStyle.ToString(); + }), (x) => + { + hasModifier = true; + hasNonfirstModifier |= x.Value != SkinToneComponents[0]; + return MatchSkinTone.ToString(); + }); - // If there is already a differently-qualified version of this character, skip it. - // FIXME: this only works well if fully-qualified appears first in the list. - var unqualified = text.Replace("\ufe0f", ""); - if (qualifiedLut.ContainsKey(unqualified)) + if (!hasNonfirstModifier) { - continue; + allText.Add(hasModifier ? regexText : text); } + } - // Fix simple fully-qualified emojis - if (CodePoint.GetCodePointCount(text.AsSpan()) == 2) - { - text = text.TrimEnd('\ufe0f'); - } + // If there is already a differently-qualified version of this character, skip it. + // FIXME: this only works well if fully-qualified appears first in the list. + var unqualified = text.Replace("\ufe0f", ""); + if (qualifiedLut.ContainsKey(unqualified)) + { + continue; + } - qualifiedLut[unqualified] = text; + // Fix simple fully-qualified emojis + if (CodePoint.GetCodePointCount(text.AsSpan()) == 2) + { + text = text.TrimEnd('\ufe0f'); + } - var emoji = new Emojis.Emoji { Name = name, Text = text, }; + qualifiedLut[unqualified] = text; - lookupByName[ToColonSyntax(name)] = emoji; + var emoji = new Emojis.Emoji { Name = name, Text = text}; - // Get the left part of the name and check whether we’re a variation of an existing - // emoji. If so, append to that emoji. Otherwise, add to current subgroup. - // FIXME: does not work properly because variations can appear before the generic emoji - if (name.Contains(":") && lookupByName.TryGetValue(ToColonSyntax(name.Split(':')[0]), out var parent_emoji)) - { - parent_emoji.Variations.Add(emoji); - } - else - { - currentSubgroup.Emojis.Add(emoji); - } + lookupByName[ToColonSyntax(name)] = emoji; + + // Get the left part of the name and check whether we’re a variation of an existing + // emoji. If so, append to that emoji. Otherwise, add to current subgroup. + // FIXME: does not work properly because variations can appear before the generic emoji + if (name.Contains(":") && lookupByName.TryGetValue(ToColonSyntax(name.Split(':')[0]), out var parentEmoji)) + { + parentEmoji.Variations.Add(emoji); + } + else + { + currentSubgroup?.Emojis?.Add(emoji); } } @@ -186,14 +198,16 @@ namespace Greenshot.Editor.Controls.Emoji private static IEnumerable EmojiDescriptionLines() { var exeDirectory = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location); - var emojiTestFile = Path.Combine(exeDirectory, @"emoji-test.txt"); + var emojiTestFile = Path.Combine(exeDirectory, EmojisTestFile); if (!File.Exists(emojiTestFile)) { throw new FileNotFoundException($"Can't find {emojiTestFile}, bad installation?"); } using var fileStream = new FileStream(emojiTestFile, FileMode.Open, FileAccess.Read); + using var gzStream = new GZipStream(fileStream, CompressionMode.Decompress); using var streamReader = new StreamReader(fileStream); return streamReader.ReadToEnd().Split('\r', '\n'); } +#endif } } \ No newline at end of file diff --git a/src/Greenshot.Editor/Controls/Emoji/Emojis.cs b/src/Greenshot.Editor/Controls/Emoji/Emojis.cs index 8ddf41e34..ff1c74971 100644 --- a/src/Greenshot.Editor/Controls/Emoji/Emojis.cs +++ b/src/Greenshot.Editor/Controls/Emoji/Emojis.cs @@ -42,14 +42,7 @@ public class Emojis [XmlElement(ElementName = "Emoji")] public List Emojis { get; set; } = new(); - public IEnumerable> EmojiChunkList => new ChunkHelper(EmojiList, 8); - public string Icon => SubGroups.FirstOrDefault()?.Emojis.FirstOrDefault()?.Text; - - public IEnumerable EmojiList - => from s in SubGroups - from e in s.Emojis - select e; } public class Emoji diff --git a/src/Greenshot.Editor/Drawing/Emoji/EmojiContainer.cs b/src/Greenshot.Editor/Drawing/Emoji/EmojiContainer.cs index 0e9eb0497..ecd6202b3 100644 --- a/src/Greenshot.Editor/Drawing/Emoji/EmojiContainer.cs +++ b/src/Greenshot.Editor/Drawing/Emoji/EmojiContainer.cs @@ -153,7 +153,10 @@ namespace Greenshot.Editor.Drawing.Emoji protected override Image ComputeBitmap() { var iconSize = Math.Min(Bounds.Width, Bounds.Height); - if (iconSize <= 0) return null; + if (iconSize <= 0) + { + return null; + } var image = EmojiRenderer.GetBitmap(Emoji, iconSize); if (RotationAngle != 0) diff --git a/src/Greenshot.Editor/Drawing/Emoji/EmojiRenderer.cs b/src/Greenshot.Editor/Drawing/Emoji/EmojiRenderer.cs index c79442510..fcb0f422a 100644 --- a/src/Greenshot.Editor/Drawing/Emoji/EmojiRenderer.cs +++ b/src/Greenshot.Editor/Drawing/Emoji/EmojiRenderer.cs @@ -21,6 +21,7 @@ using System; using System.IO; +using System.IO.Compression; using System.Reflection; using SixLabors.Fonts; using SixLabors.ImageSharp; @@ -35,9 +36,9 @@ namespace Greenshot.Editor.Drawing.Emoji /// internal static class EmojiRenderer { - private static readonly FontCollection _fontCollection = new FontCollection(); + private static readonly FontCollection TwemojiFontCollection = new(); - private static readonly Lazy _twemojiFontFamily = new Lazy(() => + private static readonly Lazy TwemojiFontFamily = new(() => { var exeDirectory = Path.GetDirectoryName(Assembly.GetCallingAssembly().Location); var twemojiFontFile = Path.Combine(exeDirectory, "TwemojiMozilla.ttf"); @@ -47,8 +48,8 @@ namespace Greenshot.Editor.Drawing.Emoji } using var fileStream = new FileStream(twemojiFontFile, FileMode.Open, FileAccess.Read); - _fontCollection.Add(fileStream); - _fontCollection.TryGet("Twemoji Mozilla", out var fontFamily); + TwemojiFontCollection.Add(fileStream); + TwemojiFontCollection.TryGet("Twemoji Mozilla", out var fontFamily); return fontFamily; }); @@ -62,7 +63,7 @@ namespace Greenshot.Editor.Drawing.Emoji private static void RenderEmoji(string emoji, int iconSize, Image image) { - var fontFamily = _twemojiFontFamily.Value; + var fontFamily = TwemojiFontFamily.Value; var font = fontFamily.CreateFont(iconSize, FontStyle.Regular); var verticalOffset = font.Size * 0.045f; var textOptions = new TextOptions(font) diff --git a/src/Greenshot.Editor/Greenshot.Editor.csproj b/src/Greenshot.Editor/Greenshot.Editor.csproj index 6a3069383..affaf81c7 100644 --- a/src/Greenshot.Editor/Greenshot.Editor.csproj +++ b/src/Greenshot.Editor/Greenshot.Editor.csproj @@ -15,6 +15,11 @@ + + + + Component @@ -89,9 +94,9 @@ - + PreserveNewest - emoji-test.txt + emoji-test.txt.gz PreserveNewest