Code refactorization + added support for multi typeface font types

This commit is contained in:
MarcinZiabek 2022-02-25 23:02:42 +01:00
Родитель 8a64abaac2
Коммит 8db283d559
3 изменённых файлов: 89 добавлений и 58 удалений

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

@ -13,11 +13,11 @@ namespace QuestPDF.Examples
[Test]
public void Example()
{
FontManager.RegisterFontType("LibreBarcode39", File.OpenRead("LibreBarcode39-Regular.ttf"));
FontManager.RegisterFontType(File.OpenRead("LibreBarcode39-Regular.ttf"));
RenderingTest
.Create()
.PageSize(400, 100)
.PageSize(400, 200)
.ShowResults()
.Render(container =>
{

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

@ -1,6 +1,7 @@
using System;
using System.Collections.Concurrent;
using System.IO;
using System.Linq;
using QuestPDF.Infrastructure;
using SkiaSharp;
@ -8,27 +9,39 @@ namespace QuestPDF.Drawing
{
public static class FontManager
{
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new ConcurrentDictionary<string, FontStyleSet>();
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new ConcurrentDictionary<string, SKFontMetrics>();
private static ConcurrentDictionary<string, SKPaint> Paints = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new ConcurrentDictionary<string, SKPaint>();
private static ConcurrentDictionary<string, FontStyleSet> StyleSets = new();
private static ConcurrentDictionary<string, SKFontMetrics> FontMetrics = new();
private static ConcurrentDictionary<string, SKPaint> Paints = new();
private static ConcurrentDictionary<string, SKPaint> ColorPaint = new();
private static void RegisterFontType(string fontName, SKTypeface typeface)
private static void RegisterFontType(SKData fontData, string? customName = null)
{
FontStyleSet set = StyleSets.GetOrAdd(fontName, _ => new FontStyleSet());
set.Add(typeface);
foreach (var index in Enumerable.Range(0, 256))
{
var typeface = SKTypeface.FromData(fontData, index);
if (typeface == null)
break;
var typefaceName = customName ?? typeface.FamilyName;
var fontStyleSet = StyleSets.GetOrAdd(typefaceName, _ => new FontStyleSet());
fontStyleSet.Add(typeface);
}
}
[Obsolete("Since version 2022.3, the FontManager class offers better font type matching support. Please use the RegisterFontType(Stream stream) overload.")]
public static void RegisterFontType(string fontName, Stream stream)
{
SKTypeface typeface = SKTypeface.FromStream(stream);
RegisterFontType(fontName, typeface);
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
RegisterFontType(fontData, customName: fontName);
}
public static void RegisterFontType(Stream stream)
{
SKTypeface typeface = SKTypeface.FromStream(stream);
RegisterFontType(typeface.FamilyName, typeface);
using var fontData = SKData.Create(stream);
RegisterFontType(fontData);
}
internal static SKPaint ColorToPaint(this string color)
@ -54,29 +67,23 @@ namespace QuestPDF.Drawing
{
Color = SKColor.Parse(style.Color),
Typeface = GetTypeface(style),
TextSize = (style.Size ?? 12),
TextSize = style.Size ?? 12,
TextEncoding = SKTextEncoding.Utf32
};
}
static SKTypeface GetTypeface(TextStyle style)
{
SKFontStyleWeight weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
SKFontStyleWidth width = SKFontStyleWidth.Normal;
SKFontStyleSlant slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
var weight = (SKFontStyleWeight)(style.FontWeight ?? FontWeight.Normal);
var slant = (style.IsItalic ?? false) ? SKFontStyleSlant.Italic : SKFontStyleSlant.Upright;
SKFontStyle skFontStyle = new SKFontStyle(weight, width, slant);
var skFontStyle = new SKFontStyle(weight, SKFontStyleWidth.Normal, slant);
FontStyleSet set;
if (StyleSets.TryGetValue(style.FontType, out set))
{
if (StyleSets.TryGetValue(style.FontType, out var set))
return set.Match(skFontStyle);
}
else
{
return SKTypeface.FromFamilyName(style.FontType, skFontStyle)
?? throw new ArgumentException($"The typeface {style.FontType} could not be found.");
}
?? throw new ArgumentException($"The typeface {style.FontType} could not be found. Please consider installing the font file on your system or loading it from a file using the FontManager.RegisterFontType() static method.");
}
}

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

@ -7,18 +7,18 @@ namespace QuestPDF.Drawing
{
internal class FontStyleSet
{
private ConcurrentDictionary<SKFontStyle, SKTypeface> Styles = new ConcurrentDictionary<SKFontStyle, SKTypeface>();
private ConcurrentDictionary<SKFontStyle, SKTypeface> Styles { get; } = new();
public void Add(SKTypeface typeface)
{
SKFontStyle style = typeface.FontStyle;
Styles.AddOrUpdate(style, (_) => typeface, (_, _) => typeface);
var style = typeface.FontStyle;
Styles.AddOrUpdate(style, _ => typeface, (_, _) => typeface);
}
public SKTypeface Match(SKFontStyle target)
public SKTypeface? Match(SKFontStyle target)
{
SKFontStyle bestStyle = null;
SKTypeface bestTypeface = null;
SKFontStyle? bestStyle = null;
SKTypeface? bestTypeface = null;
foreach (var entry in Styles)
{
@ -40,40 +40,55 @@ namespace QuestPDF.Drawing
};
// Checks whether style a is a better match for the target then style b. Uses the CSS font style matching algorithm
internal static bool IsBetterMatch(SKFontStyle target, SKFontStyle a, SKFontStyle b)
internal static bool IsBetterMatch(SKFontStyle? target, SKFontStyle? a, SKFontStyle? b)
{
// A font is better than no font
if (b == null) return true;
if (a == null) return false;
if (b == null)
return true;
if (a == null)
return false;
// First check font width
// For normal and condensed widths prefer smaller widths
// For expanded widths prefer larger widths
if (target.Width <= (int)SKFontStyleWidth.Normal)
{
if (a.Width <= target.Width && b.Width > target.Width) return true;
if (a.Width > target.Width && b.Width <= target.Width) return false;
if (a.Width <= target.Width && b.Width > target.Width)
return true;
if (a.Width > target.Width && b.Width <= target.Width)
return false;
}
else
{
if (a.Width >= target.Width && b.Width < target.Width) return true;
if (a.Width < target.Width && b.Width >= target.Width) return false;
if (a.Width >= target.Width && b.Width < target.Width)
return true;
if (a.Width < target.Width && b.Width >= target.Width)
return false;
}
// Prefer closest match
int widthDifferenceA = Math.Abs(a.Width - target.Width);
int widthDifferenceB = Math.Abs(b.Width - target.Width);
var widthDifferenceA = Math.Abs(a.Width - target.Width);
var widthDifferenceB = Math.Abs(b.Width - target.Width);
if (widthDifferenceA < widthDifferenceB) return true;
if (widthDifferenceB < widthDifferenceA) return false;
if (widthDifferenceA < widthDifferenceB)
return true;
if (widthDifferenceB < widthDifferenceA)
return false;
// Prefer closest slant based on provided fallback list
List<SKFontStyleSlant> slantFallback = SlantFallbacks[target.Slant];
int slantIndexA = slantFallback.IndexOf(a.Slant);
int slantIndexB = slantFallback.IndexOf(b.Slant);
var slantFallback = SlantFallbacks[target.Slant];
var slantIndexA = slantFallback.IndexOf(a.Slant);
var slantIndexB = slantFallback.IndexOf(b.Slant);
if (slantIndexA < slantIndexB) return true;
if (slantIndexB < slantIndexA) return false;
if (slantIndexA < slantIndexB)
return true;
if (slantIndexB < slantIndexA)
return false;
// Check weight last
// For thin (<400) weights, prefer thinner weights
@ -83,24 +98,33 @@ namespace QuestPDF.Drawing
if (target.Weight >= 400 && target.Weight <= 500)
{
if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500)) return true;
if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500)) return false;
if ((a.Weight >= 400 && a.Weight <= 500) && !(b.Weight >= 400 && b.Weight <= 500))
return true;
if (!(a.Weight >= 400 && a.Weight <= 500) && (b.Weight >= 400 && b.Weight <= 500))
return false;
}
if (target.Weight < 450)
{
if (a.Weight <= target.Weight && b.Weight > target.Weight) return true;
if (a.Weight > target.Weight && b.Weight <= target.Weight) return false;
if (a.Weight <= target.Weight && b.Weight > target.Weight)
return true;
if (a.Weight > target.Weight && b.Weight <= target.Weight)
return false;
}
else
{
if (a.Weight >= target.Weight && b.Weight < target.Weight) return true;
if (a.Weight < target.Weight && b.Weight >= target.Weight) return false;
if (a.Weight >= target.Weight && b.Weight < target.Weight)
return true;
if (a.Weight < target.Weight && b.Weight >= target.Weight)
return false;
}
// Prefer closest weight
int weightDifferenceA = Math.Abs(a.Weight - target.Weight);
int weightDifferenceB = Math.Abs(b.Weight - target.Weight);
var weightDifferenceA = Math.Abs(a.Weight - target.Weight);
var weightDifferenceB = Math.Abs(b.Weight - target.Weight);
return weightDifferenceA < weightDifferenceB;
}