committed by
GitHub
13 changed files with 327 additions and 85 deletions
@ -0,0 +1,18 @@ |
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// Font fallback definition that is used to override the default fallback lookup of the current <see cref="FontManager"/>
|
|||
/// </summary>
|
|||
public class FontFallback |
|||
{ |
|||
/// <summary>
|
|||
/// Get or set the fallback <see cref="FontFamily"/>
|
|||
/// </summary>
|
|||
public FontFamily FontFamily { get; set; } |
|||
|
|||
/// <summary>
|
|||
/// Get or set the <see cref="UnicodeRange"/> that is covered by the fallback.
|
|||
/// </summary>
|
|||
public UnicodeRange UnicodeRange { get; set; } = UnicodeRange.Default; |
|||
} |
|||
} |
|||
@ -0,0 +1,11 @@ |
|||
using System.Collections.Generic; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
public class FontManagerOptions |
|||
{ |
|||
public string DefaultFamilyName { get; set; } |
|||
|
|||
public IReadOnlyList<FontFallback> FontFallbacks { get; set; } |
|||
} |
|||
} |
|||
@ -0,0 +1,199 @@ |
|||
using System; |
|||
using System.Collections.Generic; |
|||
using System.Text.RegularExpressions; |
|||
|
|||
namespace Avalonia.Media |
|||
{ |
|||
/// <summary>
|
|||
/// The <see cref="UnicodeRange"/> descripes a set of Unicode characters.
|
|||
/// </summary>
|
|||
public readonly struct UnicodeRange |
|||
{ |
|||
public static UnicodeRange Default = Parse("0-10FFFD"); |
|||
|
|||
private readonly UnicodeRangeSegment _single; |
|||
private readonly IReadOnlyList<UnicodeRangeSegment> _segments = null; |
|||
|
|||
public UnicodeRange(int start, int end) |
|||
{ |
|||
_single = new UnicodeRangeSegment(start, end); |
|||
} |
|||
|
|||
public UnicodeRange(UnicodeRangeSegment single) |
|||
{ |
|||
_single = single; |
|||
} |
|||
|
|||
public UnicodeRange(IReadOnlyList<UnicodeRangeSegment> segments) |
|||
{ |
|||
if(segments is null || segments.Count == 0) |
|||
{ |
|||
throw new ArgumentException(nameof(segments)); |
|||
} |
|||
|
|||
_single = segments[0]; |
|||
_segments = segments; |
|||
} |
|||
|
|||
internal UnicodeRangeSegment Single => _single; |
|||
|
|||
internal IReadOnlyList<UnicodeRangeSegment> Segments => _segments; |
|||
|
|||
/// <summary>
|
|||
/// Determines if given value is inside the range.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to verify.</param>
|
|||
/// <returns>
|
|||
/// <c>true</c> If given value is inside the range, <c>false</c> otherwise.
|
|||
/// </returns>
|
|||
public bool IsInRange(int value) |
|||
{ |
|||
if(_segments is null) |
|||
{ |
|||
return _single.IsInRange(value); |
|||
} |
|||
|
|||
foreach(var segment in _segments) |
|||
{ |
|||
if (segment.IsInRange(value)) |
|||
{ |
|||
return true; |
|||
} |
|||
} |
|||
|
|||
return false; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses a <see cref="UnicodeRange"/>.
|
|||
/// </summary>
|
|||
/// <param name="s">The string to parse.</param>
|
|||
/// <returns>The parsed <see cref="UnicodeRange"/>.</returns>
|
|||
/// <exception cref="FormatException"></exception>
|
|||
public static UnicodeRange Parse(string s) |
|||
{ |
|||
if (string.IsNullOrEmpty(s)) |
|||
{ |
|||
throw new FormatException("Could not parse specified Unicode range."); |
|||
} |
|||
|
|||
var parts = s.Split(','); |
|||
|
|||
var length = parts.Length; |
|||
|
|||
if(length == 0) |
|||
{ |
|||
throw new FormatException("Could not parse specified Unicode range."); |
|||
} |
|||
|
|||
if(length == 1) |
|||
{ |
|||
return new UnicodeRange(UnicodeRangeSegment.Parse(parts[0])); |
|||
} |
|||
|
|||
var segments = new UnicodeRangeSegment[length]; |
|||
|
|||
for (int i = 0; i < length; i++) |
|||
{ |
|||
segments[i] = UnicodeRangeSegment.Parse(parts[i].Trim()); |
|||
} |
|||
|
|||
return new UnicodeRange(segments); |
|||
} |
|||
} |
|||
|
|||
public readonly struct UnicodeRangeSegment |
|||
{ |
|||
private static Regex s_regex = new Regex(@"^(?:[uU]\+)?(?:([0-9a-fA-F](?:[0-9a-fA-F?]{1,5})?))$"); |
|||
|
|||
public UnicodeRangeSegment(int start, int end) |
|||
{ |
|||
Start = start; |
|||
End = end; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Get the start of the segment.
|
|||
/// </summary>
|
|||
public int Start { get; } |
|||
|
|||
/// <summary>
|
|||
/// Get the end of the segment.
|
|||
/// </summary>
|
|||
public int End { get; } |
|||
|
|||
/// <summary>
|
|||
/// Determines if given value is inside the range segment.
|
|||
/// </summary>
|
|||
/// <param name="value">The value to verify.</param>
|
|||
/// <returns>
|
|||
/// <c>true</c> If given value is inside the range segment, <c>false</c> otherwise.
|
|||
/// </returns>
|
|||
public bool IsInRange(int value) |
|||
{ |
|||
return value - Start <= End - Start; |
|||
} |
|||
|
|||
/// <summary>
|
|||
/// Parses a <see cref="UnicodeRangeSegment"/>.
|
|||
/// </summary>
|
|||
/// <param name="s">The string to parse.</param>
|
|||
/// <returns>The parsed <see cref="UnicodeRangeSegment"/>.</returns>
|
|||
/// <exception cref="FormatException"></exception>
|
|||
public static UnicodeRangeSegment Parse(string s) |
|||
{ |
|||
if (string.IsNullOrEmpty(s)) |
|||
{ |
|||
throw new FormatException("Could not parse specified Unicode range segment."); |
|||
} |
|||
|
|||
var parts = s.Split('-'); |
|||
|
|||
int start, end; |
|||
|
|||
switch (parts.Length) |
|||
{ |
|||
case 1: |
|||
{ |
|||
//e.g. U+20, U+3F U+30??
|
|||
var single = s_regex.Match(parts[0]); |
|||
|
|||
if (!single.Success) |
|||
{ |
|||
throw new FormatException("Could not parse specified Unicode range segment."); |
|||
} |
|||
|
|||
if (!single.Value.Contains("?")) |
|||
{ |
|||
start = int.Parse(single.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); |
|||
end = start; |
|||
} |
|||
else |
|||
{ |
|||
start = int.Parse(single.Groups[1].Value.Replace('?', '0'), System.Globalization.NumberStyles.HexNumber); |
|||
end = int.Parse(single.Groups[1].Value.Replace('?', 'F'), System.Globalization.NumberStyles.HexNumber); |
|||
} |
|||
break; |
|||
} |
|||
case 2: |
|||
{ |
|||
var first = s_regex.Match(parts[0]); |
|||
var second = s_regex.Match(parts[1]); |
|||
|
|||
if (!first.Success || !second.Success) |
|||
{ |
|||
throw new FormatException("Could not parse specified Unicode range segment."); |
|||
} |
|||
|
|||
start = int.Parse(first.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); |
|||
end = int.Parse(second.Groups[1].Value, System.Globalization.NumberStyles.HexNumber); |
|||
break; |
|||
} |
|||
default: |
|||
throw new FormatException("Could not parse specified Unicode range segment."); |
|||
} |
|||
|
|||
return new UnicodeRangeSegment(start, end); |
|||
} |
|||
} |
|||
} |
|||
Binary file not shown.
Binary file not shown.
@ -1,78 +0,0 @@ |
|||
using System.Globalization; |
|||
using Avalonia.Media; |
|||
using Avalonia.Platform; |
|||
using Avalonia.Skia; |
|||
using SkiaSharp; |
|||
|
|||
namespace Avalonia.Web.Blazor |
|||
{ |
|||
public class CustomFontManagerImpl : IFontManagerImpl |
|||
{ |
|||
private readonly Typeface[] _customTypefaces; |
|||
private readonly string _defaultFamilyName; |
|||
|
|||
private readonly Typeface _defaultTypeface = |
|||
new Typeface("avares://Avalonia.Web.Blazor/Assets#Noto Mono"); |
|||
private readonly Typeface _italicTypeface = |
|||
new Typeface("avares://Avalonia.Web.Blazor/Assets#Noto Sans"); |
|||
|
|||
public CustomFontManagerImpl() |
|||
{ |
|||
_customTypefaces = new[] { _italicTypeface, _defaultTypeface }; |
|||
_defaultFamilyName = _defaultTypeface.FontFamily.FamilyNames.PrimaryFamilyName; |
|||
} |
|||
|
|||
public string GetDefaultFontFamilyName() |
|||
{ |
|||
return _defaultFamilyName; |
|||
} |
|||
|
|||
public IEnumerable<string> GetInstalledFontFamilyNames(bool checkForUpdates = false) |
|||
{ |
|||
return _customTypefaces.Select(x => x.FontFamily.Name); |
|||
} |
|||
|
|||
public bool TryMatchCharacter(int codepoint, FontStyle fontStyle, FontWeight fontWeight, FontFamily fontFamily, |
|||
CultureInfo culture, out Typeface typeface) |
|||
{ |
|||
foreach (var customTypeface in _customTypefaces) |
|||
{ |
|||
if (customTypeface.GlyphTypeface.GetGlyph((uint)codepoint) == 0) |
|||
{ |
|||
continue; |
|||
} |
|||
|
|||
typeface = new Typeface(customTypeface.FontFamily, fontStyle, fontWeight); |
|||
|
|||
return true; |
|||
} |
|||
|
|||
typeface = _defaultTypeface; |
|||
|
|||
return true; |
|||
} |
|||
|
|||
public IGlyphTypefaceImpl CreateGlyphTypeface(Typeface typeface) |
|||
{ |
|||
SKTypeface skTypeface; |
|||
|
|||
switch (typeface.FontFamily.Name) |
|||
{ |
|||
case "Noto Sans": |
|||
{ |
|||
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_italicTypeface.FontFamily); |
|||
skTypeface = typefaceCollection.Get(typeface); |
|||
break; |
|||
} |
|||
default: |
|||
{ |
|||
var typefaceCollection = SKTypefaceCollectionCache.GetOrAddTypefaceCollection(_defaultTypeface.FontFamily); |
|||
skTypeface = typefaceCollection.Get(_defaultTypeface); |
|||
break; |
|||
} |
|||
} |
|||
|
|||
return new GlyphTypefaceImpl(skTypeface); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,22 @@ |
|||
using Avalonia.Media; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Visuals.UnitTests.Media |
|||
{ |
|||
public class UnicodeRangeSegmentTests |
|||
{ |
|||
[InlineData("u+00-FF", 0, 255)] |
|||
[InlineData("U+00-FF", 0, 255)] |
|||
[InlineData("U+00-U+FF", 0, 255)] |
|||
[InlineData("U+AB??", 43776, 44031)] |
|||
[Theory] |
|||
public void Should_Parse(string s, int expectedStart, int expectedEnd) |
|||
{ |
|||
var segment = UnicodeRangeSegment.Parse(s); |
|||
|
|||
Assert.Equal(expectedStart, segment.Start); |
|||
|
|||
Assert.Equal(expectedEnd, segment.End); |
|||
} |
|||
} |
|||
} |
|||
@ -0,0 +1,17 @@ |
|||
using System.Linq; |
|||
using Avalonia.Media; |
|||
using Xunit; |
|||
|
|||
namespace Avalonia.Visuals.UnitTests.Media |
|||
{ |
|||
public class UnicodeRangeTests |
|||
{ |
|||
[Fact] |
|||
public void Should_Parse_Segments() |
|||
{ |
|||
var range = UnicodeRange.Parse("U+0, U+1, U+2, U+3"); |
|||
|
|||
Assert.Equal(new[] { 0, 1, 2, 3 }, range.Segments.Select(x => x.Start)); |
|||
} |
|||
} |
|||
} |
|||
Loading…
Reference in new issue