|
1 | 1 | // Licensed to the .NET Foundation under one or more agreements.
|
2 | 2 | // The .NET Foundation licenses this file to you under the MIT license.
|
3 | 3 |
|
| 4 | +using System.Buffers; |
4 | 5 | using System.Collections.Concurrent;
|
5 | 6 | using System.Diagnostics;
|
6 | 7 | using System.Reflection;
|
@@ -32,6 +33,8 @@ namespace Docfx.Pdf;
|
32 | 33 |
|
33 | 34 | static class PdfBuilder
|
34 | 35 | {
|
| 36 | + private static readonly SearchValues<char> InvalidPathChars = SearchValues.Create(Path.GetInvalidPathChars()); |
| 37 | + |
35 | 38 | class Outline
|
36 | 39 | {
|
37 | 40 | public string name { get; init; } = "";
|
@@ -94,7 +97,8 @@ public static async Task CreatePdf(string outputFolder)
|
94 | 97 |
|
95 | 98 | using var pageLimiter = new SemaphoreSlim(Environment.ProcessorCount, Environment.ProcessorCount);
|
96 | 99 | var pagePool = new ConcurrentBag<IPage>();
|
97 |
| - var headerFooterCache = new ConcurrentDictionary<(string, string), Task<byte[]>>(); |
| 100 | + var headerFooterTemplateCache = new ConcurrentDictionary<string, string>(); |
| 101 | + var headerFooterPageCache = new ConcurrentDictionary<(string, string), Task<byte[]>>(); |
98 | 102 |
|
99 | 103 | await AnsiConsole.Progress().StartAsync(async progress =>
|
100 | 104 | {
|
@@ -187,7 +191,7 @@ Task<byte[]> PrintHeaderFooter(Outline toc, int pageNumber, int totalPages, Page
|
187 | 191 | var headerTemplate = ExpandTemplate(GetHeaderFooter(toc.pdfHeaderTemplate), pageNumber, totalPages);
|
188 | 192 | var footerTemplate = ExpandTemplate(GetHeaderFooter(toc.pdfFooterTemplate) ?? DefaultFooterTemplate, pageNumber, totalPages);
|
189 | 193 |
|
190 |
| - return headerFooterCache.GetOrAdd((headerTemplate, footerTemplate), _ => PrintHeaderFooterCore()); |
| 194 | + return headerFooterPageCache.GetOrAdd((headerTemplate, footerTemplate), _ => PrintHeaderFooterCore()); |
191 | 195 |
|
192 | 196 | async Task<byte[]> PrintHeaderFooterCore()
|
193 | 197 | {
|
@@ -241,16 +245,29 @@ static string ExpandTemplate(string? pdfTemplate, int pageNumber, int totalPages
|
241 | 245 | if (string.IsNullOrEmpty(template))
|
242 | 246 | return template;
|
243 | 247 |
|
244 |
| - try |
245 |
| - { |
246 |
| - var path = Path.Combine(outputFolder, template); |
247 |
| - return File.Exists(path) ? File.ReadAllText(path) : template; |
248 |
| - } |
249 |
| - catch |
250 |
| - { |
| 248 | + // Check path chars. If it's contains HTML chars. Skip access to file content to optimmize performance |
| 249 | + if (template.AsSpan().ContainsAny(InvalidPathChars)) |
251 | 250 | return template;
|
252 |
| - } |
| 251 | + |
| 252 | + return headerFooterTemplateCache.GetOrAdd(template, (_) => |
| 253 | + { |
| 254 | + // Note: This valueFactory might be called multiple times. |
| 255 | + try |
| 256 | + { |
| 257 | + var path = Path.GetFullPath(Path.Combine(outputFolder, template)); |
| 258 | + if (!File.Exists(path)) |
| 259 | + return template; |
| 260 | + |
| 261 | + var templateContent = File.ReadAllText(path); |
| 262 | + return templateContent; |
| 263 | + } |
| 264 | + catch |
| 265 | + { |
| 266 | + return template; |
| 267 | + } |
| 268 | + }); |
253 | 269 | }
|
| 270 | + |
254 | 271 | }
|
255 | 272 | }
|
256 | 273 |
|
|
0 commit comments