From 3d98f2c38f17d7179f070f3a770d8bad43f11ae5 Mon Sep 17 00:00:00 2001 From: BobLd <38405645+BobLd@users.noreply.github.com> Date: Sun, 1 Mar 2026 09:31:28 +0000 Subject: [PATCH] Improve SystemFontFinder performance and add benchmarks * Update SystemFontFinder to enhance font lookup efficiency. * Update NameTable and TrueTypeFontParser with nullability annotations. * Add SystemFontFinderBenchmarks and a sample PDF to the benchmarks tool to monitor font resolution performance. --- .../SystemFonts/SystemFontFinder.cs | 426 ++++++++++-------- .../TrueType/Parser/TrueTypeFontParser.cs | 2 +- .../TrueType/Tables/NameTable.cs | 8 +- tools/UglyToad.PdfPig.Benchmarks/Program.cs | 15 +- .../SystemFontFinderBenchmarks.cs | 44 ++ .../UglyToad.PdfPig.Benchmarks.csproj | 5 +- .../iizieileamidagi.ARVE_2745540212.pdf | Bin 0 -> 27499 bytes 7 files changed, 299 insertions(+), 201 deletions(-) create mode 100644 tools/UglyToad.PdfPig.Benchmarks/SystemFontFinderBenchmarks.cs create mode 100644 tools/UglyToad.PdfPig.Benchmarks/iizieileamidagi.ARVE_2745540212.pdf diff --git a/src/UglyToad.PdfPig.Fonts/SystemFonts/SystemFontFinder.cs b/src/UglyToad.PdfPig.Fonts/SystemFonts/SystemFontFinder.cs index a740419b1..17c891bac 100644 --- a/src/UglyToad.PdfPig.Fonts/SystemFonts/SystemFontFinder.cs +++ b/src/UglyToad.PdfPig.Fonts/SystemFonts/SystemFontFinder.cs @@ -1,34 +1,39 @@ -using System.Collections.Concurrent; - -namespace UglyToad.PdfPig.Fonts.SystemFonts +namespace UglyToad.PdfPig.Fonts.SystemFonts; + +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.IO; +using System.Linq; +using Standard14Fonts; +using System.Runtime.InteropServices; +using TrueType; +using TrueType.Parser; + +/// +public sealed class SystemFontFinder : ISystemFontFinder { - using System; - using System.Collections.Generic; - using System.IO; - using System.Linq; - using System.Runtime.InteropServices; - using Core; - using Standard14Fonts; - using TrueType; - using TrueType.Parser; + private static readonly IReadOnlyDictionary NameSubstitutes; + private static readonly Lazy> AvailableFonts; - /// - public sealed class SystemFontFinder : ISystemFontFinder - { - private static readonly IReadOnlyDictionary NameSubstitutes; - private static readonly Lazy> AvailableFonts; + private static readonly ConcurrentDictionary Cache = new(StringComparer.OrdinalIgnoreCase); - private static readonly object CacheLock = new object(); - private static readonly Dictionary Cache = new Dictionary(StringComparer.OrdinalIgnoreCase); + /// + /// Fonts grouped by the upper-case first character of their filename (e.g. 'A' → all fonts + /// whose file starts with 'a' or 'A'). Built lazily from so the + /// O(n) grouping is paid only once, turning the per-lookup first-letter scan into an O(1) dict + /// lookup. + /// + private static readonly Lazy> FontsByFirstChar; - /// - /// The instance of . - /// - public static readonly ISystemFontFinder Instance = new SystemFontFinder(); + /// + /// The instance of . + /// + public static readonly ISystemFontFinder Instance = new SystemFontFinder(); - static SystemFontFinder() - { - var dict = new Dictionary + static SystemFontFinder() + { + var dict = new Dictionary { {"Courier", new[] {"CourierNew", "CourierNewPSMT", "LiberationMono", "NimbusMonL-Regu"}}, {"Courier-Bold", new[] {"CourierNewPS-BoldMT", "CourierNew-Bold", "LiberationMono-Bold", "NimbusMonL-Bold"}}, @@ -46,49 +51,49 @@ static SystemFontFinder() {"ZapfDingbats", new[] {"ZapfDingbatsITC", "Dingbats", "MS-Gothic"}} }; - HashSet names; - try - { - names = Standard14.GetNames(); - } - catch (Exception ex) - { - throw new InvalidOperationException("Failed to load the Standard 14 fonts from the assembly's resources.", ex); - } + HashSet names; + try + { + names = Standard14.GetNames(); + } + catch (Exception ex) + { + throw new InvalidOperationException("Failed to load the Standard 14 fonts from the assembly's resources.", ex); + } - foreach (var name in names) + foreach (var name in names) + { + if (!dict.ContainsKey(name)) { - if (!dict.ContainsKey(name)) + var value = Standard14.GetMappedFontName(name); + + if (dict.TryGetValue(value, out var subs)) { - var value = Standard14.GetMappedFontName(name); - - if (dict.TryGetValue(value, out var subs)) - { - dict[name] = subs; - } - else - { - dict[name] = new[] { value }; - } + dict[name] = subs; + } + else + { + dict[name] = new[] { value }; } } + } - NameSubstitutes = dict; + NameSubstitutes = dict; - ISystemFontLister lister; + ISystemFontLister lister; #if NETSTANDARD2_0_OR_GREATER || NET - if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) - { - lister = new WindowsSystemFontLister(); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) - { - lister = new MacSystemFontLister(); - } - else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) - { - lister = new LinuxSystemFontLister(); - } + if (RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + lister = new WindowsSystemFontLister(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.OSX)) + { + lister = new MacSystemFontLister(); + } + else if (RuntimeInformation.IsOSPlatform(OSPlatform.Linux)) + { + lister = new LinuxSystemFontLister(); + } #if NET else if (OperatingSystem.IsAndroid()) { @@ -107,203 +112,250 @@ static SystemFontFinder() lister = new IOSSystemFontLister(); } #endif - else - { - throw new NotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}."); - } + else + { + throw new NotSupportedException($"Unsupported operating system: {RuntimeInformation.OSDescription}."); + } #elif NETFRAMEWORK lister = new WindowsSystemFontLister(); #else #error Missing ISystemFontLister for target framework #endif - AvailableFonts = new Lazy>(() => lister.GetAllFonts().ToArray()); - } - - private readonly ConcurrentDictionary nameToFileNameMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); - private readonly object readFilesLock = new object(); - private readonly HashSet readFiles = new HashSet(); + AvailableFonts = new Lazy>(() => lister.GetAllFonts().ToArray()); - /// - /// Create a new . - /// - private SystemFontFinder() + FontsByFirstChar = new Lazy>(() => { - } - - /// - public TrueTypeFont GetTrueTypeFont(string name) - { - var result = GetTrueTypeFontNamed(name); - - if (result != null) - { - return result; - } + var fonts = AvailableFonts.Value; + var byChar = new Dictionary>(); - if (name.Contains("-")) + foreach (var record in fonts) { - result = GetTrueTypeFontNamed(name.Replace("-", string.Empty)); - - if (result != null) + var fn = Path.GetFileName(record.Path); + if (string.IsNullOrEmpty(fn)) { - return result; + continue; } - } - if (name.Contains(",")) - { - result = GetTrueTypeFontNamed(name.Replace(',', '-')); - - if (result != null) + var key = char.ToUpperInvariant(fn[0]); + if (!byChar.TryGetValue(key, out var list)) { - return result; + byChar[key] = list = new List(); } + + list.Add(record); } - foreach (var substituteName in GetSubstituteNames(name)) + var result = new Dictionary(byChar.Count); + foreach (var kvp in byChar) { - result = GetTrueTypeFontNamed(substituteName); - - if (result != null) - { - return result; - } + result[kvp.Key] = kvp.Value.ToArray(); } - result = GetTrueTypeFontNamed(name + "-Regular"); + return result; + }); + } + + private readonly ConcurrentDictionary nameToFileNameMap = new ConcurrentDictionary(StringComparer.OrdinalIgnoreCase); + // Tracks font file paths that have already been scanned for name matching, so we never open + // the same file twice during a search. Value is always 0 – the dictionary is used as a + // lock-free concurrent set. + private readonly ConcurrentDictionary readFiles = new ConcurrentDictionary(StringComparer.Ordinal); + + /// + /// Create a new . + /// + private SystemFontFinder() + { } + + /// + public TrueTypeFont? GetTrueTypeFont(string name) + { + var result = GetTrueTypeFontNamed(name); + + if (result is not null) + { return result; } - private IEnumerable GetSubstituteNames(string name) + if (name.Contains('-')) { - name = name.Replace(" ", string.Empty); - if (NameSubstitutes.TryGetValue(name, out var values)) + result = GetTrueTypeFontNamed(name.Replace("-", string.Empty)); + + if (result != null) { - return values; + return result; } - - return Array.Empty(); } - private TrueTypeFont GetTrueTypeFontNamed(string name) + if (name.Contains(',')) { - lock (CacheLock) + result = GetTrueTypeFontNamed(name.Replace(',', '-')); + + if (result != null) { - if (Cache.TryGetValue(name, out var cachedResult)) - { - return cachedResult; - } + return result; } + } - if (nameToFileNameMap.TryGetValue(name, out var fileName)) - { - if (TryReadFile(fileName, false, name, out var result)) - { - return result; - } + foreach (var substituteName in GetSubstituteNames(name)) + { + result = GetTrueTypeFontNamed(substituteName); - return null; + if (result != null) + { + return result; } + } + + result = GetTrueTypeFontNamed(name + "-Regular"); + + return result; + } - var nameCandidates = AvailableFonts.Value.Where(x => Path.GetFileName(x.Path)?.StartsWith(name[0].ToString(), StringComparison.OrdinalIgnoreCase) == true); + private static string[] GetSubstituteNames(string name) + { + if (name.Contains(' ')) + { + name = name.Replace(" ", string.Empty); + } + + if (NameSubstitutes.TryGetValue(name, out var values)) + { + return values; + } + + return Array.Empty(); + } - foreach (var systemFontRecord in nameCandidates) + private TrueTypeFont? GetTrueTypeFontNamed(string name) + { + if (Cache.TryGetValue(name, out var cachedResult)) + { + return cachedResult; + } + + if (nameToFileNameMap.TryGetValue(name, out var fileName)) + { + if (TryReadFile(fileName, false, name, out var result)) { - if (TryGetTrueTypeFont(name, systemFontRecord, out var font)) - { - return font; - } + return result; } - foreach (var record in AvailableFonts.Value) + return null; + } + + // First pass: fonts whose filename starts with the same letter as the requested name – + // the most likely match. FontsByFirstChar is built once, making this O(1) vs the + // previous O(n) LINQ scan that also allocated a string for name[0].ToString(). + char firstChar = char.ToUpperInvariant(name[0]); + + if (FontsByFirstChar.Value.TryGetValue(firstChar, out var candidates)) + { + foreach (var record in candidates) { if (TryGetTrueTypeFont(name, record, out var font)) { return font; } - - // TODO: OTF } - - return null; } - private bool TryGetTrueTypeFont(string name, SystemFontRecord record, out TrueTypeFont font) + // Second pass: all remaining fonts (those whose filename does NOT start with the same + // letter). We skip first-char matches to avoid re-processing what was already tried + // above – the original code iterated all fonts a second time without this guard. + // GetFirstFileNameChar avoids allocating a substring on every iteration. + foreach (var record in AvailableFonts.Value) { - font = null; - if (record.Type == SystemFontType.TrueType) +#if NET + char localFirstChar = Path.GetFileName(record.Path.AsSpan())[0]; +#else + char localFirstChar = Path.GetFileName(record.Path)[0]; +#endif + + if (char.ToUpperInvariant(localFirstChar) == firstChar) { - lock (readFilesLock) - { - if (readFiles.Contains(record.Path)) - { - return false; - } - } + continue; // Already tried in first pass + } - return TryReadFile(record.Path, true, name, out font); + if (TryGetTrueTypeFont(name, record, out var font)) + { + return font; } - return false; + // TODO: OTF } - private bool TryReadFile(string fileName, bool readNameFirst, string fontName, out TrueTypeFont font) - { - font = null; - - var bytes = File.ReadAllBytes(fileName); + return null; + } - var data = new TrueTypeDataBytes(new MemoryInputBytes(bytes)); + private bool TryGetTrueTypeFont(string name, SystemFontRecord record, out TrueTypeFont? font) + { + font = null; - if (readNameFirst) + if (record.Type == SystemFontType.TrueType) + { + if (readFiles.ContainsKey(record.Path)) { - var name = TrueTypeFontParser.GetNameTable(data); + return false; + } - if (name == null) - { - lock (readFilesLock) - { - readFiles.Add(fileName); - } + return TryReadFile(record.Path, true, name, out font); + } - return false; - } + return false; + } - var fontNameFromFile = name.GetPostscriptName() ?? name.FontName; + private bool TryReadFile(string fileName, bool readNameFirst, string fontName, out TrueTypeFont? font) + { + font = null; - nameToFileNameMap.TryAdd(fontNameFromFile, fileName); + byte[] bytes; + try + { + bytes = File.ReadAllBytes(fileName); + } + catch (Exception e) + { + System.Diagnostics.Debug.WriteLine(e); + return false; + } - if (!string.Equals(fontNameFromFile, fontName, StringComparison.OrdinalIgnoreCase)) - { - lock (readFilesLock) - { - readFiles.Add(fileName); - } + var data = new TrueTypeDataBytes(bytes); - return false; - } - } + string? psName = null; - data.Seek(0); - font = TrueTypeFontParser.Parse(data); - var psName = font.TableRegister.NameTable?.GetPostscriptName() ?? font.Name; + if (readNameFirst) + { + var nameTable = TrueTypeFontParser.GetNameTable(data); - lock (CacheLock) + if (nameTable is null) { - if (!Cache.ContainsKey(psName)) - { - Cache[psName] = font; - } + readFiles.TryAdd(fileName, 0); + return false; } - lock (readFilesLock) + psName = nameTable.GetPostscriptName(); + string? fontNameFromFile = psName ?? nameTable.FontName; + + nameToFileNameMap.TryAdd(fontNameFromFile, fileName); + + if (!string.Equals(fontNameFromFile, fontName, StringComparison.OrdinalIgnoreCase)) { - readFiles.Add(fileName); + readFiles.TryAdd(fileName, 0); + return false; } - return true; + data.Seek(0); } + + font = TrueTypeFontParser.Parse(data); + psName ??= font.TableRegister.NameTable?.GetPostscriptName() ?? font.Name; + + Cache.TryAdd(psName, font); + readFiles.TryAdd(fileName, 0); + + return true; } -} \ No newline at end of file +} diff --git a/src/UglyToad.PdfPig.Fonts/TrueType/Parser/TrueTypeFontParser.cs b/src/UglyToad.PdfPig.Fonts/TrueType/Parser/TrueTypeFontParser.cs index 8f57a032e..b29140725 100644 --- a/src/UglyToad.PdfPig.Fonts/TrueType/Parser/TrueTypeFontParser.cs +++ b/src/UglyToad.PdfPig.Fonts/TrueType/Parser/TrueTypeFontParser.cs @@ -158,7 +158,7 @@ private static TrueTypeFont ParseTables(float version, IReadOnlyDictionary /// A name table allows multilingual strings to be associated with the TrueType font. /// - public class NameTable : ITrueTypeTable + public sealed class NameTable : ITrueTypeTable { /// public string Tag => TrueTypeHeaderTable.Name; @@ -34,10 +34,10 @@ public class NameTable : ITrueTypeTable /// /// The name records contained in this name table. /// - public IReadOnlyList NameRecords { get; } + public IReadOnlyList NameRecords { get; } /// - /// Creaye a new . + /// Create a new . /// public NameTable(TrueTypeHeaderTable directoryTable, string fontName, @@ -56,7 +56,7 @@ public NameTable(TrueTypeHeaderTable directoryTable, /// Gets the PostScript name for the font if specified, preferring the Windows platform name if present. /// /// The PostScript name for the font if found or . - public string GetPostscriptName() + public string? GetPostscriptName() { string any = null; foreach (var nameRecord in NameRecords) diff --git a/tools/UglyToad.PdfPig.Benchmarks/Program.cs b/tools/UglyToad.PdfPig.Benchmarks/Program.cs index 332a2c1e0..2dece9b25 100644 --- a/tools/UglyToad.PdfPig.Benchmarks/Program.cs +++ b/tools/UglyToad.PdfPig.Benchmarks/Program.cs @@ -1,13 +1,12 @@ using BenchmarkDotNet.Running; -namespace UglyToad.PdfPig.Benchmarks +namespace UglyToad.PdfPig.Benchmarks; + +internal class Program { - internal class Program + static void Main(string[] args) { - static void Main(string[] args) - { - var summary = BenchmarkRunner.Run(); - Console.ReadKey(); - } + var summary = BenchmarkRunner.Run(); + Console.ReadKey(); } -} +} \ No newline at end of file diff --git a/tools/UglyToad.PdfPig.Benchmarks/SystemFontFinderBenchmarks.cs b/tools/UglyToad.PdfPig.Benchmarks/SystemFontFinderBenchmarks.cs new file mode 100644 index 000000000..5c19ff114 --- /dev/null +++ b/tools/UglyToad.PdfPig.Benchmarks/SystemFontFinderBenchmarks.cs @@ -0,0 +1,44 @@ +using BenchmarkDotNet.Attributes; +using UglyToad.PdfPig.Content; +using UglyToad.PdfPig.Fonts.SystemFonts; +using UglyToad.PdfPig.Fonts.TrueType; + +namespace UglyToad.PdfPig.Benchmarks; + +[Config(typeof(NuGetPackageConfig))] +[MemoryDiagnoser(displayGenColumns: false)] +public class SystemFontFinderBenchmarks +{ + [Benchmark] + public IReadOnlyList ARVE_2745540212_Open() + { + List letters = new List(); + using (var doc = PdfDocument.Open("iizieileamidagi.ARVE_2745540212.pdf")) + { + foreach (var page in doc.GetPages()) + { + letters.AddRange(page.Letters); + } + } + + return letters; + } + + [Benchmark] + public IReadOnlyList ARVE_2745540212_GetTrueTypeFont() + { + List letters = new List(); + using (var doc = PdfDocument.Open("iizieileamidagi.ARVE_2745540212.pdf")) + { + foreach (var page in doc.GetPages()) + { + foreach (var letter in page.Letters) + { + letters.Add(SystemFontFinder.Instance.GetTrueTypeFont(letter.FontName)); + } + } + } + + return letters; + } +} \ No newline at end of file diff --git a/tools/UglyToad.PdfPig.Benchmarks/UglyToad.PdfPig.Benchmarks.csproj b/tools/UglyToad.PdfPig.Benchmarks/UglyToad.PdfPig.Benchmarks.csproj index c0fecfcb7..8187fb846 100644 --- a/tools/UglyToad.PdfPig.Benchmarks/UglyToad.PdfPig.Benchmarks.csproj +++ b/tools/UglyToad.PdfPig.Benchmarks/UglyToad.PdfPig.Benchmarks.csproj @@ -23,7 +23,7 @@ - + @@ -42,6 +42,9 @@ Always + + Always + \ No newline at end of file diff --git a/tools/UglyToad.PdfPig.Benchmarks/iizieileamidagi.ARVE_2745540212.pdf b/tools/UglyToad.PdfPig.Benchmarks/iizieileamidagi.ARVE_2745540212.pdf new file mode 100644 index 0000000000000000000000000000000000000000..ed95cdf4a7a16ad554e923503d38eb61f8463c99 GIT binary patch literal 27499 zcmeFZcR&=)nm5|yoO2uykt8_>83h3W5dn!RBVounBViOINK_<95F|^MASfUsSwuuY zvPu$>Aj}Yk0Vdq$efOO6?w+%I_wIi8zpu5Yo~o|ys;9#5Nu{FKFY8Oo%P2F5F0YJE zFenMh3HdsDFlcEp$m+X$1;GP^Wc9rqg5a0oPQK1?2HC4{AJ-r^A$4Un25oJIz@PxQ zgEwthLBI99&%7Elo|H(2cz!FuhyA`IiDu)WdNOcAHqS}jPBETCAQTFV-VBL+Sel@p zDEfH3V5y)tcWg7MXL#e4K!KBB45P9gW2T5LwokW=H9FcB-+U~u-5Sn_*a~rxZ+qpJvh0X?yYa?^5N)dqmgrAOtz)d$f2SVZ?4%5TYf?0hmRrmUw&QG_q)Wt zfqx)xP%h#Xa`k*ccv$jD(D|!lZtz?=+fo~Yikcq&MoyYN;M`eT!3X6=g8eqapDMV; z@5l_4ozv9n=-#C8&VvL!O78$!y6gDYo4j zw#FyipSRdH@I_(&-I7JU{GRRMCeIFqpv}GN<n;WAnI*lo^UU4W*(A%a5gax!&WFEq7$38DY|%f1so&&J>!J6i z*SaVYx0!W$`bf`T0?vY*dw^k#Hh3Y5`#zBOdi~BY_QLZKtll}@)k8#nVC1uf?U6w& z2kPd}PeM%Lc3N!r*nEoVv-G}>Yv(U4hCHzA`R!f%Fo&@<D~+b!gb`11uj*gJFiqn z$F@_6Ia_8cSZ6p+<8gJ)3ktgxySKA<6Q=Y*GJ~B`Cu^A^y_>!wVO;>sl`On519Wz7YB3Wk|;K+w#bc$qU6R zj6N7b*lj!Ayv1me^CU{)QW^<-vB;ennRkJuP4K6;UL2DAx;u8KZDZ*((0|-kXEN%I z-+nqcQq(VyMxHb2$uQpM4)yZ=+J05!?v9Ppjfe23$9uPvE2wmMCAHZLphFf5B}3r= z+ZL=zGSymnbMrYv-q`Xb9nAtE`9m6e|6;acJQDz33~(RkKR5Czb-Mdi|C=Zvub}kr zVc@sTyw496dVI_g74e;iDEbVIIIXk7N=do|=|0(<$$h;xJxn=rLDw$CC8R_tTAEY; zT-Rf*&L`2@Ki2n_uxe2S6JII@E7x&hVFSYK)i?6aq#fL1jk9sgPilE?>2~~kcxh$S zT>~E#2AI&BS0GRgaQPmguN4i5MZ^00&ehMk3cL6($F?5qj*_hjg-g~^NWP9FZBC%D zC}j11UJ~mc9Y#$JT$fBRuN+*cgs2&E!iOG@W(1a9jeAN?_2jnb2k5o*qSV=QLMEbD zzFxkba$oBC3*lj-OMCm5bU#i^no({K?IR_slejoVEK&ZYz8+W$jx;xD)bxAYt zxqEv1w=SFJ-Ivv@pcqWeNrYWeL1k9FYp3IhksaKYcuS?SXg)yn9n%{WDNB3#<7tgi ze5$cbQu8m3Z<^_BN)Nt`9KT7898P>3WkVwyTzgxxOui&}|Aiv6Vs!4dK5Jle=2sE< zz~?R4R2%N_vn*yoQzE-YHXk)nX8Xu3!F`)u&0)NT&J$*~lh0-RL3$RnEw0!#RtB@T z7awbqmc*Y>ATPT=IOlOCm@CgM%NsrLc97Ole~pw6tt|PPN}N_&zNHWjHNH3*|DwhJ z;@H>wMxJ!J8j|%Ts)CADAa)B2NgfBugEBs+t`LE^(l_B`l6nW&O{9q z;wSwx(?0A%yE+uy4TXQ}zHsCSc_^`HbhmP7_Zgq+o0a(H(unMX?+SJ2k3x1s>l_m! zv}oSimD}gJ?OgvNBc;JBmd;#Hu715s<`*OSrr5#sn9GfRPx2x45gymicG$@&u_PND$-QE91sxMPwRcg!%E}_- zy^YS69i@?fleyL-lAkTc@mIu+JNB1_vkOoLuUwjEeCXcI>I4eq)Ep!W1k48ZI5|jg zWIqU&VplE4MO;OsuM=-xmAx8$z?_NxgN`CWynfJJy(V~ za(BPfI3$@I^x?bau+yAg-PF?JsagB(irQt{$D;W{;i^qpLv%`KKW<(6svq-a>blAa zMbc(-MPsNImm2Uzb-ICnY0pqTCZ{Eg30$BJpeeQ{f+%80tdmg=#h*w_Ucj zyWPMJzL;J>_Br*{I{)|2E^D+CM`xNkY2Ug9HfN+%eZTTtM61vD*MvIG;Nx$b`}M4f zPk3sYzrAU^i2j`)htPF%JAST;4zDQK@n-rU_ED%=72c@Xqs_g~a#l9JjrGxqh2P~~ zbscH?yPr(zCRd~tvOb5Um|QvJURl*;P=PF>1My-nM5k^X_{%h0EEK^=(M*%+))i*|AZ zS-(zq!Unz@YaGSmEJTz0BY&3ss*A-Kqu}KW#SJbhze9H9?Slid(*#Fq5N+4JuG+q< zZ+EIw^= zEt4|~uq(Y5`s3OG9iMs2`A0wMu+TTA-%h#w7xF;uFKpfj_k#<`Uh?tr4PubB2zCtm zBY)N1$CE+U9PSh(bW>SgQASQxNDWMsm4uX4RAkf@g;bT*WmME}Gsx=r1~`LSMH80msHe2N?Ao&TL{!Q!N30|q7?tBOV=G- zLH+c)Ljc?-NJt)hY|bES0(W+I(D4oXvpHpTWf=uEAvGm=8MV`{n8O2og9Du4fkFy@ zR{w*mK%twb{F)1aHFSM_g22y#MW-`uE_A9Mn+u(a$J59E=*?+!e^Z?=SBv|I@qrV5{IjIRwH_DIwV_aIX+}kh_z^Uk3cN6?q0( zJs(glclU7>lC^gCx#Sb*{#{hEX?&=owC;e}hg@S^boD#^rdr;tYcz~|2x1X=i>5QuV!yvh= zd*g5Bh_0`fZ-9lLgA=$WFT+FJo#5sMI;XtO00{{Ra0UDW5SM@! zy)btd05CL^1gOCkPYGy~PynPLWd+_)kfsDvumV6zMhb9(X*PH>n~?w{V0wD9n34aH zve5wm>tAT_5g>XO0G`K_0I~P}Oo>N-rtANfmka(ge`n**^r^<5sV~=`>2){XAm2bY zUq2y3U0or+0ACk(FE~J4rndp}uJ!Kc4akB#05~`W`k7eh-#G1;p`NZ#AXw>?{|4$@>-K{`If9UcnOUqJe-SFpD`NMk{o-5c%@2ms{t zr{zI#CpVCm18Mqz8|Jzotpxzo^saxQ9sfcH!Nb6Q0)VcsUwFVD>r&iFLP%a+T}|i; zJk$#w6eNA!!O7Dhz**>YSvvTH1Hhj=pS}gKo{p^$ILIpU>MAPI3NqmE|NZ(eAO5}8 ze@&du?VmOF%>G=6oFD&D_K$b}QRZ6$0BQ%|*gX12nPV;hw8a4c_uN0q&OQeKrUwAf zI{FuWI8WD$TTqanhOBI8XsC=k+)3tiK>vRIzgGBr&HtSEOM5b>?frf1gf7Ef9D==q zgiZ(53Ebtu0YZU)>~rb=xQPG9FaD)lf9c10bGQo}6e1v`Sb?kT6kI_u;Orjc?&~Au z?(?6`@c;0&zx3gh{BvD{1X~#aV28;7%%ki8+3$}4Ifyi5w+g{0B!8c`t29=?>CUr; zto^y}K^lDi$M3)UAx#Ifq=D|PLZ`*L<~M|#f&)TMX>d=R{!jq4029Ct@Bo5<2p|qf z0}6mDpb1yU<(x;>)q_w22q}`;$q?4qJq?@D%WB?f*87G-AnKYRi znJ$?rnH`x6SpZoySrS<`SqWJ^Sv%Pv*#y}l*%sLmIVCw8xe&QDxhDA)a!Yb2a)0t@ z@)Yts@)zW-KN*5>T2pP>T&8d>Ju7f8c`Yz8dDl)nsAzQnlhSpno*in zniE2h*p~SJJ?B9*-x_W1cpiZ@lEZlDuZTp}d8>{k&Uz?0lMhPJBsxO?-3wr2LZn=KK-- zrTicH4+I1S3E-sgPD`Dtl-=}Xcf(lydcGMqA3WnyI7WVU6`%G$|3 zmK~HO$SKQt$(6~?%d^TG$;ZgI%kL^kDmW?RD|}I8P}EmMD85!iD@iHAl?s(+l-ZO` zloOPDl<_L6Dgi3>D(k9eRqv=4s7|Y~t6f)1QG2gWp{}EjQ14Vf(ooR|(rD7y(UjKo z)U4M0r6r~X*DBLmxp3x!!-e7ti=bA1NBg<qAjnaL0ne4K`<)q7>^jP&Q_44$->z~ng)vwV<87Lb>7`(eeamDz` z<15pK0)|e8RfZ@dRih}QK4W@g3*$WF<*Slc1Fp84keV2oJTdutP4t@2wKh}0)X?;a z>9^}**Zr?|m{FQtH_JEsX)bRbX+CJdW&yLPw!qxbxsiTj-csB$*s{lp$?BF>mDQp3 zW$O&I2C@*c=o{uc<|pMB z@3-i$;h*J?3NQ|+2qX!-9r!wk9aN@128##B1uunYhvbJ~L#;xa!&t(6!ajyehCd8n zyQ_b~0 zno3M_N&A$pl3w_T@{!M@smIXAFEUs%?q;k!F?rIODU_L%xt|5g8p>A6F3O?J3C>x3 zdiCk++_Slla&dWXd6W4%`3(j91rG}jpE*ApFT7Y-UnEeJTy*^0{rOC>L2+xzxsvQs z%F>Y1pJi5MgXL=FRTaDy$rboYpUTA-<}doHRI940`K!}wNNa*?)@yIoey+P*_qtxX zzPN$2A^9cnGWg|2pdehUc z+5W0SrlYb`xHJDP$J_KS+O7v(MC4uM!8^ZqTiveRD?PBD`CgmeFMVcxpZbma-w)^y z^bTGcM83c9zH>-p=*u2cKfW4K9(naigemw>Yv-kw8px|q2t{X zmnR0k7=9U^yf!&DWjQr7eQSDg#(Cz~tk*1hE@bXx{@z!LuSwsSzh!^t|6Z~nvCy!n zviNrC^3um;i{<$hrmuS9PmkvI?No?1u$tLyY5|Emw2zD#@vuuy}}@bhFOX8=+b5;7JNVjlnj zbsCC4zkRB!oL(fPWaJc-RMa%IbYO*7%m66~85t=#83hG7sLzl@fzJVQ77A8j`Ad{+ zW)4(m{Mi-mKQ5*g(P`}FFdsvmRdfu9p`qpE;^yHMJtrnEA*rOSqN=8@p?g_R-{6X& zk;M&5D{D~Ea&m^dxVpJ}1O^3%gocIRjeQUopOE-4DdS0IR(8(Q+`N*~vhs?`7gg0w z%`LB5+g`tE@9FL99~gW;^kIDB%jDGb%c&3Jgc-MiaurS56=F3jK%zqIQu(e|KMv5peG{%Cy$H;fCBjYL?EUg(Jj^KU)7KV z^@PsyuJyZDm3-SI0zA=lVS$#Dlfin|4>gMVAPIIRyl`_|RD`O7b+pB#EseTnRBN_QWEssoaV$rs@smw=@2uO{i8xfE~q6)E_galJg#F9ZRViyki z5BY!~>n-~aIk=NbB5=VdZR5$)v4iWfFNk;@|+z`7+(L(`OoLH9z5h$MMFT$E_LU6eT|FPiKGxi8c z*xq&#fdsO<3-d#`!im5>NemI_v4h|mhN1>RHfVOzkiUY^t_1eGgGAt***}$7i|Gme zyK#a1-%$b1{ohgfziZ|H&mEM7Y1mp?G36dvjI#WRc6|tUSngP5km;zt(yjlv_|fyX zV?v~c1O6GNfv}=U%-Ys05jZ1LM+BVUhlJdH$-QQypea_XN{XFcH@)C}ZK}S44%u7s zwzZ55(E2+EHXaf<@1O*4rv1#4B&nmTMBwq0quZdA7(jIM6M3qV6r)_d#t55qQ|PbMTFp>9=p&jK{)IDxxM1 z0RpbgC|0Bh(}oj;y=vQfvIN8CBPzlYR-oPKOd#;Qzq#|x1IbJfbPuT_0>ZurZC%o+ ztV|eM1yATGzJ#yCoG{!xAW*MF!*=r~gR616*zoeXN?aUr>N~cr$I1-RH-C7% z{Wq~cjTK2fib^vnm^zG_v*8amjMO}p%nP$~N}h%%?G`VN!r>5%@h{o^Ul`~Nfi`Tq zAbRbP$CU*A(-nq=GVnv_{k0WZkg=%iLxI(M_|fg%t%1tM~pHTI^BH?; zutWrtQpXJ<@Tl#0et{Ef^g$MzA58>qe|}DwI*E#}!#$loer>(mK?EqkW@6NdfG48I z^5kapkT%EZjyZNsj8?>PqYjF2ab5VS8Z^_=&m51UT}~o!Gh%sx##ZlCi(1%hXNSx! z_`aM(!WPq1S25};fj7S$y5I4i6h-~!cJNn3xVnwiiEXc6D_vd9M9j)QM^2Q^fIxti zSh4p%Df$X8pc>45hFHWY`9;e2VLyx$n7VaEKh=yLLOX_=c?$eq1d{AUT2P_AP%>=CG9=k|-0*y-{33#3Q&0*`@%8%ez4ErPeJRb~ zIyMg92_GQ*`*>15b!EO09Z16Qc6A;+Mf_~xrfsajD`fkaLyY3jTiSILkM46Cu`4TxZwFqTXaZ<12lWaZ~T^8;RqZ?8dwIwI2*USxn z?ce0%3}D#@eFEmV90X@|n0+h8_0Ut;PAs zE}G=m2QAgk%*m8`b$sT_Kll0`C)vlL{y<%x*eW}lyJ59Tn*+En!uFXp`ty4hDPJdR zcfx9Hj%vAhP|@%LN3{r<ZR?~@a-XvB)qU_xc}w}0sG{?4@a5FI z0bQQ9g)ne$8txbCv0o?ocQ=PmPtS?v+m;EX4ym`sX2{p^p9W1|w&v|(FxyL0;?myI z(&L8PK4MwfYIovQv#sHmtsE&B%JO}NJmzO*w#+jfc&-Jc2y?&rV|6$-NElrk(mOlv zU6#@Dxk@jp!3ne`zT@A%l4UGnpC!C6f;f+pn|(?m0*qR^zO|Z$T)QXVF>H!Jzj|GG$^}up`vby2(YYM9}BjXe;e=@ zZ7nVL2*I>rtf|AoeC{OaX(=SFm8SGK!qt}0Hj$UBaQ>zCS+=@3kAkw5y(>pwpQMBt zJoH>fb*lGksTVE#G0tph8y3QI>mu3OQyYU`a#tV7?c~0gYq_&M{;iZXNxVMxSI<9T z5R7GdzNpz1m)PEhPt934cKuxQX$1OBl%Rn)HbZHPB8Fsd5_;tj$Noj@65z=1f<~M0 zUk9J+{~(k+;Uxmrv;+`p7D0q;Ug#;hk7#9zKk$t!6- zQ@P$vKKh1pz16R<2enwEWDsLCP* zu-41SG$=>3V5A^Qxh2$I2Aj3QK-HZ`9@v;RE`^V{9@C>ae3WG~zutcL>fv$Ln0ad} z=QJ0iN}ng5AK~OXgIq*z8KZIxuk{7v6o^jzeJj2sw!?_k$uM$ zRUN@;BFj+DNHuh2T&G~-$RnRJv`=%1Xa-Z>&8fCjxla|yF-9a0+CaX)){0CjWsRP% z2Om)8IG1{Nyum8d(%4;DN9jf>S4nU|o^MtIS`kx&P3_+gtGO3>zSl|qwIFRn&*K&T zUZ#W^^E58)PiUT>o>^nXPsI%lxagCaGR^vy{0`VE^;><=ge?&}7Zop$_VgwZQ>5suyZ=I4E;8CgtU7n&Y}!LSiVhyd5VakxyD zpvttC@nWiLZz1>n%SGa^-=NRcl+D}89gT0>*vo#Gw0?*B>5x1eSkTqQ8kkaX9iD%c5Atu38oHip6f7D)Fi4ly^TJ8)_1%|5K)Ice4PA^a zLYWW&l1P8TcYJ*`xQs{pyO>bcNku&fnn;HF+46;iS36wpDy?_p->yKJ-P_FaZ~Gdf z*1ss>WqaqMdcLh&+Z|%Q4HWzMDA9=`eikCTcCf;bL@j6?qFbqpZoOS8d#5VQxNm{0 z6S_PZcgI#3O)=VU${AKYr`b%Wl3aR-Eyrjz-5L-rb75TBmkN^q2_n z`}gzev7R-%oXqb(Q2lt2SNz6hJrXmPibu_6=fUS&vz^ReI}tHTyf^lYw8x)ABrt3rR_Y z@Jg$4-bZytW$Tueq8h;xLp^>4w^zhJNgFAc-k31CV0tt(tax=;^y5q00kfx03Z3)P z{bqykqiop2x(9=I8lT=xs-w5!;?VJ{(`7L#bJ7TQ|D^aJ!@UBLC-!^r6DZlSKIhvx@t}X7>!)y*?L>m!sy;c9LuC6-SM(a}L z#?(tDnQxvys_{RFKx|Uvr6s&428$x(mm2yU=R82VA`%KAXpf$k=O1BX*80KCVd*U} z8<%JBTAKr{8t-D&PNUY6J;`CxbhCrNFnyqMDY+qiDBz=u7#5qtL7R2EZd8^0nXxo$ zjjvnAn1p+I`tiGCZFE}1GF6N=C%TeNHwU_`P5Zw2{9I+z4J$=HYpQF?&EjJz4z$-t z_@OOT6O|lB%>DXGIn8FEiuyJX!(5Ui4*czI*xn&fuYpo?4n?)qfimuf3TTBcISH1(@CXgPd%jJ! zbbab&8|oo!qkPHRR~IcCucf{Wr8o5rTzZSRG~97l+UM`^Nj_l8k7#598B59DPz#8tV}mP&PJKR=6OW`nT5lidwn}P_3@h7v{ONX=TWP|DaF#6|t(zEyd0iC|ALO{#KVjbM^wRPT3j0yo=(U%g`goN%ayS1=(B^NE z>&Wq7W5X?FX_?*4YhS#4O5N(cc4{jlJUfgNpHikpL?o8Q+ssfJvSg;j-q-{g^<)U_ z{Vs;DLb$_N!!E!3&W2_JopvdCYw^5Oy2{OA5mwmbWlcQ${D<&tvWl-axy!UVBWw;D zfQC(84pFN(-lpUS)#jDv*CTo?VxA=LC5IeA4pttoY`VOWG0k1}Yks$^$loA4xGZNW z`&%x!@u`to(reoV`$(+~qm+gQC4v8zUX!jw1cGv~p#O8--~%>`2%LTEdqUq0W(N>u z<)ESQfeIHN_gAAE!Hq-(6ycs(ze_(+ok1Qrg?&dhR}uHhjfVRWjBg`iV*vd& zYBT}1d&2>_#m560+h-Bsgc0PCQ8buoassWa14A1^fLlotAp!bpf9iAaH>aNC?c`-& z#trMcs)<^fHSTlUaWykWQ-^a_jb-+_FWmF8GS~NXL!BagDJENL_6(G+Y<@^0`~nrD zLLz{&Go9VUJVlwtcM78y2EKD+B~EnRF`Rj>iRW<+5$;O}(y$YYwm9J@ecIG$m0~3g zhRnQ=CF(`p?QaQ1b#or7=I(R+8u-+94_TZtRuRml}C-Y&2KbLJ5@823$ddS<%G$o7mBLX<~8bab> z92B*UQy>CA^SU&Xo3;f%{`y>8@RK{TjfKfwi}OsynK<4t)2tgt%4Jy|3LgBZ@e#cN zzw8F=@cR8F_;aZyi z6YCm!2qvCoKt|d9dLbWAO#L@ogx`00d@+Fpr;XYRYaWE~c5#H6_Ue2x#var~3=%je zf=+6#ET;uZryOdO$UJvWbGyo7gD>&SE~Ngwl5MzTP0&Fug1&QjKs-Ot> zQJdY+85ruZ#}t&Mldj>m9iqo@*_*FN!!)bhZSMEa^3b)-c0#Tf5pZdJ@;=S=O};bp z@EP1Fqu(L*>YI27D>j)dZ^g-qrQ7^?fU`g(u^`jkh+WveWz*Wi-Ry9y;!CN?m+P5` z9PQL116c7|yx-|^DR#pK!AS)VqHFDMr@fXQA-ppIBY!)3bKZz z(7dR@WbYQ|n{Qp;b%{46&f10@>Q2Bj&VG5G(f`B*?em!YC2T{(vOYiR&f(qo`>v%M zMd`|i23z1PkT2tF5nc;a*n~j@phZ`Lt?9MrL`#Jk+jf5G^j(6`4g1#1~NEXWkaP2cOp>IrS^lsj%JFF)O!>*@$^ToDd5VEnxP+77;TP}(TnSLl5})8 zmF#y2^n%+d`Po+R$yFvaiEOqWlqQ|%TcGLEh?GTl z<%D{*!Gnsp-G*yy^n1=YPT@2of<2d+nCuPK-ib{de3QL~Y*XwJYfTyE_Dx54HDIM& zx6)5U!*p#j=9k6dy5w-SCk#DUrzO)~Ul#YSv&h$zAL*xb2Q+>rpDQm9gXr7leZRSW z@G$18T z0M+dhsTViuY1ArMZC0LKcsp~IyE^lmX^o>?Fu#X6?h|$uoj-_Zf%cC=aGu!QDw&!U zMB?`|9ca!s{;Wd+6=>jj#<#gHjn{1jU0@ulcslFO8=O^{FO(3zn|I)w z3VN`*gb1IA0EZR~<+NO%F3wmbHnwT z*$t8SRHgL~tfr66Z)EsqM0RGogU&Ax1utv*JFl%J2Y%QTlrnX{z6N=>+~nnQ`z8Z@ zd;-10?uKQ+)-@^?eV9d>nXzOX?`w}Ql?U*c6fv5Jv$>$x3A7PVpw|E^VU1_mp+zix z!-Ao+5oO4~Dhw~$lx0GE-jM|3YB*IQa2_fR`u7TsL66_q0Sbg7HUc*hSj#C|n=MKQ z-JMXJ8iGJ;NdzuO^b}!3qj?Z}CIH?Zz2DovrO3B(b6J31V)2ELzmJC;o*d+Tr65Ix|az2C$K7rTzrCP zt=t+xA^Tw51SPCkcOessDL%Sau(aA3ZMuXh4!-cSGJ>(FV!hq!NrmU(-HL{)1^ZhD z{G1w5<$VO3!VSx$Pg5JqeC2&S93Gj!#y%;`z4G{^v~FIR+WV>8=!u8i17Y4Y^USjG z6m##^3~S9MWSQx`H{ap+hIV~Uq6KjpsKKqu6FY1o*3<;$%<`*< zx=U_DXL;>nY%euj1987IT+2}Dm_J&8@`7g1cMg~msQFI~`CSx)aA(T><*2t!_OL^H;PE6435 z-g(=Yy=nZ#+co3UHn&gRvpK%R(kuZgF}5WItq>B2z>k*n0+LzkFK3cotvhU3G*Bb& zKG--(Gh+4d^Us9SXbxaJSGqC_gcl72u~EixTA|Ce39$;by>b|we4ly)GyInN)78#P z4|=~Cb39&Abgxy8yeyZsX)$o8daQ2=&8;zE`72OTxe5`^3FYsi*(ekM1NTT1Y%i+K z3{{lfjOa0SLR0lvM+@ve9pHrhu8FcYQ5|+t5{CWOG|bjwdmD%q0U-g5W5i73&Y(Gp_{WjHPH3^^(UhR!9ePg$hn8?q&UbRGXXys@)ZLNM1e__2ioG;C^?UyLsdkRF zxV5*R_j$IFt(DWT8{@@eG9bDjm%ufLGH*B*`OXe5z7Etd5ynU$Oc96^m7a65^zuUyS3b4LD|{Kc|+%{}QQYoCi^)Fxz> zqa}|m!Vt?B=qu%l(}%FGEPVw$8F0(SOeviTTJ$SW)NJ1b!U0Qb`2LA!N(e zxQqkjmLe4qsN^AJWSZ$A#38%9e+`Rp;2goh_<-0AhLJ+I7QoD8>dIZhry6WOXt-vA zp^1$ya>Q&Gn5*F%I5^Z6Kn$716M>gru7szL@oOh4L||0{>q;0(Qv}}?bvhaX=nTg)?|u%2HOH>!<7mJ;*J$GeiN_hiT33{ zEGQsODEkrXa|YPh6I-zTCX_E>s1St0$@&`{M+T>&g9v2W6DE*{ddP#~Xg2GO)oAdL zLANh*l?&OjgCFz7yTKkzqWCWm0TEm}VUWj_Q1KZ$3N1~?mC4>C0+{elSt2mCAOan& zM>Zp|D$N8FixnikfPdk*UW*AfAc}BBpuAmid6k2R6E$_@!L2Ysq%WF_U>IqNf3OIK z*>I+ZPMF8w$qOko6rcGOv=(aSsP;WUFY6v?m8gjwEh4}`*(w;PG{vVzvx1$6FD4!B zVHkWjbmovGnDsAo17?PfI0RT2h}G>DBRlwM@ri7oqw8&D0abXg{5DbLVGl%)xtxKh0p1ly(PQ|*-_S(<3mx3d->_T#!fp1%+Y8-vEm zy|&|MRI4=|TTAf6dZ`E&Y+CnO*DbX&KI3HFWME+y9|y_9&$fe8{ED<#>>7$K%V1(2)vV>GhHnDt-HiW z1Oy|g(48>mNP!B^P(i*9G(%zBaunnmVZNoMxvOL1cR5-nxl|d~+B%Ifzpb2ftJlZJ zG0C0PYDT^f9$v_rUzar8i1m;edC*@MyfJ(YeW{i*)6Jf zDH&ImQ&+Rb#Y^-v49~)^tfaCxcXTknNPOO`$XxwA<01zR?0>f_VzET_#5vLjLLbfN zd2R(0f)%aV&_KlfY{}&vT!y4M?X`>`mNm8L$L!s8WHm3Nq&hw}{-PuTc5R-nX!t=U zq#1^?hhW8DVr5YT-QG!_vtIp6Amps z(1=g|u(6qmUc)64KuBt|=z|@*bvE=@ffiE{b_E1R5!|I=#1AJB30{7cB@|U4vL-Hi}3P721~|G1GUFs!;M;abh20X}~VcHG$M3!9z%7aoePwcds!Nx20K z1L%2}V1hnQzXpdHjh>*!E(GXw&9t)U43{^*hM00FD5wIM* zzlrT@*xDg5)Pmi-f!#Tk0jh+Wbh7E+gfk#VvJb&|DS!})9ul^q$Dv1h(PE%4CO;N> z5OI-^dg_V^gRYSi0WGG}pn6#i2AtT&h91~)Z6^yXck&n%C#T`@%&h21C`K7;47QmQ zBekc2tlpJ9V)?OvksL-Iu!4=)5lF!1l2JDaBdQ6|Ue11daLwADGZb!9oW6aQ$fP$O$3(xR~St6YwAqshAXfJJ*}EP zk;cov?-bu|kB+N2Kdo*0^ofUR#qgD;J!Jv?PdtcmzD3Bp9UyuHN!3Y)GFXbw#pf&{ z6XqCNlPk{V^B$VLra#oEe_J!kdpXB$Au-r}C-|+)y3<1$WvP?gGhme4Z}12lYVw<) zvJ}ZQbFwIheTL@KI}!U$fgZj(@yeI&Qj}L!RT$a3hSd#0UhLg2$Nc0AI=4S#V+S4~ zO5aSMeL3dq{R2U3_jTyR1U6a_-iMr5Px$ikm3q_Hu7DJNSjWSDPu|z5N}0lSl1!FE zqvkS`c`)$kG2z$*JiOz9X2R<&Y4y%y-IuMJQtJ zrzPG?R$8(4L?IM32|g{_@sWJ;G9XI^FE2h{`uH&({;F*!jM zd;i7FPjf0y;<7a#jBQ;u-_#cCYCGr2#N>XsJAeK-c4qE&l=reM^DleW&&o)Sup;Y; zeI^F1ui=;A--z3j^~H_j``&zpE!jlCVp5rJKK_dId9U-&^ENh?U2T@)#U9T{K7p-) zYmh<*|2BAZeh`!RCDN?&mIo({q6^YhzJ!P=0P- zos_v@QAj6V{M2#Diu=W4m7_K%wj8ZGu)sq&6E?iu(3cvr*;lBJ-bTI%d(ziFGE1_3 zAfRI*a+XErO!Ld;L_Mbp_o-ol!u-mccIy~3U3YJK!)_2UuIMmVQd?AV_dHV@V18hp z;y_)_Js@udO7RRssk0ddc!fKtu&W(ZalMq=vDX_ zbTXZcQfb*8Xm7Y%q&QqF5TVx7mpJn;A-iGkLc@L!J{I|75b7j*q70dx2e%+3BBi*`k@qlA7_7Q}kVZhQNxRycSP z4GM1Qts9^UmM1HP_CWMi64XHN?=rVe9j-6Ca1Um3cnfP0LC1N`nv8jVQ*KluX=Lqrxfl(WV9;n$G(juD6Z&IP8zg4CM~Fc6 z%DaOTL?Uw6cp0%J27ab;8PNf0Nhg@WK+N5$SRBRMe}T}0UEzw_5AJ2*Hh19ccBRK$jj^X-=I1ZoSsEu9}QpeO~A`+!}HQgUFnhQ3@E0>(0fpqh1l;T zSP@k3Rl+a|*87`sUT=ccBsJp2SIUTI*7lQYsb`GqeET$74#B`$P-9;XZx;UJV?FwF zYEDOj&}G*zb=;RA zEDH*@KG`My-WhDdvJA|=P?jaGW;kgnjlNFu>D6fGO0M5>NAclmjtVIy{$^E1DT-b_ zSzg*wWU*N=SV_{+4yJsH0Chu|$;BRbpL@EX zqZ>k(Z*EG_b-?H^j(hmI{c_E*{Zi66QJy44mpngj+Wl)hc++h4Is2s1q=-?b z`j664tFpON4ga@m>Pn^T^D{)C2=_#)FuVXBuB_!_n{#7Ck^UTvzgOd<-0U^8`(f>< z;)Bi!#er*uTqO5;15do{Wja@`#Zg}?MM@X#AwWDo0%c*+tB;4!mzCXkKRQo$CA=}_ zA(}GR&+zpd%hpLHrZ|&ToRHx=!|Vh3pik9ZH1OqsMW;Gs6Oc5Wc9YItPFse3?=%C4DCUuE;T0d}JWXDOIsnjL|P4 z>AN6W8gr-kDekr3F=2-teu2^C@}EqVZk@qjkB5vn0n)BjI<*86(~~76%i~dsbUPM z7!^dcYLp--iWCG9Ur0m|1PzZu$YZ#BBDN}Y-MUs?cir{>S!*(9&STG>Ju~O*z0cX- zw4s*B6$$tYj#o7+15MgCb_GFy9p8e}WW_h5`41BU{6&0vt5df?vn>}C&YMdgbGcP< zz339e5c<>t8BRl5?IWuPkE@#+G_ATFsOe)#58)zmJ9^rXj@wOoRR=g^p4~*R{H?jm zcUzP7!KfoiXUAwZL~Ki=de?Ne>f;KeJ=Np^Qp83*1j$I}?niWuhc|t14)n46Ym|#< zTSgy8p6IFOhubIeRj%lb?@9_2LWS+V%t~ad?{-l^hs}y_!ju~X!QEq|jW(wn8&Bh= zoD%L67JJ+a2=I3{o+@|jscJVO>i2B`OTI+2i~jV9B(z3N1+ADqcu-uScHi$?g0tY{ zHyqAOE-$d>w2}54VO;GIMf9CPtr8m|)2KIWC1tvg?I;mBi}`xpORU@$TsskWyKZmq z*M()|;N(0n&&-Ty!k*>Ml$|H~W{l?J>90|fPj`X=;6zr@kd`(D+^=f0#5KsJVES5q z(~wRjQ|?-GXJOfeaf){N#tPcp2Bc*L5)#KQlNj)($YyMESLs&@l9cH$Zj%yb`JJoB z(JDnv_nYr0YG4nH~+4TbV{f^m2t-CC56?z{sBDL3v zT>NM+A!esD1<^hAmm{pQ3r5&@8ii-5V5zKm5p{{P)qV<=e88)owy-+?c37@$ z_lqQ+dH(A5HgFnmnV}5|f`b(-hgdSr6C$oNUQ+zErpUi!sTCY2^(3 zji-#5bYrUJ%Jo~hJKe*ryzA)s_Ev2*iU@X-C%QeNp9db+8aWu)IShJBp(NN%X(w*# z9n#UKOZjM+)u5_lRgLnaaxgNtNz~sD=`0j<_!uI!W{{%@tVRZRRwuN{Q~t_&TS|X; z^ub(tV>J)}=QfiRn%=3b0J?YG^94t7 ztZg8K)=d$qQ_PcBtxBE6HCc>YGNgyR00sF2Rq1mUi&v%+#&2__Vt$C<7O*d`n+KZ zH{CX6?CwU9bf(_AvCgff)cN5*SFm!Nww{b!pg*hZ+eZv;7JbI<*ndMVBX-6j$V|?5~;0DtPv>UQDdan*=e+w3ZXvX z#`@niNxe>hg(yeXgxeM8dp{OUd45y)k?f9$F`o2rU-LY#yBpc)daC<%wNLj(era|& zZGOwKIpv}LF1zb1yhIbzY~vz7o@z;oQHwq>+8xHoW$yYmtVKS3F2h4`e7~;OK1N-E z2doNW(h#X%VwIpTmRZz>&O}m7)OGHou!(H_#y@sYPfE?*0 za57%cjd(%Za^M0yoFjXwJk;oJKRB22ax?7(;qKK!xkG zHIx0pbCPV_IHdD4wedm1C(J8tYV2rMWhXqaYeW?|%|i7ear>6h2uiYW3foGVxC~am z(}V)STcpvw#5BG>e-0LS-xD=l*-4t|o zZ4UQ`kRNSRt2F!%0#A?>q6PcDUi96%6%`LWg&qW-i?Ye*grD!a>m1N=pWpeSX;5nH zlBKcY*;Ry47v+A7&phrQeOPwl%yRmKnr)dofNoNBk_=ej?#$udexY9;zUXbNzcQeAl6;}X^V z+84j5*q>W?9obyfVCiLfD(&bUk*=$-)8kinS7G8&v`uq3-Z5&A0%>ubC$hR8%gBtY zwA&XRzhQzw`o6g?W?NbI|1I@6GS|^#VCBJ#nA(McgX9sTHyB4F9v>gVBlaC>|2L)b z(6mChx-jNugBp4=SrM3Q@{?eaFe@y$?pU4efpn^4&FqYYmjC)!)`l-vPW~@(Mf=0< z+&YuaQo(k5k4)T)du}2t3Ap4RR5Wo6&#e6FypSm+J;n{QcYPOCf))f7g62TPhTcMb zrDnV;wm%ZQm(#pKh9_0v_*72eIgPMP7n1&5-*oZ+Zhh0-1KV`oW&3|NA@znO|GHKG zSLqR9SLNK1(LudMZUfH9b^)5n(@$mgxZDEG^STMTH&n@RUphto3vy-c%MchW4CZqk zpYF6Folh(<*DY@xSgi?g=xvk!Ol3d1Z{ePT3%0%E7wDxE0_7T~0YN9YQZhv?0@WN_ zu=-c-;Po}GjJ~LkJmwx%NqSVnn*?bJFP%d{Q`WO8a zv0|V7j-O=rf-*BQY7g-RNDFwZf|*JRyJ=uZXJU<1fQCGU(aA-#3whck32jyr>64Jb zJ>p;T3oo33r|XxaZm9kdc;Mo3r33WF)iCFB;578$75QY zQliezP;Jq0AK{c%aRN~En#8aWeh^nWIDi`B`e8uM3(*J=`E@lIu1})*iXcgo51PKG zA6!sUqPz`8)3eI)+h}hy`5zwt&E@#m2c0kmRJzdOoSWq)r|6yBhu1b)rCG(FVx}P< zs?85MMTKn)i!#jUt)x%vn@^ja{6P8O^9a5{9MSdkvfroIxrR#nuN|J>FE|V?*?%%} zwV~8Jy{j`O@<~Z<7j=^T(ULoO>6#EFCvUZztA!R&LfO8seY+SrW<=543bh?Md>}X+Dy--gjLWGT`5WrDk&ss?<;<@A)CfWycYKTXt&G@e=TG>95Ujy+w0L?m30dTWp$Y@4%hw>DL? zH{KV1U%4_{S{#^EUai@e=U6QGRcO!8&H|e`=)w;m2|i21e<(_iuY8WI&st|PGX%8C zE%Mq+@=Ak8RxS$q*ooQpV){Mw#h(B-bI2o=-BCi}nfZTCq7CV+qB8y(jo`y~=zK=2 z`%iU9i65hH&J6ZtipwQ;oKW2q75*808r9{|qDeKds7Zf8qUo{=*4?y}hcjHHN@jBO zIj?+|MOlT%e8Y?cNvHN73W%8HTJmdsS6_Qq@!;=s&el)8VfE|XGa(w6CRG zFME~SKIJ+d!hXmm$R1rhd@Yncs#a|zk0dH*d#56OMpBICJ0O@n1Fy? z8{pFhj$frQ;ZT5O?S{31df27Ex@1fwWFlOS1AGyP^VT_mHW@@k0wHU80NNyIONL`C z9}|%_jqw9%((sLeDtIz5scnPNz{;>|_+RL0X=5TDru#=5tUY#Kj3&^)cqYMhU~T2#*?( z02>+piExZ<(BQQPdL*vBLm($Gk{yac+XJNhD`b1Ob-~eJv9y@PelOAG;d@P-Wv0>G z$Vhncu)1Fd#zcBHrIfoMs<{hRd&aFgE7vnJaC z>i(PcK%67#5k|BHo(XTawT2r3UB?^rtZnetukpf(2?W^n7;VSo8TMT;CM%o=n+@Ro yT6sgC6dl4gC15=ebZgE_O#S9s%(c*d6(E)q7{k$u*xH6*ZKG#qw$#m0?|%Rlb6=nU literal 0 HcmV?d00001