Merge pull request #2 from wieslawsoltes/Avalonia

This commit is contained in:
Wiesław Šoltés 2021-07-21 12:39:14 -07:00 коммит произвёл GitHub
Родитель 9ea9da7aa3 a6c235a504
Коммит 711c8e86f9
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
18 изменённых файлов: 829 добавлений и 4 удалений

Просмотреть файл

@ -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();
}
}
}

Двоичные данные
src/TypefaceUtil.Avalonia/Assets/avalonia-logo.ico Normal file

Двоичный файл не отображается.

После

Ширина:  |  Высота:  |  Размер: 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'">