### Conditional Compilation Example Source: https://github.com/empira/pdfsharp/blob/master/docs/coding/DevNotes.md Use this technique to toggle between new and old code implementations during development. ```csharp #if true «new code» #else «old code» #endif ``` -------------------------------- ### KEEP Tag Example Source: https://github.com/empira/pdfsharp/blob/master/docs/coding/DevNotes.md Use the 'KEEP' tag for code that is not currently used but is retained for documentation or reference purposes. ```csharp // KEEP for future reference. // … ``` -------------------------------- ### IMPROVE Tag Example Source: https://github.com/empira/pdfsharp/blob/master/docs/coding/DevNotes.md Use the 'IMPROVE' tag for correct but potentially improvable code, indicating a need for enhanced reliability. ```csharp // IMPROVE robustness of the code. // … ``` -------------------------------- ### Basic xUnit Runner Configuration Source: https://github.com/empira/pdfsharp/blob/master/docs/testing/xUnitRunnerSettings.md This snippet shows a basic configuration for xUnit runner settings. Use this file to control parallel execution and theory handling. ```json { "$schema": "https://xunit.net/schema/current/xunit.runner.schema.json", "parallelizeAssembly": true, "parallelizeTestCollections": true, "preEnumerateTheories": true } ``` -------------------------------- ### Configure Multiple Target Frameworks Source: https://github.com/empira/pdfsharp/blob/master/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md Specifies the target frameworks for the build. Use this to ensure compatibility across different .NET versions and platforms. ```xml net8.0;net462;netstandard2.0 ``` -------------------------------- ### Rebuild PDFsharp Solution for .NET 4.6.2 Source: https://github.com/empira/pdfsharp/wiki/Use-PDFsharp-with-.NET-Framework-4.6.2 Follow these steps to rebuild the PDFsharp solution for .NET Framework 4.6.2. This involves modifying project files and recompiling. ```bash dotnet build ``` -------------------------------- ### Enable Package Generation Source: https://github.com/empira/pdfsharp/blob/master/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md Set `GeneratePackageOnBuild` to true in the project file to enable NuGet package creation during the build process. ```XML false ``` -------------------------------- ### Download Assets for PDFsharp Source: https://github.com/empira/pdfsharp/blob/master/README.md Execute this PowerShell script to download necessary assets like bitmaps, fonts, or PDF files before compiling the solution. This is required for some unit tests and projects. ```powershell .\dev\download-assets.ps1 ``` -------------------------------- ### WSL Debugging Profile in launchSettings.json Source: https://github.com/empira/pdfsharp/blob/master/docs/development/WSL/DevelopmentWithWSL.md Configure Visual Studio to debug .NET applications using a WSL environment by adding this profile to your project's launchSettings.json file. ```json { "profiles": { "Windows": { "commandName": "Project" }, "WSL": { "commandName": "WSL", "distributionName": "" } } } ``` -------------------------------- ### WSL Test Environment Configuration in testenvironments.json Source: https://github.com/empira/pdfsharp/blob/master/docs/development/WSL/DevelopmentWithWSL.md Set up Visual Studio to run tests remotely in a WSL distribution by defining the environment in testenvironments.json. ```json { "version": "1", "environments": [ // See https://aka.ms/remotetesting for more details // about how to configure remote environments. { "name": "WSL Ubuntu", "type": "wsl", "wslDistribution": "Ubuntu" } ] } ``` -------------------------------- ### Ascii85Decoder Class Implementation Source: https://github.com/empira/pdfsharp/wiki/Bug-fix-in-Ascii85Decoder This C# code implements the ASCII85Decode filter for PDFsharp. It includes rewritten encode and decode methods addressing previously reported bugs and improving robustness. The encode method handles padding and converts data from radix-256 to radix-85, while the decode method processes ASCII85 encoded data. ```csharp // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using Microsoft.Extensions.Logging; using PdfSharp.Logging; using PdfSharp.Pdf.IO; namespace PdfSharp.Pdf.Filters { /// /// Implements the ASCII85Decode filter. /// public class Ascii85Decode : Filter { // Reference 1.7: 3.3.2 ASCII85Decode Filter / Page 69 // Reference 2.0: 7.4.3 ASCII85Decode filter / Page 37 // Wikipedia https://en.wikipedia.org/wiki/Ascii85 // Padding the input data to a multiple of 4 bytes for encoding or // 5 bytes for decoding respectively is well explained in the Wikipedia article. // It may not be intuitively utterly clear why it works. // Rewritten March 2025. /// /// Encodes the specified data. /// public override byte[] Encode(byte[] data) { if (data == null) throw new ArgumentNullException(nameof(data)); // Number of bytes in source data. int sourceLength = data.Length; // length == 0 is not treated as a special case. // Number of 4 byte groups. int words = sourceLength / 4; // Number of bytes modulo 4 (final padding group, if any). int rest = sourceLength - 4 * words; // Number of four-byte groups including an optional padding group. int wordsPadded = words + (rest == 0 ? 0 : 1); // If rest != 0, the last block is padded and must not // be encoded as 'z' in case it is 0. int lastBlock = rest == 0 ? wordsPadded + 1 : wordsPadded - 1; // Allocate source bytes including padding. byte[] source = new byte[4 * wordsPadded]; Array.Copy(data, source, sourceLength); // 0..3 trailing zeros. // Max result length including suffix. int resultLength = wordsPadded * 5 + 2; // Allocate result bytes. byte[] result = new byte[resultLength]; int idxSrc = 0, idxRes = 0; int wordCount = 0; while (wordCount < wordsPadded) { uint value = ((uint)source[idxSrc++] << 24) + ((uint)source[idxSrc++] << 16) + ((uint)source[idxSrc++] << 8) + source[idxSrc++]; if (value == 0 && wordCount != lastBlock) { // Encode 0 as 'z' instead of '!!!!!' result[idxRes++] = (byte)'z'; } else { // Convert from radix-256 to radix-85. byte ch5 = (byte)(value % 85 + '!'); value /= 85; byte ch4 = (byte)(value % 85 + '!'); value /= 85; byte ch3 = (byte)(value % 85 + '!'); value /= 85; byte ch2 = (byte)(value % 85 + '!'); value /= 85; byte ch1 = (byte)(value + '!'); result[idxRes++] = ch1; result[idxRes++] = ch2; result[idxRes++] = ch3; result[idxRes++] = ch4; result[idxRes++] = ch5; } wordCount++; } // Chop result if rest is not 0. int effectiveResultLength = idxRes - (rest != 0 ? 4 - rest : 0) + 2; if (effectiveResultLength < resultLength) Array.Resize(ref result, effectiveResultLength); // Set suffix. result[^2] = (byte)'~'; result[^1] = (byte)'>'; return result; } /// /// Decodes the specified data. /// public override byte[] Decode(byte[] data, FilterParms? _) { if (data == null) throw new ArgumentNullException(nameof(data)); // Number of bytes in source data. int sourceLength = data.Length; // Allocate max. source bytes including up to 3 'u' digits for padding. byte[] source = new byte[sourceLength + 3]; // Number of z characters. int zCount = 0; // Digit index (0..4) in the 5 digit of the radix-85 number. ``` -------------------------------- ### Reactivating Old Font Behavior Source: https://github.com/empira/pdfsharp/blob/master/docs/change-log/gn-v6.2.0-log.md Use these settings to reactivate the old font behavior for the Core build on Windows or WSL2. Note that these settings only have an effect on Windows or WSL2, respectively, and are not for portable Core applications. ```cs GlobalFontSettings.UseWindowsFontsUnderWindows = true; GlobalFontSettings.UseWindowsFontsUnderWsl2 = true; ``` -------------------------------- ### Original Code Block for Image Hashing (GDI Build) Source: https://github.com/empira/pdfsharp/wiki/PDFsharp-6.1.0-and-6.1.1-incorrectly-duplicate-first-image This is the original code block in PDFsharp 6.1.1 that handles image hashing for GDI builds. It calculates an MD5 hash based on the image data. ```csharp else if (iid is ImageDataBitmap bmp) { var md5 = System.Security.Cryptography.MD5.Create(); var hash = md5.ComputeHash(bmp.Data, 0, bmp.Length); image._path = "*md5:" + HashToString(hash); } ``` -------------------------------- ### Reference to PDF Text Rendering Modes Table Source: https://github.com/empira/pdfsharp/blob/master/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/Documentation.md This snippet is a C# comment pointing to TABLE 5.3 in the PDF Reference, sixth edition, which details Text rendering modes. ```C# // Reference: TABLE 5.3 Text rendering modes / Page 402 ``` -------------------------------- ### Fix PDFsharp Encryption for .NET Framework 4.6.2 Source: https://github.com/empira/pdfsharp/wiki/Bug-fix-for-encryption-with-.NET-Framework This code snippet demonstrates the fix for PDFsharp encryption issues on .NET Framework 4.6.2. It ensures the HashAlgorithm is re-initialized correctly after TransformFinalBlock, a step implicitly handled in .NET 6 and later. ```csharp void ComputeAndStoreEncryptionKey(byte[] documentId, byte[] paddedPassword, byte[] ownerValue, uint permissions) { _md5.Initialize(); // Skipped code. if (RevisionValue >= 4 && !EncryptMetadata) { var additionalBytes = new Byte[] { 0xFF, 0xFF, 0xFF, 0xFF }; _md5.TransformBlock(additionalBytes, 0, additionalBytes.Length, additionalBytes, 0); } // Finalize Hash by calling TransformFinalBlock() with an input count of 0. _md5.TransformFinalBlock(permission, 0, 0); var hash = _md5.Hash!; int keyLength; if (RevisionValue >= 3) { // The encryption and MD5 hashing key length (in bytes) shall depend on the Length value (in bits). keyLength = ActualLength!.Value / 8; // *** Begin of new code. *** #if !NET6_0_OR_GREATER // We have to call Initialize here for .NET 4.6.2. // .NET 6/8 include Initialize in "_md5.TransformFinalBlock()". _md5.Initialize(); #endif // *** End of new code. *** // Create the hash 50 times (only for 128 bit). for (var idx = 0; idx < 50; idx++) { // Only use hash with a length of keyLength. hash = _md5.ComputeHash(hash, 0, keyLength); } } else { // The encryption key length for revision 2 shall be 5. keyLength = 5; } // Skipped code. } ``` -------------------------------- ### IsExternalInit Declaration for Older .NET Frameworks Source: https://github.com/empira/pdfsharp/blob/master/src/foundation/src/PDFsharp/docs/Notebook.md This code provides a workaround for init-only setters in .NET Framework versions prior to .NET 6. It ensures compatibility by defining the IsExternalInit class. ```C# #if !NET6_0_OR_GREATER namespace System.Runtime.CompilerServices { internal static class IsExternalInit { } } #endif ``` -------------------------------- ### Color.Parse with Number Value Source: https://github.com/empira/pdfsharp/blob/master/docs/MigraDoc/change-log/MD-v6.2.0-log.md Demonstrates the corrected behavior of Color.Parse when invoked with a number value, preventing internal exceptions. ```csharp var color = Color.Parse("#f00"); ``` -------------------------------- ### Ascii85Decoder Logic Source: https://github.com/empira/pdfsharp/wiki/Bug-fix-in-Ascii85Decoder This snippet contains the core logic for decoding ASCII85 encoded data. It handles character validation, whitespace skipping, prefix and suffix detection, and calculates the necessary padding for accurate byte reconstruction. It also logs errors for illegal characters. ```C# int digitIndex = 0; // Flag indicating '~' as first character of suffix was parsed. // A PDFsharp user found a PDF file where '~>' was separated by // a LF. So take that into account. bool tilde = false; // Analyse, validate, and clean up input data. int idx; int idxSrc = 0; for (idx = 0; idx < sourceLength; idx++) { char ch = (char)data[idx]; // According to Wikipedia the string can start with '<~'. // PDF specs does not mention this case. if (idx == 0 && sourceLength >= 2) { // Here we do not expect any white-space before or between // '<' and '~'. if (ch == '<' && (char)data[1] == '~') { idx++; continue; } } // According to the specs skip white-space. if (Lexer.IsWhiteSpace(ch)) continue; // Check for start of postfix. if (tilde) { if (ch != '>') throw new ArgumentException($"'~' followed by illegal character '{ch}'.", nameof(data)); // Ensure that idx never gets the value of length. // Also ignore any characters beyond suffix. break; } if (ch is >= '!' and <= 'u') { source[idxSrc++] = (byte)ch; if (++digitIndex == 5) digitIndex = 0; } else if (ch == 'z') { if (digitIndex != 0) throw new ArgumentException($"A 'z' appears illegally within a 5 digit radix-85 number.", nameof(data)); source[idxSrc++] = (byte)ch; zCount++; } else if (ch == '~') { // We cannot expect that the next character is a '>'. // It can be some white-space. tilde = true; } else { // Ignore unknown character, but log an error. PdfSharpLogHost.PdfReadingLogger.LogError("Illegal char in ASCII85 string: '{ch}'.", ch); } } // Loop not ended with break? if (idx == sourceLength) throw new ArgumentException("Illegal character.", nameof(data)); // Effective source length cleaned up by prefix, suffix, and white-space. sourceLength = idxSrc; // Number of radix-85 digits. int nonZero = sourceLength - zCount; // Number of decoded bytes in case no padding was needed. // Full 4 byte blocks. int resultLength = 4 * (zCount + nonZero / 5); // Can be 2, 3, or 4 single radix-85 digits. int rest = nonZero % 5; // The rest cannot be 1 as a result of the padding method. if (rest == 1) throw new ArgumentException("The ASCII-85 string has an illegal number of padding characters.", nameof(data)); // Number of 'u' digits to be padded for decoding (1, 2, or 3). int padding = 0; // It is not intuitively completely clear to me why the padding method works this way, but it does. if (rest != 0) { padding = 5 - rest; // 1, 2, or 3 resultLength += rest - 1; // Append 'u' digits to make length a multiple of 5. for (idx = 0; idx < padding; idx++) source[sourceLength++] = (byte)'u'; } Debug.Assert((resultLength + padding) % 4 == 0); // Allocate result bytes with padding. byte[] result = new byte[resultLength + padding]; int idxRes = 0; idx = 0; while (idx + 5 <= sourceLength) { char ch = (char)source[idx]; if (ch == 'z') { // Add 4 zero bytes. idx++; idxRes += 4; } else { // We already ensured above that there is no 'z' and no white-space in the next 5 radix-85 digits. // Using '!!!!!' instead of 'z' is not treated as an error. var value = (long)(source[idx++] - '!') * (85 * 85 * 85 * 85) + // Interesting: Without parentheses the compiler (uint)(source[idx++] - '!') * (85 * 85 * 85) + // does not treat the powers of 85 as a constant ``` -------------------------------- ### Reference to PDF Dictionary Objects Section Source: https://github.com/empira/pdfsharp/blob/master/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/Documentation.md This snippet shows a C# comment referencing section 3.2.6 of the PDF Reference, sixth edition, concerning Dictionary Objects. ```C# // Reference: 3.2.6 Dictionary Objects / Page 59 ```