Added gz support, and improved upon some additional code.

This commit is contained in:
Robin Krom 2022-10-25 21:48:40 +02:00
commit 84be12b826
No known key found for this signature in database
GPG key ID: BCC01364F1371490
7 changed files with 148 additions and 183 deletions

View file

@ -10,4 +10,22 @@
<Message Text="Processing: $(ProjectDir)$(ProjectName).Credentials.template" Importance="high"/>
<TemplateFile Template="$(ProjectDir)$(ProjectName).Credentials.template" OutputFilename="$(ProjectDir)$(ProjectName).Credentials.cs" Tokens="@(Tokens)" />
</Target>
<UsingTask TaskName="GZipTask" TaskFactory="CodeTaskFactory" AssemblyFile="$(MSBuildToolsPath)\Microsoft.Build.Tasks.v4.0.dll">
<ParameterGroup>
<InputFile ParameterType="System.String" Required="true" />
<OutputFile ParameterType="System.String" Required="true" />
</ParameterGroup>
<Task>
<Reference Include="System.IO.Compression" />
<Using Namespace="System.IO.Compression" />
<Code Type="Fragment" Language="cs"><![CDATA[
Directory.CreateDirectory(Path.GetDirectoryName(OutputFile));
var data = File.ReadAllBytes(InputFile);
using (var s = new FileStream(OutputFile, FileMode.Create))
using (var gs = new GZipStream(s, CompressionMode.Compress, false))
gs.Write(data, 0, data.Length);
]]></Code>
</Task>
</UsingTask>
</Project>

View file

@ -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 <https://www.gnu.org/licenses/>.
*/
using System.Collections;
using System.Collections.Generic;
using System.Linq;
namespace Greenshot.Editor.Controls.Emoji;
/// <summary>
/// This seems to enumerate multiple IEnumerables.
/// TODO: Replace this with LINQ
/// </summary>
/// <typeparam name="T"></typeparam>
internal sealed class ChunkHelper<T> : IEnumerable<IEnumerable<T>>
{
private readonly IEnumerable<T> _elements;
private readonly int _size;
private bool _hasMore;
public ChunkHelper(IEnumerable<T> elements, int size)
{
_elements = elements;
_size = size;
}
public IEnumerator<IEnumerable<T>> GetEnumerator()
{
using var enumerator = _elements.GetEnumerator();
_hasMore = enumerator.MoveNext();
while (_hasMore)
{
yield return GetNextBatch(enumerator).ToList();
}
}
private IEnumerable<T> GetNextBatch(IEnumerator<T> enumerator)
{
for (int i = 0; i < _size; ++i)
{
yield return enumerator.Current;
_hasMore = enumerator.MoveNext();
if (!_hasMore)
{
yield break;
}
}
}
IEnumerator IEnumerable.GetEnumerator() => GetEnumerator();
}

View file

@ -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
/// </summary>
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<string> SkinToneComponents = new List<string>
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<string> 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<string> HairStyleComponents = new List<string>
private static readonly List<string> 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<string, Emojis.Emoji>();
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<string, string>();
var allText = new List<string>();
@ -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 were 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 were 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<string> 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
}
}

View file

@ -42,14 +42,7 @@ public class Emojis
[XmlElement(ElementName = "Emoji")]
public List<Emoji> Emojis { get; set; } = new();
public IEnumerable<IEnumerable<Emoji>> EmojiChunkList => new ChunkHelper<Emoji>(EmojiList, 8);
public string Icon => SubGroups.FirstOrDefault()?.Emojis.FirstOrDefault()?.Text;
public IEnumerable<Emoji> EmojiList
=> from s in SubGroups
from e in s.Emojis
select e;
}
public class Emoji

View file

@ -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)

View file

@ -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
/// </summary>
internal static class EmojiRenderer
{
private static readonly FontCollection _fontCollection = new FontCollection();
private static readonly FontCollection TwemojiFontCollection = new();
private static readonly Lazy<FontFamily> _twemojiFontFamily = new Lazy<FontFamily>(() =>
private static readonly Lazy<FontFamily> 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)

View file

@ -15,6 +15,11 @@
<ProjectReference Include="..\Greenshot.Base\Greenshot.Base.csproj" />
</ItemGroup>
<Target Name="CompressEmojiTxtResource" BeforeTargets="BeforeBuild" Condition="!Exists('Controls\Emoji\emoji-test.txt.gz')">
<GZipTask InputFile="Controls\Emoji\emoji-test.txt"
OutputFile="Controls\Emoji\emoji-test.txt.gz" />
</Target>
<ItemGroup>
<Compile Update="Controls\BindableToolStripButton.cs">
<SubType>Component</SubType>
@ -89,9 +94,9 @@
</ItemGroup>
<ItemGroup>
<ContentWithTargetPath Include="Controls\Emoji\emoji-test.txt">
<ContentWithTargetPath Include="Controls\Emoji\emoji-test.txt.gz">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
<TargetPath>emoji-test.txt</TargetPath>
<TargetPath>emoji-test.txt.gz</TargetPath>
</ContentWithTargetPath>
<ContentWithTargetPath Include="Drawing\Emoji\TwemojiMozilla.ttf">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>