From e279819fcc03cc826b9985b814827316e5f8f530 Mon Sep 17 00:00:00 2001 From: Stepami Date: Sun, 21 Dec 2025 17:40:11 +0300 Subject: [PATCH 01/14] comment gitversion.msbuild --- src/Directory.Build.props | 4 +- src/Directory.Packages.props | 76 +++++++++---------- .../MigraDoc.GrammarByExample-GDI.csproj | 4 +- .../Internal/PdfSharpGitVersionInformation.cs | 20 ++--- 4 files changed, 53 insertions(+), 51 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ec393bfd..9f786e55 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -41,9 +41,9 @@ - + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index b3272e12..e33e982b 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,39 +1,39 @@ - - - - 8.0.1 - 8.0.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + 8.0.1 + 8.0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj index 60749bf9..d163f31a 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj @@ -93,9 +93,9 @@ - + diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs index 4a52820a..de0498fa 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs @@ -1,6 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Globalization; + namespace PdfSharp.Internal { /// @@ -11,46 +13,46 @@ public static class PdfSharpGitVersionInformation /// /// The major version number of the product. /// - public static string Major = global::GitVersionInformation.Major; + public static string Major = "1";//global::GitVersionInformation.Major; /// /// The minor version number of the product. /// - public static string Minor = global::GitVersionInformation.Minor; + public static string Minor = "1";//global::GitVersionInformation.Minor; /// /// The patch number of the product. /// - public static string Patch = global::GitVersionInformation.Patch; + public static string Patch = "1";//global::GitVersionInformation.Patch; /// /// The Version pre-release string for NuGet. /// - public static string PreReleaseLabel = global::GitVersionInformation.PreReleaseLabel; + public static string PreReleaseLabel = string.Empty;//global::GitVersionInformation.PreReleaseLabel; /// /// The full version number. /// - public static string MajorMinorPatch = global::GitVersionInformation.MajorMinorPatch; + public static string MajorMinorPatch = "1.1.1";//global::GitVersionInformation.MajorMinorPatch; /// /// The full semantic version number created by GitVersion. /// - public static string SemVer = global::GitVersionInformation.SemVer; + public static string SemVer = "1.1.1";//global::GitVersionInformation.SemVer; /// /// The full informational version number created by GitVersion. /// - public static string InformationalVersion = global::GitVersionInformation.InformationalVersion; + public static string InformationalVersion = "1.1.1";//global::GitVersionInformation.InformationalVersion; /// /// The branch name of the product. /// - public static string BranchName = global::GitVersionInformation.BranchName; + public static string BranchName = "refactoring/locks";//global::GitVersionInformation.BranchName; /// /// The commit date of the product. /// - public static string CommitDate = global::GitVersionInformation.CommitDate; + public static string CommitDate = DateTime.Today.ToString(CultureInfo.InvariantCulture);//global::GitVersionInformation.CommitDate; } } From 8ff516552a1533f1a6995d5a1b041cb859ebf038 Mon Sep 17 00:00:00 2001 From: Stepami Date: Sun, 21 Dec 2025 17:40:31 +0300 Subject: [PATCH 02/14] new benchmark project --- PdfSharp.sln | 7 +++++++ src/Directory.Packages.props | 8 +------- .../PdfSharp.Benchmarks.csproj | 19 +++++++++++++++++++ 3 files changed, 27 insertions(+), 7 deletions(-) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj diff --git a/PdfSharp.sln b/PdfSharp.sln index 3c82ce5b..b3c3ef98 100644 --- a/PdfSharp.sln +++ b/PdfSharp.sln @@ -274,6 +274,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs-dummy", "docs\docs-dum EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Cryptography", "src\foundation\src\PDFsharp\src\PdfSharp.Cryptography\PdfSharp.Cryptography.csproj", "{769ED050-15AF-4EB5-A89F-D7123EE5AA95}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Benchmarks", "src\foundation\src\PDFsharp\src\PdfSharp.Benchmarks\PdfSharp.Benchmarks.csproj", "{24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -568,6 +570,10 @@ Global {769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Debug|Any CPU.Build.0 = Debug|Any CPU {769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Release|Any CPU.ActiveCfg = Release|Any CPU {769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Release|Any CPU.Build.0 = Release|Any CPU + {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -675,6 +681,7 @@ Global {F4AB506C-AD13-4383-9AF9-48D74085ECC1} = {7C753636-7947-46E0-95E0-135EAA7BFEB3} {76B94284-402D-4951-8DA4-7FFAF15E6C95} = {76BA9372-65AE-479C-AEF7-D50E6B486CEF} {769ED050-15AF-4EB5-A89F-D7123EE5AA95} = {7C753636-7947-46E0-95E0-135EAA7BFEB3} + {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF} = {7C753636-7947-46E0-95E0-135EAA7BFEB3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D5FF5562-3C79-434B-B951-B84542D01625} diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index e33e982b..263878e5 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -4,19 +4,16 @@ 8.0.1 8.0.1 - - + - - @@ -26,14 +23,11 @@ - - - \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj new file mode 100644 index 00000000..78d935f7 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj @@ -0,0 +1,19 @@ + + + + Exe + net8.0 + enable + enable + NETSDK1138 + + + + + + + + + + + From b496d2e2af42e95ccb72ff52ce29ddc7fd80f3e4 Mon Sep 17 00:00:00 2001 From: Stepami Date: Sun, 21 Dec 2025 17:40:52 +0300 Subject: [PATCH 03/14] visibility --- .../PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs | 2 +- .../src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs | 5 ++--- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs index c481dc50..21aabcaa 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs @@ -22,7 +22,7 @@ namespace PdfSharp.Fonts.OpenType /// Base class for all font descriptors. /// Currently only OpenTypeDescriptor is derived from this base class. /// - class FontDescriptor + public class FontDescriptor { protected FontDescriptor(string key) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs index 19007492..0711a40f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs @@ -3,14 +3,13 @@ using PdfSharp.Drawing; using PdfSharp.Fonts.OpenType; -using PdfSharp.Internal; namespace PdfSharp.Fonts { /// /// Global table of OpenType font descriptor objects. /// - static class FontDescriptorCache + public static class FontDescriptorCache { /// /// Gets the FontDescriptor identified by the specified XFont. If no such object @@ -88,7 +87,7 @@ public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontS finally { Locks.ExitFontFactory(); } } - internal static void Reset() + public static void Reset() { Globals.Global.Fonts.FontDescriptorCache.Clear(); } From b1bb7ed3de84152b20ec3a19be25e1c9c2b33523 Mon Sep 17 00:00:00 2001 From: Stepami Date: Sun, 21 Dec 2025 17:41:00 +0300 Subject: [PATCH 04/14] v2 cache --- .../PdfSharp/Fonts/FontDescriptorCacheV2.cs | 96 +++++++++++++++++++ 1 file changed, 96 insertions(+) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs new file mode 100644 index 00000000..1e62b8e9 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs @@ -0,0 +1,96 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Collections.Concurrent; +using PdfSharp.Drawing; +using PdfSharp.Fonts.OpenType; + +namespace PdfSharp.Fonts +{ + /// + /// Global table of OpenType font descriptor objects. + /// + public static class FontDescriptorCacheV2 + { + /// + /// Gets the FontDescriptor identified by the specified XFont. If no such object + /// exists, a new FontDescriptor is created and added to the cache. + /// + public static FontDescriptor GetOrCreateDescriptorFor(XFont font) + { + if (font == null) + throw new ArgumentNullException(nameof(font)); + + font.GlyphTypeface.CheckVersion(); + + //FontSelector1 selector = new FontSelector1(font); + string fontDescriptorKey = font.GlyphTypeface.Key; + var cache = Globals.Global.Fonts.FontDescriptorCacheV2; + if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) + return descriptor; + + descriptor = new OpenTypeDescriptor(fontDescriptorKey, font); + cache.TryAdd(fontDescriptorKey, descriptor); + return descriptor; + } + + public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypeface) + { + glyphTypeface.CheckVersion(); + + string fontDescriptorKey = glyphTypeface.Key; + var cache = Globals.Global.Fonts.FontDescriptorCacheV2; + if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) + return descriptor; + + descriptor = new OpenTypeDescriptor(fontDescriptorKey, glyphTypeface); + cache.TryAdd(fontDescriptorKey, descriptor); + return descriptor; + } + + /// + /// Gets the FontDescriptor identified by the specified FontSelector. If no such object + /// exists, a new FontDescriptor is created and added to the stock. + /// + public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontStyleEx style) + { + if (String.IsNullOrEmpty(fontFamilyName)) + throw new ArgumentNullException(nameof(fontFamilyName)); + + //FontSelector1 selector = new FontSelector1(fontFamilyName, style); + string fontDescriptorKey = FontDescriptor.ComputeFdKey(fontFamilyName, style); + var cache = Globals.Global.Fonts.FontDescriptorCache; + Locks.EnterFontFactory(); + if (!cache.TryGetValue(fontDescriptorKey, out var descriptor)) + { + var font = new XFont(fontFamilyName, 10, style); + descriptor = GetOrCreateDescriptorFor(font); + // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because there is not TryAdd in .NET Framework + if (cache.ContainsKey(fontDescriptorKey)) + _ = typeof(int); // Just a NOP for a break point. + else + cache.Add(fontDescriptorKey, descriptor); + } + return descriptor; + } + + public static void Reset() + { + Globals.Global.Fonts.FontDescriptorCacheV2.Clear(); + } + } +} + +namespace PdfSharp.Internal +{ + partial class Globals + { + partial class FontStorage + { + /// + /// Maps font descriptor key to font descriptor which is currently only an OpenTypeFontDescriptor. + /// + public readonly ConcurrentDictionary FontDescriptorCacheV2 = []; + } + } +} From 0e0b5996f5df49f875c0c6c095dc5de4d80c1d59 Mon Sep 17 00:00:00 2001 From: Stepami Date: Sun, 21 Dec 2025 17:41:05 +0300 Subject: [PATCH 05/14] benchmark --- .../src/PdfSharp.Benchmarks/Program.cs | 53 +++++++++++++++++++ 1 file changed, 53 insertions(+) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs new file mode 100644 index 00000000..087d2d16 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs @@ -0,0 +1,53 @@ +using BenchmarkDotNet.Attributes; +using BenchmarkDotNet.Engines; +using BenchmarkDotNet.Jobs; +using BenchmarkDotNet.Running; +using PdfSharp.Drawing; +using PdfSharp.Fonts; + +BenchmarkRunner.Run(); + +[SimpleJob(RuntimeMoniker.Net80)] +public class FontCacheBenchmarks +{ + private static readonly XGlyphTypeface GlyphTypeface = new( + XFontSource.CreateFromFile(@"C:\Windows\Fonts\arial.ttf")); + private static readonly Consumer Consumer = new(); + + [Params(1, 10, 100)] + public int Iterations { get; set; } + + [GlobalSetup] + public void GlobalSetup() + { + FontDescriptorCache.Reset(); + FontDescriptorCacheV2.Reset(); + } + + [GlobalCleanup] + public void GlobalCleanup() + { + FontDescriptorCache.Reset(); + FontDescriptorCacheV2.Reset(); + } + + [Benchmark(Baseline = true)] + public void LockFactory() + { + for (var i = 0; i < Iterations; i++) + { + Consumer.Consume( + FontDescriptorCache.GetOrCreateDescriptorFor(GlyphTypeface)); + } + } + + [Benchmark] + public void ConcurrentDictionary() + { + for (var i = 0; i < Iterations; i++) + { + Consumer.Consume( + FontDescriptorCacheV2.GetOrCreateDescriptorFor(GlyphTypeface)); + } + } +} \ No newline at end of file From 5099abc7338e9eed379ee528d1f285d78e0ed42c Mon Sep 17 00:00:00 2001 From: Stepami Date: Sun, 21 Dec 2025 17:43:13 +0300 Subject: [PATCH 06/14] fix v2 cache --- .../src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs index 1e62b8e9..e1ba03e4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs @@ -59,8 +59,7 @@ public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontS //FontSelector1 selector = new FontSelector1(fontFamilyName, style); string fontDescriptorKey = FontDescriptor.ComputeFdKey(fontFamilyName, style); - var cache = Globals.Global.Fonts.FontDescriptorCache; - Locks.EnterFontFactory(); + var cache = Globals.Global.Fonts.FontDescriptorCacheV2; if (!cache.TryGetValue(fontDescriptorKey, out var descriptor)) { var font = new XFont(fontFamilyName, 10, style); @@ -69,7 +68,7 @@ public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontS if (cache.ContainsKey(fontDescriptorKey)) _ = typeof(int); // Just a NOP for a break point. else - cache.Add(fontDescriptorKey, descriptor); + cache.TryAdd(fontDescriptorKey, descriptor); } return descriptor; } From 39e13c1ade3714302918e735ba74030a0b1d75e5 Mon Sep 17 00:00:00 2001 From: Stepami Date: Mon, 12 Jan 2026 23:54:25 +0300 Subject: [PATCH 07/14] render benchmark --- .../PdfSharp.Benchmarks/BenchmarkHelper.cs | 49 ++++++++++ .../PdfSharp.Benchmarks.csproj | 1 + .../src/PdfSharp.Benchmarks/Program.cs | 53 ++++------- .../src/PdfSharp.Benchmarks/readme.md | 9 ++ .../PdfSharp/Fonts.OpenType/FontDescriptor.cs | 2 +- .../src/PdfSharp/Fonts/FontDescriptorCache.cs | 4 +- .../PdfSharp/Fonts/FontDescriptorCacheV2.cs | 95 ------------------- 7 files changed, 83 insertions(+), 130 deletions(-) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs new file mode 100644 index 00000000..6726a290 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs @@ -0,0 +1,49 @@ +using PdfSharp.Drawing; +using PdfSharp.Pdf; + +namespace PdfSharp.Benchmarks; + +public static class BenchmarkHelper +{ + public static PdfDocument CreateSampleDocument() + { + // Create a new PDF document. + var result = new PdfDocument(); + result.Info.Title = "Created with PDFsharp"; + result.Info.Subject = "Just a simple Hello-World program."; + + // Create an empty page in this document. + var page = result.AddPage(); + page.Size = PageSize.A4; + + // Get an XGraphics object for drawing on this page. + var gfx = XGraphics.FromPdfPage(page); + + // Create a font. + var font = new XFont("Times New Roman", 12, XFontStyleEx.BoldItalic); + + // Draw the text. + gfx.DrawString( + "Hello, PDFsharp!", font, XBrushes.Black, + new XRect(0, 0, page.Width.Point, page.Height.Point), XStringFormats.TopLeft); + for (var i = 0; i < 20; i += 2) + { + var numbers = Enumerable.Repeat( + () => Random.Shared.Next(100, 999), 10) + .Select(x => x()); + var line = string.Join( + ", ", + ); + gfx.DrawString( + line, font, XBrushes.Black, + new XRect(0, (i + 1) * 20, page.Width.Point, page.Height.Point), + XStringFormats.TopLeft); + gfx.DrawString( + Guid.NewGuid().ToString(), font, XBrushes.Black, + new XRect(0, (i + 2) * 20, page.Width.Point, page.Height.Point), + XStringFormats.TopLeft); + } + + return result; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj index 78d935f7..cbe249ad 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj @@ -9,6 +9,7 @@ + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs index 087d2d16..dc438b1f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs @@ -2,52 +2,41 @@ using BenchmarkDotNet.Engines; using BenchmarkDotNet.Jobs; using BenchmarkDotNet.Running; -using PdfSharp.Drawing; +using PdfSharp.Benchmarks; using PdfSharp.Fonts; +using PdfSharp.Quality; -BenchmarkRunner.Run(); +/* +GlobalFontSettings.FontResolver = new SamplesFontResolver(); +var document = CreateSampleDocument(); +var filename = PdfFileUtility.GetTempPdfFullFileName("samples/HelloWorldSample"); +await document.SaveAsync(filename); +// ...and start a viewer. +PdfFileUtility.ShowDocument(filename); +*/ + +BenchmarkRunner.Run(); [SimpleJob(RuntimeMoniker.Net80)] -public class FontCacheBenchmarks +public class PdfDocumentBenchmarks { - private static readonly XGlyphTypeface GlyphTypeface = new( - XFontSource.CreateFromFile(@"C:\Windows\Fonts\arial.ttf")); private static readonly Consumer Consumer = new(); [Params(1, 10, 100)] public int Iterations { get; set; } - [GlobalSetup] - public void GlobalSetup() - { - FontDescriptorCache.Reset(); - FontDescriptorCacheV2.Reset(); - } - - [GlobalCleanup] - public void GlobalCleanup() - { - FontDescriptorCache.Reset(); - FontDescriptorCacheV2.Reset(); - } - - [Benchmark(Baseline = true)] - public void LockFactory() - { - for (var i = 0; i < Iterations; i++) - { - Consumer.Consume( - FontDescriptorCache.GetOrCreateDescriptorFor(GlyphTypeface)); - } - } - [Benchmark] - public void ConcurrentDictionary() + public async Task CreateAndSave() { for (var i = 0; i < Iterations; i++) { - Consumer.Consume( - FontDescriptorCacheV2.GetOrCreateDescriptorFor(GlyphTypeface)); + var document = BenchmarkHelper.CreateSampleDocument(); + Consumer.Consume(document); + await document.SaveAsync(Stream.Null); } } + + [GlobalSetup] + public void GlobalSetup() => + GlobalFontSettings.FontResolver = new SamplesFontResolver(); } \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md new file mode 100644 index 00000000..2bddf255 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md @@ -0,0 +1,9 @@ +# before + +| Method | Iterations | Mean | Error | StdDev | Median | +|---------------|------------|------------:|------------:|------------:|------------:| +| CreateAndSave | 1 | 958.3 us | 18.85 us | 21.70 us | 960.2 us | +| CreateAndSave | 10 | 9,723.9 us | 191.92 us | 488.50 us | 9,560.7 us | +| CreateAndSave | 100 | 94,945.9 us | 1,892.31 us | 1,770.07 us | 94,644.6 us | + +# after \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs index 21aabcaa..c481dc50 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs @@ -22,7 +22,7 @@ namespace PdfSharp.Fonts.OpenType /// Base class for all font descriptors. /// Currently only OpenTypeDescriptor is derived from this base class. /// - public class FontDescriptor + class FontDescriptor { protected FontDescriptor(string key) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs index 0711a40f..9a3b711b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs @@ -9,7 +9,7 @@ namespace PdfSharp.Fonts /// /// Global table of OpenType font descriptor objects. /// - public static class FontDescriptorCache + static class FontDescriptorCache { /// /// Gets the FontDescriptor identified by the specified XFont. If no such object @@ -87,7 +87,7 @@ public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontS finally { Locks.ExitFontFactory(); } } - public static void Reset() + internal static void Reset() { Globals.Global.Fonts.FontDescriptorCache.Clear(); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs deleted file mode 100644 index e1ba03e4..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCacheV2.cs +++ /dev/null @@ -1,95 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using System.Collections.Concurrent; -using PdfSharp.Drawing; -using PdfSharp.Fonts.OpenType; - -namespace PdfSharp.Fonts -{ - /// - /// Global table of OpenType font descriptor objects. - /// - public static class FontDescriptorCacheV2 - { - /// - /// Gets the FontDescriptor identified by the specified XFont. If no such object - /// exists, a new FontDescriptor is created and added to the cache. - /// - public static FontDescriptor GetOrCreateDescriptorFor(XFont font) - { - if (font == null) - throw new ArgumentNullException(nameof(font)); - - font.GlyphTypeface.CheckVersion(); - - //FontSelector1 selector = new FontSelector1(font); - string fontDescriptorKey = font.GlyphTypeface.Key; - var cache = Globals.Global.Fonts.FontDescriptorCacheV2; - if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) - return descriptor; - - descriptor = new OpenTypeDescriptor(fontDescriptorKey, font); - cache.TryAdd(fontDescriptorKey, descriptor); - return descriptor; - } - - public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypeface) - { - glyphTypeface.CheckVersion(); - - string fontDescriptorKey = glyphTypeface.Key; - var cache = Globals.Global.Fonts.FontDescriptorCacheV2; - if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) - return descriptor; - - descriptor = new OpenTypeDescriptor(fontDescriptorKey, glyphTypeface); - cache.TryAdd(fontDescriptorKey, descriptor); - return descriptor; - } - - /// - /// Gets the FontDescriptor identified by the specified FontSelector. If no such object - /// exists, a new FontDescriptor is created and added to the stock. - /// - public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontStyleEx style) - { - if (String.IsNullOrEmpty(fontFamilyName)) - throw new ArgumentNullException(nameof(fontFamilyName)); - - //FontSelector1 selector = new FontSelector1(fontFamilyName, style); - string fontDescriptorKey = FontDescriptor.ComputeFdKey(fontFamilyName, style); - var cache = Globals.Global.Fonts.FontDescriptorCacheV2; - if (!cache.TryGetValue(fontDescriptorKey, out var descriptor)) - { - var font = new XFont(fontFamilyName, 10, style); - descriptor = GetOrCreateDescriptorFor(font); - // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because there is not TryAdd in .NET Framework - if (cache.ContainsKey(fontDescriptorKey)) - _ = typeof(int); // Just a NOP for a break point. - else - cache.TryAdd(fontDescriptorKey, descriptor); - } - return descriptor; - } - - public static void Reset() - { - Globals.Global.Fonts.FontDescriptorCacheV2.Clear(); - } - } -} - -namespace PdfSharp.Internal -{ - partial class Globals - { - partial class FontStorage - { - /// - /// Maps font descriptor key to font descriptor which is currently only an OpenTypeFontDescriptor. - /// - public readonly ConcurrentDictionary FontDescriptorCacheV2 = []; - } - } -} From 9f9dbd760c93e6b892e5b3bc2ae022b11d5bda0c Mon Sep 17 00:00:00 2001 From: Stepami Date: Tue, 13 Jan 2026 00:38:15 +0300 Subject: [PATCH 08/14] refactor to concurrent dictionary --- .../PDFsharp/src/PdfSharp/Drawing/XFont.cs | 47 ++- .../src/PdfSharp/Drawing/XGlyphTypeface.cs | 214 ++++++----- .../Fonts.OpenType/GlyphTypefaceCache.cs | 23 +- .../Fonts.OpenType/OpenTypeFontfaceCache.cs | 43 +-- .../src/PdfSharp/Fonts/FontDescriptorCache.cs | 63 ++-- .../src/PdfSharp/Fonts/FontFactory.cs | 344 ++++++++---------- .../src/PdfSharp/Fonts/FontFamilyCache.cs | 31 +- .../src/PdfSharp/Fonts/FontFamilyInternal.cs | 27 +- 8 files changed, 350 insertions(+), 442 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs index ab342412..719eec7d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs @@ -372,36 +372,31 @@ void Initialize() /// void InitializeFromGdi() { - try + if (GdiFontFamily != null!) + { + // Create font based on its family. + GdiFont = new Font(GdiFontFamily, (float)_emSize, (GdiFontStyle)_style, GraphicsUnit.World); + } + + if (GdiFont != null!) { - Locks.EnterFontFactory(); - if (GdiFontFamily != null!) - { - // Create font based on its family. - GdiFont = new Font(GdiFontFamily, (float)_emSize, (GdiFontStyle)_style, GraphicsUnit.World); - } - - if (GdiFont != null!) - { #if DEBUG_ - string name1 = _gdiFont.Name; - string name2 = _gdiFont.OriginalFontName; - string name3 = _gdiFont.SystemFontName; + string name1 = _gdiFont.Name; + string name2 = _gdiFont.OriginalFontName; + string name3 = _gdiFont.SystemFontName; #endif - _familyName = GdiFont.FontFamily.Name; - // TODO_OLD: _glyphTypeface = XGlyphTypeface.GetOrCreateFrom(_gdiFont); - } - else - { - Debug.Assert(false); - } - - if (GlyphTypeface == null!) - GlyphTypeface = XGlyphTypeface.GetOrCreateFromGdi(GdiFont); - - CreateDescriptorAndInitializeFontMetrics(); + _familyName = GdiFont.FontFamily.Name; + // TODO_OLD: _glyphTypeface = XGlyphTypeface.GetOrCreateFrom(_gdiFont); } - finally { Locks.ExitFontFactory(); } + else + { + Debug.Assert(false); + } + + if (GlyphTypeface == null!) + GlyphTypeface = XGlyphTypeface.GetOrCreateFromGdi(GdiFont); + + CreateDescriptorAndInitializeFontMetrics(); } #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs index d7b6c902..8ad3955e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs @@ -138,131 +138,129 @@ internal static XGlyphTypeface GetOrCreateFrom(string familyName, FontResolvingO // Check cache for requested type face. string typefaceKey = ComputeGtfKey(familyName, fontResolvingOptions); XGlyphTypeface? glyphTypeface; - try + if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) { - // Lock around TryGetGlyphTypeface and AddGlyphTypeface. - Locks.EnterFontFactory(); - if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) - { - // Just return existing one. - return glyphTypeface; - } + // Just return existing one. + return glyphTypeface; + } + + //// Resolve typeface by FontFactory. If no success, try fallback font resolver. + //var fontResolverInfo = FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, false) ?? + // FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, true); + FontResolverInfo? fontResolverInfo = null; + try // Custom font resolvers may throw an exception. + { + // Try custom font resolver. + fontResolverInfo = FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, false); + } + catch (Exception ex) + { + LogErrorBecauseFontResolverThrowsException(familyName, ex); + } - //// Resolve typeface by FontFactory. If no success, try fallback font resolver. - //var fontResolverInfo = FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, false) ?? - // FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, true); - FontResolverInfo? fontResolverInfo = null; - try // Custom font resolvers may throw an exception. + if (fontResolverInfo == null) + { + try // Custom fallback font resolvers may throw an exception. { - // Try custom font resolver. - fontResolverInfo = FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, false); + // Try fallback font resolver. + fontResolverInfo = FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, true); } catch (Exception ex) { LogErrorBecauseFontResolverThrowsException(familyName, ex); } + } - if (fontResolverInfo == null) - { - try // Custom fallback font resolvers may throw an exception. - { - // Try fallback font resolver. - fontResolverInfo = FontFactory.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey, true); - } - catch (Exception ex) - { - LogErrorBecauseFontResolverThrowsException(familyName, ex); - } - } - - void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) - => PdfSharpLogHost.Logger.LogError("A font resolver cannot resolve font family '{}' and throws an exception, " + - "but it must return null if the font cannot be resolved. Exception text: " + ex.Message, name); + void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) + => PdfSharpLogHost.Logger.LogError( + "A font resolver cannot resolve font family '{}' and throws an exception, " + + "but it must return null if the font cannot be resolved. Exception text: " + ex.Message, name); - if (fontResolverInfo == null) - { - // No fallback - just stop. + if (fontResolverInfo == null) + { + // No fallback - just stop. #if CORE - if (GlobalFontSettings.FontResolver is null) - { - throw new InvalidOperationException( - $"No appropriate font found for family name '{familyName}'. " + - "Implement IFontResolver and assign to 'GlobalFontSettings.FontResolver' to use fonts. " + - $"See {UrlLiterals.LinkToFontResolving}"); - } -#endif - throw new InvalidOperationException($"No appropriate font found for family name '{familyName}'."); + if (GlobalFontSettings.FontResolver is null) + { + throw new InvalidOperationException( + $"No appropriate font found for family name '{familyName}'. " + + "Implement IFontResolver and assign to 'GlobalFontSettings.FontResolver' to use fonts. " + + $"See {UrlLiterals.LinkToFontResolving}"); } +#endif + throw new InvalidOperationException($"No appropriate font found for family name '{familyName}'."); + } #if GDI - GdiFont gdiFont = default!; + GdiFont gdiFont = default!; #endif #if WPF - // ReSharper disable once TooWideLocalVariableScope - // ReSharper disable once RedundantAssignment - WpfFontFamily? wpfFontFamily = null; - WpfTypeface? wpfTypeface = null; - WpfGlyphTypeface? wpfGlyphTypeface = null; + // ReSharper disable once TooWideLocalVariableScope + // ReSharper disable once RedundantAssignment + WpfFontFamily? wpfFontFamily = null; + WpfTypeface? wpfTypeface = null; + WpfGlyphTypeface? wpfGlyphTypeface = null; #endif #if WUI - // Nothing to do. + // Nothing to do. #endif - // Now create the font family at the first. - XFontFamily? fontFamily; - if (fontResolverInfo is PlatformFontResolverInfo platformFontResolverInfo) - { - // Case: fontResolverInfo was created by platform font resolver - // and contains platform specific objects that are reused. + // Now create the font family at the first. + XFontFamily? fontFamily; + if (fontResolverInfo is PlatformFontResolverInfo platformFontResolverInfo) + { + // Case: fontResolverInfo was created by platform font resolver + // and contains platform specific objects that are reused. #if CORE - // Get or create font family for custom font resolver retrieved font source. - fontFamily = XFontFamily.GetOrCreateFontFamily(familyName); - //// Cannot come here - //fontFamily = null; - //Debug.Assert(false); + // Get or create font family for custom font resolver retrieved font source. + fontFamily = XFontFamily.GetOrCreateFontFamily(familyName); + //// Cannot come here + //fontFamily = null; + //Debug.Assert(false); #endif #if GDI - // Reuse GDI+ font from platform font resolver. - gdiFont = platformFontResolverInfo.GdiFont; - fontFamily = XFontFamily.GetOrCreateFromGdi(gdiFont); + // Reuse GDI+ font from platform font resolver. + gdiFont = platformFontResolverInfo.GdiFont; + fontFamily = XFontFamily.GetOrCreateFromGdi(gdiFont); #endif #if WPF - // Reuse WPF font family created from platform font resolver. - wpfFontFamily = platformFontResolverInfo.WpfFontFamily; - wpfTypeface = platformFontResolverInfo.WpfTypeface; - wpfGlyphTypeface = platformFontResolverInfo.WpfGlyphTypeface; - fontFamily = XFontFamily.GetOrCreateFromWpf(wpfFontFamily); + // Reuse WPF font family created from platform font resolver. + wpfFontFamily = platformFontResolverInfo.WpfFontFamily; + wpfTypeface = platformFontResolverInfo.WpfTypeface; + wpfGlyphTypeface = platformFontResolverInfo.WpfGlyphTypeface; + fontFamily = XFontFamily.GetOrCreateFromWpf(wpfFontFamily); #endif #if WUI - fontFamily = null; + fontFamily = null; #endif - } - else - { - // Case: fontResolverInfo was created by custom font resolver. + } + else + { + // Case: fontResolverInfo was created by custom font resolver. - // Get or create font family for custom font resolver retrieved font source. - fontFamily = XFontFamily.GetOrCreateFontFamily(familyName); - } + // Get or create font family for custom font resolver retrieved font source. + fontFamily = XFontFamily.GetOrCreateFontFamily(familyName); + } - // We have a valid font resolver info. That means we also have an XFontSource object loaded in the cache. - XFontSource fontSource = FontFactory.GetFontSourceByFontName(fontResolverInfo.FaceName); - Debug.Assert(fontSource != null); + // We have a valid font resolver info. That means we also have an XFontSource object loaded in the cache. + XFontSource fontSource = FontFactory.GetFontSourceByFontName(fontResolverInfo.FaceName); + Debug.Assert(fontSource != null); - // Each font source already contains its OpenTypeFontFace. + // Each font source already contains its OpenTypeFontFace. #if CORE - glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations); + glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations); #endif #if GDI - glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations, gdiFont); + glyphTypeface = + new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations, gdiFont); #endif #if WPF - glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations, wpfTypeface, wpfGlyphTypeface); + glyphTypeface = + new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations, wpfTypeface, wpfGlyphTypeface); #endif #if WUI - glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations); + glyphTypeface = + new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations); #endif - GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); - } - finally { Locks.ExitFontFactory(); } + GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); return glyphTypeface; } @@ -274,33 +272,27 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) { XGlyphTypeface? glyphTypeface; - try + string typefaceKey = ComputeGtfKey(gdiFont); + if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) { - // Lock around TryGetGlyphTypeface and AddGlyphTypeface. - Locks.EnterFontFactory(); - string typefaceKey = ComputeGtfKey(gdiFont); - if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) - { - // We have the glyph typeface already in cache. - return glyphTypeface; - } + // We have the glyph typeface already in cache. + return glyphTypeface; + } - var fontFamily = XFontFamily.GetOrCreateFromGdi(gdiFont); - Debug.Assert(fontFamily != null); - var fontSource = XFontSource.GetOrCreateFromGdi(typefaceKey, gdiFont); - Debug.Assert(fontSource != null); + var fontFamily = XFontFamily.GetOrCreateFromGdi(gdiFont); + Debug.Assert(fontFamily != null); + var fontSource = XFontSource.GetOrCreateFromGdi(typefaceKey, gdiFont); + Debug.Assert(fontSource != null); - // Check if styles must be simulated. - XStyleSimulations styleSimulations = XStyleSimulations.None; - if (gdiFont.Bold && !fontSource.FontFace.os2.IsBold) - styleSimulations |= XStyleSimulations.BoldSimulation; - if (gdiFont.Italic && !fontSource.FontFace.os2.IsItalic) - styleSimulations |= XStyleSimulations.ItalicSimulation; + // Check if styles must be simulated. + XStyleSimulations styleSimulations = XStyleSimulations.None; + if (gdiFont.Bold && !fontSource.FontFace.os2.IsBold) + styleSimulations |= XStyleSimulations.BoldSimulation; + if (gdiFont.Italic && !fontSource.FontFace.os2.IsItalic) + styleSimulations |= XStyleSimulations.ItalicSimulation; - glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, styleSimulations, gdiFont); - GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); - } - finally { Locks.ExitFontFactory(); } + glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, styleSimulations, gdiFont); + GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); return glyphTypeface; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs index 13e04a2c..e8e85a2b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs @@ -1,10 +1,10 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Collections.Concurrent; using System.Diagnostics.CodeAnalysis; using System.Text; using PdfSharp.Drawing; -using PdfSharp.Internal; namespace PdfSharp.Fonts.OpenType { @@ -15,24 +15,13 @@ static class GlyphTypefaceCache { public static bool TryGetGlyphTypeface(string key, [MaybeNullWhen(false)] out XGlyphTypeface glyphTypeface) { - try - { - Locks.EnterFontFactory(); - bool result = Globals.Global.Fonts.GlyphTypefacesByKey.TryGetValue(key, out glyphTypeface); - return result; - } - finally { Locks.ExitFontFactory(); } + return Globals.Global.Fonts.GlyphTypefacesByKey.TryGetValue(key, out glyphTypeface); } public static void AddGlyphTypeface(XGlyphTypeface glyphTypeface) { - try - { - Locks.EnterFontFactory(); - Debug.Assert(!Globals.Global.Fonts.GlyphTypefacesByKey.ContainsKey(glyphTypeface.Key)); - Globals.Global.Fonts.GlyphTypefacesByKey.Add(glyphTypeface.Key, glyphTypeface); - } - finally { Locks.ExitFontFactory(); } + Debug.Assert(!Globals.Global.Fonts.GlyphTypefacesByKey.ContainsKey(glyphTypeface.Key)); + Globals.Global.Fonts.GlyphTypefacesByKey.TryAdd(glyphTypeface.Key, glyphTypeface); } internal static void Reset() @@ -45,7 +34,7 @@ internal static string GetCacheState() var state = new StringBuilder(); state.Append("====================\n"); state.Append("Glyph typefaces by name\n"); - Dictionary.KeyCollection familyKeys = Globals.Global.Fonts.GlyphTypefacesByKey.Keys; + var familyKeys = Globals.Global.Fonts.GlyphTypefacesByKey.Keys; int count = familyKeys.Count; string[] keys = new string[count]; familyKeys.CopyTo(keys, 0); @@ -67,7 +56,7 @@ partial class FontStorage /// /// Maps typeface key to glyph typeface. /// - public readonly Dictionary GlyphTypefacesByKey = new(StringComparer.Ordinal); + public readonly ConcurrentDictionary GlyphTypefacesByKey = new(StringComparer.Ordinal); } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs index 99ad3332..3d9ac654 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs @@ -1,6 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Collections.Concurrent; using System.Text; using PdfSharp.Internal; using System.Diagnostics.CodeAnalysis; @@ -21,13 +22,7 @@ public static bool TryGetFontFace(string key, [MaybeNullWhen(false)] out OpenTypeFontFace fontFace) { - try - { - Locks.EnterFontFactory(); - var result = Globals.Global.Fonts.FontFaceCache.TryGetValue(key, out fontFace); - return result; - } - finally { Locks.ExitFontFactory(); } + return Globals.Global.Fonts.FontFaceCache.TryGetValue(key, out fontFace); } /// @@ -37,31 +32,21 @@ public static bool TryGetFontFace(ulong checkSum, [MaybeNullWhen(false)] out OpenTypeFontFace fontFace) { - try - { - Locks.EnterFontFactory(); - var result = Globals.Global.Fonts.FontFacesByCheckSum.TryGetValue(checkSum, out fontFace); - return result; - } - finally { Locks.ExitFontFactory(); } + return Globals.Global.Fonts.FontFacesByCheckSum.TryGetValue(checkSum, out fontFace); } public static OpenTypeFontFace AddFontFace(OpenTypeFontFace fontFace) { - try + if (TryGetFontFace(fontFace.FullFaceName, out var fontFaceCheck)) { - Locks.EnterFontFactory(); - if (TryGetFontFace(fontFace.FullFaceName, out var fontFaceCheck)) - { - if (fontFaceCheck.CheckSum != fontFace.CheckSum) - throw new InvalidOperationException("OpenTypeFontFace with same signature but different bytes."); - return fontFaceCheck; - } - Globals.Global.Fonts.FontFaceCache.Add(fontFace.FullFaceName, fontFace); - Globals.Global.Fonts.FontFacesByCheckSum.Add(fontFace.CheckSum, fontFace); - return fontFace; + if (fontFaceCheck.CheckSum != fontFace.CheckSum) + throw new InvalidOperationException("OpenTypeFontFace with same signature but different bytes."); + return fontFaceCheck; } - finally { Locks.ExitFontFactory(); } + + Globals.Global.Fonts.FontFaceCache.TryAdd(fontFace.FullFaceName, fontFace); + Globals.Global.Fonts.FontFacesByCheckSum.TryAdd(fontFace.CheckSum, fontFace); + return fontFace; } internal static void Reset() @@ -75,7 +60,7 @@ internal static string GetCacheState() StringBuilder state = new StringBuilder(); state.Append("====================\n"); state.Append("OpenType font faces by name\n"); - Dictionary.KeyCollection familyKeys = Globals.Global.Fonts.FontFaceCache.Keys; + var familyKeys = Globals.Global.Fonts.FontFaceCache.Keys; int count = familyKeys.Count; string[] keys = new string[count]; familyKeys.CopyTo(keys, 0); @@ -105,12 +90,12 @@ partial class FontStorage /// /// Maps face name to OpenType font face. /// - public readonly Dictionary FontFaceCache = []; + public readonly ConcurrentDictionary FontFaceCache = []; /// /// Maps font source key to OpenType font face. /// - public readonly Dictionary FontFacesByCheckSum = []; + public readonly ConcurrentDictionary FontFacesByCheckSum = []; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs index 9a3b711b..a337b370 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs @@ -1,6 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Collections.Concurrent; using PdfSharp.Drawing; using PdfSharp.Fonts.OpenType; @@ -24,18 +25,13 @@ public static FontDescriptor GetOrCreateDescriptorFor(XFont font) //FontSelector1 selector = new FontSelector1(font); string fontDescriptorKey = font.GlyphTypeface.Key; - try - { - var cache = Globals.Global.Fonts.FontDescriptorCache; - Locks.EnterFontFactory(); - if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) - return descriptor; - - descriptor = new OpenTypeDescriptor(fontDescriptorKey, font); - cache.Add(fontDescriptorKey, descriptor); + var cache = Globals.Global.Fonts.FontDescriptorCache; + if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) return descriptor; - } - finally { Locks.ExitFontFactory(); } + + descriptor = new OpenTypeDescriptor(fontDescriptorKey, font); + cache.TryAdd(fontDescriptorKey, descriptor); + return descriptor; } public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypeface) @@ -43,18 +39,13 @@ public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypefa glyphTypeface.CheckVersion(); string fontDescriptorKey = glyphTypeface.Key; - try - { - var cache = Globals.Global.Fonts.FontDescriptorCache; - Locks.EnterFontFactory(); - if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) - return descriptor; - - descriptor = new OpenTypeDescriptor(fontDescriptorKey, glyphTypeface); - cache.Add(fontDescriptorKey, descriptor); + var cache = Globals.Global.Fonts.FontDescriptorCache; + if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) return descriptor; - } - finally { Locks.ExitFontFactory(); } + + descriptor = new OpenTypeDescriptor(fontDescriptorKey, glyphTypeface); + cache.TryAdd(fontDescriptorKey, descriptor); + return descriptor; } /// @@ -68,23 +59,19 @@ public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontS //FontSelector1 selector = new FontSelector1(fontFamilyName, style); string fontDescriptorKey = FontDescriptor.ComputeFdKey(fontFamilyName, style); - try + var cache = Globals.Global.Fonts.FontDescriptorCache; + if (!cache.TryGetValue(fontDescriptorKey, out var descriptor)) { - var cache = Globals.Global.Fonts.FontDescriptorCache; - Locks.EnterFontFactory(); - if (!cache.TryGetValue(fontDescriptorKey, out var descriptor)) - { - var font = new XFont(fontFamilyName, 10, style); - descriptor = GetOrCreateDescriptorFor(font); - // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because there is not TryAdd in .NET Framework - if (cache.ContainsKey(fontDescriptorKey)) - _ = typeof(int); // Just a NOP for a break point. - else - cache.Add(fontDescriptorKey, descriptor); - } - return descriptor; + var font = new XFont(fontFamilyName, 10, style); + descriptor = GetOrCreateDescriptorFor(font); + // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because there is not TryAdd in .NET Framework + if (cache.ContainsKey(fontDescriptorKey)) + _ = typeof(int); // Just a NOP for a break point. + else + cache.TryAdd(fontDescriptorKey, descriptor); } - finally { Locks.ExitFontFactory(); } + + return descriptor; } internal static void Reset() @@ -103,7 +90,7 @@ partial class FontStorage /// /// Maps font descriptor key to font descriptor which is currently only an OpenTypeFontDescriptor. /// - public readonly Dictionary FontDescriptorCache = []; + public readonly ConcurrentDictionary FontDescriptorCache = []; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs index 78257168..21a4a15e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs @@ -1,6 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Collections.Concurrent; using System.Text; #if GDI using System.Drawing; @@ -18,7 +19,6 @@ using PdfSharp.Drawing; using PdfSharp.Fonts.Internal; using PdfSharp.Fonts.OpenType; -using PdfSharp.Internal; using System.Diagnostics.CodeAnalysis; using Microsoft.Extensions.Logging; using PdfSharp.Logging; @@ -42,78 +42,74 @@ static class FontFactory /// /// Information about the typeface, or null if no typeface can be found. /// - public static FontResolverInfo? ResolveTypeface(string familyName, FontResolvingOptions fontResolvingOptions, string typefaceKey, bool useFallbackFontResolver) + public static FontResolverInfo? ResolveTypeface(string familyName, FontResolvingOptions fontResolvingOptions, + string typefaceKey, bool useFallbackFontResolver) { if (String.IsNullOrEmpty(typefaceKey)) typefaceKey = XGlyphTypeface.ComputeGtfKey(familyName, fontResolvingOptions); - try - { - var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; - var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; + var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; - Locks.EnterFontFactory(); - // Was this typeface requested before? - if (fontResolverInfosByName.TryGetValue(typefaceKey, out var fontResolverInfo)) - return fontResolverInfo; + // Was this typeface requested before? + if (fontResolverInfosByName.TryGetValue(typefaceKey, out var fontResolverInfo)) + return fontResolverInfo; - // Case: This typeface was not yet resolved before. + // Case: This typeface was not yet resolved before. - // Choose the font resolver to invoke. - IFontResolver? fontResolver; - if (useFallbackFontResolver) - { - // Use fallback font resolver if one is set. - fontResolver = GlobalFontSettings.FallbackFontResolver; + // Choose the font resolver to invoke. + IFontResolver? fontResolver; + if (useFallbackFontResolver) + { + // Use fallback font resolver if one is set. + fontResolver = GlobalFontSettings.FallbackFontResolver; - if (fontResolver != null) - { - // Set a flag to prevent the case that a font resolver used as fall back font resolver - // illegally tries to invoke PlatformFontResolver.ResolveTypeface. - _fallbackFontResolverInvoked = true; - } - } - else + if (fontResolver != null) { - // Use regular font resolver if one is set. - fontResolver = GlobalFontSettings.FontResolver; + // Set a flag to prevent the case that a font resolver used as fall back font resolver + // illegally tries to invoke PlatformFontResolver.ResolveTypeface. + _fallbackFontResolverInvoked = true; } + } + else + { + // Use regular font resolver if one is set. + fontResolver = GlobalFontSettings.FontResolver; + } - if (fontResolver != null) - { - // Case: Use custom font resolver. - fontResolverInfo = fontResolver.ResolveTypeface(familyName, fontResolvingOptions.IsBold, - fontResolvingOptions.IsItalic); + if (fontResolver != null) + { + // Case: Use custom font resolver. + fontResolverInfo = fontResolver.ResolveTypeface(familyName, fontResolvingOptions.IsBold, + fontResolvingOptions.IsItalic); - // If resolved by custom font resolver register info and font source. - // If the custom font resolver calls the platform font resolver registration is already done. - if (fontResolverInfo != null && fontResolverInfo is not PlatformFontResolverInfo) - { - RegisterResolverResult(fontResolver, familyName, fontResolvingOptions, fontResolverInfo, typefaceKey); - } - } - else + // If resolved by custom font resolver register info and font source. + // If the custom font resolver calls the platform font resolver registration is already done. + if (fontResolverInfo != null && fontResolverInfo is not PlatformFontResolverInfo) { - // Case: There was no custom font resolver set. - // Use platform font resolver. - // If it was successful resolver info and font source are cached - // automatically by PlatformFontResolver.ResolveTypeface. - if (!useFallbackFontResolver) - { - fontResolverInfo = PlatformFontResolver.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey); - } + RegisterResolverResult(fontResolver, familyName, fontResolvingOptions, fontResolverInfo, + typefaceKey); } - - // Return value is null if the typeface could not be resolved. - // In this case PDFsharp throws an exception. - return fontResolverInfo; } - finally + else { - _fallbackFontResolverInvoked = false; - Locks.ExitFontFactory(); + // Case: There was no custom font resolver set. + // Use platform font resolver. + // If it was successful resolver info and font source are cached + // automatically by PlatformFontResolver.ResolveTypeface. + if (!useFallbackFontResolver) + { + fontResolverInfo = + PlatformFontResolver.ResolveTypeface(familyName, fontResolvingOptions, typefaceKey); + } } + + // Return value is null if the typeface could not be resolved. + // In this case PDFsharp throws an exception. + + _fallbackFontResolverInvoked = false; + return fontResolverInfo; } + static bool _fallbackFontResolverInvoked; /// @@ -125,7 +121,8 @@ static class FontFactory /// /// /// - internal static void RegisterResolverResult(IFontResolver fontResolver, string familyName, FontResolvingOptions fontResolvingOptions, + internal static void RegisterResolverResult(IFontResolver fontResolver, string familyName, + FontResolvingOptions fontResolvingOptions, FontResolverInfo fontResolverInfo, string typefaceKey) { #if CORE && DEBUG @@ -136,88 +133,82 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f if (platformInfo) Debug.Assert(fontResolver is WindowsPlatformFontResolver); #endif - try - { - var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; - var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; - - Locks.EnterFontFactory(); + var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; + var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; - // OverrideStyleSimulations is true only for internal quality tests. - // With this code we can simulate bold and/or italic for a font face even if - // a native font face exists. This is done only vor internal testing. - if (fontResolvingOptions.OverrideStyleSimulations) - { - // Override style simulation returned by CFR or OSFR. - // We do not create a new object here to preserve the fact that the font resolver info - // comes from a platform font resolver. - fontResolverInfo.MustSimulateBold = fontResolvingOptions.MustSimulateBold; - fontResolverInfo.MustSimulateItalic = fontResolvingOptions.MustSimulateItalic; - //fontResolverInfo = new FontResolverInfo(fontResolverInfo.FaceName, fontResolvingOptions.MustSimulateBold, fontResolvingOptions.MustSimulateItalic, fontResolverInfo.CollectionNumber); - } + // OverrideStyleSimulations is true only for internal quality tests. + // With this code we can simulate bold and/or italic for a font face even if + // a native font face exists. This is done only vor internal testing. + if (fontResolvingOptions.OverrideStyleSimulations) + { + // Override style simulation returned by CFR or OSFR. + // We do not create a new object here to preserve the fact that the font resolver info + // comes from a platform font resolver. + fontResolverInfo.MustSimulateBold = fontResolvingOptions.MustSimulateBold; + fontResolverInfo.MustSimulateItalic = fontResolvingOptions.MustSimulateItalic; + //fontResolverInfo = new FontResolverInfo(fontResolverInfo.FaceName, fontResolvingOptions.MustSimulateBold, fontResolvingOptions.MustSimulateItalic, fontResolverInfo.CollectionNumber); + } - // The typeface key was never resolved before, because otherwise we would not come here. - // But it is possible that it was mapped to the same resolver info as another typeface earlier. - string resolverInfoKey = fontResolverInfo.Key; - if (fontResolverInfosByName.TryGetValue(resolverInfoKey, out var existingFontResolverInfo)) - { - // Case: A new typeface was resolved to the same info as a previous one. - // Discard new object and reuse previous one. - fontResolverInfo = existingFontResolverInfo; - // Associate existing resolver info with the new typeface key. - fontResolverInfosByName.Add(typefaceKey, fontResolverInfo); + // The typeface key was never resolved before, because otherwise we would not come here. + // But it is possible that it was mapped to the same resolver info as another typeface earlier. + string resolverInfoKey = fontResolverInfo.Key; + if (fontResolverInfosByName.TryGetValue(resolverInfoKey, out var existingFontResolverInfo)) + { + // Case: A new typeface was resolved to the same info as a previous one. + // Discard new object and reuse previous one. + fontResolverInfo = existingFontResolverInfo; + // Associate existing resolver info with the new typeface key. + fontResolverInfosByName.TryAdd(typefaceKey, fontResolverInfo); #if DEBUG_ // The font source should exist. Debug.Assert(fontResolverInfosByName.ContainsKey(fontResolverInfo.FaceName)); #endif + } + else + { + // Case: No such font resolver info exists. + // Add typeface key to fontResolverInfosByName. + // Thereby resolving a typeface with the same key is not needed anymore. + fontResolverInfosByName.TryAdd(typefaceKey, fontResolverInfo); // Map TFK immediately to FRI. + // Add resolver info key to fontResolverInfosByName. + // Thereby a typeface with the same resolver info as a previous one is not cached twice. + fontResolverInfosByName.TryAdd(resolverInfoKey, fontResolverInfo); + + // Create font source if it does not yet exist. + // The face name is considered to be unique. So check if it already exists. + // Note that different resolver infos may map to the same font face because + // of style simulation. + if (fontSourcesByName.TryGetValue(fontResolverInfo.FaceName, out _)) + { + // Case: The font source exists, because a previous font resolver info comes + // with the same face name, but was different in style simulation flags. + // Nothing to do. } else { - // Case: No such font resolver info exists. - // Add typeface key to fontResolverInfosByName. - // Thereby resolving a typeface with the same key is not needed anymore. - fontResolverInfosByName.Add(typefaceKey, fontResolverInfo); // Map TFK immediately to FRI. - // Add resolver info key to fontResolverInfosByName. - // Thereby a typeface with the same resolver info as a previous one is not cached twice. - fontResolverInfosByName.Add(resolverInfoKey, fontResolverInfo); - - // Create font source if it does not yet exist. - // The face name is considered to be unique. So check if it already exists. - // Note that different resolver infos may map to the same font face because - // of style simulation. - if (fontSourcesByName.TryGetValue(fontResolverInfo.FaceName, out _)) + // Case: Get font from custom font resolver and create font source. + byte[]? bytes = fontResolver.GetFont(fontResolverInfo.FaceName); + if (bytes == null || bytes.Length == 0) { - // Case: The font source exists, because a previous font resolver info comes - // with the same face name, but was different in style simulation flags. - // Nothing to do. + var message = + $"The custom font resolver '{fontResolver.GetType().FullName}' resolved for typeface '{familyName}" + + $"{(fontResolvingOptions.IsItalic ? " italic" : "")}{(fontResolvingOptions.IsBold ? " bold" : "")}' " + + $"the face name '{fontResolverInfo.FaceName}', but returned no bytes for this name. " + + "This is most likely a bug in your custom font resolver."; + PdfSharpLogHost.Logger.LogCritical(message); + throw new InvalidOperationException(message); } - else - { - // Case: Get font from custom font resolver and create font source. - byte[]? bytes = fontResolver.GetFont(fontResolverInfo.FaceName); - if (bytes == null || bytes.Length == 0) - { - var message = - $"The custom font resolver '{fontResolver.GetType().FullName}' resolved for typeface '{familyName}" + - $"{(fontResolvingOptions.IsItalic ? " italic" : "")}{(fontResolvingOptions.IsBold ? " bold" : "")}' " + - $"the face name '{fontResolverInfo.FaceName}', but returned no bytes for this name. " + - "This is most likely a bug in your custom font resolver."; - PdfSharpLogHost.Logger.LogCritical(message); - throw new InvalidOperationException(message); - } - // Create a new font source if no such one exists. It can already exist if a - // custom font resolver returns the same bytes for more than one face name. - var fontSource = XFontSource.GetOrCreateFrom(bytes); + // Create a new font source if no such one exists. It can already exist if a + // custom font resolver returns the same bytes for more than one face name. + var fontSource = XFontSource.GetOrCreateFrom(bytes); - // Add font source’s font resolver name if it is different to the face name. - if (String.Compare(fontResolverInfo.FaceName, fontSource.FontName, - StringComparison.OrdinalIgnoreCase) != 0) - fontSourcesByName.Add(fontResolverInfo.FaceName, fontSource); - } + // Add font source’s font resolver name if it is different to the face name. + if (String.Compare(fontResolverInfo.FaceName, fontSource.FontName, + StringComparison.OrdinalIgnoreCase) != 0) + fontSourcesByName.TryAdd(fontResolverInfo.FaceName, fontSource); } } - finally { Locks.ExitFontFactory(); } } #if GDI /// @@ -226,31 +217,27 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f // ReSharper disable once UnusedMember.Global public static XFontSource RegisterFontFace_unused(byte[] fontBytes) { - try + var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; + var fontSourcesByKey = Globals.Global.Fonts.FontSourcesByKey; + + ulong key = FontHelper.CalcChecksum(fontBytes); + if (fontSourcesByKey.TryGetValue(key, out var fontSource)) { - var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; - var fontSourcesByKey = Globals.Global.Fonts.FontSourcesByKey; + throw new InvalidOperationException("Font face already registered."); + } - Locks.EnterFontFactory(); - ulong key = FontHelper.CalcChecksum(fontBytes); - if (fontSourcesByKey.TryGetValue(key, out var fontSource)) - { - throw new InvalidOperationException("Font face already registered."); - } - fontSource = XFontSource.GetOrCreateFrom(fontBytes); - Debug.Assert(fontSourcesByKey.ContainsKey(key)); - Debug.Assert(fontSource.FontFace != null); + fontSource = XFontSource.GetOrCreateFrom(fontBytes); + Debug.Assert(fontSourcesByKey.ContainsKey(key)); + Debug.Assert(fontSource.FontFace != null); - //fontSource.FontFace = new OpenTypeFontFace(fontSource); - //FontSourcesByKey.Add(checksum, fontSource); - //FontSourcesByFontName.Add(fontSource.FontName, fontSource); + //fontSource.FontFace = new OpenTypeFontFace(fontSource); + //FontSourcesByKey.Add(checksum, fontSource); + //FontSourcesByFontName.Add(fontSource.FontName, fontSource); - XGlyphTypeface glyphTypeface = new XGlyphTypeface(fontSource); - fontSourcesByName.Add(glyphTypeface.Key, fontSource); - GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); - return fontSource; - } - finally { Locks.ExitFontFactory(); } + XGlyphTypeface glyphTypeface = new XGlyphTypeface(fontSource); + fontSourcesByName.TryAdd(glyphTypeface.Key, fontSource); + GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); + return fontSource; } #endif @@ -318,8 +305,8 @@ internal static void CacheFontResolverInfo(string typefaceKey, FontResolverInfo throw new InvalidOperationException($"A font resolver already exists with the specified key '{fontResolverInfo.Key}'."); } // Add to both dictionaries. - fontResolverInfosByName.Add(typefaceKey, fontResolverInfo); - fontResolverInfosByName.Add(fontResolverInfo.Key, fontResolverInfo); + fontResolverInfosByName.TryAdd(typefaceKey, fontResolverInfo); + fontResolverInfosByName.TryAdd(fontResolverInfo.Key, fontResolverInfo); } /// @@ -327,12 +314,9 @@ internal static void CacheFontResolverInfo(string typefaceKey, FontResolverInfo /// public static XFontSource CacheFontSource(XFontSource fontSource) { - try + // Check whether an identical font source with a different face name already exists. + if (Globals.Global.Fonts.FontSourcesByKey.TryGetValue(fontSource.Key, out var existingFontSource)) { - Locks.EnterFontFactory(); - // Check whether an identical font source with a different face name already exists. - if (Globals.Global.Fonts.FontSourcesByKey.TryGetValue(fontSource.Key, out var existingFontSource)) - { #if DEBUG // Fonts have same length and check sum. Now check byte by byte identity. int length = fontSource.Bytes.Length; @@ -347,26 +331,25 @@ public static XFontSource CacheFontSource(XFontSource fontSource) } Debug.Assert(existingFontSource.FontFace != null); #endif - return existingFontSource; + return existingFontSource; - //FontsAreNotIdentical: - //// Incredible rare case: Two different fonts have the same size and check sum. - //// Give the new one a new key until it do not clash with an existing one. - //while (FontSourcesByKey.ContainsKey(fontSource.Key)) - // fontSource.IncrementKey(); - } + //FontsAreNotIdentical: + //// Incredible rare case: Two different fonts have the same size and check sum. + //// Give the new one a new key until it do not clash with an existing one. + //while (FontSourcesByKey.ContainsKey(fontSource.Key)) + // fontSource.IncrementKey(); + } - OpenTypeFontFace? fontFace = fontSource.FontFace; - if (fontFace == null!) - { - // Create OpenType font face for this font source. - fontSource.FontFace = new OpenTypeFontFace(fontSource); - } - Globals.Global.Fonts.FontSourcesByKey.Add(fontSource.Key, fontSource); - Globals.Global.Fonts.FontSourcesByName.Add(fontSource.FontName, fontSource); - return fontSource; + OpenTypeFontFace? fontFace = fontSource.FontFace; + if (fontFace == null!) + { + // Create OpenType font face for this font source. + fontSource.FontFace = new OpenTypeFontFace(fontSource); } - finally { Locks.ExitFontFactory(); } + + Globals.Global.Fonts.FontSourcesByKey.TryAdd(fontSource.Key, fontSource); + Globals.Global.Fonts.FontSourcesByName.TryAdd(fontSource.FontName, fontSource); + return fontSource; } /// @@ -409,21 +392,16 @@ public static XFontSource CacheNewFontSource(string typefaceKey, XFontSource fon fontSource.FontFace = fontFace; // Also sets the font name in fontSource } - Globals.Global.Fonts.FontSourcesByName.Add(typefaceKey, fontSource); - Globals.Global.Fonts.FontSourcesByName.Add(fontSource.FontName, fontSource); - Globals.Global.Fonts.FontSourcesByKey.Add(fontSource.Key, fontSource); + Globals.Global.Fonts.FontSourcesByName.TryAdd(typefaceKey, fontSource); + Globals.Global.Fonts.FontSourcesByName.TryAdd(fontSource.FontName, fontSource); + Globals.Global.Fonts.FontSourcesByKey.TryAdd(fontSource.Key, fontSource); return fontSource; } public static void CacheExistingFontSourceWithNewTypefaceKey(string typefaceKey, XFontSource fontSource) { - try - { - Locks.EnterFontFactory(); - Globals.Global.Fonts.FontSourcesByName.Add(typefaceKey, fontSource); - } - finally { Locks.ExitFontFactory(); } + Globals.Global.Fonts.FontSourcesByName.TryAdd(typefaceKey, fontSource); } public static void CheckInvocationOfPlatformFontResolver() @@ -456,7 +434,7 @@ internal static string GetFontCachesState() // FontResolverInfo by name. state.Append("====================\n"); state.Append("Font resolver info by name\n"); - Dictionary.KeyCollection keyCollection = Globals.Global.Fonts.FontResolverInfosByName.Keys; + var keyCollection = Globals.Global.Fonts.FontResolverInfosByName.Keys; count = keyCollection.Count; keys = new string[count]; keyCollection.CopyTo(keys, 0); @@ -467,14 +445,14 @@ internal static string GetFontCachesState() // FontSource by key. state.Append("Font source by key and name\n"); - Dictionary.KeyCollection fontSourceKeys = Globals.Global.Fonts.FontSourcesByKey.Keys; + var fontSourceKeys = Globals.Global.Fonts.FontSourcesByKey.Keys; count = fontSourceKeys.Count; ulong[] ulKeys = new ulong[count]; fontSourceKeys.CopyTo(ulKeys, 0); Array.Sort(ulKeys, (x, y) => x == y ? 0 : (x > y ? 1 : -1)); foreach (ulong ul in ulKeys) state.AppendFormat(" {0}: {1}\n", ul, Globals.Global.Fonts.FontSourcesByKey[ul].DebuggerDisplay); - Dictionary.KeyCollection fontSourceNames = Globals.Global.Fonts.FontSourcesByName.Keys; + var fontSourceNames = Globals.Global.Fonts.FontSourcesByName.Keys; count = fontSourceNames.Count; keys = new string[count]; fontSourceNames.CopyTo(keys, 0); @@ -506,18 +484,18 @@ partial class FontStorage /// Maps typeface key (TFK) to font resolver info (FRI) and /// maps font resolver key to font resolver info. /// - public readonly Dictionary FontResolverInfosByName = new(StringComparer.Ordinal); + public readonly ConcurrentDictionary FontResolverInfosByName = new(StringComparer.Ordinal); /// /// Maps typeface key or font name to font source. /// - public readonly Dictionary FontSourcesByName = new(StringComparer.OrdinalIgnoreCase); + public readonly ConcurrentDictionary FontSourcesByName = new(StringComparer.OrdinalIgnoreCase); /// /// Maps font source key (FSK) to font source. /// The key is a simple hash code of the font face data. /// - public readonly Dictionary FontSourcesByKey = []; + public readonly ConcurrentDictionary FontSourcesByKey = []; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs index de77d6a5..038b5d66 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs @@ -1,6 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Collections.Concurrent; using System.Text; #if GDI using GdiFontFamily = System.Drawing.FontFamily; @@ -10,7 +11,6 @@ #if WPF using WpfFontFamily = System.Windows.Media.FontFamily; #endif -using PdfSharp.Internal; using PdfSharp.Fonts; namespace PdfSharp.Fonts @@ -22,13 +22,8 @@ static class FontFamilyCache { public static FontFamilyInternal? GetFamilyByName(string familyName) { - try - { - Locks.EnterFontFactory(); - Globals.Global.Fonts.FontFamiliesByName.TryGetValue(familyName, out var family); - return family; - } - finally { Locks.ExitFontFactory(); } + Globals.Global.Fonts.FontFamiliesByName.TryGetValue(familyName, out var family); + return family; } /// @@ -36,22 +31,18 @@ static class FontFamilyCache /// public static FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFamily) { - try + // Recall that a font family is uniquely identified by its case-insensitive name. + if (Globals.Global.Fonts.FontFamiliesByName.TryGetValue(fontFamily.Name, out var existingFontFamily)) { - Locks.EnterFontFactory(); - // Recall that a font family is uniquely identified by its case-insensitive name. - if (Globals.Global.Fonts.FontFamiliesByName.TryGetValue(fontFamily.Name, out var existingFontFamily)) - { #if DEBUG if (fontFamily.Name == "xxx") _ = typeof(int); #endif - return existingFontFamily; - } - Globals.Global.Fonts.FontFamiliesByName.Add(fontFamily.Name, fontFamily); - return fontFamily; + return existingFontFamily; } - finally { Locks.ExitFontFactory(); } + + Globals.Global.Fonts.FontFamiliesByName.TryAdd(fontFamily.Name, fontFamily); + return fontFamily; } internal static void Reset() @@ -64,7 +55,7 @@ internal static string GetCacheState() var state = new StringBuilder(); state.Append("====================\n"); state.Append("Font families by name\n"); - Dictionary.KeyCollection familyKeys = Globals.Global.Fonts.FontFamiliesByName.Keys; + var familyKeys = Globals.Global.Fonts.FontFamiliesByName.Keys; int count = familyKeys.Count; string[] keys = new string[count]; familyKeys.CopyTo(keys, 0); @@ -86,7 +77,7 @@ partial class FontStorage /// /// Maps family name to internal font family. /// - public readonly Dictionary FontFamiliesByName = []; + public readonly ConcurrentDictionary FontFamiliesByName = []; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs index 821a202c..56086c1d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs @@ -91,31 +91,22 @@ public sealed class FontFamilyInternal internal static FontFamilyInternal GetOrCreateFromName(string familyName, bool createPlatformObject) { - try + var family = FontFamilyCache.GetFamilyByName(familyName); + if (family == null) { - Locks.EnterFontFactory(); - var family = FontFamilyCache.GetFamilyByName(familyName); - if (family == null) - { - family = new FontFamilyInternal(familyName, createPlatformObject); - family = FontFamilyCache.CacheOrGetFontFamily(family); - } - return family; + family = new FontFamilyInternal(familyName, createPlatformObject); + family = FontFamilyCache.CacheOrGetFontFamily(family); } - finally { Locks.ExitFontFactory(); } + + return family; } #if GDI internal static FontFamilyInternal GetOrCreateFromGdi(GdiFontFamily gdiFontFamily) { - try - { - Locks.EnterFontFactory(); - FontFamilyInternal fontFamily = new FontFamilyInternal(gdiFontFamily); - fontFamily = FontFamilyCache.CacheOrGetFontFamily(fontFamily); - return fontFamily; - } - finally { Locks.ExitFontFactory(); } + FontFamilyInternal fontFamily = new FontFamilyInternal(gdiFontFamily); + fontFamily = FontFamilyCache.CacheOrGetFontFamily(fontFamily); + return fontFamily; } #endif From 7fc1971f786ed513e74423c5be5f7a9fd3f3a17d Mon Sep 17 00:00:00 2001 From: Stepami Date: Tue, 13 Jan 2026 00:52:03 +0300 Subject: [PATCH 09/14] update bench --- .../src/PdfSharp.Benchmarks/BenchmarkHelper.cs | 4 +--- .../src/PdfSharp.Benchmarks/Program.cs | 4 ++-- .../PDFsharp/src/PdfSharp.Benchmarks/readme.md | 18 ++++++++++++------ 3 files changed, 15 insertions(+), 11 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs index 6726a290..1b11318d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs @@ -31,9 +31,7 @@ public static PdfDocument CreateSampleDocument() var numbers = Enumerable.Repeat( () => Random.Shared.Next(100, 999), 10) .Select(x => x()); - var line = string.Join( - ", ", - ); + var line = string.Join(", ", numbers); gfx.DrawString( line, font, XBrushes.Black, new XRect(0, (i + 1) * 20, page.Width.Point, page.Height.Point), diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs index dc438b1f..b07c5a3e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs @@ -26,13 +26,13 @@ public class PdfDocumentBenchmarks public int Iterations { get; set; } [Benchmark] - public async Task CreateAndSave() + public void CreateAndSave() { for (var i = 0; i < Iterations; i++) { var document = BenchmarkHelper.CreateSampleDocument(); Consumer.Consume(document); - await document.SaveAsync(Stream.Null); + //await document.SaveAsync(Stream.Null); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md index 2bddf255..35ae490e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md @@ -1,9 +1,15 @@ # before -| Method | Iterations | Mean | Error | StdDev | Median | -|---------------|------------|------------:|------------:|------------:|------------:| -| CreateAndSave | 1 | 958.3 us | 18.85 us | 21.70 us | 960.2 us | -| CreateAndSave | 10 | 9,723.9 us | 191.92 us | 488.50 us | 9,560.7 us | -| CreateAndSave | 100 | 94,945.9 us | 1,892.31 us | 1,770.07 us | 94,644.6 us | +| Method | Iterations | Mean | Error | StdDev | +|---------------|------------|------------:|-----------:|-----------:| +| CreateAndSave | 1 | 56.78 us | 1.677 us | 4.945 us | +| CreateAndSave | 10 | 529.62 us | 10.451 us | 20.872 us | +| CreateAndSave | 100 | 5,377.21 us | 103.454 us | 186.549 us | -# after \ No newline at end of file +# after + +| Method | Iterations | Mean | Error | StdDev | +|---------------|------------|------------:|-----------:|-----------:| +| CreateAndSave | 1 | 51.83 us | 0.802 us | 0.891 us | +| CreateAndSave | 10 | 518.13 us | 10.099 us | 12.022 us | +| CreateAndSave | 100 | 5,102.81 us | 105.648 us | 201.006 us | \ No newline at end of file From a4b2f3ec14a23834ee6faee7a71b904ec8eac584 Mon Sep 17 00:00:00 2001 From: Stepami Date: Tue, 13 Jan 2026 01:00:25 +0300 Subject: [PATCH 10/14] improve bench --- .../PdfSharp.Benchmarks/BenchmarkHelper.cs | 54 +++++++++++-------- .../src/PdfSharp.Benchmarks/Program.cs | 2 +- 2 files changed, 33 insertions(+), 23 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs index 1b11318d..f74ba268 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs @@ -12,33 +12,43 @@ public static PdfDocument CreateSampleDocument() result.Info.Title = "Created with PDFsharp"; result.Info.Subject = "Just a simple Hello-World program."; - // Create an empty page in this document. - var page = result.AddPage(); - page.Size = PageSize.A4; + var lastY = 0; + for (var pageNumber = 0; pageNumber < 10; pageNumber++) + { + // Create an empty page in this document. + var page = result.AddPage(); + page.Size = PageSize.A4; - // Get an XGraphics object for drawing on this page. - var gfx = XGraphics.FromPdfPage(page); + // Get an XGraphics object for drawing on this page. + var gfx = XGraphics.FromPdfPage(page); - // Create a font. - var font = new XFont("Times New Roman", 12, XFontStyleEx.BoldItalic); + // Create a font. + var font = new XFont("Times New Roman", 12, XFontStyleEx.BoldItalic); - // Draw the text. - gfx.DrawString( - "Hello, PDFsharp!", font, XBrushes.Black, - new XRect(0, 0, page.Width.Point, page.Height.Point), XStringFormats.TopLeft); - for (var i = 0; i < 20; i += 2) - { - var numbers = Enumerable.Repeat( - () => Random.Shared.Next(100, 999), 10) - .Select(x => x()); - var line = string.Join(", ", numbers); + // Draw the text. gfx.DrawString( - line, font, XBrushes.Black, - new XRect(0, (i + 1) * 20, page.Width.Point, page.Height.Point), - XStringFormats.TopLeft); + "Hello, PDFsharp!", font, XBrushes.Black, + new XRect(0, 0, page.Width.Point, page.Height.Point), XStringFormats.TopLeft); + for (var i = 0; i < 40; i += 2) + { + var numbers = Enumerable.Repeat( + () => Random.Shared.Next(100, 999), 10) + .Select(x => x()); + var line = string.Join(", ", numbers); + gfx.DrawString( + line, font, XBrushes.Black, + new XRect(0, (i + 1) * 20, page.Width.Point, page.Height.Point), + XStringFormats.TopLeft); + lastY = (i + 2) * 20; + gfx.DrawString( + Guid.NewGuid().ToString(), font, XBrushes.Black, + new XRect(0, (i + 2) * 20, page.Width.Point, page.Height.Point), + XStringFormats.TopLeft); + } + gfx.DrawString( - Guid.NewGuid().ToString(), font, XBrushes.Black, - new XRect(0, (i + 2) * 20, page.Width.Point, page.Height.Point), + $"Page Number {pageNumber + 1}", font, XBrushes.Black, + new XRect(0, lastY + 20, page.Width.Point, page.Height.Point), XStringFormats.TopLeft); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs index b07c5a3e..32d656b9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs @@ -8,7 +8,7 @@ /* GlobalFontSettings.FontResolver = new SamplesFontResolver(); -var document = CreateSampleDocument(); +var document = BenchmarkHelper.CreateSampleDocument(); var filename = PdfFileUtility.GetTempPdfFullFileName("samples/HelloWorldSample"); await document.SaveAsync(filename); // ...and start a viewer. From 9079a2a315005df06f17415d9c3ca66f9e641c79 Mon Sep 17 00:00:00 2001 From: Stepami Date: Tue, 13 Jan 2026 01:25:03 +0300 Subject: [PATCH 11/14] new results --- .../src/PdfSharp.Benchmarks/readme.md | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md index 35ae490e..659fea94 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md @@ -1,15 +1,15 @@ # before -| Method | Iterations | Mean | Error | StdDev | -|---------------|------------|------------:|-----------:|-----------:| -| CreateAndSave | 1 | 56.78 us | 1.677 us | 4.945 us | -| CreateAndSave | 10 | 529.62 us | 10.451 us | 20.872 us | -| CreateAndSave | 100 | 5,377.21 us | 103.454 us | 186.549 us | +| Method | Iterations | Mean | Error | StdDev | Median | +|---------------|------------|------------:|------------:|------------:|------------:| +| CreateAndSave | 1 | 878.8 us | 18.28 us | 53.62 us | 869.4 us | +| CreateAndSave | 10 | 8,357.5 us | 153.39 us | 143.49 us | 8,361.5 us | +| CreateAndSave | 100 | 83,972.1 us | 1,764.56 us | 5,175.14 us | 82,510.5 us | # after -| Method | Iterations | Mean | Error | StdDev | -|---------------|------------|------------:|-----------:|-----------:| -| CreateAndSave | 1 | 51.83 us | 0.802 us | 0.891 us | -| CreateAndSave | 10 | 518.13 us | 10.099 us | 12.022 us | -| CreateAndSave | 100 | 5,102.81 us | 105.648 us | 201.006 us | \ No newline at end of file +| Method | Iterations | Mean | Error | StdDev | Median | +|---------------|------------|------------:|------------:|------------:|------------:| +| CreateAndSave | 1 | 784.6 us | 17.22 us | 46.86 us | 869.4 us | +| CreateAndSave | 10 | 7,692.0 us | 138.45 us | 175.10 us | 7,723.0 us | +| CreateAndSave | 100 | 78,402.7 us | 1,636.04 us | 4,772.40 us | 77,814.7 us | From 35cb01adea5d7a91ec70ab9aa5e0a438fcb9c1c9 Mon Sep 17 00:00:00 2001 From: Stepami Date: Tue, 13 Jan 2026 17:14:49 +0300 Subject: [PATCH 12/14] Revert "comment gitversion.msbuild" This reverts commit e279819f --- src/Directory.Build.props | 4 ++-- src/Directory.Packages.props | 2 +- .../MigraDoc.GrammarByExample-GDI.csproj | 4 ++-- .../Internal/PdfSharpGitVersionInformation.cs | 20 +++++++++---------- 4 files changed, 14 insertions(+), 16 deletions(-) diff --git a/src/Directory.Build.props b/src/Directory.Build.props index e7e8244f..3aeeb07c 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -41,9 +41,9 @@ - + diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 8a3de252..5bf4b256 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -26,7 +26,7 @@ - + diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj index d163f31a..60749bf9 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-GDI/MigraDoc.GrammarByExample-GDI.csproj @@ -93,9 +93,9 @@ - + diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs index de0498fa..4a52820a 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs @@ -1,8 +1,6 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Globalization; - namespace PdfSharp.Internal { /// @@ -13,46 +11,46 @@ public static class PdfSharpGitVersionInformation /// /// The major version number of the product. /// - public static string Major = "1";//global::GitVersionInformation.Major; + public static string Major = global::GitVersionInformation.Major; /// /// The minor version number of the product. /// - public static string Minor = "1";//global::GitVersionInformation.Minor; + public static string Minor = global::GitVersionInformation.Minor; /// /// The patch number of the product. /// - public static string Patch = "1";//global::GitVersionInformation.Patch; + public static string Patch = global::GitVersionInformation.Patch; /// /// The Version pre-release string for NuGet. /// - public static string PreReleaseLabel = string.Empty;//global::GitVersionInformation.PreReleaseLabel; + public static string PreReleaseLabel = global::GitVersionInformation.PreReleaseLabel; /// /// The full version number. /// - public static string MajorMinorPatch = "1.1.1";//global::GitVersionInformation.MajorMinorPatch; + public static string MajorMinorPatch = global::GitVersionInformation.MajorMinorPatch; /// /// The full semantic version number created by GitVersion. /// - public static string SemVer = "1.1.1";//global::GitVersionInformation.SemVer; + public static string SemVer = global::GitVersionInformation.SemVer; /// /// The full informational version number created by GitVersion. /// - public static string InformationalVersion = "1.1.1";//global::GitVersionInformation.InformationalVersion; + public static string InformationalVersion = global::GitVersionInformation.InformationalVersion; /// /// The branch name of the product. /// - public static string BranchName = "refactoring/locks";//global::GitVersionInformation.BranchName; + public static string BranchName = global::GitVersionInformation.BranchName; /// /// The commit date of the product. /// - public static string CommitDate = DateTime.Today.ToString(CultureInfo.InvariantCulture);//global::GitVersionInformation.CommitDate; + public static string CommitDate = global::GitVersionInformation.CommitDate; } } From 92bcc2cc2884e10268105bab4e6304fa628bbd2f Mon Sep 17 00:00:00 2001 From: Stepami Date: Tue, 13 Jan 2026 17:16:46 +0300 Subject: [PATCH 13/14] rm bench proj --- PdfSharp.sln | 7 --- .../PdfSharp.Benchmarks/BenchmarkHelper.cs | 57 ------------------- .../PdfSharp.Benchmarks.csproj | 20 ------- .../src/PdfSharp.Benchmarks/Program.cs | 42 -------------- .../src/PdfSharp.Benchmarks/readme.md | 15 ----- 5 files changed, 141 deletions(-) delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md diff --git a/PdfSharp.sln b/PdfSharp.sln index b3c3ef98..3c82ce5b 100644 --- a/PdfSharp.sln +++ b/PdfSharp.sln @@ -274,8 +274,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs-dummy", "docs\docs-dum EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Cryptography", "src\foundation\src\PDFsharp\src\PdfSharp.Cryptography\PdfSharp.Cryptography.csproj", "{769ED050-15AF-4EB5-A89F-D7123EE5AA95}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Benchmarks", "src\foundation\src\PDFsharp\src\PdfSharp.Benchmarks\PdfSharp.Benchmarks.csproj", "{24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}" -EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -570,10 +568,6 @@ Global {769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Debug|Any CPU.Build.0 = Debug|Any CPU {769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Release|Any CPU.ActiveCfg = Release|Any CPU {769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Release|Any CPU.Build.0 = Release|Any CPU - {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}.Debug|Any CPU.Build.0 = Debug|Any CPU - {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}.Release|Any CPU.ActiveCfg = Release|Any CPU - {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -681,7 +675,6 @@ Global {F4AB506C-AD13-4383-9AF9-48D74085ECC1} = {7C753636-7947-46E0-95E0-135EAA7BFEB3} {76B94284-402D-4951-8DA4-7FFAF15E6C95} = {76BA9372-65AE-479C-AEF7-D50E6B486CEF} {769ED050-15AF-4EB5-A89F-D7123EE5AA95} = {7C753636-7947-46E0-95E0-135EAA7BFEB3} - {24B3A17B-FDEE-496B-A2F4-A5F35127CCCF} = {7C753636-7947-46E0-95E0-135EAA7BFEB3} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D5FF5562-3C79-434B-B951-B84542D01625} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs deleted file mode 100644 index f74ba268..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/BenchmarkHelper.cs +++ /dev/null @@ -1,57 +0,0 @@ -using PdfSharp.Drawing; -using PdfSharp.Pdf; - -namespace PdfSharp.Benchmarks; - -public static class BenchmarkHelper -{ - public static PdfDocument CreateSampleDocument() - { - // Create a new PDF document. - var result = new PdfDocument(); - result.Info.Title = "Created with PDFsharp"; - result.Info.Subject = "Just a simple Hello-World program."; - - var lastY = 0; - for (var pageNumber = 0; pageNumber < 10; pageNumber++) - { - // Create an empty page in this document. - var page = result.AddPage(); - page.Size = PageSize.A4; - - // Get an XGraphics object for drawing on this page. - var gfx = XGraphics.FromPdfPage(page); - - // Create a font. - var font = new XFont("Times New Roman", 12, XFontStyleEx.BoldItalic); - - // Draw the text. - gfx.DrawString( - "Hello, PDFsharp!", font, XBrushes.Black, - new XRect(0, 0, page.Width.Point, page.Height.Point), XStringFormats.TopLeft); - for (var i = 0; i < 40; i += 2) - { - var numbers = Enumerable.Repeat( - () => Random.Shared.Next(100, 999), 10) - .Select(x => x()); - var line = string.Join(", ", numbers); - gfx.DrawString( - line, font, XBrushes.Black, - new XRect(0, (i + 1) * 20, page.Width.Point, page.Height.Point), - XStringFormats.TopLeft); - lastY = (i + 2) * 20; - gfx.DrawString( - Guid.NewGuid().ToString(), font, XBrushes.Black, - new XRect(0, (i + 2) * 20, page.Width.Point, page.Height.Point), - XStringFormats.TopLeft); - } - - gfx.DrawString( - $"Page Number {pageNumber + 1}", font, XBrushes.Black, - new XRect(0, lastY + 20, page.Width.Point, page.Height.Point), - XStringFormats.TopLeft); - } - - return result; - } -} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj deleted file mode 100644 index cbe249ad..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/PdfSharp.Benchmarks.csproj +++ /dev/null @@ -1,20 +0,0 @@ - - - - Exe - net8.0 - enable - enable - NETSDK1138 - - - - - - - - - - - - diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs deleted file mode 100644 index 32d656b9..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/Program.cs +++ /dev/null @@ -1,42 +0,0 @@ -using BenchmarkDotNet.Attributes; -using BenchmarkDotNet.Engines; -using BenchmarkDotNet.Jobs; -using BenchmarkDotNet.Running; -using PdfSharp.Benchmarks; -using PdfSharp.Fonts; -using PdfSharp.Quality; - -/* -GlobalFontSettings.FontResolver = new SamplesFontResolver(); -var document = BenchmarkHelper.CreateSampleDocument(); -var filename = PdfFileUtility.GetTempPdfFullFileName("samples/HelloWorldSample"); -await document.SaveAsync(filename); -// ...and start a viewer. -PdfFileUtility.ShowDocument(filename); -*/ - -BenchmarkRunner.Run(); - -[SimpleJob(RuntimeMoniker.Net80)] -public class PdfDocumentBenchmarks -{ - private static readonly Consumer Consumer = new(); - - [Params(1, 10, 100)] - public int Iterations { get; set; } - - [Benchmark] - public void CreateAndSave() - { - for (var i = 0; i < Iterations; i++) - { - var document = BenchmarkHelper.CreateSampleDocument(); - Consumer.Consume(document); - //await document.SaveAsync(Stream.Null); - } - } - - [GlobalSetup] - public void GlobalSetup() => - GlobalFontSettings.FontResolver = new SamplesFontResolver(); -} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md b/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md deleted file mode 100644 index 659fea94..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Benchmarks/readme.md +++ /dev/null @@ -1,15 +0,0 @@ -# before - -| Method | Iterations | Mean | Error | StdDev | Median | -|---------------|------------|------------:|------------:|------------:|------------:| -| CreateAndSave | 1 | 878.8 us | 18.28 us | 53.62 us | 869.4 us | -| CreateAndSave | 10 | 8,357.5 us | 153.39 us | 143.49 us | 8,361.5 us | -| CreateAndSave | 100 | 83,972.1 us | 1,764.56 us | 5,175.14 us | 82,510.5 us | - -# after - -| Method | Iterations | Mean | Error | StdDev | Median | -|---------------|------------|------------:|------------:|------------:|------------:| -| CreateAndSave | 1 | 784.6 us | 17.22 us | 46.86 us | 869.4 us | -| CreateAndSave | 10 | 7,692.0 us | 138.45 us | 175.10 us | 7,723.0 us | -| CreateAndSave | 100 | 78,402.7 us | 1,636.04 us | 4,772.40 us | 77,814.7 us | From ebe6c5b9acea7f3d3c6eed8fd9a9a91cee9bcd21 Mon Sep 17 00:00:00 2001 From: Stepami Date: Tue, 13 Jan 2026 17:17:59 +0300 Subject: [PATCH 14/14] revert Directory.Packages.props --- src/Directory.Packages.props | 72 +++++++++++++++++++----------------- 1 file changed, 39 insertions(+), 33 deletions(-) diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 5bf4b256..d2d86f15 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,35 +1,41 @@ - - - 8.0.3 - 8.0.1 - 8.0.1 - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + 8.0.3 + 8.0.1 + 8.0.1 + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file