Merge pull request #2 from wieslawsoltes/Avalonia
This commit is contained in:
Коммит
711c8e86f9
|
@ -13,6 +13,8 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{FA0D294D-294
|
|||
EndProject
|
||||
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "unicode", "unicode", "{6CBF405F-FC1E-4595-93B2-94D1D0ECC109}"
|
||||
EndProject
|
||||
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TypefaceUtil.Avalonia", "src\TypefaceUtil.Avalonia\TypefaceUtil.Avalonia.csproj", "{DDCA69DA-405C-4A3B-AF4F-A969FE107D23}"
|
||||
EndProject
|
||||
Global
|
||||
GlobalSection(SolutionConfigurationPlatforms) = preSolution
|
||||
Debug|Any CPU = Debug|Any CPU
|
||||
|
@ -31,6 +33,10 @@ Global
|
|||
{FB936A0D-7B38-4B6D-9DC4-31D819BEDD82}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{FB936A0D-7B38-4B6D-9DC4-31D819BEDD82}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{FB936A0D-7B38-4B6D-9DC4-31D819BEDD82}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
{DDCA69DA-405C-4A3B-AF4F-A969FE107D23}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
|
||||
{DDCA69DA-405C-4A3B-AF4F-A969FE107D23}.Debug|Any CPU.Build.0 = Debug|Any CPU
|
||||
{DDCA69DA-405C-4A3B-AF4F-A969FE107D23}.Release|Any CPU.ActiveCfg = Release|Any CPU
|
||||
{DDCA69DA-405C-4A3B-AF4F-A969FE107D23}.Release|Any CPU.Build.0 = Release|Any CPU
|
||||
EndGlobalSection
|
||||
GlobalSection(SolutionProperties) = preSolution
|
||||
HideSolutionNode = FALSE
|
||||
|
@ -39,6 +45,7 @@ Global
|
|||
{35DC5579-5E51-458F-BFFC-65133AE83F50} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
|
||||
{A97DD9DD-60D0-4D3C-847E-BAF7322BDBAE} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
|
||||
{FB936A0D-7B38-4B6D-9DC4-31D819BEDD82} = {6CBF405F-FC1E-4595-93B2-94D1D0ECC109}
|
||||
{DDCA69DA-405C-4A3B-AF4F-A969FE107D23} = {FA0D294D-2942-4349-AC60-BB837AA95DAB}
|
||||
EndGlobalSection
|
||||
GlobalSection(ExtensibilityGlobals) = postSolution
|
||||
SolutionGuid = {3D7D93DB-65A8-4680-ADDD-EE67EEC2C975}
|
||||
|
|
|
@ -0,0 +1,12 @@
|
|||
<Application xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:local="using:TypefaceUtil.Avalonia"
|
||||
x:Class="TypefaceUtil.Avalonia.App">
|
||||
<Application.DataTemplates>
|
||||
<local:ViewLocator/>
|
||||
</Application.DataTemplates>
|
||||
|
||||
<Application.Styles>
|
||||
<FluentTheme Mode="Light"/>
|
||||
</Application.Styles>
|
||||
</Application>
|
|
@ -0,0 +1,26 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.Markup.Xaml;
|
||||
using TypefaceUtil.Avalonia.ViewModels;
|
||||
using TypefaceUtil.Avalonia.Views;
|
||||
|
||||
namespace TypefaceUtil.Avalonia
|
||||
{
|
||||
public class App : Application
|
||||
{
|
||||
public override void Initialize()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
|
||||
public override void OnFrameworkInitializationCompleted()
|
||||
{
|
||||
if (ApplicationLifetime is IClassicDesktopStyleApplicationLifetime desktop)
|
||||
{
|
||||
desktop.MainWindow = new MainWindow { DataContext = new MainWindowViewModel(), };
|
||||
}
|
||||
|
||||
base.OnFrameworkInitializationCompleted();
|
||||
}
|
||||
}
|
||||
}
|
Двоичный файл не отображается.
После Ширина: | Высота: | Размер: 172 KiB |
|
@ -0,0 +1,23 @@
|
|||
using System;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using Avalonia.ReactiveUI;
|
||||
|
||||
namespace TypefaceUtil.Avalonia
|
||||
{
|
||||
class Program
|
||||
{
|
||||
// Initialization code. Don't use any Avalonia, third-party APIs or any
|
||||
// SynchronizationContext-reliant code before AppMain is called: things aren't initialized
|
||||
// yet and stuff might break.
|
||||
public static void Main(string[] args) => BuildAvaloniaApp()
|
||||
.StartWithClassicDesktopLifetime(args);
|
||||
|
||||
// Avalonia configuration, don't remove; also used by visual designer.
|
||||
public static AppBuilder BuildAvaloniaApp()
|
||||
=> AppBuilder.Configure<App>()
|
||||
.UsePlatformDetect()
|
||||
.LogToTrace()
|
||||
.UseReactiveUI();
|
||||
}
|
||||
}
|
|
@ -0,0 +1,13 @@
|
|||
<UserControl xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
mc:Ignorable="d" d:DesignWidth="600" d:DesignHeight="600"
|
||||
x:Class="TypefaceUtil.Avalonia.SandBoxView">
|
||||
<UserControl.Resources>
|
||||
|
||||
</UserControl.Resources>
|
||||
<Viewbox>
|
||||
|
||||
</Viewbox>
|
||||
</UserControl>
|
|
@ -0,0 +1,20 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TypefaceUtil.Avalonia
|
||||
{
|
||||
public class SandBoxView : UserControl
|
||||
{
|
||||
public SandBoxView()
|
||||
{
|
||||
InitializeComponent();
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
|
@ -0,0 +1,20 @@
|
|||
<Project Sdk="Microsoft.NET.Sdk">
|
||||
<PropertyGroup>
|
||||
<OutputType>WinExe</OutputType>
|
||||
<TargetFramework>net5.0</TargetFramework>
|
||||
<Nullable>enable</Nullable>
|
||||
</PropertyGroup>
|
||||
<ItemGroup>
|
||||
<AvaloniaResource Include="Assets\**" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<PackageReference Include="Avalonia" Version="0.10.6" />
|
||||
<PackageReference Include="Avalonia.Desktop" Version="0.10.6" />
|
||||
<PackageReference Include="Avalonia.Diagnostics" Version="0.10.6" />
|
||||
<PackageReference Include="Avalonia.ReactiveUI" Version="0.10.6" />
|
||||
<PackageReference Include="Avalonia.Controls.Skia" Version="0.10.6.2" />
|
||||
</ItemGroup>
|
||||
<ItemGroup>
|
||||
<ProjectReference Include="..\TypefaceUtil.OpenType\TypefaceUtil.OpenType.csproj" />
|
||||
</ItemGroup>
|
||||
</Project>
|
|
@ -0,0 +1,32 @@
|
|||
using System;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.Templates;
|
||||
using TypefaceUtil.Avalonia.ViewModels;
|
||||
|
||||
namespace TypefaceUtil.Avalonia
|
||||
{
|
||||
public class ViewLocator : IDataTemplate
|
||||
{
|
||||
public bool SupportsRecycling => false;
|
||||
|
||||
public IControl Build(object data)
|
||||
{
|
||||
var name = data.GetType().FullName!.Replace("ViewModel", "View");
|
||||
var type = Type.GetType(name);
|
||||
|
||||
if (type != null)
|
||||
{
|
||||
return (Control)Activator.CreateInstance(type)!;
|
||||
}
|
||||
else
|
||||
{
|
||||
return new TextBlock { Text = "Not Found: " + name };
|
||||
}
|
||||
}
|
||||
|
||||
public bool Match(object data)
|
||||
{
|
||||
return data is ViewModelBase;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,108 @@
|
|||
using System.Globalization;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Avalonia;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
|
||||
namespace TypefaceUtil.Avalonia.ViewModels
|
||||
{
|
||||
public class GlyphViewModel : ViewModelBase
|
||||
{
|
||||
private int _charCode;
|
||||
private ushort _glyphIndex;
|
||||
private SKPath? _path;
|
||||
private SKPaint? _paint;
|
||||
private string? _brush;
|
||||
private string? _svgPathData;
|
||||
|
||||
public int CharCode
|
||||
{
|
||||
get => _charCode;
|
||||
set => this.RaiseAndSetIfChanged(ref _charCode, value);
|
||||
}
|
||||
|
||||
public ushort GlyphIndex
|
||||
{
|
||||
get => _glyphIndex;
|
||||
set => this.RaiseAndSetIfChanged(ref _glyphIndex, value);
|
||||
}
|
||||
|
||||
public SKPath? Path
|
||||
{
|
||||
get => _path;
|
||||
set => this.RaiseAndSetIfChanged(ref _path, value);
|
||||
}
|
||||
|
||||
public SKPaint? Paint
|
||||
{
|
||||
get => _paint;
|
||||
set => this.RaiseAndSetIfChanged(ref _paint, value);
|
||||
}
|
||||
|
||||
public string? Brush
|
||||
{
|
||||
get => _brush;
|
||||
set => this.RaiseAndSetIfChanged(ref _brush, value);
|
||||
}
|
||||
|
||||
public string? SvgPathData
|
||||
{
|
||||
get => _svgPathData;
|
||||
set => this.RaiseAndSetIfChanged(ref _svgPathData, value);
|
||||
}
|
||||
|
||||
public ICommand CopyAsCommand { get; }
|
||||
|
||||
public GlyphViewModel()
|
||||
{
|
||||
CopyAsCommand = ReactiveCommand.CreateFromTask<string>(async (format) =>
|
||||
{
|
||||
await CopyAs(format, _brush ?? "#000000");
|
||||
});
|
||||
}
|
||||
|
||||
public string? Export(string format, string brush, bool addXamlKey)
|
||||
{
|
||||
if (Path is null || Path.Bounds.IsEmpty)
|
||||
{
|
||||
return default;
|
||||
}
|
||||
|
||||
var indent = " ";
|
||||
var xamlKey = addXamlKey ? $" x:Key=\"{CharCode}\"" : "";
|
||||
var text = format switch
|
||||
{
|
||||
"XamlStreamGeometry" => $"<StreamGeometry{xamlKey}>{SvgPathData}</StreamGeometry>",
|
||||
"XamlPathIcon" => $"<PathIcon{xamlKey} Width=\"{Path?.Bounds.Width.ToString(CultureInfo.InvariantCulture)}\" Height=\"{Path?.Bounds.Height.ToString(CultureInfo.InvariantCulture)}\" Foreground=\"{brush}\" Data=\"{SvgPathData}\"/>",
|
||||
"XamlPath" => $"<Path{xamlKey} Width=\"{Path?.Bounds.Width.ToString(CultureInfo.InvariantCulture)}\" Height=\"{Path?.Bounds.Height.ToString(CultureInfo.InvariantCulture)}\" Fill=\"{brush}\" Data=\"{SvgPathData}\"/>",
|
||||
"XamlCanvas" => $"<Canvas{xamlKey} Width=\"{Path?.Bounds.Width.ToString(CultureInfo.InvariantCulture)}\" Height=\"{Path?.Bounds.Height.ToString(CultureInfo.InvariantCulture)}\">\r\n{indent}<Path Fill=\"{brush}\" Data=\"{SvgPathData}\"/>\r\n</Canvas>",
|
||||
"XamlGeometryDrawing" => $"<GeometryDrawing{xamlKey} Brush=\"{brush}\" Geometry=\"{SvgPathData}\"/>",
|
||||
"XamlDrawingGroup" => $"<DrawingGroup{xamlKey}>\r\n{indent}<GeometryDrawing Brush=\"{brush}\" Geometry=\"{SvgPathData}\"/>\r\n</DrawingGroup>",
|
||||
"XamlDrawingImage" => $"<DrawingImage{xamlKey}>\r\n{indent}<GeometryDrawing Brush=\"{brush}\" Geometry=\"{SvgPathData}\"/>\r\n</DrawingImage>",
|
||||
"XamlImage" => $"<Image{xamlKey}>\r\n{indent}<DrawingImage>\r\n{indent}{indent}<GeometryDrawing Brush=\"{brush}\" Geometry=\"{SvgPathData}\"/>\r\n</DrawingImage>\r\n</Image>",
|
||||
"SvgPathData" => $"{SvgPathData}",
|
||||
"SvgPath" => $"<path fill=\"{brush}\" d=\"{SvgPathData}\"/>",
|
||||
"Svg" => $"<svg viewBox=\"{Path?.Bounds.Left.ToString(CultureInfo.InvariantCulture)} {Path?.Bounds.Top.ToString(CultureInfo.InvariantCulture)} {Path?.Bounds.Width.ToString(CultureInfo.InvariantCulture)} {Path?.Bounds.Height.ToString(CultureInfo.InvariantCulture)}\" xmlns=\"http://www.w3.org/2000/svg\">\r\n{indent}<path fill=\"{brush}\" d=\"{SvgPathData}\"/>\r\n</svg>",
|
||||
_ => default
|
||||
};
|
||||
return text;
|
||||
}
|
||||
|
||||
public async Task CopyAs(string format, string brush)
|
||||
{
|
||||
var text = Export(format, _brush ?? "#000000", false);
|
||||
if (!string.IsNullOrWhiteSpace(text))
|
||||
{
|
||||
try
|
||||
{
|
||||
await Application.Current.Clipboard.SetTextAsync(text);
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,286 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using System.Text;
|
||||
using System.Threading.Tasks;
|
||||
using System.Windows.Input;
|
||||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Controls.ApplicationLifetimes;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
using TypefaceUtil.OpenType;
|
||||
|
||||
namespace TypefaceUtil.Avalonia.ViewModels
|
||||
{
|
||||
public class MainWindowViewModel : ViewModelBase
|
||||
{
|
||||
private string? _inputFile;
|
||||
private string? _familyName;
|
||||
private ObservableCollection<string>? _fontFamilies;
|
||||
private float _fontSize;
|
||||
private string? _brush;
|
||||
private TypefaceViewModel? _typeface;
|
||||
|
||||
public string? InputFile
|
||||
{
|
||||
get => _inputFile;
|
||||
set => this.RaiseAndSetIfChanged(ref _inputFile, value);
|
||||
}
|
||||
|
||||
public string? FamilyName
|
||||
{
|
||||
get => _familyName;
|
||||
set => this.RaiseAndSetIfChanged(ref _familyName, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<string>? FontFamilies
|
||||
{
|
||||
get => _fontFamilies;
|
||||
set => this.RaiseAndSetIfChanged(ref _fontFamilies, value);
|
||||
}
|
||||
|
||||
public float FontSize
|
||||
{
|
||||
get => _fontSize;
|
||||
set => this.RaiseAndSetIfChanged(ref _fontSize, value);
|
||||
}
|
||||
|
||||
public string? Brush
|
||||
{
|
||||
get => _brush;
|
||||
set => this.RaiseAndSetIfChanged(ref _brush, value);
|
||||
}
|
||||
|
||||
public TypefaceViewModel? Typeface
|
||||
{
|
||||
get => _typeface;
|
||||
set => this.RaiseAndSetIfChanged(ref _typeface, value);
|
||||
}
|
||||
|
||||
public ICommand InputFileCommand { get; }
|
||||
|
||||
public ICommand LoadInputFileCommand { get; }
|
||||
|
||||
public ICommand LoadFamilyNameCommand { get; }
|
||||
|
||||
public ICommand CloseCommand { get; }
|
||||
|
||||
public ICommand CopyAllAsCommand { get; }
|
||||
|
||||
public MainWindowViewModel()
|
||||
{
|
||||
FontFamilies = new ObservableCollection<string>(SetGetFontFamilies());
|
||||
FamilyName = "Segoe MDL2 Assets";
|
||||
|
||||
FontSize = 32f;
|
||||
Brush = "#000000";
|
||||
|
||||
InputFileCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
var dlg = new OpenFileDialog();
|
||||
dlg.Filters.Add(new FileDialogFilter { Extensions = new List<string> {"ttf", "otf"}, Name = "Font Files"});
|
||||
dlg.Filters.Add(new FileDialogFilter { Extensions = new List<string> {"ttf"}, Name = "TTF Files"});
|
||||
dlg.Filters.Add(new FileDialogFilter { Extensions = new List<string> {"otf"}, Name = "OTF Files"});
|
||||
dlg.Filters.Add(new FileDialogFilter { Extensions = new List<string> {"*"}, Name = "All Files"});
|
||||
var paths = await dlg.ShowAsync((Application.Current.ApplicationLifetime as IClassicDesktopStyleApplicationLifetime)?.MainWindow);
|
||||
if (paths is { Length: 1 })
|
||||
{
|
||||
InputFile = paths[0];
|
||||
}
|
||||
});
|
||||
|
||||
LoadInputFileCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await Task.Run(LoadInputFile);
|
||||
});
|
||||
|
||||
LoadFamilyNameCommand = ReactiveCommand.CreateFromTask(async () =>
|
||||
{
|
||||
await Task.Run(LoadFamilyName);
|
||||
});
|
||||
|
||||
CloseCommand = ReactiveCommand.Create(() =>
|
||||
{
|
||||
Typeface = null;
|
||||
});
|
||||
|
||||
CopyAllAsCommand = ReactiveCommand.CreateFromTask<string>(async (format) =>
|
||||
{
|
||||
if (Typeface?.Glyphs is { })
|
||||
{
|
||||
try
|
||||
{
|
||||
var allText = await Task.Run(() =>
|
||||
{
|
||||
var sb = new StringBuilder();
|
||||
|
||||
foreach (var glyph in Typeface.Glyphs)
|
||||
{
|
||||
var glyphText = glyph.Export(format, glyph.Brush ?? "#000000", true);
|
||||
if (!string.IsNullOrWhiteSpace(glyphText))
|
||||
{
|
||||
sb.AppendLine(glyphText);
|
||||
}
|
||||
}
|
||||
|
||||
return sb.ToString();
|
||||
});
|
||||
|
||||
if (!string.IsNullOrWhiteSpace(allText))
|
||||
{
|
||||
await Application.Current.Clipboard.SetTextAsync(allText);
|
||||
}
|
||||
}
|
||||
catch
|
||||
{
|
||||
// ignored
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private string[] SetGetFontFamilies()
|
||||
{
|
||||
var fontFamilies = SKFontManager.Default.GetFontFamilies();
|
||||
|
||||
Array.Sort(fontFamilies, StringComparer.InvariantCulture);
|
||||
|
||||
return fontFamilies;
|
||||
}
|
||||
|
||||
private void LoadInputFile()
|
||||
{
|
||||
var inputFile = InputFile;
|
||||
var fontSize = FontSize;
|
||||
var brush = Brush ?? "#000000";
|
||||
if (string.IsNullOrEmpty(inputFile))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var typefaceViewModel = LoadFromFile(inputFile);
|
||||
if (typefaceViewModel?.Typeface is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Process(typefaceViewModel, fontSize, brush);
|
||||
Typeface = typefaceViewModel;
|
||||
}
|
||||
|
||||
private void LoadFamilyName()
|
||||
{
|
||||
var familyName = FamilyName;
|
||||
var fontSize = FontSize;
|
||||
var brush = Brush ?? "#000000";
|
||||
if (string.IsNullOrEmpty(familyName))
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var typefaceViewModel = LoadFromFamilyName(familyName);
|
||||
if (typefaceViewModel?.Typeface is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
Process(typefaceViewModel, fontSize, brush);
|
||||
Typeface = typefaceViewModel;
|
||||
}
|
||||
|
||||
private void Process(TypefaceViewModel? typefaceViewModel, float fontSize, string brush)
|
||||
{
|
||||
if (typefaceViewModel?.Typeface is null || typefaceViewModel?.CharacterMaps is null || typefaceViewModel?.Glyphs is null)
|
||||
{
|
||||
return;
|
||||
}
|
||||
|
||||
var skFont = typefaceViewModel.Typeface.ToFont(fontSize, 1f, 0f);
|
||||
|
||||
foreach (var characterMap in typefaceViewModel.CharacterMaps)
|
||||
{
|
||||
if (characterMap.CharacterToGlyphMap != null)
|
||||
{
|
||||
var characterToGlyphMap = characterMap.CharacterToGlyphMap;
|
||||
|
||||
foreach (var kvp in characterToGlyphMap)
|
||||
{
|
||||
var charCode = kvp.Key;
|
||||
var glyphIndex = kvp.Value;
|
||||
var skPath = skFont.GetGlyphPath(glyphIndex);
|
||||
|
||||
var skTranslationMatrix = SKMatrix.CreateTranslation(-skPath.Bounds.Left, -skPath.Bounds.Top);
|
||||
skPath.Transform(skTranslationMatrix);
|
||||
|
||||
var svgPathData = skPath.ToSvgPathData();
|
||||
|
||||
var glyph = new GlyphViewModel()
|
||||
{
|
||||
CharCode = charCode,
|
||||
GlyphIndex = glyphIndex,
|
||||
Path = skPath,
|
||||
Paint = new SKPaint
|
||||
{
|
||||
IsAntialias = true,
|
||||
Color = SKColor.Parse(brush),
|
||||
Style = SKPaintStyle.Fill
|
||||
},
|
||||
Brush = brush,
|
||||
SvgPathData = svgPathData
|
||||
};
|
||||
|
||||
typefaceViewModel.Glyphs.Add(glyph);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private static List<CharacterMap> Read(SKTypeface typeface)
|
||||
{
|
||||
var cmap = typeface.GetTableData(TableReader.GetIntTag("cmap"));
|
||||
var characterMaps = TableReader.ReadCmapTable(cmap, false);
|
||||
return characterMaps;
|
||||
}
|
||||
|
||||
private TypefaceViewModel? LoadFromFamilyName(string fontFamily)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(fontFamily))
|
||||
{
|
||||
using var typeface = SKTypeface.FromFamilyName(fontFamily);
|
||||
if (typeface != null)
|
||||
{
|
||||
var characterMaps = Read(typeface);
|
||||
return new TypefaceViewModel()
|
||||
{
|
||||
Typeface = typeface,
|
||||
CharacterMaps = characterMaps,
|
||||
Glyphs = new ObservableCollection<GlyphViewModel>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
private TypefaceViewModel? LoadFromFile(string path)
|
||||
{
|
||||
if (!string.IsNullOrEmpty(path))
|
||||
{
|
||||
using var typeface = SKTypeface.FromFile(path);
|
||||
if (typeface != null)
|
||||
{
|
||||
var characterMaps = Read(typeface);
|
||||
return new TypefaceViewModel()
|
||||
{
|
||||
Typeface = typeface,
|
||||
CharacterMaps = characterMaps,
|
||||
Glyphs = new ObservableCollection<GlyphViewModel>()
|
||||
};
|
||||
}
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,33 @@
|
|||
using System.Collections.Generic;
|
||||
using System.Collections.ObjectModel;
|
||||
using ReactiveUI;
|
||||
using SkiaSharp;
|
||||
using TypefaceUtil.OpenType;
|
||||
|
||||
namespace TypefaceUtil.Avalonia.ViewModels
|
||||
{
|
||||
public class TypefaceViewModel : ViewModelBase
|
||||
{
|
||||
private SKTypeface? _typeface;
|
||||
private List<CharacterMap>? _characterMaps;
|
||||
private ObservableCollection<GlyphViewModel>? _glyphs;
|
||||
|
||||
public SKTypeface? Typeface
|
||||
{
|
||||
get => _typeface;
|
||||
set => this.RaiseAndSetIfChanged(ref _typeface, value);
|
||||
}
|
||||
|
||||
public List<CharacterMap>? CharacterMaps
|
||||
{
|
||||
get => _characterMaps;
|
||||
set => this.RaiseAndSetIfChanged(ref _characterMaps, value);
|
||||
}
|
||||
|
||||
public ObservableCollection<GlyphViewModel>? Glyphs
|
||||
{
|
||||
get => _glyphs;
|
||||
set => this.RaiseAndSetIfChanged(ref _glyphs, value);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -0,0 +1,11 @@
|
|||
using System;
|
||||
using System.Collections.Generic;
|
||||
using System.Text;
|
||||
using ReactiveUI;
|
||||
|
||||
namespace TypefaceUtil.Avalonia.ViewModels
|
||||
{
|
||||
public class ViewModelBase : ReactiveObject
|
||||
{
|
||||
}
|
||||
}
|
|
@ -0,0 +1,212 @@
|
|||
<Window xmlns="https://github.com/avaloniaui"
|
||||
xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml"
|
||||
xmlns:vm="using:TypefaceUtil.Avalonia.ViewModels"
|
||||
xmlns:d="http://schemas.microsoft.com/expression/blend/2008"
|
||||
xmlns:mc="http://schemas.openxmlformats.org/markup-compatibility/2006"
|
||||
xmlns:controls="clr-namespace:Avalonia.Controls.Skia;assembly=Avalonia.Controls.Skia"
|
||||
mc:Ignorable="d" d:DesignWidth="800" d:DesignHeight="450"
|
||||
x:Class="TypefaceUtil.Avalonia.Views.MainWindow"
|
||||
UseLayoutRounding="True"
|
||||
Icon="/Assets/avalonia-logo.ico"
|
||||
Title="TypefaceUtil">
|
||||
<Design.DataContext>
|
||||
<vm:MainWindowViewModel/>
|
||||
</Design.DataContext>
|
||||
|
||||
<Panel>
|
||||
|
||||
<Panel.Styles>
|
||||
<Style Selector=":is(Control).transition">
|
||||
<Setter Property="Transitions">
|
||||
<Transitions>
|
||||
<TransformOperationsTransition Property="RenderTransform" Duration="0:0:0.35" Easing="0.4,0,0.6,1" />
|
||||
<DoubleTransition Property="Opacity" Duration="0:0:0.50" Easing="0.4,0,0.6,1" />
|
||||
</Transitions>
|
||||
</Setter>
|
||||
<Setter Property="Opacity" Value="0"/>
|
||||
</Style>
|
||||
<Style Selector=":is(Control)[IsVisible=True].transition">
|
||||
<Setter Property="RenderTransform" Value="scaleX(1) scaleY(1)" />
|
||||
<Setter Property="Opacity" Value="1"/>
|
||||
</Style>
|
||||
<Style Selector=":is(Control)[IsVisible=False].transition">
|
||||
<Setter Property="RenderTransform" Value="scaleX(0.1) scaleY(0.1)" />
|
||||
<Setter Property="Opacity" Value="0"/>
|
||||
</Style>
|
||||
</Panel.Styles>
|
||||
|
||||
<StackPanel MaxWidth="640"
|
||||
HorizontalAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
IsVisible="{Binding Typeface, Converter={x:Static ObjectConverters.IsNull}}">
|
||||
<DockPanel Margin="12,12,6,12" DockPanel.Dock="Top">
|
||||
<Label Content="Font size:"
|
||||
VerticalAlignment="Center"
|
||||
Width="100"
|
||||
DockPanel.Dock="Left"/>
|
||||
<TextBox Text="{Binding FontSize}"
|
||||
Watermark="Font size"
|
||||
VerticalAlignment="Center"
|
||||
Margin="6,0,12,0"
|
||||
DockPanel.Dock="Left"/>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="12,12,6,12" DockPanel.Dock="Top">
|
||||
<Label Content="Font size:"
|
||||
VerticalAlignment="Center"
|
||||
Width="100"
|
||||
DockPanel.Dock="Left"/>
|
||||
<TextBox Text="{Binding Brush}"
|
||||
Watermark="Brush"
|
||||
VerticalAlignment="Center"
|
||||
Margin="6,0,12,0"/>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="12,6,6,12" DockPanel.Dock="Top">
|
||||
<Label Content="Input file:"
|
||||
VerticalAlignment="Center"
|
||||
Width="100"
|
||||
DockPanel.Dock="Left"/>
|
||||
<Button Content="Load"
|
||||
Command="{Binding LoadInputFileCommand}"
|
||||
MinWidth="60"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,12,0"
|
||||
DockPanel.Dock="Right"/>
|
||||
<Button Content="..."
|
||||
Command="{Binding InputFileCommand}"
|
||||
MinWidth="60"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
DockPanel.Dock="Right"/>
|
||||
<TextBox Text="{Binding InputFile}"
|
||||
Watermark="Input file path"
|
||||
VerticalAlignment="Center"
|
||||
Margin="6,0,12,0"/>
|
||||
</DockPanel>
|
||||
<DockPanel Margin="12,12,12,12" DockPanel.Dock="Top">
|
||||
<Label Content="Input family name:"
|
||||
VerticalAlignment="Center"
|
||||
Width="100"
|
||||
DockPanel.Dock="Left"/>
|
||||
<Button Content="Load"
|
||||
Command="{Binding LoadFamilyNameCommand}"
|
||||
MinWidth="60"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,6,0"
|
||||
DockPanel.Dock="Right"/>
|
||||
<ComboBox Items="{Binding FontFamilies}"
|
||||
SelectedItem="{Binding FamilyName, Mode=TwoWay}"
|
||||
MinWidth="200"
|
||||
HorizontalContentAlignment="Center"
|
||||
VerticalAlignment="Center"
|
||||
Margin="12,0,0,0"
|
||||
MaxDropDownHeight="210"
|
||||
DockPanel.Dock="Right"/>
|
||||
<TextBox Text="{Binding FamilyName}"
|
||||
Watermark="Input family name"
|
||||
VerticalAlignment="Center"
|
||||
Margin="6,0,0,0"/>
|
||||
</DockPanel>
|
||||
</StackPanel>
|
||||
|
||||
<DockPanel Classes="transition"
|
||||
IsVisible="{Binding Typeface, Converter={x:Static ObjectConverters.IsNotNull}}">
|
||||
<DockPanel DockPanel.Dock="Top">
|
||||
<Button Command="{Binding CloseCommand}"
|
||||
Background="Transparent"
|
||||
HorizontalAlignment="Right"
|
||||
HorizontalContentAlignment="Center"
|
||||
Margin="12,12,36,12"
|
||||
DockPanel.Dock="Right">
|
||||
<PathIcon Width="15.703125" Height="15.703125" Opacity="0.6" Foreground="#000000" Data="M8.5625 7.85156L15.7031 15L15 15.7031L7.85156 8.5625L0.703125 15.7031L0 15L7.14063 7.85156L0 0.703125L0.703125 0L7.85156 7.14063L15 0L15.7031 0.703125L8.5625 7.85156Z"/>
|
||||
</Button>
|
||||
<Button Background="Transparent"
|
||||
HorizontalAlignment="Left"
|
||||
HorizontalContentAlignment="Center"
|
||||
Margin="36,12,12,12"
|
||||
DockPanel.Dock="Left">
|
||||
<Button.Flyout>
|
||||
<MenuFlyout ShowMode="Standard" Placement="BottomEdgeAlignedLeft">
|
||||
<MenuItem Header="Copy as Xaml (StreamGeometry)" Command="{Binding CopyAllAsCommand}" CommandParameter="XamlStreamGeometry" />
|
||||
<MenuItem Header="Copy as Xaml (PathIcon)" Command="{Binding CopyAllAsCommand}" CommandParameter="XamlPathIcon" />
|
||||
<MenuItem Header="Copy as Xaml (Path)" Command="{Binding CopyAllAsCommand}" CommandParameter="XamlPath" />
|
||||
<MenuItem Header="Copy as Xaml (Canvas)" Command="{Binding CopyAllAsCommand}" CommandParameter="XamlCanvas" />
|
||||
<MenuItem Header="Copy as Xaml (GeometryDrawing)" Command="{Binding CopyAllAsCommand}" CommandParameter="XamlGeometryDrawing" />
|
||||
<MenuItem Header="Copy as Xaml (DrawingGroup)" Command="{Binding CopyAllAsCommand}" CommandParameter="XamlDrawingGroup" />
|
||||
<MenuItem Header="Copy as Xaml (DrawingImage)" Command="{Binding CopyAllAsCommand}" CommandParameter="XamlDrawingImage" />
|
||||
<MenuItem Header="Copy as Xaml (Image)" Command="{Binding CopyAllAsCommand}" CommandParameter="XamlImage" />
|
||||
<MenuItem Header="Copy as Svg (Path Data)" Command="{Binding CopyAllAsCommand}" CommandParameter="SvgPathData" />
|
||||
<MenuItem Header="Copy as Svg (Path)" Command="{Binding CopyAllAsCommand}" CommandParameter="SvgPath" />
|
||||
<MenuItem Header="Copy as Svg (Svg)" Command="{Binding CopyAllAsCommand}" CommandParameter="Svg" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
<PathIcon Width="14" Height="16" Opacity="0.6" Foreground="#000000" Data="M14 6.28906L14 16L4 16L4 13L0 13L0 0L6.71094 0L9.71094 3L10.7109 3L14 6.28906ZM11 6L12.2891 6L11 4.71094L11 6ZM4 3L8.28906 3L6.28906 1L1 1L1 12L4 12L4 3ZM13 7L10 7L10 4L5 4L5 15L13 15L13 7Z"/>
|
||||
</Button>
|
||||
</DockPanel>
|
||||
<Panel Margin="36,0,36,36">
|
||||
<ScrollViewer HorizontalScrollBarVisibility="Disabled"
|
||||
VerticalScrollBarVisibility="Auto">
|
||||
<ItemsRepeater Items="{Binding Typeface.Glyphs, FallbackValue={x:Null}}">
|
||||
<ItemsRepeater.Layout>
|
||||
<UniformGridLayout Orientation="Horizontal"
|
||||
ItemsJustification="Center"
|
||||
MinColumnSpacing="6"
|
||||
MinRowSpacing="6"
|
||||
MinItemWidth="64"
|
||||
MinItemHeight="64"/>
|
||||
</ItemsRepeater.Layout>
|
||||
<ItemsRepeater.ItemTemplate>
|
||||
<DataTemplate DataType="vm:GlyphViewModel">
|
||||
<Button Background="Transparent"
|
||||
BorderBrush="Black"
|
||||
BorderThickness="1"
|
||||
HorizontalContentAlignment="Stretch"
|
||||
VerticalContentAlignment="Stretch"
|
||||
HorizontalAlignment="Stretch"
|
||||
VerticalAlignment="Stretch">
|
||||
<Button.ContextMenu>
|
||||
<ContextMenu>
|
||||
<MenuItem Header="Copy as Xaml (StreamGeometry)" Command="{Binding CopyAsCommand}" CommandParameter="XamlStreamGeometry" />
|
||||
<MenuItem Header="Copy as Xaml (PathIcon)" Command="{Binding CopyAsCommand}" CommandParameter="XamlPathIcon" />
|
||||
<MenuItem Header="Copy as Xaml (Path)" Command="{Binding CopyAsCommand}" CommandParameter="XamlPath" />
|
||||
<MenuItem Header="Copy as Xaml (Canvas)" Command="{Binding CopyAsCommand}" CommandParameter="XamlCanvas" />
|
||||
<MenuItem Header="Copy as Xaml (GeometryDrawing)" Command="{Binding CopyAsCommand}" CommandParameter="XamlGeometryDrawing" />
|
||||
<MenuItem Header="Copy as Xaml (DrawingGroup)" Command="{Binding CopyAsCommand}" CommandParameter="XamlDrawingGroup" />
|
||||
<MenuItem Header="Copy as Xaml (DrawingImage)" Command="{Binding CopyAsCommand}" CommandParameter="XamlDrawingImage" />
|
||||
<MenuItem Header="Copy as Xaml (Image)" Command="{Binding CopyAsCommand}" CommandParameter="XamlImage" />
|
||||
<MenuItem Header="Copy as Svg (Path Data)" Command="{Binding CopyAsCommand}" CommandParameter="SvgPathData" />
|
||||
<MenuItem Header="Copy as Svg (Path)" Command="{Binding CopyAsCommand}" CommandParameter="SvgPath" />
|
||||
<MenuItem Header="Copy as Svg (Svg)" Command="{Binding CopyAsCommand}" CommandParameter="Svg" />
|
||||
</ContextMenu>
|
||||
</Button.ContextMenu>
|
||||
<Button.Flyout>
|
||||
<MenuFlyout ShowMode="Standard" Placement="BottomEdgeAlignedLeft">
|
||||
<MenuItem Header="Copy as Xaml (StreamGeometry)" Command="{Binding CopyAsCommand}" CommandParameter="XamlStreamGeometry" />
|
||||
<MenuItem Header="Copy as Xaml (PathIcon)" Command="{Binding CopyAsCommand}" CommandParameter="XamlPathIcon" />
|
||||
<MenuItem Header="Copy as Xaml (Path)" Command="{Binding CopyAsCommand}" CommandParameter="XamlPath" />
|
||||
<MenuItem Header="Copy as Xaml (Canvas)" Command="{Binding CopyAsCommand}" CommandParameter="XamlCanvas" />
|
||||
<MenuItem Header="Copy as Xaml (GeometryDrawing)" Command="{Binding CopyAsCommand}" CommandParameter="XamlGeometryDrawing" />
|
||||
<MenuItem Header="Copy as Xaml (DrawingGroup)" Command="{Binding CopyAsCommand}" CommandParameter="XamlDrawingGroup" />
|
||||
<MenuItem Header="Copy as Xaml (DrawingImage)" Command="{Binding CopyAsCommand}" CommandParameter="XamlDrawingImage" />
|
||||
<MenuItem Header="Copy as Xaml (Image)" Command="{Binding CopyAsCommand}" CommandParameter="XamlImage" />
|
||||
<MenuItem Header="Copy as Svg (Path Data)" Command="{Binding CopyAsCommand}" CommandParameter="SvgPathData" />
|
||||
<MenuItem Header="Copy as Svg (Path)" Command="{Binding CopyAsCommand}" CommandParameter="SvgPath" />
|
||||
<MenuItem Header="Copy as Svg (Svg)" Command="{Binding CopyAsCommand}" CommandParameter="Svg" />
|
||||
</MenuFlyout>
|
||||
</Button.Flyout>
|
||||
<controls:SKPathControl Path="{Binding Path}"
|
||||
Paint="{Binding Paint}"
|
||||
Stretch="None"
|
||||
StretchDirection="Both"/>
|
||||
</Button>
|
||||
</DataTemplate>
|
||||
</ItemsRepeater.ItemTemplate>
|
||||
</ItemsRepeater>
|
||||
</ScrollViewer>
|
||||
</Panel>
|
||||
</DockPanel>
|
||||
|
||||
</Panel>
|
||||
|
||||
</Window>
|
|
@ -0,0 +1,22 @@
|
|||
using Avalonia;
|
||||
using Avalonia.Controls;
|
||||
using Avalonia.Markup.Xaml;
|
||||
|
||||
namespace TypefaceUtil.Avalonia.Views
|
||||
{
|
||||
public partial class MainWindow : Window
|
||||
{
|
||||
public MainWindow()
|
||||
{
|
||||
InitializeComponent();
|
||||
#if DEBUG
|
||||
this.AttachDevTools();
|
||||
#endif
|
||||
}
|
||||
|
||||
private void InitializeComponent()
|
||||
{
|
||||
AvaloniaXamlLoader.Load(this);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -39,7 +39,7 @@ namespace TypefaceUtil
|
|||
using var outlinePath = skTextPaint.GetTextPath(utf32, x, y);
|
||||
using var fillPath = skTextPaint.GetFillPath(outlinePath);
|
||||
|
||||
fillPath.Transform(SKMatrix.MakeTranslation(-fillPath.Bounds.Left, -fillPath.Bounds.Top));
|
||||
fillPath.Transform(SKMatrix.CreateTranslation(-fillPath.Bounds.Left, -fillPath.Bounds.Top));
|
||||
|
||||
var bounds = fillPath.Bounds;
|
||||
var svgPathData = fillPath.ToSvgPathData();
|
||||
|
|
|
@ -42,7 +42,7 @@ namespace TypefaceUtil
|
|||
using var outlinePath = skTextPaint.GetTextPath(utf32, x, y);
|
||||
using var fillPath = skTextPaint.GetFillPath(outlinePath);
|
||||
|
||||
fillPath.Transform(SKMatrix.MakeTranslation(-fillPath.Bounds.Left, -fillPath.Bounds.Top));
|
||||
fillPath.Transform(SKMatrix.CreateTranslation(-fillPath.Bounds.Left, -fillPath.Bounds.Top));
|
||||
|
||||
var svgPathData = fillPath.ToSvgPathData();
|
||||
|
||||
|
|
|
@ -51,8 +51,8 @@
|
|||
|
||||
<ItemGroup>
|
||||
<PackageReference Include="System.CommandLine" Version="2.0.0-beta1.20303.1" />
|
||||
<PackageReference Include="SkiaSharp" Version="1.68.3" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="1.68.3" />
|
||||
<PackageReference Include="SkiaSharp" Version="2.88.0-preview.61" />
|
||||
<PackageReference Include="SkiaSharp.NativeAssets.Linux" Version="2.88.0-preview.61" />
|
||||
</ItemGroup>
|
||||
|
||||
<ItemGroup Condition="'$(CoreRT)' == 'True'">
|
||||
|
|
Загрузка…
Ссылка в новой задаче