diff --git a/QuestPDF.Examples/BarcodeExamples.cs b/QuestPDF.Examples/BarcodeExamples.cs index 0ce3beb..f4fa0dc 100644 --- a/QuestPDF.Examples/BarcodeExamples.cs +++ b/QuestPDF.Examples/BarcodeExamples.cs @@ -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 => { @@ -25,7 +25,7 @@ namespace QuestPDF.Examples .Background(Colors.White) .AlignCenter() .AlignMiddle() - .Text("*QuestPDF*", TextStyle.Default.FontType("LibreBarcode39").Size(64)); + .Text("*QuestPDF*", TextStyle.Default.FontType("Libre Barcode 39").Size(64)); }); } } diff --git a/QuestPDF/Drawing/FontManager.cs b/QuestPDF/Drawing/FontManager.cs index 131c1bc..fd5cac5 100644 --- a/QuestPDF/Drawing/FontManager.cs +++ b/QuestPDF/Drawing/FontManager.cs @@ -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 StyleSets = new ConcurrentDictionary(); - private static ConcurrentDictionary FontMetrics = new ConcurrentDictionary(); - private static ConcurrentDictionary Paints = new ConcurrentDictionary(); - private static ConcurrentDictionary ColorPaint = new ConcurrentDictionary(); + private static ConcurrentDictionary StyleSets = new(); + private static ConcurrentDictionary FontMetrics = new(); + private static ConcurrentDictionary Paints = new(); + private static ConcurrentDictionary 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."); - } + + return SKTypeface.FromFamilyName(style.FontType, skFontStyle) + ?? 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."); } } diff --git a/QuestPDF/Drawing/FontStyleSet.cs b/QuestPDF/Drawing/FontStyleSet.cs index a96b633..fd70aa6 100644 --- a/QuestPDF/Drawing/FontStyleSet.cs +++ b/QuestPDF/Drawing/FontStyleSet.cs @@ -7,18 +7,18 @@ namespace QuestPDF.Drawing { internal class FontStyleSet { - private ConcurrentDictionary Styles = new ConcurrentDictionary(); + private ConcurrentDictionary 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 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; }