From 8d2dd02564a163b821fdc854bc56d48df70eadfe Mon Sep 17 00:00:00 2001 From: Sabin Pop Date: Wed, 28 May 2025 12:16:06 +0300 Subject: [PATCH 1/7] Create the PdfAcroFieldEnumerator used to enumerate the elements of a PdfAcroFieldCollection --- .../PdfSharp/Pdf.AcroForms/PdfAcroField.cs | 81 ++++++++++++++++--- 1 file changed, 70 insertions(+), 11 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs index f69b1cc8..f4c0a644 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Pdf.Advanced; +using System.Collections; namespace PdfSharp.Pdf.AcroForms { @@ -261,10 +262,10 @@ public PdfAcroFieldCollection Fields } PdfAcroFieldCollection? _fields; - /// - /// Holds a collection of interactive fields. - /// - public sealed class PdfAcroFieldCollection : PdfArray + /// + /// Holds a collection of interactive fields. + /// + public sealed class PdfAcroFieldCollection : PdfArray, IEnumerable { internal PdfAcroFieldCollection(PdfArray array) : base(array) @@ -366,12 +367,12 @@ public PdfAcroField this[int index] return null; } - /// - /// Create a derived type like PdfTextField or PdfCheckBox if possible. - /// If the actual cannot be guessed by PDFsharp the function returns an instance - /// of PdfGenericField. - /// - PdfAcroField CreateAcroField(PdfDictionary dict) + /// + /// Create a derived type like PdfTextField or PdfCheckBox if possible. + /// If the actual cannot be guessed by PDFsharp the function returns an instance + /// of PdfGenericField. + /// + PdfAcroField CreateAcroField(PdfDictionary dict) { string ft = dict.Elements.GetName(Keys.FT); PdfAcroFieldFlags flags = (PdfAcroFieldFlags)dict.Elements.GetInteger(Keys.Ff); @@ -402,7 +403,65 @@ PdfAcroField CreateAcroField(PdfDictionary dict) return new PdfGenericField(dict); } } - } + + public new PdfAcroFieldEnumerator GetEnumerator() + { + return new PdfAcroFieldEnumerator(this); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return GetEnumerator(); + } + + IEnumerator IEnumerable.GetEnumerator() + { + return new PdfAcroFieldEnumerator(this); + } + + /// + /// Enumerates the elements of a . + /// + public struct PdfAcroFieldEnumerator : IEnumerator, IEnumerator, IDisposable + { + private readonly PdfAcroFieldCollection _collection; + private int _currentIndex; + private PdfAcroField? _currentItem; + + internal PdfAcroFieldEnumerator(PdfAcroFieldCollection fieldCollection) + { + _collection = fieldCollection; + _currentIndex = -1; + _currentItem = default; + } + + public readonly PdfAcroField Current => _currentItem!; + + readonly Object IEnumerator.Current => Current; + + public readonly void Dispose() { } + + public Boolean MoveNext() + { + if (++_currentIndex >= _collection.Count) + { + return false; + } + else + { + // Gettig the current item using the index will convert + // the PdfItem to PdfAcroField for us + _currentItem = _collection[_currentIndex]; + } + return true; + } + + public void Reset() + { + _currentIndex = -1; + } + } + } /// /// Predefined keys of this dictionary. From 3258c10af6ad7afc3cdfb77fcf0214b3d69259fe Mon Sep 17 00:00:00 2001 From: PDFsharp-Team Date: Thu, 24 Jul 2025 12:30:57 +0200 Subject: [PATCH 2/7] =?UTF-8?q?=E2=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- docs/MigraDoc/change-log/MD-v6.2.1-log.md | 17 ++ docs/PDFsharp/change-log/PS-v6.2.1-log.md | 38 +++ docs/change-log/gn-v6.2.1-log.md | 20 ++ gitversion.yml | 2 +- .../DocumentObjectModel.Tables/Cells.cs | 4 +- .../DocumentObjectModel.Tables/Column.cs | 4 +- .../VisitorBase.cs | 4 +- .../DocumentObjectModel/DdlEncoder.cs | 4 +- .../MigraDoc.DocumentObjectModel.csproj | 1 + .../MigraDoc.Rendering-gdi.csproj | 1 + .../Rendering.Forms/DocumentPreview.cs | 31 +- .../Rendering.Forms/enums/Zoom.cs | 31 +- .../Rendering/IAreaProvider.cs | 4 +- .../Rendering/ParagraphIterator.cs | 4 +- .../Rendering/TopDownFormatter.cs | 4 +- .../MigraDoc.RtfRendering-gdi.csproj | 1 + .../MigraDoc.RtfRendering-wpf.csproj | 1 + .../RtfRendering/CharacterRenderer.cs | 10 +- .../RtfRendering/MdRtfMsgs.cs | 4 +- .../RtfRendering/RendererBase.cs | 4 +- .../Structs/UnitTests.cs | 6 +- .../MigraDoc.Tests-gdi.csproj | 1 + .../MigraDoc.Tests-wpf.csproj | 1 + .../MigraDoc.Tests/CultureAndRegionTests.cs | 4 +- .../MigraDoc.Tests/Extensions/XunitHelper.cs | 5 +- .../MigraDoc.Tests/MigraDoc.Tests.csproj | 1 + .../tests/MigraDoc.Tests/PageSizeTests.cs | 5 +- .../tests/MigraDoc.Tests/SecurityTests.cs | 138 ++++++++- .../src/PdfSharp-gdi/PdfSharp-gdi.csproj | 1 + .../PdfSharp.BarCodes-gdi.csproj | 1 + .../Drawing.BarCodes/CodeDataMatrix.cs | 4 +- .../src/PdfSharp.BarCodes/Extensions.cs | 5 +- .../PdfSharp.Charting-gdi.csproj | 1 + .../Pdf.Signatures/PdfSharpDefaultSigner.cs | 6 +- .../src/PdfSharp/!internal/Directives.cs | 14 +- .../PDFsharp/src/PdfSharp/Drawing/XColor.cs | 6 +- .../src/PdfSharp/Drawing/XStringFormat.cs | 4 +- .../PdfSharp/Pdf.AcroForms/PdfButtonField.cs | 4 +- .../PdfSharp/Pdf.Advanced/PdfImageTable.cs | 4 +- .../PdfSharp/Pdf.Content.Objects/CObjects.cs | 4 +- .../src/PdfSharp/Pdf.Content/enums/Symbol.cs | 4 +- .../src/PdfSharp/Pdf.Filters/Filtering.cs | 4 +- .../src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs | 20 +- .../PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs | 2 +- .../PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs | 281 +++++++++++++++--- .../src/PdfSharp/Pdf/PdfDictionary.cs | 26 +- .../PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 12 +- .../src/PdfSharp/Pdf/PdfDocumentOptions.cs | 16 + .../src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs | 4 +- .../PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs | 25 +- .../Build/CSharpFeaturesTests.cs | 4 +- .../PdfSharp.Tests/Build/ReleaseBuildTests.cs | 17 +- .../PdfSharp.Tests/Helper/XunitHelper.cs | 5 +- .../tests/PdfSharp.Tests/IO/WriterTests.cs | 45 +++ .../PdfSharp.Tests/Structs/XUnitTests.cs | 6 +- .../PdfSharp.Quality-gdi.csproj | 1 + .../System/CompilerServices.cs | 8 +- .../PdfSharp.Snippets-gdi.csproj | 1 + .../PdfSharp.System/System/CodeAnalysis.cs | 3 + .../extensions/SystemStringExtensions.cs | 5 +- 61 files changed, 665 insertions(+), 232 deletions(-) create mode 100644 docs/MigraDoc/change-log/MD-v6.2.1-log.md create mode 100644 docs/PDFsharp/change-log/PS-v6.2.1-log.md create mode 100644 docs/change-log/gn-v6.2.1-log.md diff --git a/README.md b/README.md index defc0f4c..40fa5271 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PDFsharp & MigraDoc 6 -Version **6.2.0** -Published **2025-05-19** +Version **6.2.1** +Published **2025-07-24** This is a final version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 6 with updates for C# 12 and .NET 6. diff --git a/docs/MigraDoc/change-log/MD-v6.2.1-log.md b/docs/MigraDoc/change-log/MD-v6.2.1-log.md new file mode 100644 index 00000000..dfdb779d --- /dev/null +++ b/docs/MigraDoc/change-log/MD-v6.2.1-log.md @@ -0,0 +1,17 @@ +# MigraDoc 6.2.1 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`. + +## What’s new in version 6.2.1 + +### Breaking changes + +*(none)* + +### Features + +The new formatting option of PDFsharp is also useful when generating PDF files from MigraDoc. + +### Issues + +The bug fixes of PDFsharp are also useful when generating PDF files from MigraDoc. diff --git a/docs/PDFsharp/change-log/PS-v6.2.1-log.md b/docs/PDFsharp/change-log/PS-v6.2.1-log.md new file mode 100644 index 00000000..c40e6171 --- /dev/null +++ b/docs/PDFsharp/change-log/PS-v6.2.1-log.md @@ -0,0 +1,38 @@ +# PDFsharp 6.2.1 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`. + +## What’s new in version 6.2 + +### Breaking changes + +*(none)* + +### Features + +**Formatting options for PDF files** +Using the *Options.Layout* member of the **PdfDocument** class, you can set how PDF files will be generated. +Use **PdfWriterLayout.Verbose** to get readable output. This is the default for Debug builds of PDFsharp. +Use **PdfWriterLayout.Compact** to get small output files. This is the default for Release builds of PDFsharp and should be used for production code. + +### Issues + +**Encrypted hyperlinks are now working** +We fixed a bug that caused PDF file encryption to throw an exception if the PDF file contained certain types of hyperlinks. + +**Character escaping for PDF names** +**PdfName** now correctly includes the curly braces ('{' and '}') in the list of characters to be escaped. + +**Fixed incorrect number formatting** +This bug could lead to incorrectly formatted Outline entries. + +**Fixed exception that occurred for files of size 1030 bytes** +PDFsharp can now open files with a size of 1030 bytes. + +**Arrays in PDF files are now written without line feeds** +When using **PdfWriterLayout.Compact**, arrays in PDF files will be written without line feeds. +The files with line feeds are technically correct, but we were informed that some printer software cannot handle those files correctly. + +**Keep both /A and /Dest entries for outlines in PDF** +According to the PDF Reference 1.7, an /A entry is not permitted if a /Dest entry is present. +PDFsharp 6.2.1 no longer deletes the /A entry if both are present. diff --git a/docs/change-log/gn-v6.2.1-log.md b/docs/change-log/gn-v6.2.1-log.md new file mode 100644 index 00000000..518a8c5e --- /dev/null +++ b/docs/change-log/gn-v6.2.1-log.md @@ -0,0 +1,20 @@ +# General 6.2.1 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **general** `History.md`. + +## What’s new in version 6.2.1 + +### Breaking changes + +*(none)* + +### General features + +The new formatting option of PDFsharp is useful when generating PDF files from PDFsharp or MigraDoc. + +**Automatic download of assets folder** +TODO + +### General issues + +The bug fixes of PDFsharp are useful when generating PDF files from PDFsharp or MigraDoc. diff --git a/gitversion.yml b/gitversion.yml index be0052e7..19d2b4ec 100644 --- a/gitversion.yml +++ b/gitversion.yml @@ -3,7 +3,7 @@ assembly-versioning-scheme: MajorMinorPatch # PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE # Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; # C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7443}' +assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7508}' mode: Mainline assembly-informational-format: '{NuGetVersion}' branches: diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Cells.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Cells.cs index 12f03117..302ed6b7 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Cells.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Cells.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using System.Diagnostics.CodeAnalysis; @@ -92,7 +92,7 @@ public Row Row } /// - /// Resizes these cells list if necessary. + /// Resizes these cells¹ list if necessary. /// void Resize(int index) { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Column.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Column.cs index 37653e7d..7d902a82 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Column.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Tables/Column.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using System.Diagnostics.CodeAnalysis; @@ -118,7 +118,7 @@ public int Index /// /// Gets a cell by its row index. The first cell has index 0. /// - public Cell? this[int index] => Values.Index is not null ? Table.Rows[index][Values.Index.Value] : null; // BUG_OLD Doesnt use Index property to guarantee getter loop ran. + public Cell? this[int index] => Values.Index is not null ? Table.Rows[index][Values.Index.Value] : null; // BUG_OLD Doesn’t use Index property to guarantee getter loop ran. /// /// Sets or gets the default style name for all cells of the column. diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/VisitorBase.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/VisitorBase.cs index d4e8faa8..987686f3 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/VisitorBase.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/VisitorBase.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using MigraDoc.DocumentObjectModel.Tables; @@ -422,7 +422,7 @@ static void RealizeOrientation(PageSetup pageSetup) } /// - /// Updates the orientation according to a PageSetups PageWidth and PageHeight. + /// Updates the orientation according to a PageSetup’s PageWidth and PageHeight. /// static void UpdateOrientation(PageSetup pageSetup) { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/DdlEncoder.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/DdlEncoder.cs index 806174bc..a841a237 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/DdlEncoder.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/DdlEncoder.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using System.Text; @@ -22,7 +22,7 @@ public static class DdlEncoder var sb = new StringBuilder(length + (length >> 2)); for (int index = 0; index < length; index++) { - // Dont convert characters into DDL. + // Don’t convert characters into DDL. char ch = str[index]; switch (ch) { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj index b97949f3..2f388d42 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj @@ -5,6 +5,7 @@ MigraDoc True ..\..\..\..\..\StrongnameKey.snk + true diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj index ba85e789..f7a05e8c 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj @@ -9,6 +9,7 @@ GDI True ..\..\..\..\..\StrongnameKey.snk + true diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs index 7356ef0e..c5e3a5c0 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs @@ -1,32 +1,5 @@ -#region MigraDoc - Creating Documents on the Fly -// -// Authors: -// Stefan Lange -// -// Copyright (c) 2001-2019 empira Software GmbH, Cologne Area (Germany) -// -// http://www.pdfsharp.com -// http://www.migradoc.com -// http://sourceforge.net/projects/pdfsharp -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -#endregion +// MigraDoc - Creating Documents on the Fly +// See the LICENSE file in the solution root for more information. using System; using System.ComponentModel; diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs index 3c8ce68d..3884bf37 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs @@ -1,32 +1,5 @@ -#region MigraDoc - Creating Documents on the Fly -// -// Authors: -// Stefan Lange -// -// Copyright (c) 2001-2019 empira Software GmbH, Cologne Area (Germany) -// -// http://www.pdfsharp.com -// http://www.migradoc.com -// http://sourceforge.net/projects/pdfsharp -// -// Permission is hereby granted, free of charge, to any person obtaining a -// copy of this software and associated documentation files (the "Software"), -// to deal in the Software without restriction, including without limitation -// the rights to use, copy, modify, merge, publish, distribute, sublicense, -// and/or sell copies of the Software, and to permit persons to whom the -// Software is furnished to do so, subject to the following conditions: -// -// The above copyright notice and this permission notice shall be included -// in all copies or substantial portions of the Software. -// -// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR -// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, -// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL -// THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER -// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING -// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER -// DEALINGS IN THE SOFTWARE. -#endregion +// MigraDoc - Creating Documents on the Fly +// See the LICENSE file in the solution root for more information. namespace MigraDoc.Rendering.Forms { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/IAreaProvider.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/IAreaProvider.cs index eb5d4d2b..be957814 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/IAreaProvider.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/IAreaProvider.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. namespace MigraDoc.Rendering @@ -14,7 +14,7 @@ interface IAreaProvider Area? GetNextArea(); /// - /// Probes the next area to render into like GetNextArea, but doesnt change the provider state. + /// Probes the next area to render into like GetNextArea, but doesn’t change the provider state. /// /// The area for the next rendering act. Area? ProbeNextArea(); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphIterator.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphIterator.cs index bc231bb3..5b65013f 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphIterator.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphIterator.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using MigraDoc.DocumentObjectModel; @@ -98,7 +98,7 @@ internal bool IsLastLeaf /// Returns the next iterator in the tree pointing to a leaf. /// /// This function is intended to receive the renderable objects of a paragraph. - /// Thus, empty ParagraphElement objects (which are collections) dont count as leafs. + /// Thus, empty ParagraphElement objects (which are collections) don’t count as leafs. internal ParagraphIterator? GetNextLeaf() { // Move up to appropriate parent element. diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TopDownFormatter.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TopDownFormatter.cs index 2ded913e..277478d1 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TopDownFormatter.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TopDownFormatter.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using MigraDoc.DocumentObjectModel; @@ -66,7 +66,7 @@ public void FormatOnAreas(XGraphics gfx, bool topLevel) { if (docObj is Paragraph p && p.Format.SpaceBefore > Unit.Zero) { - // IsFirstOnPage isnt true for an element in _elements following a PageBreak. IsFirstOnRenderedPage is also true if the last element was a PageBreak. + // IsFirstOnPage isn’t true for an element in _elements following a PageBreak. IsFirstOnRenderedPage is also true if the last element was a PageBreak. bool isFirstOnRenderedPage = isFirstOnPage || idx > 0 && _elements[idx - 1] is PageBreak; if (isFirstOnRenderedPage) p.Format.SpaceBefore = Unit.Zero; diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj index 6f390de3..773d24f6 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj @@ -8,6 +8,7 @@ OnBuildSuccess True ..\..\..\..\..\StrongnameKey.snk + true diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj index 0eec2a3f..9ac96081 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj @@ -9,6 +9,7 @@ OnBuildSuccess True ..\..\..\..\..\StrongnameKey.snk + true diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/CharacterRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/CharacterRenderer.cs index 9550de19..a78a4d52 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/CharacterRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/CharacterRenderer.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using System.Diagnostics; @@ -37,7 +37,7 @@ internal override void Render() case SymbolName.Blank: for (int idx = 0; idx < count; idx++) _rtfWriter.WriteBlank(); - //WriteText wouldnt work if there was a control before. + //WriteText wouldn’t work if there was a control before. break; case SymbolName.Bullet: @@ -54,7 +54,7 @@ internal override void Render() for (int idx = 0; idx < count; idx++) { _rtfWriter.WriteControl("u", "8195"); - //I dont know why, but it works: + //I don’t know why, but it works: _rtfWriter.WriteHex(0x20); } break; @@ -63,7 +63,7 @@ internal override void Render() for (int idx = 0; idx < count; idx++) { _rtfWriter.WriteControl("u", "8197"); - //I dont know why, but it works: + //I don’t know why, but it works: _rtfWriter.WriteHex(0x20); } break; @@ -72,7 +72,7 @@ internal override void Render() for (int idx = 0; idx < count; idx++) { _rtfWriter.WriteControl("u", "8194"); - //I dont know why, but it works: + //I don’t know why, but it works: _rtfWriter.WriteHex(0x20); } break; diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/MdRtfMsgs.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/MdRtfMsgs.cs index c5bfca27..234a965f 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/MdRtfMsgs.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/MdRtfMsgs.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using PdfSharp.Internal; @@ -48,7 +48,7 @@ internal static MdRtfMsg InvalidNumericFieldFormat(string format) => new(MdRtfMsgId.InvalidNumericFieldFormat, $"'{format}' is not a valid format for a numeric field and will be ignored."); internal static MdRtfMsg CharacterNotAllowedInDateFormat(char character) - => new(MdRtfMsgId.CharacterNotAllowedInDateFormat, $"The character '{character}' is not allowed in a date fields format string and will be ignored."); + => new(MdRtfMsgId.CharacterNotAllowedInDateFormat, $"The character '{character}' is not allowed in a date field’s format string and will be ignored."); internal static MdRtfMsg UpdateField => new(MdRtfMsgId.UpdateField, "< Please update this field. >"); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs index fa9f7782..7994c802 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using System.Diagnostics; @@ -357,7 +357,7 @@ protected object GetValueOrDefault(string valName, object valDefault) } /// - /// Renders a trailing standard paragraph in case the last element in elements isnt a paragraph. + /// Renders a trailing standard paragraph in case the last element in elements isn’t a paragraph. /// (Some RTF elements need to close with a paragraph.) /// protected void RenderTrailingParagraph(DocumentElements elements) diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Structs/UnitTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Structs/UnitTests.cs index 8ea56049..040dc0a5 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Structs/UnitTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Structs/UnitTests.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using FluentAssertions; @@ -60,7 +60,7 @@ public void Unit_Test() (unitNull == null).Should().BeTrue(); (unitNull != null).Should().BeFalse(); - // Only check cm variables, which are totally equal because str2Cm with "2cm" wont cause rounding errors. + // Only check cm variables, which are totally equal because str2Cm with "2cm" won’t cause rounding errors. (unit2Cm2 == unit2Cm).Should().BeTrue(); // Use IsSameValue() for other variables. @@ -69,7 +69,7 @@ public void Unit_Test() unit2CmPt2.IsSameValue(unit2CmPt).Should().BeTrue(); unit2CmPc2.IsSameValue(unit2CmPc).Should().BeTrue(); - // Only check cm variables, which are totally equal because str2Cm with "2cm" wont cause rounding errors. + // Only check cm variables, which are totally equal because str2Cm with "2cm" won’t cause rounding errors. (unit2Cm2 != unit2Cm).Should().BeFalse(); // Same value - other unit. diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj index 1fe83cdd..a1e08c8b 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj @@ -42,6 +42,7 @@ + diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj index b53d9512..23dde58f 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj @@ -43,6 +43,7 @@ + diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs index 9d73c186..70b1adf8 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using System.Globalization; @@ -50,7 +50,7 @@ public void DateTimeTest() var englishMonths = new[] { "January", "February", "March", "April", "May", "June", "July", "August", "September", "October", "November", "December" }; var amPm = new[] { "AM", "PM" }; var germanWeekdays = new[] { "Montag", "Dienstag", "Mittwoch", "Donnerstag", "Freitag", "Samstag", "Sonntag" }; - var germanMonths = new[] { "Januar", "Februar", "Mrz", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" }; + var germanMonths = new[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" }; // filenamePattern with placeholder for cultureInfo. var filenamePattern = IOUtility.GetTempFileName("DateTime{0}", "pdf"); diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs index 209a0881..a51ac72d 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs @@ -1,4 +1,7 @@ -using System; +// MigraDoc - Creating Documents on the Fly +// See the LICENSE file in the solution root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj index 60f0258a..615d07a3 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj @@ -30,6 +30,7 @@ + diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs index b3c41387..891cf7b9 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs @@ -1,4 +1,7 @@ -using System; +// MigraDoc - Creating Documents on the Fly +// See the LICENSE file in the solution root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs index 6709fc87..aa5ce0b9 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs @@ -1,23 +1,27 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using PdfSharp.Pdf; -using PdfSharp.Pdf.IO; -using PdfSharp.Pdf.Security; -using MigraDoc.Rendering; -using Xunit; +using System.IO; +using System.Security.Cryptography.X509Certificates; using FluentAssertions; using Microsoft.Extensions.Logging; using MigraDoc.DocumentObjectModel; +using MigraDoc.Rendering; using PdfSharp; using PdfSharp.Drawing; +using PdfSharp.Drawing.Layout; using PdfSharp.Fonts; using PdfSharp.Logging; +using PdfSharp.Pdf; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.Security; +using PdfSharp.Pdf.Signatures; using PdfSharp.Quality; using PdfSharp.TestHelper; using PdfSharp.TestHelper.Analysis.ContentStream; -using static PdfSharp.TestHelper.SecurityTestHelper; +using Xunit; using static MigraDoc.Tests.Helper.SecurityTestHelper; +using static PdfSharp.TestHelper.SecurityTestHelper; namespace MigraDoc.Tests { @@ -355,7 +359,7 @@ public void Test_Read_OwnerPassword(TestOptionsEnum optionsEnum) [SkippableTheory] [ClassData(typeof(TestData.AllWriteVersions))] [ClassData(typeof(TestData.AllWriteVersionsSkipped), Skip = SkippedTestOptionsMessage)] - public void Test_Write_UserOwnerPassword(TestOptionsEnum optionsEnum) + public void Test_Write_UserAndOwnerPassword(TestOptionsEnum optionsEnum) { Skip.If(SkippableTests.SkipSlowTestsUnderDotNetFramework()); @@ -991,5 +995,123 @@ public void Test_Strings(TestOptionsEnum optionsEnum) LogHost.Factory = oldLoggerFactory; } } + + + [Fact] + public void Test_Hyperlink() + { + // Create a MigraDoc document. + var document = CreateDocument(); + // Associate the MigraDoc document with a renderer. + var pdfRenderer = new PdfDocumentRenderer + { + Document = document, + PdfDocument = new PdfDocument + { + PageLayout = PdfPageLayout.SinglePage + } + }; + // Layout and render document to PDF. + pdfRenderer.RenderDocument(); + // Set security settings directly on the PDF document + var securitySettings = pdfRenderer.PdfDocument.SecuritySettings; + securitySettings.OwnerPassword = "Secret"; + // Save the document... + var filename = PdfFileUtility.GetTempPdfFullFileName("HyperlinkWithEncryptionTest"); + pdfRenderer.PdfDocument.Save(filename); + // ...and start a viewer. + // Process.Start(new ProcessStartInfo(filename) { UseShellExecute = true }); + // Creates minimalistic document with hyperlink. + static Document CreateDocument() + { + // Create a new MigraDoc document. + var document = new Document(); + // Add a section to the document. + var section = document.AddSection(); + // Add a paragraph to the section. + var paragraph = section.AddParagraph(); + // Add a hyperlink to a web URL to the paragraph. + var hyperlink = paragraph.AddHyperlink("https://docs.pdfsharp.net", HyperlinkType.Url); + hyperlink.AddText("link"); + return document; + } + } + + [SkippableTheory] + [ClassData(typeof(TestData.AllWriteVersions))] + [ClassData(typeof(TestData.AllWriteVersionsSkipped), Skip = SkippedTestOptionsMessage)] + public void Test_SignedDocument(TestOptionsEnum optionsEnum) + { + var options = TestOptions.ByEnum(optionsEnum); + options.SetDefaultPasswords(true); + + var filename = AddPrefixToFilename("SigningWithEncryptionTest.pdf", options); + + var document = CreateDocument(); + SecureDocument(document, options); + + // Save the document. + document.Save(filename); + + + // Creates minimalistic document with hyperlink. + static PdfDocument CreateDocument() + { + const int requiredAssets = 1014; + string? timestampURL = null; + + var certType = "test-cert_rsa_1024"; + var digestType = PdfMessageDigestType.SHA256; + + IOUtility.EnsureAssetsVersion(requiredAssets); + + var font = new XFont("Verdana", 10, XFontStyleEx.Regular); + var fontHeader = new XFont("Verdana", 18, XFontStyleEx.Regular); + var document = new PdfDocument(); + var pdfPage = document.AddPage(); + var xGraphics = XGraphics.FromPdfPage(pdfPage); + var layoutRectangle = new XRect(0, 72, pdfPage.Width.Point, pdfPage.Height.Point); + xGraphics.DrawString("Document Signature Test", fontHeader, XBrushes.Black, layoutRectangle, XStringFormats.TopCenter); + var textFormatter = new XTextFormatter(xGraphics); + layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144); + + var text = "Lorem ipsum..."; + textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), layoutRectangle, XStringFormats.TopLeft); + + var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216)); + var options = new DigitalSignatureOptions + { + // We do not set an appearance handler, so the default handler is used. + // It is highly recommended to set an appearance handler to get a nicer representation of the signature. + ContactInfo = "John Doe", + Location = "Seattle", + Reason = "License Agreement", + Rectangle = new XRect(pdfPosition.X, pdfPosition.Y, 200, 50), + AppName = "PDFsharp Library" + }; + + Uri? timestampURI = String.IsNullOrEmpty(timestampURL) ? null : new Uri(timestampURL, UriKind.Absolute); + + var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options); + + return document; + } + + static X509Certificate2 GetCertificate(string certName) + { + var certFolder = IOUtility.GetAssetsPath("pdfsharp-6.x/signatures"); + var pfxFile = Path.Combine(certFolder ?? throw new InvalidOperationException("Call Download-Assets.ps1 before running the tests."), $"{certName}.pfx"); + var rawData = File.ReadAllBytes(pfxFile); + + // Do not use password literals for real certificates in source code. + var certificatePassword = "Seecrit1243"; //@@@??? + + var certificate = new X509Certificate2(rawData, + certificatePassword, + X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + + return certificate; + } + } } -} +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj index 77294d79..c71a220d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj @@ -14,6 +14,7 @@ true + true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj index 1be8e542..6100a1fd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj @@ -9,6 +9,7 @@ GDI True ..\..\..\..\..\StrongnameKey.snk + true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeDataMatrix.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeDataMatrix.cs index de687c0f..5b45f2af 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeDataMatrix.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeDataMatrix.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using System; @@ -119,7 +119,7 @@ static string CreateEncoding(DataMatrixEncoding dmEncoding, int length) } /// - /// Gets or sets the size of the Matrix Quiet Zone. + /// Gets or sets the size of the Matrix¹ Quiet Zone. /// public int QuietZone { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Extensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Extensions.cs index 4bd65a76..6caa5a5a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Extensions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Extensions.cs @@ -1,4 +1,7 @@ -using System; +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj index 657d0d42..35d7b6d6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj @@ -8,6 +8,7 @@ true True ..\..\..\..\..\StrongnameKey.snk + true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs index 1a05f0d4..b9642363 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. #if NET6_0_OR_GREATER @@ -34,7 +34,7 @@ public PdfSharpDefaultSigner(X509Certificate2 certificate, PdfMessageDigestType #if NET6_0_OR_GREATER TimeStampAuthorityUri = timeStampAuthorityUri; #else - // We dont know how to get a time stamp with .NET Standard. + // We don’t know how to get a time stamp with .NET Standard. // If you need it you must implement your own signer. if (timeStampAuthorityUri != null) throw new ArgumentException(nameof(timeStampAuthorityUri) + " must be null when using .NET Framework or .NET Standard."); @@ -60,7 +60,7 @@ public async Task GetSignatureSizeAsync() _signatureSize = (await GetSignatureAsync(new MemoryStream([0])).ConfigureAwait(false)).Length; if (MustAddTimeStamp) { - // Add arbitrary padding because TSA timestamp responses length seems to vary from one call to another by 1 byte. + // Add arbitrary padding because TSA timestamp response’s length seems to vary from one call to another by 1 byte. _signatureSize += 10; // 2 was found to be too small. Make it 10 to allow for some DSA variation. } else diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs index da95b90e..5ad69353 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. // @@ -8,16 +8,16 @@ #if !DEBUG && TEST_CODE #warning *********************************************************** -#warning ***** TEST_CODE MUST BE UNDEFINED FOR FINAL RELEASE ***** +#warning ***** ‘TEST_CODE’ MUST BE UNDEFINED FOR FINAL RELEASE ***** #warning *********************************************************** #endif -#if TEST_CODE_ // -// Ensure not to accidentally rename TEST_CODE to TEST_CODE_. -// This would compile code previously disabled with #if TEST_CODE_. -// Rename TEST_CODE always to TEST_CODE_xxx in Directory.Build.targets. +#if TEST_CODE_ // ‘’ +// Ensure not to accidentally rename ‘TEST_CODE’ to ‘TEST_CODE_’. +// This would compile code previously disabled with ‘#if TEST_CODE_’. +// Rename ‘TEST_CODE’ always to ‘TEST_CODE_xxx’ in ‘Directory.Build.targets’. #warning ***************************************************** -#warning ***** TEST_CODE_ MUST NEVER BE DEFINED ***** +#warning ***** ‘TEST_CODE_’ MUST NEVER BE DEFINED ***** #warning ***** THIS ACCIDENTALLY ACTIVATES EXCLUDED CODE ***** #warning ***************************************************** #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs index 79886098..7f162c37 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using System.ComponentModel; @@ -291,8 +291,8 @@ public static XColor FromName(string name) { #if GDI // The implementation in System.Drawing.dll is interesting. It uses a ColorConverter - // with hash tables, locking mechanisms etc. Im not sure what problems that solves. - // So I dont use the source, but the reflection. + // with hash tables, locking mechanisms etc. I’m not sure what problems that solves. + // So I don’t use the source, but the reflection. try { return new XColor((KnownColor)Enum.Parse(typeof(KnownColor), name, true)); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XStringFormat.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XStringFormat.cs index 0addd457..4e1292aa 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XStringFormat.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XStringFormat.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using System; @@ -161,7 +161,7 @@ internal StringFormat RealizeGdiStringFormat() //_stringFormat.FormatFlags = (StringFormatFlags)_formatFlags; // Bugfix: Set MeasureTrailingSpaces to get the correct width with Graphics.MeasureString(). - // Before, MeasureString() didnt include blanks in width calculation, which could result in text overflowing table or page border before wrapping. $MaOs + // Before, MeasureString() didn’t include blanks in width calculation, which could result in text overflowing table or page border before wrapping. $MaOs _stringFormat.FormatFlags = _stringFormat.FormatFlags | StringFormatFlags.MeasureTrailingSpaces; } return _stringFormat; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs index 1982cf1f..fb19b509 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using System; @@ -34,7 +34,7 @@ protected string GetNonOffValue() { // Try to get the information from the appearance dictionary. // Just return the first key that is not /Off. - // Im not sure what is the right solution to get this value. + // I’m not sure what is the right solution to get this value. var ap = Elements[PdfAnnotation.Keys.AP] as PdfDictionary; if (ap != null) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs index fc1928f9..e9ef262e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; @@ -164,7 +164,7 @@ public ImageSelector(XImage image, PdfDocumentOptions options) } /// - /// Creates an instance of HashAlgorithm fr use in ImageSelector. + /// Creates an instance of HashAlgorithm für use in ImageSelector. /// private HashAlgorithm GetHashCreator() { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs index 22273bf1..b215cc26 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using System.Collections; @@ -686,7 +686,7 @@ internal override void WriteObject(ContentWriter writer) } #region Printing/Debugger display - /// Function returning string that will be used to display objects value in debugger for this type of objects. + /// Function returning string that will be used to display object’s value in debugger for this type of objects. public static Func debuggerDisplay { get; set; } = o => o.ToString(15); string DebuggerDisplay => debuggerDisplay(this); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs index 4c615988..4ebbcd10 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. namespace PdfSharp.Pdf.Content @@ -23,7 +23,7 @@ public enum CSymbol BeginArray, EndArray, // IMPROVE - // Content dictionary << >> is scanned as string literal. + // Content dictionary << … >> is scanned as string literal. // Scan as an object tree. Dictionary, Eof, diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs index 3f76f57e..aaf29ec9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using PdfSharp.Pdf.Advanced; @@ -285,7 +285,7 @@ public static byte[] Decode(byte[] data, PdfItem filterItem, PdfItem? decodeParm else if (filterItem is PdfArray itemArray && decodeParms is null or PdfArray) { var decodeArray = decodeParms as PdfArray; - // Array length of filter and decode parms should match. If they dont, return data unmodified. + // Array length of filter and decode parms should match. If they don’t, return data unmodified. if (decodeArray != null && decodeArray.Elements.Count != itemArray.Elements.Count) return data; for (var i = 0; i < itemArray.Elements.Count; i++) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs index c02e6add..c352d4a3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs @@ -1306,16 +1306,16 @@ internal static bool IsDelimiter(char ch) // Reference 2.0: 7.1 Table 2 — Delimiter characters / Page 23 return ch switch { - '(' => true, - ')' => true, - '<' => true, - '>' => true, - '[' => true, - ']' => true, - '{' => true, - '}' => true, - '/' => true, - '%' => true, + '(' => true, // PDF string delimiter + ')' => true, // " + '<' => true, // PDF dictionary delimiter + '>' => true, // " + '[' => true, // PDF array delimiter + ']' => true, // " + '{' => true, // Type 4 PostScript calculator functions + '}' => true, // " + '/' => true, // PDF names delimiter + '%' => true, // PDF comments delimiter _ => false }; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs index 3bc87391..8683963d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs @@ -1336,7 +1336,7 @@ internal PdfTrailer ReadTrailer() // Implementation note 18 Appendix H: // Acrobat viewers require only that the %%EOF marker appear somewhere within the last 1024 bytes of the file. int idx; - if (length < 1030) + if (length <= 1030) { // Reading the final 30 bytes should work for all files. But often it does not. string trail = _lexer.ScanRawString(length - 31, 30); //lexer.Pdf.Substring(length - 30); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs index dbbc1c19..cd70d0e6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs @@ -19,9 +19,8 @@ public PdfWriter(Stream pdfStream, PdfDocument document, PdfStandardSecurityHand _stream = pdfStream ?? throw new ArgumentNullException(nameof(pdfStream)); _document = document ?? throw new ArgumentNullException(nameof(document)); EffectiveSecurityHandler = effectiveSecurityHandler; -#if DEBUG - Layout = PdfWriterLayout.Verbose; -#endif + + Layout = document.Options.Layout; } public void Close(bool closeUnderlyingStream) @@ -40,6 +39,14 @@ public void Close(bool closeUnderlyingStream) /// public PdfWriterLayout Layout { get; set; } + internal bool IsCompactLayout => Layout == PdfWriterLayout.Compact; + + internal bool IsStandardLayout => Layout >= PdfWriterLayout.Standard; + + internal bool IsIndentedLayout => Layout >= PdfWriterLayout.Indented; + + internal bool IsVerboseLayout => Layout >= PdfWriterLayout.Verbose; + public PdfWriterOptions Options { get; set; } // ----------------------------------------------------------- @@ -226,12 +233,12 @@ public void Write(PdfName value) // in any natural language, subject to the implementation limit on the length of a // name. - WriteSeparator(CharCat.Delimiter/*, '/'*/); + WriteSeparator(CharCat.Delimiter); string name = value.Value; Debug.Assert(name[0] == '/'); - // Encode to raw UTF-8 is any char is larger than 126. - // 127 [DEL] is not a valid value and is also get encoded. + // Encode to raw UTF-8 if any char is larger than 126. + // 127 [DEL] is not a valid value and is also encoded. for (int idx = 1; idx < name.Length; idx++) { char ch = name[idx]; @@ -265,6 +272,8 @@ public void Write(PdfName value) case ')': case '[': case ']': + case '{': + case '}': case '#': break; @@ -286,9 +295,12 @@ public void Write(PdfName value) public void Write(PdfLiteral value) { - WriteSeparator(CharCat.Character); - WriteRaw(value.Value); - _lastCat = CharCat.Character; + var rawString = value.Value; + var first = rawString[0]; + var last = rawString[^1]; + WriteSeparator(GetCategory(first)); + WriteRaw(rawString); + _lastCat = GetCategory(last); } public void Write(PdfRectangle rect) @@ -345,38 +357,115 @@ public void WriteDocStringHex(string text) /// public void WriteBeginObject(PdfObject obj) { - bool indirect = obj.IsIndirect; - if (indirect) + bool isIndirect = obj.IsIndirect; + if (isIndirect) { WriteObjectAddress(obj); - EffectiveSecurityHandler?.EnterObject(obj.ObjectID); } _stack.Add(new StackItem(obj)); - if (indirect) + + string? suffix = null; + if (IsVerboseLayout && _stack.Count > 1) + suffix = GetTypeAndComment(obj); + + if (isIndirect) { if (obj is PdfArray) - WriteRaw("[\n"); + { + if (IsCompactLayout) + { + WriteRaw('['); + } + else + { + if (suffix != null) + WriteRaw("[" + suffix); + else + WriteRaw("[\n"); + + } + } else if (obj is PdfDictionary) - WriteRaw("<<\n"); + { + if (IsCompactLayout) + { + WriteRaw("<<"); + } + else + { + if (suffix != null) + WriteRaw("<<" + suffix); + else + WriteRaw("<<\n"); + } + } + else + { + // Case: PdfIntegerObject or PdfNullObject + + //Debug.Assert(false, "Should not come here."); + Debug.Assert(obj is not null, "Should not come here."); + } _lastCat = CharCat.NewLine; } else { if (obj is PdfArray) { +#if true_ + // Same as PdfDictionary + NewLine(); WriteSeparator(CharCat.Delimiter); - WriteRaw('['); - _lastCat = CharCat.Delimiter; + WriteRaw("[\n"); + _lastCat = CharCat.NewLine; +#else + if (IsCompactLayout) + { + WriteRaw('['); + } + else + { + //NewLine(); + //WriteSeparator(CharCat.Delimiter); + if (suffix != null) + { + WriteRaw("[ " + GetTypeAndComment(obj)); + _lastCat = CharCat.NewLine; + } + else + { + WriteRaw('['); + _lastCat = CharCat.Delimiter; + } + } +#endif } else if (obj is PdfDictionary) { - NewLine(); - WriteSeparator(CharCat.Delimiter); - WriteRaw("<<\n"); - _lastCat = CharCat.NewLine; + if (IsCompactLayout) + { + WriteRaw("<<"); + } + else + { + NewLine(); + WriteSeparator(CharCat.Delimiter); + if (suffix != null) + WriteRaw("<< " + GetTypeAndComment(obj)); + else + WriteRaw("<<\n"); + _lastCat = CharCat.NewLine; + } + } + else + { + // Case: PdfIntegerObject or PdfNullObject + + //Debug.Assert(false, "Should not come here."); + Debug.Assert(obj is not null, "Should not come here."); } } - if (Layout == PdfWriterLayout.Verbose) + if (IsVerboseLayout) IncreaseIndent(); } @@ -385,6 +474,8 @@ public void WriteBeginObject(PdfObject obj) /// public void WriteEndObject() { + bool noLayout = Layout == PdfWriterLayout.Compact; + int count = _stack.Count; Debug.Assert(count > 0, "PdfWriter stack underflow."); @@ -393,72 +484,140 @@ public void WriteEndObject() PdfObject value = stackItem.Object; var indirect = value.IsIndirect; - if (indirect) - EffectiveSecurityHandler?.LeaveObject(); - if (Layout == PdfWriterLayout.Verbose) + + if (IsVerboseLayout) DecreaseIndent(); + if (value is PdfArray) { if (indirect) { - WriteRaw("\n]\n"); - _lastCat = CharCat.NewLine; + if (IsCompactLayout) + { + WriteRaw("]\n"); + _lastCat = CharCat.NewLine; + } + else + { + + WriteRaw("\n]\n"); + _lastCat = CharCat.Delimiter; + } } else { - WriteRaw("]"); - _lastCat = CharCat.Delimiter; + if (IsCompactLayout) + { + WriteRaw("]"); + _lastCat = CharCat.Delimiter; + } + else + { + //WriteSeparator(CharCat.NewLine); + WriteRaw("]"); + _lastCat = CharCat.Delimiter; + } } } else if (value is PdfDictionary) { if (indirect) { - if (!stackItem.HasStream) - WriteRaw(_lastCat == CharCat.NewLine ? ">>\n" : " >>\n"); + if (IsCompactLayout) + { + if (!stackItem.HasStream) + WriteRaw(">>\n"); + _lastCat = CharCat.NewLine; + } + else + { + if (!stackItem.HasStream) + WriteRaw(">>\n"); + _lastCat = CharCat.NewLine; + } } else { Debug.Assert(!stackItem.HasStream, "Direct object with stream??"); - WriteSeparator(CharCat.NewLine); - WriteRaw(">>\n"); - _lastCat = CharCat.NewLine; + if (IsCompactLayout) + { + WriteSeparator(CharCat.NewLine); + WriteRaw(">>"); + _lastCat = CharCat.Delimiter; + } + else + { + WriteSeparator(CharCat.NewLine); + if (IsVerboseLayout) + { + WriteRaw(">>\n"); + _lastCat = CharCat.NewLine; + } + else + { + WriteRaw(">>"); + _lastCat = CharCat.Delimiter; + } + } } } if (indirect) { - NewLine(); - WriteRaw("endobj\n"); - if (Layout == PdfWriterLayout.Verbose) - WriteRaw("%--------------------------------------------------------------------------------------------------\n"); + if (IsCompactLayout) + { + NewLine(); + WriteRaw("endobj\n"); + } + else + { + NewLine(); + WriteRaw("endobj\n"); + if (IsVerboseLayout) + WriteRaw("%--------------------------------------------------------------------------------------------------\n"); + } } } /// /// Writes the stream of the specified dictionary. /// - public void WriteStream(PdfDictionary value, bool omitStream) + public void WriteStream(PdfDictionary dict, bool omitStream) { var stackItem = _stack[^1]; Debug.Assert(stackItem.Object is PdfDictionary); Debug.Assert(stackItem.Object.IsIndirect); stackItem.HasStream = true; - WriteRaw(_lastCat == CharCat.NewLine ? ">>\nstream\n" : " >>\nstream\n"); - - if (omitStream) + var bytes = dict.Stream!.Value; + if (IsCompactLayout) { - WriteRaw(" «…stream content omitted…»\n"); // useful for debugging only + WriteRaw(">>\nstream\n"); + + // Earlier versions of PDFsharp skipped the '\n' before 'endstream' if the last byte of + // the stream is a linefeed. This was wrong and is now fixed. + Write(bytes); + + WriteRaw("\nendstream\n"); } else { - // Earlier versions of PDFsharp skipped the '\n' before 'endstream' if the last byte of - // the stream is a linefeed. This was wrong and now fixed. - var bytes = value.Stream.Value; - if (bytes.Length != 0) + //WriteRaw(_lastCat == CharCat.NewLine ? ">>\nstream\n" : " >>\nstream\n"); + + if (IsVerboseLayout) + WriteRaw(Invariant($">>\n% Length: {bytes.Length}\nstream\n")); + else + WriteRaw(">>\nstream\n"); + + if (omitStream) + { + WriteRaw(" «…stream content omitted…»\n"); // Useful for debugging only. PDF file is always invalid. + } + else + { Write(bytes); + } + WriteRaw("\nendstream\n"); } - WriteRaw("\nendstream\n"); } public void WriteRaw(string rawString) @@ -573,6 +732,32 @@ public void WriteEof(PdfDocument document, SizeType startxref) } } + //static string GetFullTypeName(PdfObject obj) => obj.GetType().FullName ?? "?"; + static string? GetTypeAndComment(PdfObject value, bool typenameAlways = false) + { + var type = value.GetType(); + string comment = value.Comment; + + bool showType = typenameAlways || (type != typeof(PdfDictionary) && type != typeof(PdfArray)); + string? result; + + if (showType) + { + if (!String.IsNullOrEmpty(comment)) + result = Invariant($"% {value.GetType().Name} ({value.GetType().FullName}) -- {comment}\n"); + else + result = $"% {value.GetType().Name} ({value.GetType().FullName})\n"; + } + else + { + if (!String.IsNullOrEmpty(comment)) + result = Invariant($"% {comment}\n"); + else + result = null; + } + return result; + } + /// /// Gets or sets the indentation for a new indentation level. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs index 062cd9c8..62d014f3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs @@ -159,7 +159,6 @@ internal override void WriteObject(PdfWriter writer) if (Stream != null) Debug.Assert(Elements.ContainsKey("/Length"), "Dictionary has a stream but no length is set."); #endif - if (_stream is not null && writer.EffectiveSecurityHandler != null) { // Encryption could change the size of the stream. @@ -170,16 +169,14 @@ internal override void WriteObject(PdfWriter writer) Elements[PdfStream.Keys.Length] = new PdfInteger(_stream?.Length ?? 0); } -#if DEBUG // Sort keys for debugging purposes. Comparing PDF files with for example programs like // Araxis Merge is easier with sorted keys. - if (writer.Layout == PdfWriterLayout.Verbose) + if (writer.IsVerboseLayout) { var list = new List(keys); list.Sort(PdfName.Comparer); list.CopyTo(keys, 0); } -#endif foreach (var key in keys) WriteDictionaryElement(writer, key); @@ -194,22 +191,17 @@ internal override void WriteObject(PdfWriter writer) /// internal virtual void WriteDictionaryElement(PdfWriter writer, PdfName key) { - if (key == null) - throw new ArgumentNullException(nameof(key)); - var item = Elements[key]; + Debug.Assert(key != null); #if DEBUG - // TODO_OLD: simplify PDFsharp - if (item is PdfObject { IsIndirect: true } pdfObject) - { - // Replace an indirect object by its Reference. - item = pdfObject.Reference; - Debug.Assert(false, "Check when we come here."); - } + if (key == "/Kids") + _ = typeof(int); #endif + var item = Elements[key]!; key.WriteObject(writer); - item?.WriteObject(writer); - writer.NewLine(); - } + item.WriteObject(writer); + if (writer.Layout == PdfWriterLayout.Verbose) + writer.NewLine(); +} /// /// Writes the stream of this dictionary. This function is intended to be overridden diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index 68cd0250..d1eb2a39 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -369,8 +369,18 @@ async Task DoSaveAsync(PdfWriter writer) _ = typeof(int); #endif iref.Position = writer.Position; - iref.Value.WriteObject(writer); + + var obj = iref.Value; + + // Enter indirect object in SecurityHandler to allow object encryption key generation for this object. + effectiveSecurityHandler?.EnterObject(obj.ObjectID); + + obj.WriteObject(writer); } + + // Leaving only the last indirect object in SecurityHandler is sufficient, as this is the first time no indirect object is entered anymore. + effectiveSecurityHandler?.LeaveObject(); + // ReSharper disable once RedundantCast. Redundant only if 64 bit. var startXRef = (SizeType)writer.Position; IrefTable.WriteObject(writer); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs index fcc8df71..fe95413a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs @@ -3,6 +3,8 @@ // ReSharper disable ConvertToAutoProperty +using PdfSharp.Pdf.IO; + namespace PdfSharp.Pdf { /// @@ -81,5 +83,19 @@ public PdfUseFlateDecoderForJpegImages UseFlateDecoderForJpegImages set => _useFlateDecoderForJpegImages = value; } PdfUseFlateDecoderForJpegImages _useFlateDecoderForJpegImages = PdfUseFlateDecoderForJpegImages.Never; + + /// + /// Gets or sets a value used for the PdfWriterLayout in PdfWriter. + /// + public PdfWriterLayout Layout + { + get => _writerLayout; + set => _writerLayout = value; + } +#if DEBUG + PdfWriterLayout _writerLayout = PdfWriterLayout.Verbose; +#else + PdfWriterLayout _writerLayout = PdfWriterLayout.Compact; +#endif } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs index 2a67643b..9cbc9cd2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs @@ -152,10 +152,10 @@ public int Compare(PdfName? l, PdfName? r) { if (r != null) return String.Compare(l.Value, r.Value, StringComparison.Ordinal); - return -1; + return 1; } if (r != null) - return 1; + return -1; return 0; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs index c5989ff1..2518ea38 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs @@ -308,7 +308,6 @@ void Initialize() var dest = Elements.GetValue(Keys.Dest); var a = Elements.GetValue(Keys.A); - Debug.Assert(dest == null || a == null, "Either destination or goto action."); PdfArray? destArray; if (dest != null) @@ -317,13 +316,15 @@ void Initialize() if (destArray != null) { SplitDestinationPage(destArray); + goto Done; } else { Debug.Assert(false, "See what to do when this happened."); } } - else if (a != null) + + if (a != null) { // The dictionary should be a GoTo action. if (a is PdfDictionary action && action.Elements.GetName(PdfAction.Keys.S) == "/GoTo") @@ -332,12 +333,10 @@ void Initialize() destArray = dest as PdfArray; if (destArray != null) { - // Replace Action with /Dest entry. - Elements.Remove(Keys.A); - Elements.Add(Keys.Dest, destArray); SplitDestinationPage(destArray); + goto Done; } - else if (dest is PdfString namedDestination) + if (dest is PdfString namedDestination) { // look in Destinations and name-tree if (Owner.Catalog.Destinations.Contains(namedDestination.Value)) @@ -359,20 +358,17 @@ void Initialize() } if (destArray != null) { - // Replace Action with /Dest entry. - Elements.Remove(Keys.A); - Elements.Add(Keys.Dest, destArray); SplitDestinationPage(destArray); } } else { - throw new Exception("Destination Array or Name expected."); + //throw new Exception("Destination Array or Name expected."); } } else { - Debug.Assert(false, "See what to do when this happened."); + //Debug.Assert(false, "See what to do when this happened."); } } else @@ -380,6 +376,7 @@ void Initialize() // Neither destination page nor GoTo action. } + Done: InitializeChildren(); } @@ -594,9 +591,9 @@ static string Fd(double value) { if (Double.IsNaN(value)) throw new InvalidOperationException("Value is not a valid Double."); - return value.ToString("#.##", CultureInfo.InvariantCulture); + return value.ToString("0.##", CultureInfo.InvariantCulture); - //return Double.IsNaN(value) ? "null" : value.ToString("#.##", CultureInfo.InvariantCulture); + //return Double.IsNaN(value) ? "null" : value.ToString("0.##", CultureInfo.InvariantCulture); } /// @@ -604,7 +601,7 @@ static string Fd(double value) /// static string Fd(double? value) { - return value.HasValue ? value.Value.ToString("#.##", CultureInfo.InvariantCulture) : "null"; + return value.HasValue ? value.Value.ToString("0.##", CultureInfo.InvariantCulture) : "null"; } internal override void WriteObject(PdfWriter writer) diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CSharpFeaturesTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CSharpFeaturesTests.cs index 2debdd88..b25944ea 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CSharpFeaturesTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CSharpFeaturesTests.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using FluentAssertions; @@ -18,7 +18,7 @@ namespace PdfSharp.Tests.Build /// /// Test what features of C# 11 to C# 12 can be used with /// .NET 6/8 and C# 11 to C# 12 with .NET 4.62 / .NET Standard 2.0. - /// Thats amazing! + /// That’s amazing! /// [Collection("PDFsharp")] public class CSharpFeaturesTests diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/ReleaseBuildTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/ReleaseBuildTests.cs index 6c379b69..95a0e67d 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/ReleaseBuildTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/ReleaseBuildTests.cs @@ -37,7 +37,9 @@ public void Check_renamed_identifiers() //[Fact] public void Check_CS_files_for_non_ASCII_characters() { -#if NET6_0_OR_GREATER || CORE + // Tests runs only under .NET Framework and GDI. +#if NET6_0_OR_GREATER || CORE || WPF + // Exit here if not GDI under .NET Framework. return; #else #if DEBUG @@ -70,6 +72,19 @@ static void CheckFile(string file) bool utf16Bom = bytes is [0xFF, 0xFE, ..] or [0xFE, 0xFF, ..]; + bool hasCommentAnsi = bytes is [0x2F, 0x2F, ..]; + bool hasCommentUtf8 = bytes is [0xEF, 0xBB, 0xBF, 0x2F, 0x2F, ..]; + bool hasCommentUtf16Be = bytes is [0xFE, 0xFF, 0x00, 0x2F, 0x00, 0x2F, ..]; + bool hasCommentUtf16Le = bytes is [0xFF, 0xFE, 0x2F, 0x00, 0x2F, 0x00, ..]; + + bool hasComment = hasCommentAnsi || hasCommentUtf8 || hasCommentUtf16Be || hasCommentUtf16Le; + + if (!hasComment) + { + _ = typeof(int); + throw new InvalidOperationException($"File '{file}' does not start with a comment."); + } + int idx = 0; bool ascii = true; bool? nonAsciiBecauseOfApostrophe = null; diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Helper/XunitHelper.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Helper/XunitHelper.cs index a94d9153..8db8becf 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Helper/XunitHelper.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Helper/XunitHelper.cs @@ -1,4 +1,7 @@ -using System; +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs index 2b03e397..278b4b9c 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs @@ -6,6 +6,7 @@ using PdfSharp.Drawing; using PdfSharp.Fonts; using PdfSharp.Pdf; +using PdfSharp.Pdf.Internal; using PdfSharp.Pdf.IO; using PdfSharp.Quality; using PdfSharp.Snippets.Font; @@ -17,6 +18,50 @@ namespace PdfSharp.Tests.IO [Collection("PDFsharp")] public class WriterTests { + +#if true + [Fact] + public void Test_issues() + { + var document = CreateDocument("CompactLayout-IssuesXXX"); + //var dict = new PdfDictionary(); + document.Options.Layout = PdfWriterLayout.Compact; + + var catalog = document.Catalog; + + var dict = new PdfDictionary(document/*, true*/); + document.Internals.AddObject(dict); + var bytes = PdfEncoders.RawEncoding.GetBytes("ABC"); + dict.CreateStream(bytes); + + catalog.Elements.Add("/TestStream", dict); + + Save(document); + //Reload(); + //ReloadedDocument.Options.Layout = PdfWriterLayout.Compact; + //Resave(); + + //CreateDocument("CompactLayout-Issues"); + } + protected PdfDocument CreateDocument(string name) + { + var _document = PdfDocUtility.CreateNewPdfDocument(name); + _document.Info.Title = name; + var _page = _document.AddPage(); + + return _document; + } + protected string Save(PdfDocument document, string? filename = null) + { + filename ??= document.Info.Title; + var _fullFilename = PdfFileUtility.GetTempPdfFullFileName("Pdf-ObjectModel/" + filename); + //Document.Options.Layout = PdfWriterLayout.Compact; + document.Save(_fullFilename); + return _fullFilename; + } +#endif + + [Fact] public void Write_import_file() { diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Structs/XUnitTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Structs/XUnitTests.cs index aca5dcdd..7cde051c 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Structs/XUnitTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Structs/XUnitTests.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using FluentAssertions; @@ -54,7 +54,7 @@ public void XUnit_Test() XUnit xUnit2CmPt2 = str2CmPt; XUnit xUnit2CmPu2 = str2CmPu; - // Only check cm variables, which are totally equal because str2Cm with "2cm" wont cause rounding errors. + // Only check cm variables, which are totally equal because str2Cm with "2cm" won’t cause rounding errors. (xUnit2Cm2 == xUnit2Cm).Should().BeTrue(); // Use IsSameValue() for other variables. @@ -63,7 +63,7 @@ public void XUnit_Test() xUnit2CmPt2.IsSameValue(xUnit2CmPt).Should().BeTrue(); xUnit2CmPu2.IsSameValue(xUnit2CmPu).Should().BeTrue(); - // Only check cm variables, which are totally equal because str2Cm with "2cm" wont cause rounding errors. + // Only check cm variables, which are totally equal because str2Cm with "2cm" won’t cause rounding errors. (xUnit2Cm2 != xUnit2Cm).Should().BeFalse(); // Same value - other unit. diff --git a/src/foundation/src/shared/src/PdfSharp.Quality-gdi/PdfSharp.Quality-gdi.csproj b/src/foundation/src/shared/src/PdfSharp.Quality-gdi/PdfSharp.Quality-gdi.csproj index ad6a35b3..fc387284 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality-gdi/PdfSharp.Quality-gdi.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Quality-gdi/PdfSharp.Quality-gdi.csproj @@ -8,6 +8,7 @@ GDI True ..\..\..\..\..\StrongnameKey.snk + true diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/System/CompilerServices.cs b/src/foundation/src/shared/src/PdfSharp.Shared/System/CompilerServices.cs index 559e8563..ab6f55c5 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/System/CompilerServices.cs +++ b/src/foundation/src/shared/src/PdfSharp.Shared/System/CompilerServices.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. // This file contains code from the .NET source to use some newer C# features in .NET Standard / Framework. @@ -151,8 +151,8 @@ public int Value /// Calculate the offset from the start using the giving collection length. /// The length of the collection that the Index will be used with. length has to be a positive value /// - /// For performance reason, we dont validate the input length parameter and the returned offset value against negative values. - /// we dont validate either the returned offset is greater than the input length. + /// For performance reason, we don’t validate the input length parameter and the returned offset value against negative values. + /// we don’t validate either the returned offset is greater than the input length. /// It is expected Index will be used with collections which always have non-negative length/count. If the returned offset is negative and /// then used to index a collection will get out of range exception which will be same affect as the validation. /// @@ -257,7 +257,7 @@ public override string ToString() /// Calculate the start offset and length of range object using a collection length. /// The length of the collection that the range will be used with. length has to be a positive value. /// - /// For performance reason, we dont validate the input length parameter against negative values. + /// For performance reason, we don’t validate the input length parameter against negative values. /// It is expected Range will be used with collections which always have non-negative length/count. /// We validate the range is inside the length scope though. /// diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets-gdi/PdfSharp.Snippets-gdi.csproj b/src/foundation/src/shared/src/PdfSharp.Snippets-gdi/PdfSharp.Snippets-gdi.csproj index 9bde7391..ff46ec46 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets-gdi/PdfSharp.Snippets-gdi.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Snippets-gdi/PdfSharp.Snippets-gdi.csproj @@ -8,6 +8,7 @@ TRACE;GDI True ..\..\..\..\..\StrongnameKey.snk + true diff --git a/src/foundation/src/shared/src/PdfSharp.System/System/CodeAnalysis.cs b/src/foundation/src/shared/src/PdfSharp.System/System/CodeAnalysis.cs index 4bc9a49c..5d147f42 100644 --- a/src/foundation/src/shared/src/PdfSharp.System/System/CodeAnalysis.cs +++ b/src/foundation/src/shared/src/PdfSharp.System/System/CodeAnalysis.cs @@ -1,3 +1,6 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + using System; namespace System.Diagnostics.CodeAnalysis diff --git a/src/foundation/src/shared/src/PdfSharp.System/extensions/SystemStringExtensions.cs b/src/foundation/src/shared/src/PdfSharp.System/extensions/SystemStringExtensions.cs index 1d77dafa..685ce093 100644 --- a/src/foundation/src/shared/src/PdfSharp.System/extensions/SystemStringExtensions.cs +++ b/src/foundation/src/shared/src/PdfSharp.System/extensions/SystemStringExtensions.cs @@ -1,4 +1,7 @@ -using System; +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System; using System.Collections.Generic; using System.Linq; using System.Text; From feb68cdf011ea5d356abd78bf836b6ee61dedf0e Mon Sep 17 00:00:00 2001 From: PDFsharp-Team Date: Mon, 22 Sep 2025 14:19:04 +0200 Subject: [PATCH 3/7] =?UTF-8?q?=E2=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 4 +- docs/Chars.md | 3 + docs/MigraDoc/change-log/MD-v6.2.2-log.md | 17 ++ docs/PDFsharp/change-log/PS-v6.2.2-log.md | 26 +++ docs/change-log/gn-v6.2.2-log.md | 17 ++ docs/publishing/BeforeReleases.md | 4 +- gitversion.yml | 2 +- .../src/Dummy-PDFsharp.NuGet-wpf/README.md | 3 +- .../src/MigraDoc.NuGet-gdi/Description.txt | 6 +- .../src/MigraDoc.NuGet-wpf/Description.txt | 6 +- .../nuget/src/MigraDoc.NuGet/Description.txt | 6 +- .../src/PDFsharp.NuGet-gdi/Description.txt | 6 +- .../src/PDFsharp.NuGet-wpf/Description.txt | 6 +- .../nuget/src/PDFsharp.NuGet/Description.txt | 6 +- .../DocumentObjectModel/Hyperlink.cs | 6 +- .../MigraDocProductVersionInformation.cs | 158 +++++++-------- .../src/PdfSharp-gdi/PdfSharp-gdi.csproj | 2 +- .../src/PdfSharp-wpf/PdfSharp-wpf.csproj | 2 +- .../Drawing.Internal/IImageImporter.cs | 7 + .../PdfSharp/Pdf.Advanced/PdfObjectStream.cs | 52 ++--- .../src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs | 101 ++++++++++ .../PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs | 17 +- .../Pdf.Signatures/PdfArrayWithPadding.cs | 35 ---- .../Pdf.Signatures/PdfPlaceholderObject.cs | 66 +++++++ .../Pdf.Signatures/PdfSignatureHandler.cs | 17 +- .../PdfSharpProductVersionInformation.cs | 180 +++++++++--------- 26 files changed, 482 insertions(+), 273 deletions(-) create mode 100644 docs/MigraDoc/change-log/MD-v6.2.2-log.md create mode 100644 docs/PDFsharp/change-log/PS-v6.2.2-log.md create mode 100644 docs/change-log/gn-v6.2.2-log.md delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfArrayWithPadding.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs diff --git a/README.md b/README.md index 40fa5271..9a8ae93e 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,7 @@ # PDFsharp & MigraDoc 6 -Version **6.2.1** -Published **2025-07-24** +Version **6.2.2** +Published **2025-09-22** This is a final version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 6 with updates for C# 12 and .NET 6. diff --git a/docs/Chars.md b/docs/Chars.md index bce9e51e..03ebbe84 100644 --- a/docs/Chars.md +++ b/docs/Chars.md @@ -4,6 +4,9 @@ · × ² ³ ½ € † … ✔ ✘ ↯ ± − × ÷ ⋅ √ ≠ ≤ ≥ ≡ ® © ← ↑ → ↓ ↔ ↕ ∅ + ✔⇒ + + Shaker´'´`’ás # Use of typographic quotation marks diff --git a/docs/MigraDoc/change-log/MD-v6.2.2-log.md b/docs/MigraDoc/change-log/MD-v6.2.2-log.md new file mode 100644 index 00000000..9b62ac2e --- /dev/null +++ b/docs/MigraDoc/change-log/MD-v6.2.2-log.md @@ -0,0 +1,17 @@ +# MigraDoc 6.2.2 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`. + +## What’s new in version 6.2.2 + +### Breaking changes + +*(none)* + +### Features + +*(none)* + +### Issues + +The bug fixes of PDFsharp are also useful when generating PDF files from MigraDoc. diff --git a/docs/PDFsharp/change-log/PS-v6.2.2-log.md b/docs/PDFsharp/change-log/PS-v6.2.2-log.md new file mode 100644 index 00000000..5937ddaf --- /dev/null +++ b/docs/PDFsharp/change-log/PS-v6.2.2-log.md @@ -0,0 +1,26 @@ +# PDFsharp 6.2.2 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`. + +## What’s new in version 6.2.2 + +### Breaking changes + +*(none)* + +### Features + +*(none)* + +### Issues + +**Fixed problem with JPEG images from MemoryStreams** +In special cases, JPEG images from a MemoryStream were not handled correctly. GitHub #303 + +**Avoid crash on ID duplicates in an object stream** +PDF files with duplicate IDs in an object stream were not handled correctly. +We consider such files corrupted, but PDFsharp should now handle them correctly. GitHub #296 + +**Fixed problems with signatures in DEBUG build** +The Debug build of PDFsharp adds comments to the generated PDF files. +This caused problems with the Signature field in PDFs, but was fixed now.Github #293 diff --git a/docs/change-log/gn-v6.2.2-log.md b/docs/change-log/gn-v6.2.2-log.md new file mode 100644 index 00000000..15466d90 --- /dev/null +++ b/docs/change-log/gn-v6.2.2-log.md @@ -0,0 +1,17 @@ +# General 6.2.2 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **general** `History.md`. + +## What’s new in version 6.2.2 + +### Breaking changes + +*(none)* + +### General features + +*(none)* + +### General issues + +The bug fixes of PDFsharp are useful when generating PDF files from PDFsharp or MigraDoc. diff --git a/docs/publishing/BeforeReleases.md b/docs/publishing/BeforeReleases.md index 4bdbbd05..24489fdb 100644 --- a/docs/publishing/BeforeReleases.md +++ b/docs/publishing/BeforeReleases.md @@ -17,8 +17,8 @@ assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VE ### Update and sync used NuGet packages Update version numbers of NuGet packages used in PDFsharp code (like `MicroSoft.Extensions.Logging`) and -build tools (xunit, gitversion, ...). -Do this with `"Manage NuGet Packages for Solution..."` to see what’s new and update the version numbers +build tools (xunit, gitversion, …). +Do this with `"Manage NuGet Packages for Solution…"` to see what’s new and update the version numbers manually in `Directory.Packages.props`. Sync `Directory.Packages.props` with all other solutions, like `PDFsharp.Samples` etc. diff --git a/gitversion.yml b/gitversion.yml index 19d2b4ec..3f9021f9 100644 --- a/gitversion.yml +++ b/gitversion.yml @@ -3,7 +3,7 @@ assembly-versioning-scheme: MajorMinorPatch # PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE # Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; # C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7508}' +assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7569}' mode: Mainline assembly-informational-format: '{NuGetVersion}' branches: diff --git a/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md b/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md index a5d9f7a8..436b3fc5 100644 --- a/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md +++ b/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/README.md @@ -14,4 +14,5 @@ According to the docs it should be possible to create NuGet packages without nus all stuff can be set in the csproj file. We still use nuspec because I cannot fix the following issues: * Set the package icon in cssproj so that it is displayed. -* We want a single package for all (e.g.) PDFsharp assemblies. Currently I don’t know how to manage that without a nuspec file. +* We want a single package for all (e.g.) PDFsharp assemblies. + Currently I don’t know how to manage that without a nuspec file. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt index 03e61dbb..52986d6f 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt @@ -1,5 +1,9 @@ -MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications. +MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications. This package relies on Windows Forms (GDI+) and can be used under Windows only. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt index a24bae03..92f0069a 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt @@ -1,5 +1,9 @@ -MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications. +MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications. This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt index 0cc42ad4..99d2af4b 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt @@ -1,5 +1,9 @@ -MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications. +MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications. This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt index 39e800a7..be622cff 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt @@ -1,5 +1,9 @@ -PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents. +PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents. This package relies on Windows Forms (GDI+) and can be used under Windows only. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt index 2627b98f..b82a01aa 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt @@ -1,5 +1,9 @@ -PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents. +PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents. This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt index b6b011a5..432a269f 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt @@ -1,5 +1,9 @@ -PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents. +PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents. This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Hyperlink.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Hyperlink.cs index 2f5e97b7..531e936d 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Hyperlink.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Hyperlink.cs @@ -457,14 +457,14 @@ public Font Font public bool NoHyperlinkStyle { // This Property is not part of Word’s Automation Model. With this property Hyperlinks can be used without implicitly setting the Hyperlink style. - // This way they occur like linked cross references in Word, which are not implemented as an own DocumentObject type in MigraDoc. + // This way they occur like linked cross-references in Word, which are not implemented as an own DocumentObject type in MigraDoc. get => Values.NoHyperlinkStyle ?? false; set => Values.NoHyperlinkStyle = value; } /// /// For HyperlinkType Local/Bookmark: Gets or sets the target bookmark name of the Hyperlink. - /// For HyperlinkTypes File and Url/Web: Gets or sets the target filename of the Hyperlink, e.g. a path to a file or an URL. + /// For HyperlinkTypes File and Url/Web: Gets or sets the target filename of the Hyperlink, e.g. a path to a file or a URL. /// For HyperlinkType ExternalBookmark: Not valid - throws Exception. /// This property is retained due to compatibility reasons. /// @@ -503,7 +503,7 @@ public string Name } /// - /// Gets or sets the target filename of the Hyperlink, e.g. a path to a file or an URL. + /// Gets or sets the target filename of the Hyperlink, e.g. a path to a file or a URL. /// Used for HyperlinkTypes ExternalBookmark, File and Url/Web. /// public string Filename diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs index cddf77a9..b0ccef5d 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs @@ -63,7 +63,7 @@ public static class MigraDocProductVersionInformation /// /// The home page of this product. /// - public const string Url = "www.pdfsharp.net"; + public const string Url = "www.pdfsharp.com"; /// /// Unused. @@ -103,83 +103,83 @@ public static class MigraDocProductVersionInformation // ReSharper restore RedundantNameQualifier #endif -#if Not_used_anymore // #DELETE 25-12-31 - - /// - /// E.g. "2005-01-01", for use in NuGet Script - /// - public const string VersionReferenceDate = "2005-01-01"; - - /// - /// Use _ instead of blanks and special characters. Can be complemented with a suffix in the NuGet Script. - /// Nuspec Doc: The unique identifier for the package. This is the package name that is shown when packages are listed using the Package Manager Console. - /// These are also used when installing a package using the Install-Package command within the Package Manager Console. - /// Package IDs may not contain any spaces or characters that are invalid in an URL. In general, they follow the same rules as .NET namespaces do. - /// So Foo.Bar is a valid ID, Foo! and Foo Bar are not. - /// - public const string NuGetID = "PDFsharp-MigraDoc"; - - /// - /// Nuspec Doc: The human-friendly title of the package displayed in the Manage NuGet Packages dialog. If none is specified, the ID is used instead. - /// - public const string NuGetTitle = "PDFsharp + MigraDoc"; - - /// - /// Nuspec Doc: A comma-separated list of authors of the package code. - /// - public const string NuGetAuthors = "empira Software GmbH"; - - /// - /// Nuspec Doc: A comma-separated list of the package creators. This is often the same list as in authors. This is ignored when uploading the package to the NuGet.org Gallery. - /// - public const string NuGetOwners = "empira Software GmbH"; - - /// - /// Nuspec Doc: A long description of the package. This shows up in the right pane of the Add Package Dialog as well as in the Package Manager Console when listing packages using the Get-Package command. - /// - public const string NuGetDescription = "MigraDoc - the Open Source .NET library that easily creates documents based on an object model with paragraphs, tables, styles, etc. and renders them into PDF or RTF."; - - /// - /// Nuspec Doc: A description of the changes made in each release of the package. This field only shows up when the _Updates_ tab is selected and the package is an update to a previously installed package. - /// It is displayed where the Description would normally be displayed. - /// - public const string NuGetReleaseNotes = ""; - - /// - /// Nuspec Doc: A short description of the package. If specified, this shows up in the middle pane of the Add Package Dialog. If not specified, a truncated version of the description is used instead. - /// - public const string NuGetSummary = "Creating Documents on the Fly."; - - /// - /// Nuspec Doc: The locale ID for the package, such as en-us. - /// - public const string NuGetLanguage = ""; - - /// - /// Nuspec Doc: A URL for the home page of the package. - /// - public const string NuGetProjectUrl = "http://www.pdfsharp.net/"; - - /// - /// Nuspec Doc: A URL for the image to use as the icon for the package in the Manage NuGet Packages dialog box. This should be a 32x32-pixel .png file that has a transparent background. - /// - public const string NuGetIconUrl = "http://www.pdfsharp.net/resources/MigraDoc-Logo-32x32.png"; - - /// - /// Nuspec Doc: A link to the license that the package is under. - /// - public const string NuGetLicenseUrl = "http://www.pdfsharp.net/MigraDoc_License.ashx"; - - /// - /// Nuspec Doc: A Boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed. - /// - public const bool NuGetRequireLicenseAcceptance = false; - - /// - /// Nuspec Doc: A space-delimited list of tags and keywords that describe the package. This information is used to help make sure users can find the package using - /// searches in the Add Package Reference dialog box or filtering in the Package Manager Console window. - /// - public const string NuGetTags = "MigraDoc PdfSharp Pdf Document Generation"; -#endif +//#if Not_used_anymore // #DELETE 25-12-31 + +// /// +// /// E.g. "2005-01-01", for use in NuGet Script +// /// +// public const string VersionReferenceDate = "2005-01-01"; + +// /// +// /// Use _ instead of blanks and special characters. Can be complemented with a suffix in the NuGet Script. +// /// Nuspec Doc: The unique identifier for the package. This is the package name that is shown when packages are listed using the Package Manager Console. +// /// These are also used when installing a package using the Install-Package command within the Package Manager Console. +// /// Package IDs may not contain any spaces or characters that are invalid in a URL. In general, they follow the same rules as .NET namespaces do. +// /// So Foo.Bar is a valid ID, Foo! and Foo Bar are not. +// /// +// public const string NuGetID = "PDFsharp-MigraDoc"; + +// /// +// /// Nuspec Doc: The human-friendly title of the package displayed in the Manage NuGet Packages dialog. If none is specified, the ID is used instead. +// /// +// public const string NuGetTitle = "PDFsharp + MigraDoc"; + +// /// +// /// Nuspec Doc: A comma-separated list of authors of the package code. +// /// +// public const string NuGetAuthors = "empira Software GmbH"; + +// /// +// /// Nuspec Doc: A comma-separated list of the package creators. This is often the same list as in authors. This is ignored when uploading the package to the NuGet.org Gallery. +// /// +// public const string NuGetOwners = "empira Software GmbH"; + +// /// +// /// Nuspec Doc: A long description of the package. This shows up in the right pane of the Add Package Dialog as well as in the Package Manager Console when listing packages using the Get-Package command. +// /// +// public const string NuGetDescription = "MigraDoc - the Open Source .NET library that easily creates documents based on an object model with paragraphs, tables, styles, etc. and renders them into PDF or RTF."; + +// /// +// /// Nuspec Doc: A description of the changes made in each release of the package. This field only shows up when the _Updates_ tab is selected and the package is an update to a previously installed package. +// /// It is displayed where the Description would normally be displayed. +// /// +// public const string NuGetReleaseNotes = ""; + +// /// +// /// Nuspec Doc: A short description of the package. If specified, this shows up in the middle pane of the Add Package Dialog. If not specified, a truncated version of the description is used instead. +// /// +// public const string NuGetSummary = "Creating Documents on the Fly."; + +// /// +// /// Nuspec Doc: The locale ID for the package, such as en-us. +// /// +// public const string NuGetLanguage = ""; + +// /// +// /// Nuspec Doc: A URL for the home page of the package. +// /// +// public const string NuGetProjectUrl = "http://www.pdfsharp.com/"; + +// /// +// /// Nuspec Doc: A URL for the image to use as the icon for the package in the Manage NuGet Packages dialog box. This should be a 32x32-pixel .png file that has a transparent background. +// /// +// public const string NuGetIconUrl = "http://www.pdf/sharp.net/resources/MigraDoc-Logo-32x32.png"; + +// /// +// /// Nuspec Doc: A link to the license that the package is under. +// /// +// public const string NuGetLicenseUrl = "http://www.pdf/sharp.net/MigraDoc_License.ashx"; + +// /// +// /// Nuspec Doc: A Boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed. +// /// +// public const bool NuGetRequireLicenseAcceptance = false; + +// /// +// /// Nuspec Doc: A space-delimited list of tags and keywords that describe the package. This information is used to help make sure users can find the package using +// /// searches in the Add Package Reference dialog box or filtering in the Package Manager Console window. +// /// +// public const string NuGetTags = "MigraDoc PdfSharp Pdf Document Generation"; +//#endif } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj index c71a220d..e054c783 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj @@ -316,7 +316,7 @@ - + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj index 9fc703b1..ff99fdb5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj @@ -313,7 +313,7 @@ - + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs index ce495960..9e1e7f9f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs @@ -47,6 +47,13 @@ internal StreamReaderHelper(Stream stream, int streamLength) // Buffer is accessible - use it. Data = buffer.Array ?? throw new ArgumentNullException(nameof(stream), "Stream has no content byte array."); Length = (int)ms.Length; + // If buffer is larger than needed, create a new buffer with required size. + if (Data.Length > Length) + { + var tmp = new Byte[Length]; + Buffer.BlockCopy(Data, 0, tmp, 0, Length); + Data = tmp; + } } else { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs index 76775b86..63ae5adb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs @@ -1,6 +1,8 @@ // 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.Advanced @@ -49,55 +51,36 @@ internal PdfObjectStream(PdfDictionary dict, Parser documentParser) } /// - /// Reads all references inside the ObjectStream and returns all ObjectIDs and offsets for its objects. + /// Returns all ObjectIDs and read positions for the objects inside this ObjectStream. /// - internal ICollection> ReadReferencesAndOffsets(PdfCrossReferenceTable xrefTable) + internal ICollection> ReadObjectIDsWithOffsets() { var length = _header.Length; - _objectOffsets = []; - ////// Create parser for stream. - ////Parser parser = new Parser(_document, new MemoryStream(Stream.Value)); - for (var idx = 0; idx < length; idx++) + Dictionary objectOffsets = []; + + // For duplicate IDs the newest object should be read first, to ignore older objects with the same ID read later. + // Therefore, we read the offsets from high to low. + for (var idx = length - 1; idx >= 0; idx--) { int objectNumber = _header[idx][0]; int offset = _header[idx][1]; var objectID = new PdfObjectID(objectNumber); - // -1 indicates compressed object. - var iref = PdfReference.CreateForObjectID(objectID, -1); - - _objectOffsets.Add(objectID, offset); - - if (!xrefTable.Contains(iref.ObjectID)) + // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd + if (!objectOffsets.ContainsKey(objectID)) { - xrefTable.Add(iref); + objectOffsets.Add(objectID, offset); } else { -#if DEBUG_ - _ = typeof(int); -#endif + // Ignore object with objectID already on the list. + PdfSharpLogHost.PdfReadingLogger.LogWarning("Ignoring object with ID {objectID} while reading offsets in object stream {objectStreamId} because an object with that ID was already read in that object stream.", objectID, ObjectID); } } - return _objectOffsets; - } - - /// - /// Tries to get the position of the PdfObject inside this ObjectStream. - /// - internal bool TryGetObjectOffset(PdfObjectID pdfObjectID, out SizeType offset, SuppressExceptions? suppressObjectOrderExceptions) - { - if (_objectOffsets == null) - { - SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.InvalidOperationException_ReferencesOfObjectStreamNotYetRead()); - offset = -1; - return false; - } - - return _objectOffsets.TryGetValue(pdfObjectID, out offset); + return objectOffsets; } /// @@ -108,11 +91,6 @@ internal bool TryGetObjectOffset(PdfObjectID pdfObjectID, out SizeType offset, S /// readonly int[][] _header = default!; // Reference: Page 102 - /// - /// Manages the read positions for all PdfObjects inside this ObjectStream. - /// - Dictionary? _objectOffsets; - /// /// Predefined keys common to all font dictionaries. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs index c352d4a3..1822b675 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs @@ -722,6 +722,106 @@ public Symbol ScanStringLiteral() return Symbol = Symbol.String; } +#if true + /// + /// Scans a hex encoded literal byte string, contained between "<" and ">". + /// + public Symbol ScanHexadecimalString() + { + Debug.Assert(_currChar == Chars.Less); + + // 25-09-16/StL + // Now we can handle Panose style codes that may look like this: + // < 0 0 2 b 6 6 3 8 4 2 2 4> + // This is technically an illegal format, but found by a user. + + ClearToken(); + ScanNextChar(true); + bool tryUsePanoseHack = false; + while (true) + { + MoveToNonWhiteSpace(); + + if (_currChar == '>') + { + ScanNextChar(true); + break; + } + + // TODO Handle EOF correctly. Check if other methods must also handle EOF. + + var hex = _currChar switch + { + >= '0' and <= '9' => _currChar - '0', + >= 'A' and <= 'F' => _currChar - ('A' - 10), // Not optimized in IL without parenthesis. + >= 'a' and <= 'f' => _currChar - ('a' - 10), + _ => LogError(_currChar) + }; + + ScanNextChar(true); + + // Does the string ends before the 2nd hex value? + if (_currChar == '>') + { + if (tryUsePanoseHack) + { + // Consider this: + // "< 0 0 2 b 6 6 3 8 4 2 2 4>" + // Ensure that the last "4" does not become a "40" but a "04". + + // Is it presumably a 12 byte Panose style code? + if (_token.Length == 11) + { + _token.Append((char)hex); + goto ScanNextChar; + } + } + // Second char is assumed to be '0' if not exists according to the PDF specs. + _token.Append((char)(hex << 4)); + + ScanNextChar: + ScanNextChar(true); + break; + } + + if (_currChar == ' ') // Explicitly not: "if (Lexer.IsDelimiter(_currChar))" + { + // Obviously a single hex character. + // Can occur for Panose style codes. + tryUsePanoseHack = true; + + _token.Append((char)hex); + + // Go on with next byte. + MoveToNonWhiteSpace(); + continue; + } + + // Some fool may add a line-break between the hex digits. + MoveToNonWhiteSpace(); + + // TODO Handle EOF correctly. + + hex = (hex << 4) + _currChar switch + { + >= '0' and <= '9' => _currChar - '0', + >= 'A' and <= 'F' => _currChar - ('A' - 10), + >= 'a' and <= 'f' => _currChar - ('a' - 10), + _ => LogError(_currChar) + }; + _token.Append((char)hex); + ScanNextChar(true); + } + return Symbol = Symbol.HexString; + + char LogError(char ch) + { + PdfSharpLogHost.Logger.LogError("Illegal character {char} in hex string.", ch); + DumpNeighborhoodOfPosition( /*SizeType position = -1, bool hex = false, int range = 25*/); + return '\0'; + } + } +#else /// /// Scans a hex encoded literal string, contained between "<" and ">". /// @@ -776,6 +876,7 @@ static char LogError(char ch) return '\0'; } } +#endif /// /// Tries to scan the specified literal from the current stream position. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs index 8683963d..b14f93c0 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs @@ -1055,10 +1055,6 @@ internal void ReadAllIndirectObjects() { if (pdfReference.Value == null!) { -#if DEBUG_ - if (pdfReference.ObjectNumber == 25) - _ = typeof(int); -#endif var pdfObject = ReadIndirectObject(pdfReference, null); Debug.Assert(pdfObject.Reference == pdfReference); @@ -1163,8 +1159,8 @@ internal void ReadAllObjectStreamsAndTheirReferences() // Create the parser for the object stream. var objectStreamParser = new Parser(_document, new MemoryStream(objectStream.Stream.UnfilteredValue), _documentParser); - // Read and add all References to objects residing in the object stream and get all ObjectIDs and offsets . - var objectIDsWithOffset = objectStream.ReadReferencesAndOffsets(_document.IrefTable); + // Get all ObjectIDs and offsets of objects residing in the object stream. + var objectIDsWithOffset = objectStream.ReadObjectIDsWithOffsets(); // Save all ObjectIDs with the parser of its ObjectStream and its offset. foreach (var objectIDWithOffset in objectIDsWithOffset) @@ -1179,6 +1175,15 @@ internal void ReadAllObjectStreamsAndTheirReferences() { // Add object with new objectID. _objectStreamObjectSources.Add(objectID, (objectStreamParser, offset)); + + // Create and add reference to the object if not existing yet. + if (!_document.IrefTable.Contains(objectID)) + { + // -1 indicates compressed object. + var iref = PdfReference.CreateForObjectID(objectID, -1); + + _document.IrefTable.Add(iref); + } } else { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfArrayWithPadding.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfArrayWithPadding.cs deleted file mode 100644 index 8c4af860..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfArrayWithPadding.cs +++ /dev/null @@ -1,35 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using PdfSharp.Pdf.IO; - -namespace PdfSharp.Pdf.Signatures -{ - /// - /// Internal PDF array used for digital signatures. - /// For digital signatures, we have to add an array with four integers, - /// but at the time we add the array we cannot yet determine - /// how many digits those integers will have. - /// - /// The document. - /// The count of spaces added after the array. - /// The contents of the array. - sealed class PdfArrayWithPadding(PdfDocument document, int paddingRight, params PdfItem[] items) - : PdfArray(document, items) - { - public int PaddingRight { get; init; } = paddingRight; - - internal override void WriteObject(PdfWriter writer) - { - StartPosition = writer.Position; - - base.WriteObject(writer); - writer.WriteRaw(new String(' ', PaddingRight)); - } - - /// - /// Position of the first byte of this string in PdfWriter’s stream. - /// - public SizeType StartPosition { get; internal set; } - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs new file mode 100644 index 00000000..9df1197e --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs @@ -0,0 +1,66 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.IO; + +namespace PdfSharp.Pdf.Signatures +{ + /// + /// This object reserves space in the stream of the PdfWriter by adding spaces for objects that can not be written at this time. + /// It is used e.g. for digital signatures to reserve space for the ByteRange Array. + /// + /// The length of the reserved space. + sealed class PdfPlaceholderObject(int length) : PdfObject() + { + public int Length { get; init; } = length; + + /// + /// This only writes spaces as placeholder for the actual object. + /// + internal override void WriteObject(PdfWriter writer) + { + StartPosition = writer.Position; + + writer.WriteRaw(new String(' ', Length)); + } + + /// + /// Writes the object, the placeholder is used for. + /// + /// The object to write. + /// The PDFWriter. + /// Throws an exception, when the space reserved by the placeholder isn’t enough. + internal void WriteActualObject(PdfObject obj, PdfWriter writer) + { + Object = obj; + + // Cache current writer position. + var initialPosition = writer.Position; + + // Write Object at StartPosition. + writer.Stream.Position = StartPosition; + Object.WriteObject(writer); + + // Ensure that object doesn’t exceed the reserved space. + var endPosition = writer.Position; + var actualLength = endPosition - StartPosition; + if (actualLength > Length) + { + throw new Exception($"The actual length {actualLength} of this object is larger than the length {Length} of its placeholder."); + } + + // Restore writer position. + writer.Stream.Position = initialPosition; + } + + /// + /// Position of this object in PdfWriter’s stream. + /// + public SizeType StartPosition { get; internal set; } + + /// + /// The object replacing the reserved space. + /// + public PdfObject? Object { get; private set; } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs index 64c6497f..f1f4b752 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs @@ -19,10 +19,10 @@ namespace PdfSharp.Pdf.Signatures public class DigitalSignatureHandler { /// - /// Space ... big enough reserved space to replace ByteRange placeholder [0 0 0 0] with the actual computed value of the byte range to sign + /// Big enough space reserved by PdfPlaceholderObject to be replaced by the actual computed value of the byte range to sign /// Worst case: signature dictionary is near the end of an 10 GB PDF file. /// - const int ByteRangePaddingLength = 36; // = "[0 9999999999 9999999999 9999999999]".Length + const int ByteRangePlaceholderLength = 36; // = "[0 9999999999 9999999999 9999999999]".Length DigitalSignatureHandler(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options) { @@ -64,9 +64,8 @@ internal async Task ComputeSignatureAndRange(PdfWriter writer) { var (rangedStreamToSign, byteRangeArray) = GetRangeToSignAndByteRangeArray(writer.Stream); - Debug.Assert(_signatureFieldByteRangePdfArray != null); - writer.Stream.Position = _signatureFieldByteRangePdfArray.StartPosition; - byteRangeArray.WriteObject(writer); + Debug.Assert(_signatureFieldByteRangePlaceholder != null); + _signatureFieldByteRangePlaceholder.WriteActualObject(byteRangeArray, writer); // Computing signature from document’s digest. var signature = await Signer.GetSignatureAsync(rangedStreamToSign).ConfigureAwait(false); @@ -146,9 +145,9 @@ internal async Task AddSignatureComponentsAsync() var signatureSize = await Signer.GetSignatureSizeAsync().ConfigureAwait(false); _placeholderItem = new(signatureSize); - _signatureFieldByteRangePdfArray = new PdfArrayWithPadding(Document, ByteRangePaddingLength, new PdfLongInteger(0), new PdfLongInteger(0), new PdfLongInteger(0), new PdfLongInteger(0)); + _signatureFieldByteRangePlaceholder = new PdfPlaceholderObject(ByteRangePlaceholderLength); - var signatureDictionary = GetSignatureDictionary(_placeholderItem, _signatureFieldByteRangePdfArray); + var signatureDictionary = GetSignatureDictionary(_placeholderItem, _signatureFieldByteRangePlaceholder); var signatureField = GetSignatureField(signatureDictionary); var annotations = Document.Pages[Options.PageIndex].Elements.GetArray(PdfPage.Keys.Annots); @@ -209,7 +208,7 @@ PdfSignatureField GetSignatureField(PdfSignature2 signatureDic) return signatureField; } - PdfSignature2 GetSignatureDictionary(PdfSignaturePlaceholderItem contents, PdfArray byteRange) + PdfSignature2 GetSignatureDictionary(PdfSignaturePlaceholderItem contents, PdfPlaceholderObject byteRange) { PdfSignature2 signatureDic = new(Document); @@ -238,6 +237,6 @@ PdfSignature2 GetSignatureDictionary(PdfSignaturePlaceholderItem contents, PdfAr } PdfSignaturePlaceholderItem? _placeholderItem; - PdfArrayWithPadding? _signatureFieldByteRangePdfArray; + PdfPlaceholderObject? _signatureFieldByteRangePlaceholder; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs index d989e384..1d2261ac 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs @@ -68,7 +68,7 @@ public static class PdfSharpProductVersionInformation /// /// The home page of this product. /// - public const string Url = "www.pdfsharp.net"; + public const string Url = "www.pdfsharp.com"; /// /// Unused. @@ -109,95 +109,95 @@ public static class PdfSharpProductVersionInformation // ReSharper restore RedundantNameQualifier #endif -#if Not_used_anymore - /// - /// E.g. "2005-01-01", for use in NuGet Script. - /// - public const string VersionReferenceDate = "2005-01-01"; - - /// - /// Use _ instead of blanks and special characters. Can be complemented with a suffix in the NuGet Script. - /// Nuspec Doc: The unique identifier for the package. This is the package name that is shown when packages - /// are listed using the Package Manager Console. These are also used when installing a package using the - /// Install-Package command within the Package Manager Console. Package IDs may not contain any spaces - /// or characters that are invalid in an URL. In general, they follow the same rules as .NET namespaces do. - /// So Foo.Bar is a valid ID, Foo! and Foo Bar are not. - /// - public const string NuGetID = "PDFsharp"; - - /// - /// Nuspec Doc: The human-friendly title of the package displayed in the Manage NuGet Packages dialog. - /// If none is specified, the ID is used instead. - /// - public const string NuGetTitle = "PDFsharp"; - - /// - /// Nuspec Doc: A comma-separated list of authors of the package code. - /// - public const string NuGetAuthors = "empira Software GmbH"; - - /// - /// Nuspec Doc: A comma-separated list of the package creators. This is often the same list as in authors. - /// This is ignored when uploading the package to the NuGet.org Gallery. - /// - public const string NuGetOwners = "empira Software GmbH"; - - /// - /// Nuspec Doc: A long description of the package. This shows up in the right pane of the Add Package Dialog - /// as well as in the Package Manager Console when listing packages using the Get-Package command. - /// - // This assignment must be written in one line because it will be parsed from a PS1 file. - public const string NuGetDescription = "PDFsharp is the Open Source .NET library that easily creates and processes PDF documents on the fly from any .NET language. The same drawing routines can be used to create PDF documents, draw on the screen, or send output to any printer."; - - /// - /// Nuspec Doc: A description of the changes made in each release of the package. This field only shows up - /// when the _Updates_ tab is selected and the package is an update to a previously installed package. - /// It is displayed where the Description would normally be displayed. - /// - public const string NuGetReleaseNotes = ""; - - /// - /// Nuspec Doc: A short description of the package. If specified, this shows up in the middle pane of the - /// Add Package Dialog. If not specified, a truncated version of the description is used instead. - /// - public const string NuGetSummary = "A .NET library for processing PDF."; - - /// - /// Nuspec Doc: The locale ID for the package, such as en-us. - /// - public const string NuGetLanguage = ""; - - /// - /// Nuspec Doc: A URL for the home page of the package. - /// - /// - /// http://www.pdfsharp.net/NuGetPackage_PDFsharp-GDI.ashx - /// http://www.pdfsharp.net/NuGetPackage_PDFsharp-WPF.ashx - /// - public const string NuGetProjectUrl = "www.pdfsharp.net"; - - /// - /// Nuspec Doc: A URL for the image to use as the icon for the package in the Manage NuGet Packages - /// dialog box. This should be a 32x32-pixel .png file that has a transparent background. - /// - public const string NuGetIconUrl = "http://www.pdfsharp.net/resources/PDFsharp-Logo-32x32.png"; - - /// - /// Nuspec Doc: A link to the license that the package is under. - /// - public const string NuGetLicenseUrl = "http://www.pdfsharp.net/PDFsharp_License.ashx"; - - /// - /// Nuspec Doc: A Boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed. - /// - public const bool NuGetRequireLicenseAcceptance = false; - - /// - /// Nuspec Doc: A space-delimited list of tags and keywords that describe the package. This information is used to help make sure users can find the package using - /// searches in the Add Package Reference dialog box or filtering in the Package Manager Console window. - /// - public const string NuGetTags = "PDFsharp PDF creation"; -#endif +//#if Not_used_anymore +// /// +// /// E.g. "2005-01-01", for use in NuGet Script. +// /// +// public const string VersionReferenceDate = "2005-01-01"; + +// /// +// /// Use _ instead of blanks and special characters. Can be complemented with a suffix in the NuGet Script. +// /// Nuspec Doc: The unique identifier for the package. This is the package name that is shown when packages +// /// are listed using the Package Manager Console. These are also used when installing a package using the +// /// Install-Package command within the Package Manager Console. Package IDs may not contain any spaces +// /// or characters that are invalid in a URL. In general, they follow the same rules as .NET namespaces do. +// /// So Foo.Bar is a valid ID, Foo! and Foo Bar are not. +// /// +// public const string NuGetID = "PDFsharp"; + +// /// +// /// Nuspec Doc: The human-friendly title of the package displayed in the Manage NuGet Packages dialog. +// /// If none is specified, the ID is used instead. +// /// +// public const string NuGetTitle = "PDFsharp"; + +// /// +// /// Nuspec Doc: A comma-separated list of authors of the package code. +// /// +// public const string NuGetAuthors = "empira Software GmbH"; + +// /// +// /// Nuspec Doc: A comma-separated list of the package creators. This is often the same list as in authors. +// /// This is ignored when uploading the package to the NuGet.org Gallery. +// /// +// public const string NuGetOwners = "empira Software GmbH"; + +// /// +// /// Nuspec Doc: A long description of the package. This shows up in the right pane of the Add Package Dialog +// /// as well as in the Package Manager Console when listing packages using the Get-Package command. +// /// +// // This assignment must be written in one line because it will be parsed from a PS1 file. +// public const string NuGetDescription = "PDFsharp is the Open Source .NET library that easily creates and processes PDF documents on the fly from any .NET language. The same drawing routines can be used to create PDF documents, draw on the screen, or send output to any printer."; + +// /// +// /// Nuspec Doc: A description of the changes made in each release of the package. This field only shows up +// /// when the _Updates_ tab is selected and the package is an update to a previously installed package. +// /// It is displayed where the Description would normally be displayed. +// /// +// public const string NuGetReleaseNotes = ""; + +// /// +// /// Nuspec Doc: A short description of the package. If specified, this shows up in the middle pane of the +// /// Add Package Dialog. If not specified, a truncated version of the description is used instead. +// /// +// public const string NuGetSummary = "A .NET library for processing PDF."; + +// /// +// /// Nuspec Doc: The locale ID for the package, such as en-us. +// /// +// public const string NuGetLanguage = ""; + +// /// +// /// Nuspec Doc: A URL for the home page of the package. +// /// +// /// +// /// http://www.pdfsharp.net/NuGetPackage_PDF/sharp-GDI.ashx +// /// http://www.pdfsharp.net/NuGetPackage_PDF/sharp-WPF.ashx +// /// +// public const string NuGetProjectUrl = "www.pdfsharp.com"; + +// /// +// /// Nuspec Doc: A URL for the image to use as the icon for the package in the Manage NuGet Packages +// /// dialog box. This should be a 32x32-pixel .png file that has a transparent background. +// /// +// public const string NuGetIconUrl = "http://www.pdf/sharp.net/resources/PDF/sharp-Logo-32x32.png"; + +// /// +// /// Nuspec Doc: A link to the license that the package is under. +// /// +// public const string NuGetLicenseUrl = "http://www.pdfsharp.net/PDF/sharp_License.ashx"; + +// /// +// /// Nuspec Doc: A Boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed. +// /// +// public const bool NuGetRequireLicenseAcceptance = false; + +// /// +// /// Nuspec Doc: A space-delimited list of tags and keywords that describe the package. This information is used to help make sure users can find the package using +// /// searches in the Add Package Reference dialog box or filtering in the Package Manager Console window. +// /// +// public const string NuGetTags = "PDFsharp PDF creation"; +//#endif /// /// The technology tag of the product: From defd2a28fc8cf351ec4ffb63bd3a277841539aa1 Mon Sep 17 00:00:00 2001 From: PDFsharp-Team Date: Mon, 17 Nov 2025 15:29:15 +0100 Subject: [PATCH 4/7] =?UTF-8?q?=E2=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- README.md | 6 +- dev/run-tests.ps1 | 151 ++++++++------- docs/MigraDoc/change-log/MD-v6.2.3-log.md | 17 ++ docs/PDFsharp/change-log/PS-v6.2.3-log.md | 23 +++ docs/change-log/gn-v6.2.3-log.md | 22 +++ docs/docs-dummy.csproj | 2 +- gitversion-6.x.yml | 4 +- gitversion.yml | 4 +- .../Dummy-PDFsharp.NuGet-wpf.csproj | 2 +- .../src/MigraDoc.NuGet-gdi/Description.txt | 4 - .../MigraDoc.NuGet-gdi.csproj | 4 +- .../MigraDoc.NuGet-gdi.nuspec | 19 +- .../nuget/src/MigraDoc.NuGet-gdi/README.md | 4 + .../src/MigraDoc.NuGet-gdi/ReleaseNotes.txt | 4 +- .../src/MigraDoc.NuGet-wpf/Description.txt | 4 - .../MigraDoc.NuGet-wpf.csproj | 4 +- .../MigraDoc.NuGet-wpf.nuspec | 19 +- .../nuget/src/MigraDoc.NuGet-wpf/README.md | 4 + .../src/MigraDoc.NuGet-wpf/ReleaseNotes.txt | 4 +- .../nuget/src/MigraDoc.NuGet/Description.txt | 4 - .../src/MigraDoc.NuGet/MigraDoc.NuGet.csproj | 4 +- .../src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec | 18 +- .../nuget/src/MigraDoc.NuGet/README.md | 4 + .../nuget/src/MigraDoc.NuGet/ReleaseNotes.txt | 4 +- .../src/PDFsharp.NuGet-gdi/Description.txt | 4 - .../PDFsharp.NuGet-gdi.csproj | 4 +- .../PDFsharp.NuGet-gdi.nuspec | 18 +- .../nuget/src/PDFsharp.NuGet-gdi/README.md | 4 + .../src/PDFsharp.NuGet-gdi/ReleaseNotes.txt | 4 +- .../src/PDFsharp.NuGet-wpf/Description.txt | 4 - .../PDFsharp.NuGet-wpf.csproj | 4 +- .../PDFsharp.NuGet-wpf.nuspec | 18 +- .../nuget/src/PDFsharp.NuGet-wpf/README.md | 4 + .../src/PDFsharp.NuGet-wpf/ReleaseNotes.txt | 4 +- .../nuget/src/PDFsharp.NuGet/Description.txt | 4 - .../src/PDFsharp.NuGet/PDFsharp.NuGet.csproj | 4 +- .../src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec | 17 +- .../nuget/src/PDFsharp.NuGet/README.md | 4 + .../nuget/src/PDFsharp.NuGet/ReleaseNotes.txt | 4 +- .../MigraDoc.Features.csproj | 2 +- .../MigraDoc.DocumentObjectModel.csproj | 2 +- .../MigraDoc.DocumentObjectModel/README.md | 2 +- .../MigraDoc.Rendering-gdi.csproj | 2 +- .../Rendering.Forms/DocumentPreview.cs | 6 + .../Rendering.Forms/enums/Zoom.cs | 4 +- .../MigraDoc.Rendering-wpf.csproj | 2 +- .../MigraDoc.Rendering.csproj | 2 +- .../MigraDoc.RtfRendering-gdi.csproj | 2 +- .../MigraDoc.RtfRendering-wpf.csproj | 2 +- .../MigraDoc.RtfRendering.csproj | 2 +- .../MigraDoc.DocumentObjectModel.Tests.csproj | 2 +- .../MigraDoc.GBE-Runner.csproj | 2 +- .../MigraDoc.GrammarByExample-GDI.csproj | 2 +- .../MigraDoc.GrammarByExample-WPF.csproj | 2 +- .../MigraDoc.GrammarByExample.csproj | 2 +- .../MigraDoc.Tests-gdi.csproj | 2 +- .../MigraDoc.Tests-wpf.csproj | 2 +- .../MigraDoc.Tests/MigraDoc.Tests.csproj | 2 +- .../tests/MigraDoc.Tests/SecurityTests.cs | 12 +- .../PDFsharp.Features-gdi.csproj | 2 +- .../PDFsharp.Features-wpf.csproj | 2 +- .../PDFsharp.Features.Runner-gdi.csproj | 2 +- .../PDFsharp.Features.Runner-wpf.csproj | 2 +- .../PdfSharp.Features.Runner.csproj | 2 +- .../PdfSharp.Features.csproj | 2 +- .../src/PdfSharp-gdi/Forms/PagePreview.cs | 44 +++-- .../src/PdfSharp-gdi/Forms/enums/Zoom.cs | 2 +- .../src/PdfSharp-gdi/PdfSharp-gdi.csproj | 4 +- .../src/PdfSharp-wpf/PdfSharp-wpf.csproj | 4 +- .../PdfSharp.BarCodes-gdi.csproj | 2 +- .../PdfSharp.BarCodes-wpf.csproj | 2 +- .../PdfSharp.BarCodes.csproj | 2 +- .../PdfSharp.Charting-gdi.csproj | 2 +- .../PdfSharp.Charting-gdi.csproj - save | 2 +- .../PdfSharp.Charting-wpf.csproj | 2 +- .../PdfSharp.Charting.csproj | 2 +- .../PdfSharp.Cryptography.csproj | 2 +- .../src/PdfSharp/Drawing/XBitmapEncoder.cs | 4 +- .../src/PdfSharp/Drawing/XBitmapImage.cs | 4 +- .../src/PdfSharp/Drawing/XBitmapSource.cs | 8 +- .../PDFsharp/src/PdfSharp/Drawing/XFont.cs | 4 +- .../src/PdfSharp/Drawing/XGlyphTypeface.cs | 12 +- .../src/PdfSharp/Drawing/XGraphics.cs | 180 +++++++++--------- .../src/PdfSharp/Drawing/XGraphicsPath.cs | 144 +++++++------- .../PDFsharp/src/PdfSharp/Drawing/XImage.cs | 48 ++--- .../PdfSharp/Drawing/XLinearGradientBrush.cs | 4 +- .../src/PDFsharp/src/PdfSharp/Drawing/XPen.cs | 4 +- .../PdfSharp/Drawing/XRadialGradientBrush.cs | 4 +- .../PdfSharp/Fonts.OpenType/GlyphDataTable.cs | 4 +- .../Fonts.OpenType/GlyphTypefaceCache.cs | 8 +- .../Fonts.OpenType/OpenTypeFontfaceCache.cs | 12 +- .../src/PdfSharp/Fonts/FontDescriptorCache.cs | 12 +- .../src/PdfSharp/Fonts/FontFactory.cs | 22 +-- .../src/PdfSharp/Fonts/FontFamilyCache.cs | 8 +- .../src/PdfSharp/Fonts/FontFamilyInternal.cs | 8 +- .../src/PdfSharp/Fonts/GlobalFontSettings.cs | 20 +- .../src/PdfSharp/Internal/DotNetHelper.cs | 2 +- .../PdfSharp/Internal/{Lock.cs => Locks.cs} | 2 +- .../Pdf.Advanced/PdfEmbeddedFileStream.cs | 2 +- .../src/PdfSharp/Pdf.Advanced/PdfImage.cs | 26 +-- .../PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs | 2 +- .../src/PDFsharp/src/PdfSharp/PdfSharp.csproj | 2 +- .../PdfSharp.Tests-gdi.csproj | 2 +- .../Drawing/images/ImageTests.cs | 45 ++++- .../PdfSharp.Tests/Pdf/creation/BasicTests.cs | 23 ++- .../Pdf/signatures/BouncyCastleSignerTests.cs | 16 +- .../Pdf/signatures/DefaultSignerTests.cs | 9 +- .../PdfSharp.Tests/PdfSharp.Tests.csproj | 2 +- .../PdfSharp.tests-wpf.csproj | 2 +- .../src/PdfSharp.Fonts/PdfSharp.Fonts.csproj | 2 +- .../PdfSharp.Quality-gdi.csproj | 2 +- .../PdfSharp.Quality-wpf.csproj | 2 +- .../PdfSharp.Quality/FeatureAndSnippetBase.cs | 2 +- .../shared/src/PdfSharp.Quality/FontHelper.cs | 2 +- .../PdfSharp.Quality/PdfSharp.Quality.csproj | 2 +- .../PdfSharp.Shared/PdfSharp.Shared.csproj | 2 +- .../PdfSharp.Snippets-gdi.csproj | 2 +- .../PdfSharp.Snippets-wpf.csproj | 2 +- .../PdfSharp.Snippets.csproj | 2 +- .../PdfSharp.System/PdfSharp.System.csproj | 2 +- .../PdfSharp.Testing-gdi.csproj | 2 +- .../PdfSharp.Testing-wpf.csproj | 2 +- .../PdfSharp.Testing/PdfSharp.Testing.csproj | 2 +- .../PdfSharp.WPFonts/PdfSharp.WPFonts.csproj | 2 +- .../PdfSharp.Fonts.TestApp.csproj | 2 +- .../shared/testapps/Shared.TestApp/Program.cs | 2 - .../Shared.TestApp/Shared.TestApp.csproj | 2 +- .../PdfSharp.Fonts.Test.csproj | 2 +- .../shared/tests/Shared.Tests/BasicTests.cs | 8 +- .../tests/Shared.Tests/Shared.Tests.csproj | 17 +- .../HelloWorld,MigraDoc-gdi.csproj | 2 +- .../HelloWorld,MigraDoc-wpf.csproj | 2 +- .../src/HelloWorld/HelloWorld,MigraDoc.csproj | 2 +- .../HelloWorld-gdi,PDFsharp.csproj | 2 +- .../HelloWorld-wpf,PDFsharp.csproj | 2 +- .../src/HelloWorld/HelloWorld,PDFsharp.csproj | 2 +- src/tools/src/CopyAsLink/CopyAsLink.csproj | 2 +- src/tools/src/NRT-Tests/NRT-Tests.csproj | 2 +- .../src/PdfFileViewer/PdfFileViewer.csproj | 2 +- .../PdfSharp.TestHelper-gdi.csproj | 2 +- .../PdfSharp.TestHelper-wpf.csproj | 2 +- .../PdfSharp.TestHelper.csproj | 2 +- 142 files changed, 749 insertions(+), 557 deletions(-) create mode 100644 docs/MigraDoc/change-log/MD-v6.2.3-log.md create mode 100644 docs/PDFsharp/change-log/PS-v6.2.3-log.md create mode 100644 docs/change-log/gn-v6.2.3-log.md rename src/foundation/src/PDFsharp/src/PdfSharp/Internal/{Lock.cs => Locks.cs} (98%) diff --git a/README.md b/README.md index 9a8ae93e..223474b3 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ # PDFsharp & MigraDoc 6 -Version **6.2.2** -Published **2025-09-22** +Version **6.2.3** +Published **2025-11-17** -This is a final version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 6 with updates for C# 12 and .NET 6. +This is a final version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 6 with updates for C# 12 and .NET 8, .NET 9, and .NET 10. PDFsharp: Copyright (c) 2005-2025 empira Software GmbH, Troisdorf (Cologne Area), Germany MigraDoc: Copyright (c) 2001-2025 empira Software GmbH, Troisdorf (Cologne Area), Germany diff --git a/dev/run-tests.ps1 b/dev/run-tests.ps1 index 44caf47a..685d0982 100644 --- a/dev/run-tests.ps1 +++ b/dev/run-tests.ps1 @@ -4,15 +4,15 @@ .DESCRIPTION The script builds the solution located in the script’s root parent folder and runs 'dotnet test' for all libraries to test, that are found via its projects. - These tests are run in the following environment, as far as available: Windows with NET8 or NET6, Windows with NET462 and Linux/WSL (NET8 or NET6). + These tests are run in the following environment, as far as available: Windows with NET8 or NET10, Windows with NET462 and Linux/WSL (NET8 or NET10). For each environment, libraries not to be run (like WPF in Linux or Linux-targeting DLLs in Windows) are excluded from testing. The test results are displayed in tables per library / code base comparing the test results in the different environments. .PARAMETER Config Specifies the configuration to build and test the solution ("Debug" or "Release"). "Debug" is the default. -.PARAMETER Net6 - Specifies whether NET6 shall be tested instead of NET8. $False is the default. +.PARAMETER Net10 + Specifies whether NET10 shall be tested instead of NET8. $False is the default. .PARAMETER SkipBuild Specifies whether the build of the solution shall be skipped. $False is the default. @@ -68,7 +68,7 @@ BUG: Allow to run all tests, including GBE. param ( [Parameter(Mandatory = $false)] [string]$Config = 'Debug', - [Parameter(Mandatory = $false)] [bool]$Net6 = $false, + [Parameter(Mandatory = $false)] [bool]$Net10 = $false, [Parameter(Mandatory = $false)] [bool]$SkipBuild = $false, [Parameter(Mandatory = $false)] [bool]$RunAllTests = $false ) @@ -77,7 +77,7 @@ $script:SystemNameWindows = "Windows" $script:SystemNameLinux = "Linux" $script:SystemNameWsl = "WSL" $script:NetName462 = "net462" -$script:NetName6 = "net6" +$script:NetName10 = "net10" $script:NetName8 = "net8" @@ -112,9 +112,9 @@ function InitializeScript() exit } - Write-Host - Write-Host "Started run-tests for solution `"$script:Solution`"." - Write-Host + Write-Output "" + Write-Output "Started run-tests for solution `"$script:Solution`"." + Write-Output "" $script:TimeStart = Get-Date $script:ConsoleWidth = $Host.UI.RawUI.WindowSize.Width @@ -167,43 +167,43 @@ function InitializeScript() } # Output current system information. - Write-Host - Write-Host "Started script in $script:SystemNameHost." + Write-Output "" + Write-Output "Started script in $script:SystemNameHost." if ($script:RunOnHostedWsl) { - Write-Host "Tests will be run on $script:SystemNameHost host and hosted $script:SystemNameCurrentLinux." + Write-Output "Tests will be run on $script:SystemNameHost host and hosted $script:SystemNameCurrentLinux." } else { - Write-Host "Tests will be run on $script:SystemNameHost host only." + Write-Output "Tests will be run on $script:SystemNameHost host only." } - Write-Host + Write-Output "" - if ($script:Net6) + if ($script:Net10) { - Write-Host "NET6 Tests will be run instead of NET8." - Write-Host + Write-Output "NET10 Tests will be run instead of NET8." + Write-Output "" } if ($script:SkipBuild) { - Write-Host "Building solution in $script:Config build will be skipped." - Write-Host + Write-Output "Building solution in $script:Config build will be skipped." + Write-Output "" } if ($script:RunAllTests) { $env:PDFsharpTests = "runalltests" $script:EnvironmentForHostedWsl = "PDFsharpTests=runalltests" - Write-Host "Running all tests of solution." + Write-Output "Running all tests of solution." } else { $env:PDFsharpTests = $null $script:EnvironmentForHostedWsl = "PDFsharpTests=$null" - Write-Host "Skipping slow tests of solution." + Write-Output "Skipping slow tests of solution." } - Write-Host + Write-Output "" CheckNetRuntimes } @@ -231,8 +231,8 @@ function CheckNetRuntime($isWsl) $wslOrLocal = "the local machine" } - if ($script:Net6) { - $netMajorVersion = 6; + if ($script:Net10) { + $netMajorVersion = 10; } else { $netMajorVersion = 8; @@ -263,10 +263,10 @@ function BuildSolution() return } - Write-Host - Write-Host "Building solution in $script:Config build" - Write-Host ================================================== - Write-Host + Write-Output "" + Write-Output "Building solution in $script:Config build" + Write-Output ================================================== + Write-Output "" dotnet build $script:Solution -c $script:Config RestoreForegroundColor # The dotnet call may change the foreground color, e. g. when displaying test result exceptions. @@ -276,29 +276,31 @@ function BuildSolution() # Loads DllInfo objects for all DLLs of the solution to run dotnet test for and saves the Windows and Linux specific lists. function LoadTestDllInfos() { - Write-Host - Write-Host "Analyzing Solution to find test DLLs" - Write-Host ================================================== - Write-Host + Write-Output "" + Write-Output "Analyzing Solution to find test DLLs" + Write-Output ================================================== + Write-Output "" $dllInfos = GetDllInfos $testDllInfos = $dllInfos | Where-Object { $_.IsTestDll } - # If Net6 parameter is true, remove net8 DLLs. - if ($script:Net6) + # If Net10 parameter is true, remove net8 DLLs. + if ($script:Net10) { $testDllInfos = $testDllInfos | Where-Object ` { $_.TargetFramework.Contains("net8") -eq $false + $_.TargetFramework.Contains("net9") -eq $false } } - # If Net6 parameter is false, remove net6 DLLs. + # If Net10 parameter is false, remove net10 DLLs. else { $testDllInfos = $testDllInfos | Where-Object ` { - $_.TargetFramework.Contains("net6") -eq $false + $_.TargetFramework.Contains("net10") -eq $false + $_.TargetFramework.Contains("net9") -eq $false } } @@ -352,9 +354,9 @@ function OutputTestDlls($testDllInfos, $systemName) { $testDllsOutput = $testDllInfos | Select-Object -Property DllFileName, EnvironmentName, DllFolder - Write-Host "DLLs to test in $systemName found in `"$script:Solution`":" - Write-Host ---------------------------------------- - Write-Host ($testDllsOutput | Format-Table | Out-String) + Write-Output "DLLs to test in $systemName found in `"$script:Solution`":" + Write-Output ---------------------------------------- + Write-Output ($testDllsOutput | Format-Table | Out-String) } # Gets DllInfo objects for all DLLs of the solution. @@ -543,9 +545,9 @@ function RunTests() # Runs dotnet test for all needed DLLs for the given $systemName. function RunTestsForSystem($testDllInfos, $systemName, $isHostedWsl) { - Write-Host - Write-Host Running tests on $systemName - Write-Host ================================================== + Write-Output "" + Write-Output Running tests on $systemName + Write-Output ================================================== foreach ($testDllInfo in $testDllInfos) { # Work with absolute DLL path here to avoid errors. @@ -565,10 +567,10 @@ function RunTestsForSystem($testDllInfos, $systemName, $isHostedWsl) $TestResultsFilename = GetTestResultsFilename $testDllInfo.EnvironmentName - Write-Host - Write-Host ($systemName): dotnet test $testDllInfo.DllFileName ("(" + $testDllInfo.TargetFramework + ")")... - Write-Host ---------------------------------------- - Write-Host + Write-Output "" + Write-Output ($systemName): dotnet test $testDllInfo.DllFileName ("(" + $testDllInfo.TargetFramework + ")")... + Write-Output ---------------------------------------- + Write-Output "" # Change current location to store the trx file in the default "TestResults" folder inside the project folder. Push-Location $testDllInfo.ProjectFolder @@ -590,18 +592,18 @@ function RunTestsForSystem($testDllInfos, $systemName, $isHostedWsl) Pop-Location } } - Write-Host - Write-Host + Write-Output "" + Write-Output "" } # Gets the name of the environment for the given system and framework. function GetEnvironmentName($systemName, $targetFramework) { - if ($script:Net6 -and $targetFramework.Contains("net6")) + if ($script:Net10 -and $targetFramework.Contains("net10")) { - $frameworkName = $script:NetName6 + $frameworkName = $script:NetName10 } - elseif ($script:Net6 -eq $false -and $targetFramework.Contains("net8")) + elseif ($script:Net10 -eq $false -and $targetFramework.Contains("net8")) { $frameworkName = $script:NetName8 } @@ -617,13 +619,13 @@ function GetEnvironmentName($systemName, $targetFramework) # HACK: For Linux the frameworkName shall not be shown. if ($systemName -eq $script:SystemNameCurrentLinux) { - if ($script:Net6 -and $frameworkName -ne "net6") + if ($script:Net10 -and $frameworkName -ne "net10") { - Write-Error ("For Linux there’s only one column supported (net6) by test script with Net6 parameter set to true.") + Write-Error ("For Linux there’s only one column supported (net10) by test script with Net10 parameter set to true.") } - elseif ($script:Net6 -eq $false -and $frameworkName -ne "net8") + elseif ($script:Net10 -eq $false -and $frameworkName -ne "net8") { - Write-Error ("For Linux there’s only one column supported (net8) by test script with Net6 parameter set to false.") + Write-Error ("For Linux there’s only one column supported (net8) by test script with Net10 parameter set to false.") } return "$systemName" } @@ -662,14 +664,15 @@ function GetTestResultsFilename($environmentName) # Loads the trx files and displays the test results. function LoadAndShowTestResults() { - Write-Host - Write-Host - Write-Host "TestResults" -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output. - Write-Host "==================================================" -ForegroundColor Green + # TODO -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output. + Write-Output "" + Write-Output "" + Write-Output "TestResults" + Write-Output "==================================================" - if ($script:Net6) + if ($script:Net10) { - $netNameX = $script:NetName6 + $netNameX = $script:NetName10 } else { @@ -737,12 +740,13 @@ function LoadAndShowTestResults() $genericCodeBaseWithExtension = $genericCodeBaseInfo.GenericCodeBase + $genericCodeBaseInfo.GenericCodeBaseExtension - Write-Host - Write-Host + # TODO -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output. + Write-Output "" + Write-Output "" $title = ("CodeBase:").PadRight($padValue + 1, ' ') - Write-Host $title $genericCodeBaseWithExtension -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output. - Write-Host "----------------------------------------------------------------------------------------------------" -ForegroundColor Green - Write-Host Test files: + Write-Output $title $genericCodeBaseWithExtension + Write-Output "----------------------------------------------------------------------------------------------------" + Write-Output Test files: $codeBaseResults = @() # Collects all results for this GenericCodeBase. @@ -768,18 +772,18 @@ function LoadAndShowTestResults() # If the project is not targeting this environment. if ($notTargetingEnvironment) { - Write-Host $title Not implemented. + Write-Output $title Not implemented. } # If trx file is not found or outdated, output "No test results found" and continue loop. elseif ($testResultsFileExists -eq $false -or $testResultsFileOutdated) { $testResultsFile = $null - Write-Host $title No test results found. + Write-Output $title No test results found. } # If trx file is found and from this test run, collect results from the file. else { - Write-Host $title $testResultsFile + Write-Output $title $testResultsFile # Read trx content. [xml]$fileContent = Get-Content -Path $testResultsFile @@ -857,11 +861,12 @@ function LoadAndShowTestResults() # Add summary for all grouped test results. - Write-Host - Write-Host - Write-Host - Write-Host Summary -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output. - Write-Host "----------------------------------------------------------------------------------------------------" -ForegroundColor Green + # TODO -ForegroundColor Green # Green color is used to make it the same conspicuity like the given green Format-Table header output. + Write-Output "" + Write-Output "" + Write-Output "" + Write-Output Summary + Write-Output "----------------------------------------------------------------------------------------------------" # Create test summary from grouped test results. $summary = CreateTestSummary $allGroupedResults $environmentNames diff --git a/docs/MigraDoc/change-log/MD-v6.2.3-log.md b/docs/MigraDoc/change-log/MD-v6.2.3-log.md new file mode 100644 index 00000000..216ec71e --- /dev/null +++ b/docs/MigraDoc/change-log/MD-v6.2.3-log.md @@ -0,0 +1,17 @@ +# MigraDoc 6.2.3 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`. + +## What’s new in version 6.2.3 + +### Breaking changes + +*(none)* + +### Features + +*(none)* + +### Issues + +The bug fixes of PDFsharp are also useful when generating PDF files from MigraDoc. diff --git a/docs/PDFsharp/change-log/PS-v6.2.3-log.md b/docs/PDFsharp/change-log/PS-v6.2.3-log.md new file mode 100644 index 00000000..afd2054f --- /dev/null +++ b/docs/PDFsharp/change-log/PS-v6.2.3-log.md @@ -0,0 +1,23 @@ +# PDFsharp 6.2.3 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`. + +## What’s new in version 6.2.3 + +### Breaking changes + +*(none)* + +### Features + +*(none)* + +### Issues + +**Fixed problem with JPEG images from XImage.FromGdiPlusImage** +JPEG images created from GDI+ images were not handled correctly. GitHub #318 +This API is only available in the GDI+ build of PDFsharp. +It is recommended to avoid the *XImage.FromGdiPlusImage* API and use *XImage.FromFile* or *XImage.FromStream* instead if possible. + +**Avoid exception when reading certain corrupted files** +An incorrectly formatted logging string caused an exception when logging a warning about some corrupted PDF files. GitHub #290 diff --git a/docs/change-log/gn-v6.2.3-log.md b/docs/change-log/gn-v6.2.3-log.md new file mode 100644 index 00000000..be892714 --- /dev/null +++ b/docs/change-log/gn-v6.2.3-log.md @@ -0,0 +1,22 @@ +# General 6.2.3 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **general** `History.md`. + +## What’s new in version 6.2.3 + +### Breaking changes + +With version 6.2.3, we added support for .NET 9 and .NET 10. +Version 6.2.3 no longer compiles against .NET 6 which is out of support. +The NuGet packages can still be used for applications that use .NET 6. +Version 6.2.3 was built using Visual Studio 2026. +It still compiles with Visual Studio 2022, provided the .NET 10 SDK is installed, but warnings will be shown. +You can remove .NET 10 from the list of target frameworks if you do not need it. + +### General features + +*(none)* + +### General issues + +The bug fixes of PDFsharp are useful when generating PDF files from PDFsharp or MigraDoc. diff --git a/docs/docs-dummy.csproj b/docs/docs-dummy.csproj index 2731fd21..424fd82e 100644 --- a/docs/docs-dummy.csproj +++ b/docs/docs-dummy.csproj @@ -3,7 +3,7 @@ - net8.0 + net8.0 diff --git a/gitversion-6.x.yml b/gitversion-6.x.yml index 2fff1bd1..5457154a 100644 --- a/gitversion-6.x.yml +++ b/gitversion-6.x.yml @@ -1,9 +1,9 @@ -# Last update: 25-05-19 +# Last update: 25-11-17 assembly-versioning-scheme: MajorMinorPatch # PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE # Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; # C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7443}' +assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7625}' strategies: [Mainline] assembly-informational-format: '{InformationalVersion}' branches: diff --git a/gitversion.yml b/gitversion.yml index 3f9021f9..aa790b52 100644 --- a/gitversion.yml +++ b/gitversion.yml @@ -1,9 +1,9 @@ -# Last update: 25-05-19 +# Last update: 25-11-17 assembly-versioning-scheme: MajorMinorPatch # PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE # Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; # C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7569}' +assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7625}' mode: Mainline assembly-informational-format: '{NuGetVersion}' branches: diff --git a/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/Dummy-PDFsharp.NuGet-wpf.csproj b/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/Dummy-PDFsharp.NuGet-wpf.csproj index 57239e66..ed0f26e9 100644 --- a/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/Dummy-PDFsharp.NuGet-wpf.csproj +++ b/src/foundation/nuget/src/Dummy-PDFsharp.NuGet-wpf/Dummy-PDFsharp.NuGet-wpf.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net462 + net8.0-windows;net462 true diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt index 52986d6f..c59d5227 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt @@ -3,7 +3,3 @@ This package relies on Windows Forms (GDI+) and can be used under Windows only. See https://docs.pdfsharp.net for details. -  -See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj index b89b8c92..a6c95fb0 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj @@ -1,9 +1,9 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true - true + false True ..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec index 9a3b155a..958b70ac 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec @@ -18,12 +18,17 @@ $title$ - + - + + + + + + @@ -35,10 +40,13 @@ - + - + + + + @@ -47,8 +55,9 @@ - + + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/README.md b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/README.md index b260225d..1b3e37de 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/README.md +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/README.md @@ -5,3 +5,7 @@ MigraDoc is a .NET library that allows developers to create documents such as PD This package relies on Windows Forms (GDI+) and can be used under Windows only. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. + +See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt index 46a1582f..bdb0512f 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt @@ -1,5 +1,5 @@ This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher. -The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’. -The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’. +The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. +The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. See the project docs site for further information: https://docs.pdfsharp.net/link/readme-v6.2.html diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt index 92f0069a..ff527946 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt @@ -3,7 +3,3 @@ This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See https://docs.pdfsharp.net for details. -  -See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj index 676107fe..b4b3a187 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj @@ -1,9 +1,9 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true - true + false True ..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec index 7a5a15e7..354e0a79 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec @@ -18,12 +18,17 @@ $title$ - + - + + + + + + @@ -35,10 +40,13 @@ - + - + + + + @@ -47,8 +55,9 @@ - + + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/README.md b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/README.md index 2aa1a171..19de2b70 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/README.md +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/README.md @@ -5,3 +5,7 @@ MigraDoc is a .NET library that allows developers to create documents such as PD This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. + +See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt index 46a1582f..bdb0512f 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt @@ -1,5 +1,5 @@ This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher. -The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’. -The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’. +The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. +The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. See the project docs site for further information: https://docs.pdfsharp.net/link/readme-v6.2.html diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt index 99d2af4b..78d56e99 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt @@ -3,7 +3,3 @@ This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See https://docs.pdfsharp.net for details. -  -See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj index 6103944a..ca67c46a 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj +++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj @@ -1,8 +1,8 @@  - net6.0;net8.0;netstandard2.0 - true + net8.0;net9.0;net10.0;netstandard2.0 + false True ..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec index 3f2b5634..94472655 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec @@ -18,12 +18,17 @@ $title$ - + - + + + + + + @@ -35,17 +40,20 @@ - - + + + + - + + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/README.md b/src/foundation/nuget/src/MigraDoc.NuGet/README.md index 096861a0..be978c7c 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/README.md +++ b/src/foundation/nuget/src/MigraDoc.NuGet/README.md @@ -5,3 +5,7 @@ MigraDoc is a .NET library that allows developers to create documents such as PD This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. + +See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt index 46a1582f..bdb0512f 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt @@ -1,5 +1,5 @@ This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher. -The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’. -The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’. +The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. +The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. See the project docs site for further information: https://docs.pdfsharp.net/link/readme-v6.2.html diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt index be622cff..4b997b23 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt @@ -3,7 +3,3 @@ This package relies on Windows Forms (GDI+) and can be used under Windows only. See https://docs.pdfsharp.net for details. -  -See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj index 76da6886..5c7f5a49 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj @@ -1,9 +1,9 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true - true + false True ..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec index e44df4b4..ff34b32d 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec @@ -18,11 +18,15 @@ $title$ - + - + + + + + @@ -32,10 +36,13 @@ - + - + + + + @@ -44,8 +51,9 @@ - + + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/README.md b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/README.md index b93dbc0d..851b3420 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/README.md +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/README.md @@ -5,3 +5,7 @@ PDFsharp is the Open Source library for creating and modifying PDF documents usi This package relies on Windows Forms (GDI+) and can be used under Windows only. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. + +See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt index f5df80ae..a1689064 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt @@ -1,6 +1,6 @@ This is a version of PDFsharp compatible with .NET 6 and higher. -The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’. -The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’. +The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. +The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. We also publish packages that contain PDFsharp plus MigraDoc. See the project docs site for further information: https://docs.pdfsharp.net/link/readme-v6.2.html diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt index b82a01aa..b6028f68 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt @@ -3,7 +3,3 @@ This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See https://docs.pdfsharp.net for details. -  -See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj index 66ca02ae..a9c25477 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj @@ -1,9 +1,9 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true - true + false True ..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec index d930f34b..22a177fb 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec @@ -18,11 +18,15 @@ $title$ - + - + + + + + @@ -32,10 +36,13 @@ - + - + + + + @@ -44,8 +51,9 @@ - + + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/README.md b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/README.md index 23c061b4..63c45298 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/README.md +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/README.md @@ -5,3 +5,7 @@ PDFsharp is the Open Source library for creating and modifying PDF documents usi This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. + +See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt index f5df80ae..a1689064 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt @@ -1,6 +1,6 @@ This is a version of PDFsharp compatible with .NET 6 and higher. -The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’. -The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’. +The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. +The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. We also publish packages that contain PDFsharp plus MigraDoc. See the project docs site for further information: https://docs.pdfsharp.net/link/readme-v6.2.html diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt index 432a269f..e18be602 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt @@ -3,7 +3,3 @@ This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See https://docs.pdfsharp.net for details. -  -See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj index 1139a742..a530742a 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj +++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj @@ -1,8 +1,8 @@  - net6.0;net8.0;netstandard2.0 - true + net8.0;net9.0;net10.0;netstandard2.0 + false True diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec index 4c01c173..8d137768 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec @@ -18,11 +18,15 @@ $title$ - + - + + + + + @@ -32,17 +36,20 @@ - - + + + + - + + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/README.md b/src/foundation/nuget/src/PDFsharp.NuGet/README.md index 4e60f763..00466f60 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/README.md +++ b/src/foundation/nuget/src/PDFsharp.NuGet/README.md @@ -5,3 +5,7 @@ PDFsharp is the Open Source library for creating and modifying PDF documents usi This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. + +See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt index f5df80ae..a1689064 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt @@ -1,6 +1,6 @@ This is a version of PDFsharp compatible with .NET 6 and higher. -The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’. -The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’. +The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. +The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. We also publish packages that contain PDFsharp plus MigraDoc. See the project docs site for further information: https://docs.pdfsharp.net/link/readme-v6.2.html diff --git a/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj b/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj index 74954604..93906e8f 100644 --- a/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj +++ b/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj @@ -2,7 +2,7 @@ Exe - net6.0 + net8.0 diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj index 2f388d42..e7f47f6d 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;net462;netstandard2.0 + net8.0;net9.0;net10.0;net462;netstandard2.0 MigraDoc True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md index 6c1d18fe..aff137a8 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md @@ -3,5 +3,5 @@ One single build with all target frameworks: ```xml -net6.0;net8.0;net462;netstandard2.0 +net8.0;net9.0;net10.0;net462;netstandard2.0 ``` diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj index f7a05e8c..2a586f0c 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true MigraDoc true diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs index c5e3a5c0..301d4818 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs @@ -111,6 +111,7 @@ Zoom GetNewZoomFactor(int currentZoom, bool larger) /// /// Gets or sets a DDL string or file. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)] public string Ddl { get { return _ddl; } @@ -125,6 +126,7 @@ public string Ddl /// /// Gets or sets the current page. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int Page { get { return _page; } @@ -270,6 +272,7 @@ void DdlUpdated() /// /// Gets or sets the MigraDoc document that is previewed in this control. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public MigraDoc.DocumentObjectModel.Document Document { get { return _document; } @@ -376,6 +379,7 @@ public void MakeLarger() /// /// The color of the page. [Description("The background color of the page."), Category("Preview Properties")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color PageColor { get { return _preview.PageColor; } @@ -388,6 +392,7 @@ public Color PageColor /// /// The color of the desktop. [Description("The color of the desktop."), Category("Preview Properties")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color DesktopColor { get { return _preview.DesktopColor; } @@ -423,6 +428,7 @@ public bool ShowPage /// Gets or sets the page size in point. /// [Description("Determines the size (in points) of the page."), Category("Preview Properties")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Size PageSize { get { return new Size((int)_preview.PageSize.Width, (int)_preview.PageSize.Height); } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs index 3884bf37..9cdd63e0 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/enums/Zoom.cs @@ -69,7 +69,7 @@ public enum Zoom /// /// The smallest possible zoom factor. /// - Mininum = PdfSharp.Forms.Zoom.Mininum, + Minimum = PdfSharp.Forms.Zoom.Minimum, /// /// The largest possible zoom factor. /// @@ -136,7 +136,7 @@ public enum Zoom /// /// The smallest possible zoom factor. /// - Mininum = PdfSharp.Windows.Zoom.Mininum, + Minimum = PdfSharp.Windows.Zoom.Minimum, /// /// The largest possible zoom factor. /// diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj index 0f9d85e1..c98ca20c 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true MigraDoc true diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj index c0b70dbf..6775c2e9 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;netstandard2.0 + net8.0;net9.0;net10.0;netstandard2.0 MigraDoc True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj index 773d24f6..248fca94 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj @@ -1,6 +1,6 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true MigraDoc GDI diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj index 9ac96081..f9d150e8 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj @@ -1,6 +1,6 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true MigraDoc WPF diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj index 10518896..93dcc6ce 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;netstandard2.0 + net8.0;net9.0;net10.0;netstandard2.0 MigraDoc ..\..\..\..\..\StrongnameKey.snk True diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/MigraDoc.DocumentObjectModel.Tests.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/MigraDoc.DocumentObjectModel.Tests.csproj index f8eaccf5..da9cceef 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/MigraDoc.DocumentObjectModel.Tests.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/MigraDoc.DocumentObjectModel.Tests.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;net462 + net8.0;net9.0;net10.0;net462 diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj index 52ee0ccf..91eaa028 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net8.0;net462 + net8.0;net9.0;net10.0;net462 MigraDoc.GBE_Runner MigraDoc.GBE_Runner.Program 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 b25f466e..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 @@ -1,7 +1,7 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 MigraDoc.GrammarByExample_GDI true true diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj index ec74ce24..8afb911c 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 MigraDoc.GrammarByExample_WPF true true diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj index b69b50dc..317b2cd8 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;net462 + net8.0;net9.0;net10.0;net462 false diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj index a1e08c8b..72425076 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj index 23dde58f..46e32c27 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true true True diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj index 615d07a3..1957a8a3 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;net462 + net8.0;net9.0;net10.0;net462 True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs index aa5ce0b9..ce761805 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs @@ -1104,12 +1104,18 @@ static X509Certificate2 GetCertificate(string certName) var rawData = File.ReadAllBytes(pfxFile); // Do not use password literals for real certificates in source code. - var certificatePassword = "Seecrit1243"; //@@@??? + var certificatePassword = "Seecrit1243"; // Used in certificate. +#if NET9_0_OR_GREATER + // New API introduced with .NET 9. + var certificate = X509CertificateLoader.LoadPkcs12(rawData, certificatePassword, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#else + // Old API, obsolete since .NET 9. var certificate = new X509Certificate2(rawData, certificatePassword, - X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); - + /*X509KeyStorageFlags.MachineKeySet |*/ X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#endif return certificate; } } diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj index 0bb0db5e..cd3012c2 100644 --- a/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj +++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net462 + net8.0-windows;net462 true PDFsharp.Features true diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj index 95f17553..506da2b2 100644 --- a/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj +++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net462 + net8.0-windows;net462 true PDFsharp.Features true diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj index 3b391394..0ca4840a 100644 --- a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj +++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj @@ -2,7 +2,7 @@ Exe - net6.0-windows;net462 + net8.0-windows;net462 true PDFsharp.Features true diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj index 9f46155e..a22f8c63 100644 --- a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj +++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj @@ -2,7 +2,7 @@ Exe - net6.0-windows;net462 + net8.0-windows;net462 true PDFsharp.Features true diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj index 5a877abf..7791be9f 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net462 + net8.0;net462 CORE diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj index f1e8b630..7a112f72 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj @@ -1,7 +1,7 @@  - net6.0;netstandard2.0 + net8.0;netstandard2.0 CORE diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs index a162bca9..ae9d8e2d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs @@ -66,12 +66,12 @@ public PagePreview() //OnLayout(); _zoom = Zoom.FullPage; - _printableArea = new RectangleF(); + //_printableArea = new RectangleF(); //virtPageSize = new Size(); //showNonPrintableArea = false; //virtualPrintableArea = new Rectangle(); - _printableArea.GetType(); + //_printableArea.GetType(); //showNonPrintableArea.GetType(); //virtualPrintableArea.GetType(); @@ -150,22 +150,20 @@ protected override void Dispose(bool disposing) /// Gets or sets the XGraphicsUnit of the page. /// The default value is XGraphicsUnit.Point. /// - public XGraphicsUnit PageGraphicsUnit - { - get { return _pageGraphicsUnit; } - set { _pageGraphicsUnit = value; } - } - XGraphicsUnit _pageGraphicsUnit = XGraphicsUnit.Point; + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] + public XGraphicsUnit PageGraphicsUnit { get; set; } = XGraphicsUnit.Point; +#if !NET9_0_OR_GREATER /// /// This property was renamed. Use new property PageGraphicsUnit. /// [Obsolete("Property renamed, use PageGraphicsUnit")] public XGraphicsUnit PageUnit { - get { return _pageGraphicsUnit; } - set { _pageGraphicsUnit = value; } + get => PageGraphicsUnit; + set => PageGraphicsUnit = value; } +#endif /// /// Gets or sets a predefined zoom factor. @@ -176,7 +174,7 @@ public Zoom Zoom get { return _zoom; } set { - if ((int)value < (int)Zoom.Mininum || (int)value > (int)Zoom.Maximum) + if ((int)value < (int)Zoom.Minimum || (int)value > (int)Zoom.Maximum) { if (!Enum.IsDefined(typeof(Zoom), value)) throw new InvalidEnumArgumentException("value", (int)value, typeof(Zoom)); @@ -196,14 +194,15 @@ public Zoom Zoom /// Gets or sets an arbitrary zoom factor. The range is from 10 to 800. /// //[DefaultValue((int)Zoom.FullPage), Description("Determines the zoom of the page."), Category("Preview Properties")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int ZoomPercent { get { return _zoomPercent; } set { - if (value < (int)Zoom.Mininum || value > (int)Zoom.Maximum) + if (value < (int)Zoom.Minimum || value > (int)Zoom.Maximum) throw new ArgumentOutOfRangeException("value", value, - String.Format("Value must between {0} and {1}.", (int)Zoom.Mininum, (int)Zoom.Maximum)); + String.Format("Value must between {0} and {1}.", (int)Zoom.Minimum, (int)Zoom.Maximum)); if (value != _zoomPercent) { @@ -221,6 +220,7 @@ public int ZoomPercent /// Gets or sets the color of the page. /// [Description("The background color of the page."), Category("Preview Properties")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color PageColor { get { return _pageColor; } @@ -239,6 +239,7 @@ public Color PageColor /// Gets or sets the color of the desktop. /// [Description("The color of the desktop."), Category("Preview Properties")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color DesktopColor { get { return _desktopColor; } @@ -295,6 +296,7 @@ public bool ShowPage /// Gets or sets the page size in point. /// [Description("Determines the size (in points) of the page."), Category("Preview Properties")] + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public XSize PageSize { get { return new XSize((int)_pageSize.Width, (int)_pageSize.Height); } @@ -310,6 +312,7 @@ public XSize PageSize /// This is a hack for Visual Studio 2008. The designer uses reflection for setting the PageSize property. /// This fails, even an implicit operator that converts Size to XSize exits. /// + [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Size PageSizeF { get { return new Size(Convert.ToInt32(_pageSize.Width), Convert.ToInt32(_pageSize.Height)); } @@ -554,7 +557,7 @@ internal void CalculatePreviewDimension(out bool zoomChanged) } // Bound to zoom limits - _zoomPercent = Math.Max(Math.Min(_zoomPercent, (int)Zoom.Maximum), (int)Zoom.Mininum); + _zoomPercent = Math.Max(Math.Min(_zoomPercent, (int)Zoom.Maximum), (int)Zoom.Minimum); if ((int)_zoom > 0) _zoom = (Zoom)_zoomPercent; @@ -587,13 +590,12 @@ internal void CalculatePreviewDimension(out bool zoomChanged) zoomChanged = zoomOld != _zoom || zoomPercentOld != _zoomPercent; if (zoomChanged) - OnZoomChanged(new EventArgs()); + OnZoomChanged(EventArgs.Empty); } internal void CalculatePreviewDimension() { - bool zoomChanged; - CalculatePreviewDimension(out zoomChanged); + CalculatePreviewDimension(out _); } internal bool RenderPage(Graphics gfx) @@ -1043,9 +1045,9 @@ void SetScrollBarRange() /// Size _virtualCanvas; - /// - /// Printable area in point. - /// - readonly RectangleF _printableArea; + ///// + ///// Printable area in point. + ///// + //readonly RectangleF _printableArea; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs index 2cafee8f..04a353c6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs @@ -12,7 +12,7 @@ public enum Zoom /// /// The smallest possible zoom factor. /// - Mininum = 10, + Minimum = 10, /// /// The largest possible zoom factor. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj index e054c783..9660d8cb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj @@ -2,7 +2,7 @@ library - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true PdfSharp true @@ -172,7 +172,7 @@ - + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj index ff99fdb5..fe0ae4ed 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj @@ -2,7 +2,7 @@ library - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true PdfSharp true @@ -169,7 +169,7 @@ - + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj index 6100a1fd..72f72d23 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj @@ -2,7 +2,7 @@ library - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true PdfSharp true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj index 852faa7b..637c5bda 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj @@ -2,7 +2,7 @@ library - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true PdfSharp true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj index 0286935a..52919fd4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj @@ -2,7 +2,7 @@ library - net6.0;net8.0;netstandard2.0 + net8.0;net9.0;net10.0;netstandard2.0 PdfSharp CORE True diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj index 35d7b6d6..7ab21f61 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj @@ -2,7 +2,7 @@ library - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true PdfSharp.Charting true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save index 303c62a8..f6de3245 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save @@ -2,7 +2,7 @@ library - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true PdfSharp.Charting true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj index f615f461..9ce77dc1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj @@ -2,7 +2,7 @@ library - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true PdfSharp.Charting true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj index 0cb4ed7b..2e5d3abf 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj @@ -2,7 +2,7 @@ library - net6.0;net8.0;netstandard2.0 + net8.0;net9.0;net10.0;netstandard2.0 PdfSharp True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj index d0cfee0d..a92924f5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;netstandard2.0 + net8.0;net9.0;net10.0;netstandard2.0 PdfSharp True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs index b200bab5..92572bcb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs @@ -68,10 +68,10 @@ public override void Save(Stream stream) } try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); Source._gdiImage.Save(stream, ImageFormat.Png); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF DiagnosticsHelper.ThrowNotImplementedException("Save..."); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs index ebd7a0ba..817e5a08 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs @@ -29,11 +29,11 @@ internal XBitmapImage(int width, int height) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); // Create a default 24-bit ARGB bitmap. _gdiImage = new Bitmap(width, height); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF DiagnosticsHelper.ThrowNotImplementedException("CreateBitmap"); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs index 78eb9bc9..b4c7b117 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs @@ -36,10 +36,10 @@ public override int PixelWidth #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.Width; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF int gdiWidth = _gdiImage.Width; @@ -71,10 +71,10 @@ public override int PixelHeight #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.Height; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF int gdiHeight = _gdiImage.Height; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs index 329474fa..ab342412 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs @@ -374,7 +374,7 @@ void InitializeFromGdi() { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (GdiFontFamily != null!) { // Create font based on its family. @@ -401,7 +401,7 @@ void InitializeFromGdi() CreateDescriptorAndInitializeFontMetrics(); } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs index 45a7df93..d7b6c902 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs @@ -141,7 +141,7 @@ internal static XGlyphTypeface GetOrCreateFrom(string familyName, FontResolvingO try { // Lock around TryGetGlyphTypeface and AddGlyphTypeface. - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) { // Just return existing one. @@ -262,7 +262,7 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) #endif GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } return glyphTypeface; } @@ -277,7 +277,7 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) try { // Lock around TryGetGlyphTypeface and AddGlyphTypeface. - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); string typefaceKey = ComputeGtfKey(gdiFont); if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) { @@ -300,7 +300,7 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, styleSimulations, gdiFont); GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } return glyphTypeface; } @@ -324,7 +324,7 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) // Lock around TryGetGlyphTypeface and AddGlyphTypeface. try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); // Create WPF glyph typeface. if (!wpfTypeface.TryGetGlyphTypeface(out var wpfGlyphTypeface)) @@ -359,7 +359,7 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) return glyphTypeface; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs index 880ed3ef..a867471d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs @@ -92,10 +92,10 @@ public sealed class XGraphics : IDisposable // MigraDoc comes here when creating a MeasureContext. try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); gfx = Graphics.FromHwnd(IntPtr.Zero); // BUG_OLD: Use measure image } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } _gsStack = new GraphicsStateStack(this); @@ -434,7 +434,7 @@ public sealed class XGraphics : IDisposable #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); TargetContext = XGraphicTargetContext.GDI; // If form.Owner is null create a meta file. if (form.Owner == null!) @@ -505,7 +505,7 @@ public sealed class XGraphics : IDisposable _renderer = new PdfSharp.Drawing.Pdf.XGraphicsPdfRenderer(form, this); _pageSize = form.Size; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } Initialize(); #endif #if WPF && !GDI @@ -854,7 +854,7 @@ void Initialize() { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (_gfx != null) matrix = _gfx.Transform; @@ -882,7 +882,7 @@ void Initialize() _gfx.Transform = (GdiMatrix)matrix; } } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } #endif #if WPF @@ -953,14 +953,14 @@ void Dispose(bool disposing) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); // GDI+ requires this to disassociate it from metafiles. if (_gfx != default!) _gfx.Dispose(); _gfx = null!; Metafile = null; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1115,10 +1115,10 @@ public void DrawLine(XPen pen, double x1, double y1, double x2, double y2) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawLine(pen.RealizeGdiPen(), (float)x1, (float)y1, (float)x2, (float)y2); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1170,10 +1170,10 @@ public void DrawLines(XPen pen, GdiPointF[] points) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawLines(pen.RealizeGdiPen(), points); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } _renderer?.DrawLines(pen, MakeXPointArray(points, 0, points.Length)); @@ -1199,10 +1199,10 @@ public void DrawLines(XPen pen, XPoint[] points) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawLines(pen.RealizeGdiPen(), XGraphics.MakePointFArray(points)); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1323,10 +1323,10 @@ public void DrawBezier(XPen pen, double x1, double y1, double x2, double y2, { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawBezier(pen.RealizeGdiPen(), (float)x1, (float)y1, (float)x2, (float)y2, (float)x3, (float)y3, (float)x4, (float)y4); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1403,10 +1403,10 @@ public void DrawBeziers(XPen pen, XPoint[] points) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawBeziers(pen.RealizeGdiPen(), MakePointFArray(points)!); // points is checked. } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1588,10 +1588,10 @@ public void DrawCurve(XPen pen, XPoint[] points, double tension) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawCurve(pen.RealizeGdiPen(), MakePointFArray(points)!, (float)tension); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1674,10 +1674,10 @@ public void DrawArc(XPen pen, double x, double y, double width, double height, d { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawArc(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1747,10 +1747,10 @@ public void DrawRectangle(XPen pen, double x, double y, double width, double hei { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawRectangle(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1816,10 +1816,10 @@ public void DrawRectangle(XBrush brush, double x, double y, double width, double { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.FillRectangle(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -1886,13 +1886,13 @@ public void DrawRectangle(XPen? pen, XBrush? brush, double x, double y, double w { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillRectangle(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height); if (pen != null) _gfx.DrawRectangle(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2016,13 +2016,13 @@ public void DrawRectangles(XPen? pen, XBrush? brush, Rectangle[] rectangles) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillRectangles(brush.RealizeGdiBrush(), rectangles); if (pen != null) _gfx.DrawRectangles(pen.RealizeGdiPen(), rectangles); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } if (_renderer != null) { @@ -2051,13 +2051,13 @@ public void DrawRectangles(XPen? pen, XBrush? brush, GdiRectF[] rectangles) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillRectangles(brush.RealizeGdiBrush(), rectangles); if (pen != null) _gfx.DrawRectangles(pen.RealizeGdiPen(), rectangles); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } if (_renderer != null) { @@ -2093,13 +2093,13 @@ public void DrawRectangles(XPen? pen, XBrush? brush, XRect[] rectangles) GdiRectF[] rects = MakeRectangleFArray(rectangles, 0, rectangles.Length); try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillRectangles(brush.RealizeGdiBrush(), rects); if (pen != null) _gfx.DrawRectangles(pen.RealizeGdiPen(), rects); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2292,12 +2292,12 @@ public void DrawRoundedRectangle(XPen? pen, XBrush? brush, double x, double y, d { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); XGraphicsPath path = new XGraphicsPath(); path.AddRoundedRectangle(x, y, width, height, ellipseWidth, ellipseHeight); DrawPath(pen, brush, path); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2362,10 +2362,10 @@ public void DrawEllipse(XPen pen, double x, double y, double width, double heigh { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawArc(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, 0, 360); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2426,10 +2426,10 @@ public void DrawEllipse(XBrush brush, double x, double y, double width, double h { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.FillEllipse(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2493,13 +2493,13 @@ public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double wid { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillEllipse(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height); if (pen != null) _gfx.DrawArc(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, 0, 360); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2588,10 +2588,10 @@ public void DrawPolygon(XPen pen, XPoint[] points) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawPolygon(pen.RealizeGdiPen(), MakePointFArray(points)!); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2656,10 +2656,10 @@ public void DrawPolygon(XBrush brush, XPoint[] points, XFillMode fillMode) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.FillPolygon(brush.RealizeGdiBrush(), MakePointFArray(points), (FillMode)fillMode); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2727,13 +2727,13 @@ public void DrawPolygon(XPen? pen, XBrush? brush, XPoint[] points, XFillMode fil GdiPointF[] pts = MakePointFArray(points)!; try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillPolygon(brush.RealizeGdiBrush(), pts, (FillMode)fillMode); if (pen != null) _gfx.DrawPolygon(pen.RealizeGdiPen(), pts); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2800,10 +2800,10 @@ public void DrawPie(XPen pen, double x, double y, double width, double height, d { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawPie(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2861,10 +2861,10 @@ public void DrawPie(XBrush brush, double x, double y, double width, double heigh { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.FillPie(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -2921,13 +2921,13 @@ public void DrawPie(XPen? pen, XBrush? brush, double x, double y, double width, { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillPie(brush.RealizeGdiBrush(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle); if (pen != null) _gfx.DrawPie(pen.RealizeGdiPen(), (float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -3287,7 +3287,7 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, XFillMode { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillClosedCurve(brush.RealizeGdiBrush(), MakePointFArray(points)!, (FillMode)fillMode, (float)tension); if (pen != null) @@ -3296,7 +3296,7 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, XFillMode _gfx.DrawClosedCurve(pen.RealizeGdiPen(), MakePointFArray(points)!, (float)tension, (FillMode)fillMode); } } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -3353,10 +3353,10 @@ public void DrawPath(XPen pen, XGraphicsPath path) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.DrawPath(pen.RealizeGdiPen(), path.GdipPath); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -3387,10 +3387,10 @@ public void DrawPath(XBrush brush, XGraphicsPath path) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.FillPath(brush.RealizeGdiBrush(), path.GdipPath); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -3424,13 +3424,13 @@ public void DrawPath(XPen? pen, XBrush? brush, XGraphicsPath path) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (brush != null) _gfx.FillPath(brush.RealizeGdiBrush(), path.GdipPath); if (pen != null) _gfx.DrawPath(pen.RealizeGdiPen(), path.GdipPath); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -3561,7 +3561,7 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdiRectF rect = layoutRectangle.ToRectangleF(); if (format.LineAlignment == XLineAlignment.BaseLine) { @@ -3578,7 +3578,7 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan _gfx.DrawString(text, font.GdiFont, brush.RealizeGdiBrush(), rect, format.RealizeGdiStringFormat()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -3943,7 +3943,7 @@ public void DrawImage(XImage image, double x, double y) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (image._gdiImage != null) { InterpolationMode interpolationMode = InterpolationMode.Invalid; @@ -3966,7 +3966,7 @@ public void DrawImage(XImage image, double x, double y) //_gfx.DrawLine(Pens.Red, (float)(x + width), (float)y, (float)x, (float)(y + height)); } } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4036,7 +4036,7 @@ public void DrawImage(XImage image, double x, double y, double width, double hei { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (image._gdiImage != null) { InterpolationMode interpolationMode = InterpolationMode.Invalid; @@ -4069,7 +4069,7 @@ public void DrawImage(XImage image, double x, double y, double width, double hei } } } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4154,7 +4154,7 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (image._gdiImage != null!) { InterpolationMode interpolationMode = InterpolationMode.Invalid; @@ -4178,7 +4178,7 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit DrawMissingImageRect(new XRect(destRect.X, destRect.Y, destRect.Width, destRect.Height)); } } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4223,7 +4223,7 @@ void DrawMissingImageRect(XRect rect) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); float x = (float)rect.X; float y = (float)rect.Y; float width = (float)rect.Width; @@ -4232,7 +4232,7 @@ void DrawMissingImageRect(XRect rect) _gfx.DrawLine(Pens.Red, x, y, x + width, y + height); _gfx.DrawLine(Pens.Red, x + width, y, x, y + height); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4358,13 +4358,13 @@ public XGraphicsState Save() { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); xState = new XGraphicsState(_gfx != null! ? _gfx.Save() : null); InternalGraphicsState iState = new InternalGraphicsState(this, xState); iState.Transform = _transform; _gsStack.Push(iState); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4402,13 +4402,13 @@ public void Restore(XGraphicsState state) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gsStack.Restore(state.InternalState); if (_gfx != null!) _gfx.Restore(state.GdiState!); // BUG_OLD NRT _transform = state.InternalState.Transform; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4494,13 +4494,13 @@ public XGraphicsContainer BeginContainer(XRect dstRect, XRect srcRect, XGraphics { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GraphicsState? graphicsState = null; if (_gfx != null!) graphicsState = _gfx.Save(); xContainer = new XGraphicsContainer(graphicsState); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4549,11 +4549,11 @@ public void EndContainer(XGraphicsContainer container) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); // GdiState cannot be null if _gfx is not null. _gfx.Restore(container.GdiState!); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4596,10 +4596,10 @@ public XSmoothingMode SmoothingMode { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return (XSmoothingMode)_gfx.SmoothingMode; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4619,10 +4619,10 @@ public XSmoothingMode SmoothingMode { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.SmoothingMode = (SmoothingMode)value; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -4870,10 +4870,10 @@ void AddTransform(XMatrix transform, XMatrixOrder order) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.Transform = (GdiMatrix)matrix; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } } #endif @@ -4961,10 +4961,10 @@ public void IntersectClip(XGraphicsPath path) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.SetClip(path.GdipPath, CombineMode.Intersect); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF && !GDI @@ -4976,10 +4976,10 @@ public void IntersectClip(XGraphicsPath path) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gfx.SetClip(path._gdipPath, CombineMode.Intersect); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } else { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs index 1d28e5f7..6f566d09 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs @@ -39,10 +39,10 @@ public XGraphicsPath() #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath = new GraphicsPath(); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry = new PathGeometry(); @@ -58,10 +58,10 @@ public XGraphicsPath(PointF[] points, byte[] types, XFillMode fillMode) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath = new GraphicsPath(points, types, (FillMode)fillMode); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF // Is true only in Hybrid build. _pathGeometry = new PathGeometry(); @@ -143,10 +143,10 @@ public XGraphicsPath Clone() #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); path.GdipPath = (GraphicsPath)GdipPath.Clone(); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI path.PathGeometry = PathGeometry.Clone(); @@ -224,10 +224,10 @@ public void AddLine(double x1, double y1, double x2, double y2) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddLine((float)x1, (float)y1, (float)x2, (float)y2); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF PathFigure figure = CurrentPathFigure; @@ -307,10 +307,10 @@ public void AddLines(XPoint[] points) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddLines(XGraphics.MakePointFArray(points)); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF PathFigure figure = CurrentPathFigure; @@ -397,10 +397,10 @@ public void AddBezier(double x1, double y1, double x2, double y2, double x3, dou #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddBezier((float)x1, (float)y1, (float)x2, (float)y2, (float)x3, (float)y3, (float)x4, (float)y4); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF PathFigure figure = CurrentPathFigure; @@ -495,10 +495,10 @@ public void AddBeziers(XPoint[] points) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddBeziers(XGraphics.MakePointFArray(points)); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF PathFigure figure = CurrentPathFigure; @@ -621,10 +621,10 @@ public void AddCurve(XPoint[] points, double tension) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddCurve(XGraphics.MakePointFArray(points), (float)tension); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF tension /= 3; @@ -699,10 +699,10 @@ public void AddCurve(XPoint[] points, int offset, int numberOfSegments, double t #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddCurve(XGraphics.MakePointFArray(points), offset, numberOfSegments, (float)tension); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF throw new NotImplementedException("AddCurve not yet implemented."); @@ -750,10 +750,10 @@ public void AddArc(double x, double y, double width, double height, double start #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddArc((float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF PathFigure figure = CurrentPathFigure; @@ -866,7 +866,7 @@ public void AddRectangle(XRect rect) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); // If rect is empty GDI+ removes the rect from the path. // This is not intended if the path is used for clipping. // See http://forum.pdfsharp.net/viewtopic.php?p=9433#p9433 @@ -877,7 +877,7 @@ public void AddRectangle(XRect rect) GdipPath.AddLines([rect.TopLeft.ToPointF(), rect.TopRight.ToPointF(), rect.BottomRight.ToPointF(), rect.BottomLeft.ToPointF()]); GdipPath.CloseFigure(); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF StartFigure(); @@ -928,10 +928,10 @@ public void AddRectangles(Rectangle[] rects) try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddRectangles(rects); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif @@ -947,10 +947,10 @@ public void AddRectangles(RectangleF[] rects) try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddRectangles(rects); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif @@ -968,10 +968,10 @@ public void AddRectangles(XRect[] rects) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddRectangle(rects[idx].ToRectangleF()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF StartFigure(); @@ -1095,7 +1095,7 @@ public void AddRoundedRectangle(double x, double y, double width, double height, #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.StartFigure(); GdipPath.AddArc((float)(x + width - ellipseWidth), (float)y, (float)ellipseWidth, (float)ellipseHeight, -90, 90); GdipPath.AddArc((float)(x + width - ellipseWidth), (float)(y + height - ellipseHeight), (float)ellipseWidth, (float)ellipseHeight, 0, 90); @@ -1103,7 +1103,7 @@ public void AddRoundedRectangle(double x, double y, double width, double height, GdipPath.AddArc((float)x, (float)y, (float)ellipseWidth, (float)ellipseHeight, 180, 90); GdipPath.CloseFigure(); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI double ex = ellipseWidth / 2; @@ -1253,10 +1253,10 @@ public void AddEllipse(double x, double y, double width, double height) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddEllipse((float)x, (float)y, (float)width, (float)height); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI #if true @@ -1300,10 +1300,10 @@ public void AddPolygon(System.Drawing.Point[] points) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddPolygon(points); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif @@ -1331,10 +1331,10 @@ public void AddPolygon(PointF[] points) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddPolygon(points); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif @@ -1357,10 +1357,10 @@ public void AddPolygon(XPoint[] points) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddPolygon(XGraphics.MakePointFArray(points)); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI #if true @@ -1394,10 +1394,10 @@ public void AddPie(Rectangle rect, double startAngle, double sweepAngle) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddPie(rect, (float)startAngle, (float)sweepAngle); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif @@ -1431,10 +1431,10 @@ public void AddPie(double x, double y, double width, double height, double start #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddPie((float)x, (float)y, (float)width, (float)height, (float)startAngle, (float)sweepAngle); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI const string message = "AddPie: This operation is not yet implemented in WPF build."; @@ -1532,10 +1532,10 @@ public void AddClosedCurve(XPoint[] points, double tension) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddClosedCurve(XGraphics.MakePointFArray(points), (float)tension); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI tension /= 3; @@ -1573,10 +1573,10 @@ public void AddPath(XGraphicsPath path, bool connect) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddPath(path.GdipPath, connect); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry.AddGeometry(path.PathGeometry); @@ -1646,10 +1646,10 @@ public void AddString(string s, XFontFamily family, XFontStyleEx style, double e try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddString(s, family.GdiFamily, (int)style, (float)emSize, p, format.RealizeGdiStringFormat()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF if (family.WpfFamily == null) @@ -1726,10 +1726,10 @@ public void AddString(string s, XFontFamily family, XFontStyleEx style, double e try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddString(s, family.GdiFamily, (int)style, (float)emSize, rect, format.RealizeGdiStringFormat()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } /// @@ -1745,10 +1745,10 @@ public void AddString(string s, XFontFamily family, XFontStyleEx style, double e try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddString(s, family.GdiFamily, (int)style, (float)emSize, layoutRect, format.RealizeGdiStringFormat()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } /// @@ -1828,10 +1828,10 @@ public void AddString(string s, XFontFamily family, XFontStyleEx style, double e try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.AddString(s, family.GdiFamily, (int)style, (float)emSize, rect, format.RealizeGdiStringFormat()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF && !GDI if (family.WpfFamily == null) @@ -1972,10 +1972,10 @@ public void CloseFigure() #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.CloseFigure(); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathFigure figure = PeekCurrentFigure; @@ -1995,10 +1995,10 @@ public void StartFigure() #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.StartFigure(); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathFigure figure = CurrentPathFigure; @@ -2027,10 +2027,10 @@ public XFillMode FillMode #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.FillMode = (FillMode)value; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry.FillRule = value == XFillMode.Winding ? FillRule.Nonzero : FillRule.EvenOdd; @@ -2054,10 +2054,10 @@ public void Flatten() #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.Flatten(); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry = PathGeometry.GetFlattenedPathGeometry(); @@ -2076,10 +2076,10 @@ public void Flatten(XMatrix matrix) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.Flatten(matrix.ToGdiMatrix()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry = PathGeometry.GetFlattenedPathGeometry(); @@ -2099,10 +2099,10 @@ public void Flatten(XMatrix matrix, double flatness) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.Flatten(matrix.ToGdiMatrix(), (float)flatness); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry = PathGeometry.GetFlattenedPathGeometry(); @@ -2127,10 +2127,10 @@ public void Widen(XPen pen) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.Widen(pen.RealizeGdiPen()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry = PathGeometry.GetWidenedPathGeometry(pen.RealizeWpfPen()); @@ -2152,10 +2152,10 @@ public void Widen(XPen pen, XMatrix matrix) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.Widen(pen.RealizeGdiPen(), matrix.ToGdiMatrix()); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry = PathGeometry.GetWidenedPathGeometry(pen.RealizeWpfPen()); @@ -2174,10 +2174,10 @@ public void Widen(XPen pen, XMatrix matrix, double flatness) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); GdipPath.Widen(pen.RealizeGdiPen(), matrix.ToGdiMatrix(), (float)flatness); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF || WUI PathGeometry = PathGeometry.GetWidenedPathGeometry(pen.RealizeWpfPen()); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs index ea2ddbdd..48146f31 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs @@ -163,10 +163,10 @@ public static BitmapImage BitmapFromUri(Uri uri) #if GDI try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gdiImage = Image.FromFile(path); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF _wpfImage = BitmapFromUri(new Uri(path)); @@ -195,10 +195,10 @@ public static BitmapImage BitmapFromUri(Uri uri) // Create a GDI+ image. try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gdiImage = Image.FromStream(stream); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if WPF // Create a WPF BitmapImage. @@ -487,10 +487,10 @@ internal void Initialize() string guid; try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); guid = _gdiImage.RawFormat.Guid.ToString("B").ToUpper(); } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } switch (guid) { @@ -804,11 +804,11 @@ protected virtual void Dispose(bool disposing) { try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); _gdiImage.Dispose(); _gdiImage = null!; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } } #endif #if WPF @@ -836,10 +836,10 @@ public virtual double Width #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.Width; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF double gdiWidth = _gdiImage.Width; @@ -878,10 +878,10 @@ public virtual double Height #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.Height; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF double gdiHeight = _gdiImage.Height; @@ -939,10 +939,10 @@ public virtual double PointWidth #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.Width * 72 / _gdiImage.HorizontalResolution; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF double gdiWidth = _gdiImage.Width * 72 / _gdiImage.HorizontalResolution; @@ -990,10 +990,10 @@ public virtual double PointHeight #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.Height * 72 / _gdiImage.HorizontalResolution; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF double gdiHeight = _gdiImage.Height * 72 / _gdiImage.HorizontalResolution; @@ -1031,10 +1031,10 @@ public virtual int PixelWidth #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.Width; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF int gdiWidth = _gdiImage.Width; @@ -1071,10 +1071,10 @@ public virtual int PixelHeight #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.Height; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF int gdiHeight = _gdiImage.Height; @@ -1124,10 +1124,10 @@ public virtual double HorizontalResolution #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.HorizontalResolution; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF double gdiResolution = _gdiImage.HorizontalResolution; @@ -1172,10 +1172,10 @@ public virtual double VerticalResolution #if GDI && !WPF try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); return _gdiImage.VerticalResolution; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } #endif #if GDI && WPF double gdiResolution = _gdiImage.VerticalResolution; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs index ef43ab38..2adbb397 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs @@ -238,7 +238,7 @@ internal override System.Drawing.Brush RealizeGdiBrush() GdiLinearGradientBrush brush; try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (_useRect) { brush = new GdiLinearGradientBrush(_rect.ToRectangleF(), @@ -254,7 +254,7 @@ internal override System.Drawing.Brush RealizeGdiBrush() brush.Transform = _matrix.ToGdiMatrix(); //brush.WrapMode = WrapMode.Clamp; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } return brush; } #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs index fd950c21..db29ce1e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs @@ -241,7 +241,7 @@ public static implicit operator XPen(Pen pen) XPen xpen; try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); xpen = pen.PenType switch { PenType.SolidColor => new(pen.Color, pen.Width) @@ -263,7 +263,7 @@ public static implicit operator XPen(Pen pen) xpen._dashOffset = pen.DashOffset; } } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } return xpen; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRadialGradientBrush.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRadialGradientBrush.cs index dcbde7ea..a7ff5c0a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRadialGradientBrush.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRadialGradientBrush.cs @@ -198,7 +198,7 @@ internal override System.Drawing.Brush RealizeGdiBrush() GdiLinearGradientBrush brush; try { - Lock.EnterGdiPlus(); + Locks.EnterGdiPlus(); if (_useRect) { brush = new GdiLinearGradientBrush(_rect.ToRectangleF(), @@ -214,7 +214,7 @@ internal override System.Drawing.Brush RealizeGdiBrush() brush.Transform = _matrix.ToGdiMatrix(); //brush.WrapMode = WrapMode.Clamp; } - finally { Lock.ExitGdiPlus(); } + finally { Locks.ExitGdiPlus(); } return brush; #else return null!; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs index a161de9d..48d99ecc 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs @@ -100,11 +100,11 @@ public void CompleteGlyphClosure(Dictionary glyphs) // see https://forum.pdfsharp.net/viewtopic.php?f=2&t=2248#p10378 try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); for (int idx = 0; idx < count; idx++) AddCompositeGlyphs(glyphs, glyphArray[idx]); } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } /// 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 06265755..13e04a2c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs @@ -17,22 +17,22 @@ public static bool TryGetGlyphTypeface(string key, [MaybeNullWhen(false)] out XG { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); bool result = Globals.Global.Fonts.GlyphTypefacesByKey.TryGetValue(key, out glyphTypeface); return result; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } public static void AddGlyphTypeface(XGlyphTypeface glyphTypeface) { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); Debug.Assert(!Globals.Global.Fonts.GlyphTypefacesByKey.ContainsKey(glyphTypeface.Key)); Globals.Global.Fonts.GlyphTypefacesByKey.Add(glyphTypeface.Key, glyphTypeface); } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } internal static void Reset() 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 91c809e6..99ad3332 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs @@ -23,11 +23,11 @@ public static bool TryGetFontFace(string key, { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); var result = Globals.Global.Fonts.FontFaceCache.TryGetValue(key, out fontFace); return result; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } /// @@ -39,18 +39,18 @@ public static bool TryGetFontFace(ulong checkSum, { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); var result = Globals.Global.Fonts.FontFacesByCheckSum.TryGetValue(checkSum, out fontFace); return result; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } public static OpenTypeFontFace AddFontFace(OpenTypeFontFace fontFace) { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (TryGetFontFace(fontFace.FullFaceName, out var fontFaceCheck)) { if (fontFaceCheck.CheckSum != fontFace.CheckSum) @@ -61,7 +61,7 @@ public static OpenTypeFontFace AddFontFace(OpenTypeFontFace fontFace) Globals.Global.Fonts.FontFacesByCheckSum.Add(fontFace.CheckSum, fontFace); return fontFace; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } internal static void Reset() diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs index 9cbe0263..19007492 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs @@ -28,7 +28,7 @@ public static FontDescriptor GetOrCreateDescriptorFor(XFont font) try { var cache = Globals.Global.Fonts.FontDescriptorCache; - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) return descriptor; @@ -36,7 +36,7 @@ public static FontDescriptor GetOrCreateDescriptorFor(XFont font) cache.Add(fontDescriptorKey, descriptor); return descriptor; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypeface) @@ -47,7 +47,7 @@ public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypefa try { var cache = Globals.Global.Fonts.FontDescriptorCache; - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) return descriptor; @@ -55,7 +55,7 @@ public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypefa cache.Add(fontDescriptorKey, descriptor); return descriptor; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } /// @@ -72,7 +72,7 @@ public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontS try { var cache = Globals.Global.Fonts.FontDescriptorCache; - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (!cache.TryGetValue(fontDescriptorKey, out var descriptor)) { var font = new XFont(fontFamilyName, 10, style); @@ -85,7 +85,7 @@ public static FontDescriptor GetOrCreateDescriptor(string fontFamilyName, XFontS } return descriptor; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } internal static void Reset() diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs index f03d31bc..78257168 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs @@ -52,7 +52,7 @@ static class FontFactory var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); // Was this typeface requested before? if (fontResolverInfosByName.TryGetValue(typefaceKey, out var fontResolverInfo)) return fontResolverInfo; @@ -111,7 +111,7 @@ static class FontFactory finally { _fallbackFontResolverInvoked = false; - Lock.ExitFontFactory(); + Locks.ExitFontFactory(); } } static bool _fallbackFontResolverInvoked; @@ -141,7 +141,7 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); // OverrideStyleSimulations is true only for internal quality tests. // With this code we can simulate bold and/or italic for a font face even if @@ -217,7 +217,7 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f } } } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } #if GDI /// @@ -231,7 +231,7 @@ public static XFontSource RegisterFontFace_unused(byte[] fontBytes) var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; var fontSourcesByKey = Globals.Global.Fonts.FontSourcesByKey; - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); ulong key = FontHelper.CalcChecksum(fontBytes); if (fontSourcesByKey.TryGetValue(key, out var fontSource)) { @@ -250,7 +250,7 @@ public static XFontSource RegisterFontFace_unused(byte[] fontBytes) GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); return fontSource; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } #endif @@ -329,7 +329,7 @@ public static XFontSource CacheFontSource(XFontSource fontSource) { try { - Lock.EnterFontFactory(); + 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)) { @@ -366,7 +366,7 @@ public static XFontSource CacheFontSource(XFontSource fontSource) Globals.Global.Fonts.FontSourcesByName.Add(fontSource.FontName, fontSource); return fontSource; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } /// @@ -420,15 +420,15 @@ public static void CacheExistingFontSourceWithNewTypefaceKey(string typefaceKey, { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); Globals.Global.Fonts.FontSourcesByName.Add(typefaceKey, fontSource); } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } public static void CheckInvocationOfPlatformFontResolver() { - if (!Lock.IsFontFactoryLookTaken()) + if (!Locks.IsFontFactoryLookTaken()) throw new InvalidOperationException("You must not call PlatformFontResolver.ResolveTypeface if you are not calling from within a font resolver."); if (_fallbackFontResolverInvoked) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs index cf907943..de77d6a5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs @@ -24,11 +24,11 @@ static class FontFamilyCache { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); Globals.Global.Fonts.FontFamiliesByName.TryGetValue(familyName, out var family); return family; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } /// @@ -38,7 +38,7 @@ public static FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFam { try { - Lock.EnterFontFactory(); + 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)) { @@ -51,7 +51,7 @@ public static FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFam Globals.Global.Fonts.FontFamiliesByName.Add(fontFamily.Name, fontFamily); return fontFamily; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } internal static void Reset() diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs index 7f0fb8f1..821a202c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs @@ -93,7 +93,7 @@ internal static FontFamilyInternal GetOrCreateFromName(string familyName, bool c { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); var family = FontFamilyCache.GetFamilyByName(familyName); if (family == null) { @@ -102,7 +102,7 @@ internal static FontFamilyInternal GetOrCreateFromName(string familyName, bool c } return family; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } #if GDI @@ -110,12 +110,12 @@ internal static FontFamilyInternal GetOrCreateFromGdi(GdiFontFamily gdiFontFamil { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); FontFamilyInternal fontFamily = new FontFamilyInternal(gdiFontFamily); fontFamily = FontFamilyCache.CacheOrGetFontFamily(fontFamily); return fontFamily; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs index 987e7bd5..36a2f5a8 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs @@ -39,10 +39,10 @@ public static IFontResolver? FontResolver ref var fontResolver = ref Globals.Global.Fonts.FontResolver; try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); SetFontResolver(value, ref fontResolver); } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } } @@ -64,10 +64,10 @@ public static IFontResolver? FallbackFontResolver ref var fontResolver = ref Globals.Global.Fonts.FallbackFontResolver; try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); SetFontResolver(value, ref fontResolver); } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } } @@ -170,7 +170,7 @@ public static PdfFontEncoding DefaultFontEncoding { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (Globals.Global.Fonts.FontEncodingInitialized) { // Ignore multiple setting e.g. in a web application. @@ -182,7 +182,7 @@ public static PdfFontEncoding DefaultFontEncoding Globals.Global.Fonts.FontEncoding = value; Globals.Global.Fonts.FontEncodingInitialized = true; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } } @@ -206,7 +206,7 @@ public static bool UseWindowsFontsUnderWindows { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (Globals.Global.Fonts.UseWindowsFontsUnderWindows.HasValue) { // Ignore multiple setting e.g. in a web application. @@ -217,7 +217,7 @@ public static bool UseWindowsFontsUnderWindows Globals.Global.Fonts.UseWindowsFontsUnderWindows = value; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } } @@ -240,7 +240,7 @@ public static bool UseWindowsFontsUnderWsl2 { try { - Lock.EnterFontFactory(); + Locks.EnterFontFactory(); if (Globals.Global.Fonts.UseWindowsFontsUnderWsl2.HasValue) { // Ignore multiple setting e.g. in a web application. @@ -251,7 +251,7 @@ public static bool UseWindowsFontsUnderWsl2 Globals.Global.Fonts.UseWindowsFontsUnderWsl2 = value; } - finally { Lock.ExitFontFactory(); } + finally { Locks.ExitFontFactory(); } } } #elif GDI || WPF diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs index e8d9f177..1763d63a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs @@ -25,7 +25,7 @@ public static BigInteger CreateBigInteger(ReadOnlySpan value, bool isUnsig // Convert to little endian, which is expected by BigInteger constructor. if (isBigEndian) - bytes = bytes.Reverse().ToArray(); + bytes = ((IEnumerable)bytes).Reverse().ToArray(); // A leading bit of 1 defines a negative number. If the input should be interpreted as unsigned, prepend a new zero byte, if there’s a leading 1. // As bytes is in little endian order, check the most significant bit of the last byte. If it is 1, append the zero byte. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Locks.cs similarity index 98% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Internal/Locks.cs index 8964d121..56f94938 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Locks.cs @@ -10,7 +10,7 @@ namespace PdfSharp.Internal /// Static locking functions to make PDFsharp thread save. /// POSSIBLE BUG_OLD: Having more than one lock can lead to a deadlock. /// - static class Lock + static class Locks { public static void EnterGdiPlus() { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs index 9b403097..34fa0b8d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs @@ -17,7 +17,7 @@ public PdfEmbeddedFileStream(PdfDocument document, Stream stream) : base(documen _data = new byte[stream.Length]; using (stream) { - stream.Read(_data, 0, (int)stream.Length); + _ = stream.Read(_data, 0, (int)stream.Length); } Initialize(); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs index a12381ac..535bbb3d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs @@ -258,7 +258,7 @@ void InitializeJpeg(PdfDocumentOptions options) else { // If we have a stream, copy data from the stream. - if (_image._stream != null && _image._stream.CanSeek) + if (_image._stream != null! && _image._stream.CanSeek) { memory = new MemoryStream(); ownMemory = true; @@ -273,18 +273,20 @@ void InitializeJpeg(PdfDocumentOptions options) } else { - // TODO_OLD Anything we can do here? - //#if CORE _WITH _GDI - // // No stream, no filename, get image data. - // // Save the image to a memory stream. - // _image._gdiImage.Save(memory, ImageFormat.Jpeg); - //#endif +#if GDI + // No stream, no filename, get image data from GDI image. + // Save the image to a memory stream. + memory = new MemoryStream(); + ownMemory = true; + _image._gdiImage.Save(memory, ImageFormat.Jpeg); +#endif } } if (memory is null || (int)memory.Length == 0) { Debug.Assert(false, "Internal error? JPEG image, but file not found!"); + throw new InvalidOperationException("JPEG image used, but cannot access image data!"); } #if GDI @@ -532,9 +534,9 @@ void InitializeNonJpeg(PdfDocumentOptions options) break; default: -//#if DEBUGxxx -// image.image.Save("$$$.bmp", ImageFormat.Bmp); -//#endif + //#if DEBUGxxx + // image.image.Save("$$$.bmp", ImageFormat.Bmp); + //#endif throw new NotImplementedException("Image format not supported."); } #endif @@ -842,7 +844,7 @@ void CreateTrueColorMemoryBitmap(int components, int bits, bool hasAlpha, PdfDoc // Anything needed for CMYK? Do we have sample images? Elements[Keys.ColorSpace] = new PdfName(components == 1 ? "/DeviceGray" : "/DeviceRGB"); if (_image.Interpolate) - { + { // #PDF-A if (_document.IsPdfA) { @@ -1059,7 +1061,7 @@ void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha) // Anything needed for CMYK? Do we have sample images? Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); if (_image.Interpolate) - { + { // #PDF-A if (_document.IsPdfA) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs index a932d743..ba975027 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs @@ -50,7 +50,7 @@ public PdfObjectID(int objectNumber, int generationNumber = 0) { // We do not break existing code. // We found an iText document with generation numbers with a value of 65536... - PdfSharpLogHost.PdfReadingLogger.LogError("Generation number '{GenerationNumber}' is out of range [0..65535}.", generationNumber); + PdfSharpLogHost.PdfReadingLogger.LogError("Generation number '{GenerationNumber}' is out of range [0..65535].", generationNumber); // No high-performance logging because it is a rare case. } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj b/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj index 6fb9e5af..dd12565d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj @@ -1,7 +1,7 @@  library - net6.0;net8.0;netstandard2.0 + net8.0;net9.0;net10.0;netstandard2.0 PdfSharp CORE diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests-gdi/PdfSharp.Tests-gdi.csproj b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests-gdi/PdfSharp.Tests-gdi.csproj index a6b39f99..52fc24c9 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests-gdi/PdfSharp.Tests-gdi.csproj +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests-gdi/PdfSharp.Tests-gdi.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 true true GDI diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/images/ImageTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/images/ImageTests.cs index a1c25ec8..8988fbd0 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/images/ImageTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/images/ImageTests.cs @@ -71,7 +71,7 @@ public void PDF_with_Images() var font = new XFont("Arial", 20, XFontStyleEx.BoldItalic); // Draw the text. - gfx.DrawString("Hello, dotnet 6.0!", font, XBrushes.Black, + gfx.DrawString("Hello, World!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); //var imagePath = "PDFsharp/images/samples/jpeg/windows7problem.jpg"; // OK @@ -164,7 +164,7 @@ public void WriteAndRead_PDF_with_FlateDecode() var font = new XFont("Arial", 20, XFontStyleEx.BoldItalic); // Draw the text. - gfx.DrawString("Hello, dotnet 6.0!", font, XBrushes.Black, + gfx.DrawString("Hello, World!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); var imagePaths = new[] @@ -344,6 +344,47 @@ public void PDF_with_Image_from_public_memorystream() GC.WaitForFullGCComplete(); } +#if GDI + [Fact] + public void PDF_with_image_from_GDI() + { + // Create a new PDF document. + var document = new PdfDocument(); + +#if DEBUG + // Create PDF files that are somewhat human-readable. + document.Options.Layout = PdfWriterLayout.Verbose; +#endif + + // Create an empty page in this document. + var page = document.AddPage(); + + // Get an XGraphics object for drawing on this page. + var gfx = XGraphics.FromPdfPage(page); + + var imageFolder = IOUtility.GetAssetsPath("pdfsharp/images/samples/jpeg"); + var imageFile = Path.Combine(imageFolder ?? throw new InvalidOperationException("Call Download-Assets.ps1 before running the tests."), "truecolorA.jpg"); + + var gdiImage = Image.FromFile(imageFile); + var xImage = XImage.FromGdiPlusImage(gdiImage); + gfx.DrawImage(xImage, new RectangleF(0f, 0f, 128f, 128f)); + + imageFolder = IOUtility.GetAssetsPath("pdfsharp/images/samples/png"); + imageFile = Path.Combine(imageFolder ?? throw new InvalidOperationException("Call Download-Assets.ps1 before running the tests."), "truecolorA.png"); + + gdiImage = Image.FromFile(imageFile); + xImage = XImage.FromGdiPlusImage(gdiImage); + gfx.DrawImage(xImage, new RectangleF(0f, 144f, 128f, 128f)); + + // Save the document... + var filename = PdfFileUtility.GetTempPdfFileName("ImageFromStream"); + document.Save(filename); + // ...and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(filename); + } + +#endif + #if NET6_0_OR_GREATER [Fact] public async Task PDF_with_Image_from_http_stream() diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/BasicTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/BasicTests.cs index 41d2f455..e0b410af 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/BasicTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/BasicTests.cs @@ -58,8 +58,13 @@ public void Create_Hello_World_BasicTests() var font = new XFont("Times New Roman", 20, XFontStyleEx.BoldItalic); // Draw the text. - gfx.DrawString("Hello, dotnet 6.0!", font, XBrushes.Black, +#if NET6_0_OR_GREATER + gfx.DrawString($"Hello, dotnet {Environment.Version.Major}.{Environment.Version.Minor}!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); +#else + gfx.DrawString("Hello, World!", font, XBrushes.Black, + new XRect(0, 0, width, height), XStringFormats.Center); +#endif // Save the document... string filename = PdfFileUtility.GetTempPdfFileName("HelloWorld"); @@ -153,9 +158,13 @@ public void Create_CropBox_BasicTests() // Create a font. var font = new XFont("Times New Roman", 10, XFontStyleEx.BoldItalic); - // Draw the text. - gfx.DrawString("Hello, dotnet 6.0!", font, XBrushes.Black, +#if NET6_0_OR_GREATER + gfx.DrawString($"Hello, dotnet {Environment.Version.Major}.{Environment.Version.Minor}!", font, XBrushes.Black, + new XRect(0, 0, width, height), XStringFormats.Center); +#else + gfx.DrawString("Hello, World!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); +#endif // Save the document... string filename = PdfFileUtility.GetTempPdfFileName("BasicMediaBoxTest"); @@ -362,9 +371,13 @@ public void Create_all_boxes_BasicTests() // Create a font. var font = new XFont("Times New Roman", 10, XFontStyleEx.BoldItalic); - // Draw the text. - gfx.DrawString("Hello, dotnet 6.0!", font, XBrushes.Black, +#if NET6_0_OR_GREATER + gfx.DrawString($"Hello, dotnet {Environment.Version.Major}.{Environment.Version.Minor}!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); +#else + gfx.DrawString("Hello, World!", font, XBrushes.Black, + new XRect(0, 0, width, height), XStringFormats.Center); +#endif // Save the document... string filename = PdfFileUtility.GetTempPdfFileName("BasicAllBoxesTest"); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/BouncyCastleSignerTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/BouncyCastleSignerTests.cs index 930e0583..4865d9a1 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/BouncyCastleSignerTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/BouncyCastleSignerTests.cs @@ -116,12 +116,24 @@ public void Sign_existing_file_Bouncy() // Do not use password literals for real certificates in source code. var certificatePassword = "Seecrit1243"; +#if NET9_0_OR_GREATER + // New API introduced with .NET 9. + // Breaking change in .NET 9: X509KeyStorageFlags.MachineKeySet is treated differently. + // We do not use X509KeyStorageFlags.MachineKeySet with .NET 9. + var certificate = X509CertificateLoader.LoadPkcs12(rawData, certificatePassword, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + + var collection = X509CertificateLoader.LoadPkcs12Collection(rawData, certificatePassword, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#else + // Old API, obsolete since .NET 9. var certificate = new X509Certificate2(rawData, certificatePassword, - X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + /*X509KeyStorageFlags.MachineKeySet |*/ X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); var collection = new X509Certificate2Collection(); - collection.Import(rawData, certificatePassword, X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + collection.Import(rawData, certificatePassword, /*X509KeyStorageFlags.MachineKeySet |*/ X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#endif return (certificate, collection); } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/DefaultSignerTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/DefaultSignerTests.cs index deb4e645..7142ad2d 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/DefaultSignerTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/DefaultSignerTests.cs @@ -391,9 +391,16 @@ static X509Certificate2 GetCertificate(string certName) // Do not use password literals for real certificates in source code. var certificatePassword = "Seecrit1243"; //@@@??? +#if NET9_0_OR_GREATER + // New API introduced with .NET 9. + var certificate = X509CertificateLoader.LoadPkcs12(rawData, certificatePassword, + X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#else + // Old API, obsolete since .NET 9. var certificate = new X509Certificate2(rawData, certificatePassword, - X509KeyStorageFlags.MachineKeySet | X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); + /*X509KeyStorageFlags.MachineKeySet |*/ X509KeyStorageFlags.PersistKeySet | X509KeyStorageFlags.Exportable); +#endif return certificate; } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj index 31dd675c..3fd160cc 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;net462 + net8.0;net9.0;net10.0;net462 CORE True diff --git a/src/foundation/src/shared/src/PdfSharp.Testing-gdi/PdfSharp.Testing-gdi.csproj b/src/foundation/src/shared/src/PdfSharp.Testing-gdi/PdfSharp.Testing-gdi.csproj index 11d52042..d3e2a67b 100644 --- a/src/foundation/src/shared/src/PdfSharp.Testing-gdi/PdfSharp.Testing-gdi.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Testing-gdi/PdfSharp.Testing-gdi.csproj @@ -1,7 +1,7 @@ - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 PdfSharp.Testing_gdi enable enable diff --git a/src/foundation/src/shared/src/PdfSharp.Testing-wpf/PdfSharp.Testing-wpf.csproj b/src/foundation/src/shared/src/PdfSharp.Testing-wpf/PdfSharp.Testing-wpf.csproj index f711666e..f255f9cd 100644 --- a/src/foundation/src/shared/src/PdfSharp.Testing-wpf/PdfSharp.Testing-wpf.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Testing-wpf/PdfSharp.Testing-wpf.csproj @@ -1,7 +1,7 @@ - net6.0-windows;net8.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows;net462 PdfSharp.Testing_wpf enable enable diff --git a/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj b/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj index 7e807ee0..5eb1aeba 100644 --- a/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;netstandard2.0 + net8.0;net9.0;net10.0;netstandard2.0 PdfSharp True diff --git a/src/foundation/src/shared/src/PdfSharp.WPFonts/PdfSharp.WPFonts.csproj b/src/foundation/src/shared/src/PdfSharp.WPFonts/PdfSharp.WPFonts.csproj index ba2d9d2f..901b466e 100644 --- a/src/foundation/src/shared/src/PdfSharp.WPFonts/PdfSharp.WPFonts.csproj +++ b/src/foundation/src/shared/src/PdfSharp.WPFonts/PdfSharp.WPFonts.csproj @@ -1,7 +1,7 @@  - net6.0;net8.0;netstandard2.0 + net8.0;net9.0;net10.0;netstandard2.0 disable True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/shared/testapps/PdfSharp.Fonts.TestApp/PdfSharp.Fonts.TestApp.csproj b/src/foundation/src/shared/testapps/PdfSharp.Fonts.TestApp/PdfSharp.Fonts.TestApp.csproj index b1c164c3..b074f6f5 100644 --- a/src/foundation/src/shared/testapps/PdfSharp.Fonts.TestApp/PdfSharp.Fonts.TestApp.csproj +++ b/src/foundation/src/shared/testapps/PdfSharp.Fonts.TestApp/PdfSharp.Fonts.TestApp.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net8.0;net462 + net8.0;net9.0;net10.0;net462 true diff --git a/src/foundation/src/shared/testapps/Shared.TestApp/Program.cs b/src/foundation/src/shared/testapps/Shared.TestApp/Program.cs index 9f16c927..4946fb8c 100644 --- a/src/foundation/src/shared/testapps/Shared.TestApp/Program.cs +++ b/src/foundation/src/shared/testapps/Shared.TestApp/Program.cs @@ -34,8 +34,6 @@ static void Main( /*string[] args*/) LogHost.Factory = loggerFactory; ILogger logger = loggerFactory.CreateLogger(); - - //LogHost.Logger.LogError("Something went wrong."); //LogHost.Logger.TestMessage(LogLevel.Critical, "blah"); diff --git a/src/foundation/src/shared/testapps/Shared.TestApp/Shared.TestApp.csproj b/src/foundation/src/shared/testapps/Shared.TestApp/Shared.TestApp.csproj index 66a41c72..aa742563 100644 --- a/src/foundation/src/shared/testapps/Shared.TestApp/Shared.TestApp.csproj +++ b/src/foundation/src/shared/testapps/Shared.TestApp/Shared.TestApp.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net8.0;net462 + net8.0;net9.0;net10.0;net462 true diff --git a/src/foundation/src/shared/tests/PdfSharp.Fonts.Test/PdfSharp.Fonts.Test.csproj b/src/foundation/src/shared/tests/PdfSharp.Fonts.Test/PdfSharp.Fonts.Test.csproj index 6023e4b8..cc6d7c22 100644 --- a/src/foundation/src/shared/tests/PdfSharp.Fonts.Test/PdfSharp.Fonts.Test.csproj +++ b/src/foundation/src/shared/tests/PdfSharp.Fonts.Test/PdfSharp.Fonts.Test.csproj @@ -1,7 +1,7 @@  - net6.0;net462 + net8.0;net462 diff --git a/src/foundation/src/shared/tests/Shared.Tests/BasicTests.cs b/src/foundation/src/shared/tests/Shared.Tests/BasicTests.cs index f8fcb85a..d580fb48 100644 --- a/src/foundation/src/shared/tests/Shared.Tests/BasicTests.cs +++ b/src/foundation/src/shared/tests/Shared.Tests/BasicTests.cs @@ -55,9 +55,13 @@ public void Create_Hello_World_BasicTests() // Create a font. var font = new XFont("Times New Roman", 20, XFontStyleEx.BoldItalic); - // Draw the text. - gfx.DrawString("Hello, dotnet 6.0!", font, XBrushes.Black, +#if NET6_0_OR_GREATER + gfx.DrawString($"Hello, dotnet {Environment.Version.Major}.{Environment.Version.Minor}!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); +#else + gfx.DrawString("Hello, World!", font, XBrushes.Black, + new XRect(0, 0, width, height), XStringFormats.Center); +#endif // Save the document... string filename = PdfFileUtility.GetTempPdfFileName("HelloWorld"); diff --git a/src/foundation/src/shared/tests/Shared.Tests/Shared.Tests.csproj b/src/foundation/src/shared/tests/Shared.Tests/Shared.Tests.csproj index 96e2c680..7df23636 100644 --- a/src/foundation/src/shared/tests/Shared.Tests/Shared.Tests.csproj +++ b/src/foundation/src/shared/tests/Shared.Tests/Shared.Tests.csproj @@ -1,24 +1,9 @@  - net6.0;net462 + net8.0;net462 True ..\..\..\..\..\StrongnameKey.snk - - - - $(DefineConstants);CORE - - - - $(DefineConstants);CORE - - - - $(DefineConstants);CORE - - - $(DefineConstants);CORE diff --git a/src/samples/src/MigraDoc/src/HelloWorld-gdi/HelloWorld,MigraDoc-gdi.csproj b/src/samples/src/MigraDoc/src/HelloWorld-gdi/HelloWorld,MigraDoc-gdi.csproj index de1c7154..cd2c3ed0 100644 --- a/src/samples/src/MigraDoc/src/HelloWorld-gdi/HelloWorld,MigraDoc-gdi.csproj +++ b/src/samples/src/MigraDoc/src/HelloWorld-gdi/HelloWorld,MigraDoc-gdi.csproj @@ -2,7 +2,7 @@ Exe - net6.0-windows;net462 + net8.0-windows;net462 true true HelloWorld_gdi diff --git a/src/samples/src/MigraDoc/src/HelloWorld-wpf/HelloWorld,MigraDoc-wpf.csproj b/src/samples/src/MigraDoc/src/HelloWorld-wpf/HelloWorld,MigraDoc-wpf.csproj index 29dc10b0..a6d0e050 100644 --- a/src/samples/src/MigraDoc/src/HelloWorld-wpf/HelloWorld,MigraDoc-wpf.csproj +++ b/src/samples/src/MigraDoc/src/HelloWorld-wpf/HelloWorld,MigraDoc-wpf.csproj @@ -2,7 +2,7 @@ Exe - net6.0-windows;net462 + net8.0-windows;net462 true true HelloWorld_wpf diff --git a/src/samples/src/MigraDoc/src/HelloWorld/HelloWorld,MigraDoc.csproj b/src/samples/src/MigraDoc/src/HelloWorld/HelloWorld,MigraDoc.csproj index c8294de1..b81b60f9 100644 --- a/src/samples/src/MigraDoc/src/HelloWorld/HelloWorld,MigraDoc.csproj +++ b/src/samples/src/MigraDoc/src/HelloWorld/HelloWorld,MigraDoc.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net462 + net8.0;net462 diff --git a/src/samples/src/PDFsharp/src/HelloWorld-gdi/HelloWorld-gdi,PDFsharp.csproj b/src/samples/src/PDFsharp/src/HelloWorld-gdi/HelloWorld-gdi,PDFsharp.csproj index 27833e39..f4954fa0 100644 --- a/src/samples/src/PDFsharp/src/HelloWorld-gdi/HelloWorld-gdi,PDFsharp.csproj +++ b/src/samples/src/PDFsharp/src/HelloWorld-gdi/HelloWorld-gdi,PDFsharp.csproj @@ -2,7 +2,7 @@ Exe - net6.0-windows;net462 + net8.0-windows;net462 true true diff --git a/src/samples/src/PDFsharp/src/HelloWorld-wpf/HelloWorld-wpf,PDFsharp.csproj b/src/samples/src/PDFsharp/src/HelloWorld-wpf/HelloWorld-wpf,PDFsharp.csproj index 7a6d3f52..13ce5bb7 100644 --- a/src/samples/src/PDFsharp/src/HelloWorld-wpf/HelloWorld-wpf,PDFsharp.csproj +++ b/src/samples/src/PDFsharp/src/HelloWorld-wpf/HelloWorld-wpf,PDFsharp.csproj @@ -2,7 +2,7 @@ Exe - net6.0-windows;net462 + net8.0-windows;net462 true true diff --git a/src/samples/src/PDFsharp/src/HelloWorld/HelloWorld,PDFsharp.csproj b/src/samples/src/PDFsharp/src/HelloWorld/HelloWorld,PDFsharp.csproj index 319ff08c..3d7e7994 100644 --- a/src/samples/src/PDFsharp/src/HelloWorld/HelloWorld,PDFsharp.csproj +++ b/src/samples/src/PDFsharp/src/HelloWorld/HelloWorld,PDFsharp.csproj @@ -2,7 +2,7 @@ Exe - net6.0;net462 + net8.0;net462 HelloWorld,PDFsharp diff --git a/src/tools/src/CopyAsLink/CopyAsLink.csproj b/src/tools/src/CopyAsLink/CopyAsLink.csproj index edf7506b..43264bf1 100644 --- a/src/tools/src/CopyAsLink/CopyAsLink.csproj +++ b/src/tools/src/CopyAsLink/CopyAsLink.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows + net8.0-windows CopyAsLink true diff --git a/src/tools/src/NRT-Tests/NRT-Tests.csproj b/src/tools/src/NRT-Tests/NRT-Tests.csproj index 047bbc8a..432f1983 100644 --- a/src/tools/src/NRT-Tests/NRT-Tests.csproj +++ b/src/tools/src/NRT-Tests/NRT-Tests.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net8.0 NRT_Tests diff --git a/src/tools/src/PdfFileViewer/PdfFileViewer.csproj b/src/tools/src/PdfFileViewer/PdfFileViewer.csproj index 87e9c84f..9f950e67 100644 --- a/src/tools/src/PdfFileViewer/PdfFileViewer.csproj +++ b/src/tools/src/PdfFileViewer/PdfFileViewer.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net8.0 true true win-x64 diff --git a/src/tools/src/PdfSharp.TestHelper-gdi/PdfSharp.TestHelper-gdi.csproj b/src/tools/src/PdfSharp.TestHelper-gdi/PdfSharp.TestHelper-gdi.csproj index 91ca14ff..3061816e 100644 --- a/src/tools/src/PdfSharp.TestHelper-gdi/PdfSharp.TestHelper-gdi.csproj +++ b/src/tools/src/PdfSharp.TestHelper-gdi/PdfSharp.TestHelper-gdi.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net462 + net8.0-windows;net462 true true GDI diff --git a/src/tools/src/PdfSharp.TestHelper-wpf/PdfSharp.TestHelper-wpf.csproj b/src/tools/src/PdfSharp.TestHelper-wpf/PdfSharp.TestHelper-wpf.csproj index 54816cbd..c7a3d959 100644 --- a/src/tools/src/PdfSharp.TestHelper-wpf/PdfSharp.TestHelper-wpf.csproj +++ b/src/tools/src/PdfSharp.TestHelper-wpf/PdfSharp.TestHelper-wpf.csproj @@ -1,7 +1,7 @@  - net6.0-windows;net462 + net8.0-windows;net462 true true WPF diff --git a/src/tools/src/PdfSharp.TestHelper/PdfSharp.TestHelper.csproj b/src/tools/src/PdfSharp.TestHelper/PdfSharp.TestHelper.csproj index b203f0da..6f5be2a8 100644 --- a/src/tools/src/PdfSharp.TestHelper/PdfSharp.TestHelper.csproj +++ b/src/tools/src/PdfSharp.TestHelper/PdfSharp.TestHelper.csproj @@ -1,7 +1,7 @@  - net6.0;netstandard2.0 + net8.0;netstandard2.0 From 9c3863a71b12067ff36e68a92c1a1f260114e2d1 Mon Sep 17 00:00:00 2001 From: PDFsharp-Team Date: Tue, 6 Jan 2026 11:51:18 +0100 Subject: [PATCH 5/7] =?UTF-8?q?=E2=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- LICENSE | 2 +- README.md | 8 ++-- docs/MigraDoc/change-log/MD-v6.2.4-log.md | 27 +++++++++++ docs/PDFsharp/change-log/PS-v6.2.4-log.md | 29 ++++++++++++ docs/change-log/gn-v6.2.4-log.md | 18 ++++++++ gitversion-6.x.yml | 2 +- gitversion.yml | 2 +- src/Directory.Build.props | 2 +- src/Directory.Packages.props | 6 ++- .../nuget/src/Directory.Build.props | 2 +- .../MigraDoc.NuGet-gdi.nuspec | 8 ++-- .../MigraDoc.NuGet-wpf.nuspec | 8 ++-- .../src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec | 8 ++-- .../PDFsharp.NuGet-gdi.nuspec | 8 ++-- .../PDFsharp.NuGet-wpf.nuspec | 8 ++-- .../src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec | 8 ++-- .../MigraDocProductVersionInformation.cs | 2 +- .../Rendering/FontHandler.cs | 21 ++++++--- .../Rendering/ImageRenderer.cs | 13 +++--- .../Fonts/PredefinedFontsTests.cs | 7 +-- .../PdfSharp.BarCodes-gdi.csproj | 2 +- .../PdfSharp.BarCodes-wpf.csproj | 2 +- .../PdfSharp.BarCodes.csproj | 2 +- .../PdfSharp.Cryptography.csproj | 2 +- .../Drawing.Internal/ImageImporterJpeg.cs | 8 +++- .../PdfSharp/Pdf.Content.Objects/CObjects.cs | 11 +++++ .../src/PdfSharp/Pdf.Filters/Filtering.cs | 3 ++ .../PdfEncryptionBase.cs | 7 ++- .../PdfEncryptionV1To4.cs | 46 ++++++++----------- .../PdfEncryptionV5.cs | 17 ++++--- .../PdfSharpProductVersionInformation.cs | 2 +- .../src/PdfSharp.Fonts/PdfSharp.Fonts.csproj | 2 +- .../PdfSharp.Shared/PdfSharp.Shared.csproj | 2 +- .../PdfSharp.System/PdfSharp.System.csproj | 2 +- .../PdfSharp.Testing/PdfSharp.Testing.csproj | 2 +- 35 files changed, 198 insertions(+), 101 deletions(-) create mode 100644 docs/MigraDoc/change-log/MD-v6.2.4-log.md create mode 100644 docs/PDFsharp/change-log/PS-v6.2.4-log.md create mode 100644 docs/change-log/gn-v6.2.4-log.md diff --git a/LICENSE b/LICENSE index 70d0bf3c..03b0a374 100644 --- a/LICENSE +++ b/LICENSE @@ -1,4 +1,4 @@ -Copyright (c) 2001-2025 empira Software GmbH, Troisdorf (Cologne Area), Germany +Copyright (c) 2001-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany http://docs.pdfsharp.net diff --git a/README.md b/README.md index 223474b3..5d069c92 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # PDFsharp & MigraDoc 6 -Version **6.2.3** -Published **2025-11-17** +Version **6.2.4** +Published **2026-01-06** This is a final version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 6 with updates for C# 12 and .NET 8, .NET 9, and .NET 10. -PDFsharp: Copyright (c) 2005-2025 empira Software GmbH, Troisdorf (Cologne Area), Germany -MigraDoc: Copyright (c) 2001-2025 empira Software GmbH, Troisdorf (Cologne Area), Germany +PDFsharp: Copyright (c) 2005-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany +MigraDoc: Copyright (c) 2001-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany Published Open Source under the [MIT License](https://docs.pdfsharp.net/LICENSE.html) For more information see [docs.pdfsharp.net](https://docs.pdfsharp.net/) diff --git a/docs/MigraDoc/change-log/MD-v6.2.4-log.md b/docs/MigraDoc/change-log/MD-v6.2.4-log.md new file mode 100644 index 00000000..c0155815 --- /dev/null +++ b/docs/MigraDoc/change-log/MD-v6.2.4-log.md @@ -0,0 +1,27 @@ +# MigraDoc 6.2.4 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`. + +## What’s new in version 6.2.4 + +### Breaking changes + +*(none)* + +### Features + +*(none)* + +### Issues + +**MigraDoc shows filename in PDF if image cannot be found** +If an image cannot be found, MigraDoc renders a placeholder for that image in PDF. +The placeholder now shows the path of the image that cannot be found. + +**Fixed MigraDoc memory leak after rendering PDF** +With MigraDoc 6.2.3, a reference to the MigraDoc Document was kept after rendering PDF. +Only a reference to the last document was kept. +So, when rendering 10 documents, 9 documents were released. +With MigraDoc 6.2.4, the MigraDoc Document is released after rendering. + +The bug fixes of PDFsharp are also useful when generating PDF files from MigraDoc. diff --git a/docs/PDFsharp/change-log/PS-v6.2.4-log.md b/docs/PDFsharp/change-log/PS-v6.2.4-log.md new file mode 100644 index 00000000..1e8b7379 --- /dev/null +++ b/docs/PDFsharp/change-log/PS-v6.2.4-log.md @@ -0,0 +1,29 @@ +# PDFsharp 6.2.4 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`. + +## What’s new in version 6.2.4 + +### Breaking changes + +*(none)* + +### Features + +**Optional /Length entry for encryption added always** +The PDF viewer of Edge fails to open some encrypted PDF files if the /Length entry is not present. GitHub #261 +We now always add the /Length entry even in cases where it is optional according to PDF specifications. + +### Issues + +**CArray written without spaces** +When editing a content stream with an array, that CArray will now be saved with spaces. GitHub #300 + +**JPEG issue resolved** +JPEG JFIF files use "byte stuffing" and 0xff bytes may be followed by 0x00 bytes that must be ignored. +PDFsharp 6.2.4 now handles this 0xff 0x00 combination where PDFsharp up to 6.2.3 caused an exception. +GitHub #304, #309, #310 + +**Indirect DecodeParms now handled correctly** +PDFsharp 6.2.3 caused an exception if DecodeParms where specified as an indirect object. GitHub #323 +This indirection is valid, but very unusual. Fixed with 6.2.4. diff --git a/docs/change-log/gn-v6.2.4-log.md b/docs/change-log/gn-v6.2.4-log.md new file mode 100644 index 00000000..df14f1f0 --- /dev/null +++ b/docs/change-log/gn-v6.2.4-log.md @@ -0,0 +1,18 @@ +# General 6.2.4 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **general** `History.md`. + +## What’s new in version 6.2.4 + +### Breaking changes + +*(none)* + +### General features + +**Dependency on Microsoft.Extensions.Logging removed** +The new version depends on "Microsoft.Extensions.Logging.Abstractions" instead. GitHub #325 + +### General issues + +The bug fixes of PDFsharp are useful when generating PDF files from PDFsharp or MigraDoc. diff --git a/gitversion-6.x.yml b/gitversion-6.x.yml index 5457154a..ce850d85 100644 --- a/gitversion-6.x.yml +++ b/gitversion-6.x.yml @@ -3,7 +3,7 @@ assembly-versioning-scheme: MajorMinorPatch # PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE # Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; # C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7625}' +assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7675}' strategies: [Mainline] assembly-informational-format: '{InformationalVersion}' branches: diff --git a/gitversion.yml b/gitversion.yml index aa790b52..caa8c9f2 100644 --- a/gitversion.yml +++ b/gitversion.yml @@ -3,7 +3,7 @@ assembly-versioning-scheme: MajorMinorPatch # PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE # Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; # C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7625}' +assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7675}' mode: Mainline assembly-informational-format: '{NuGetVersion}' branches: diff --git a/src/Directory.Build.props b/src/Directory.Build.props index ec393bfd..3aeeb07c 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -27,7 +27,7 @@ PDFsharp empira Software - © 2025 empira + © 2026 empira PDFsharp Team empira Software GmbH diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index b3272e12..91654390 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,6 +1,7 @@ + 8.0.3 8.0.1 8.0.1 @@ -8,8 +9,9 @@ - - + + + diff --git a/src/foundation/nuget/src/Directory.Build.props b/src/foundation/nuget/src/Directory.Build.props index 618588a9..6b12a5c6 100644 --- a/src/foundation/nuget/src/Directory.Build.props +++ b/src/foundation/nuget/src/Directory.Build.props @@ -43,7 +43,7 @@ $(NuspecProperties);tags=$(NuGetTags) $(NuspecProperties);NetCore_PackageVersion=$(NetCore_PackageVersion) - $(NuspecProperties);Logging_PackageVersion=$(Logging_PackageVersion) + $(NuspecProperties);Logging_Abstractions_PackageVersion=$(Logging_Abstractions_PackageVersion) $(NuspecProperties);Cryptography_PackageVersion=$(Cryptography_PackageVersion) diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec index 958b70ac..aaa3f15a 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec @@ -19,22 +19,22 @@ $title$ - + - + - + - + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec index 354e0a79..3604cad9 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec @@ -19,22 +19,22 @@ $title$ - + - + - + - + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec index 94472655..679d8cb6 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec @@ -19,22 +19,22 @@ $title$ - + - + - + - + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec index ff34b32d..1971831a 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec @@ -19,19 +19,19 @@ $title$ - + - + - + - + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec index 22a177fb..8e242996 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec @@ -19,19 +19,19 @@ $title$ - + - + - + - + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec index 8d137768..1eeb3b59 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec @@ -19,19 +19,19 @@ $title$ - + - + - + - + diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs index b0ccef5d..91d45086 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs @@ -83,7 +83,7 @@ public static class MigraDocProductVersionInformation /// /// The copyright information. /// - public const string Copyright = "Copyright © 2001-2025 empira Software GmbH."; // Also used as NuGet Copyright. + public const string Copyright = "Copyright © 2001-2026 empira Software GmbH."; // Also used as NuGet Copyright. /// /// The trademark of the product. diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs index 349fe664..986f8bd6 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs @@ -23,8 +23,11 @@ class FontHandler /// internal static XFont FontToXFont(Font font) { - if (_lastXFont != null && font == _lastFont) - return _lastXFont; + // Check if both WeakReferences are still valid and point to the font we need. + if (_lastXFont != null && _lastFont != null && + _lastFont.TryGetTarget(out var lastFont) && font == lastFont && + _lastXFont.TryGetTarget(out var lastXFont)) + return lastXFont; XFontStyleEx style = GetXStyle(font); @@ -37,13 +40,19 @@ internal static XFont FontToXFont(Font font) #if DEBUG_ CreateFontCounter++; #endif - _lastFont = font; - _lastXFont = xFont; + _lastFont = new(font); + _lastXFont = new(xFont); +#if FORCE_MEMORYLEAK + _lastFont2 = font; +#endif return xFont; } - static XFont? _lastXFont; - static Font? _lastFont; + static WeakReference? _lastXFont; + static WeakReference? _lastFont; +#if FORCE_MEMORYLEAK + static Font? _lastFont2; +#endif internal static XFontStyleEx GetXStyle(Font font) { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs index d7f70d17..7e715b29 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs @@ -86,21 +86,20 @@ internal override void Render() } catch (Exception) { - RenderFailureImage(destRect); + RenderFailureImage(destRect, formatInfo.ImagePath); } finally { - if (xImage != null) - xImage.Dispose(); + xImage?.Dispose(); } } else - RenderFailureImage(destRect); + RenderFailureImage(destRect, formatInfo.ImagePath); RenderLine(); } - void RenderFailureImage(XRect destRect) + void RenderFailureImage(XRect destRect, string? imagePath) { _gfx.DrawRectangle(XBrushes.LightGray, destRect); string failureString; @@ -113,7 +112,7 @@ void RenderFailureImage(XRect destRect) break; case ImageFailure.FileNotFound: - failureString = MdPdfMsgs.DisplayImageFileNotFound("???").Message; + failureString = MdPdfMsgs.DisplayImageFileNotFound(imagePath ?? "Unknown image").Message; break; case ImageFailure.InvalidType: @@ -326,7 +325,7 @@ XImage CreateXImage(string uri) // Same for GDI. // We have to rely on the garbage collector to properly dispose the MemoryStream. { - Stream stream = new MemoryStream(bytes); + Stream stream = new MemoryStream(bytes, 0, bytes.Length, false, true); XImage image = XImage.FromStream(stream); return image; } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs index 7ce3ca3c..29571259 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs @@ -267,8 +267,9 @@ void CheckErrorFont(PdfDocument pdfDocument, string expectedFontName) // Check font of the error message. var fontName = PdfFileHelper.GetCurrentFontName(0, pdfDocument, streamEnumerator => { - // Move to "Image '???' not found.". - streamEnumerator.Text.MoveAndGetNext(x => x.Text == "Image '???' not found.", true, out _).Should().BeTrue(); + // Move to "Image '' not found.". + // Move to "Image 'c:/not-existing-image' not found.". + streamEnumerator.Text.MoveAndGetNext(x => x.Text == "Image 'c:/not-existing-image' not found.", true, out _).Should().BeTrue(); }); fontName.ToLower().Should().Be(expectedFontName.ToLower()); } @@ -340,7 +341,7 @@ static Document CreateDocumentWithErrorMessage() var section = document.AddSection(); // Add a not existing image to force error message. - section.AddImage("not-existing-image"); + section.AddImage("c:/not-existing-image"); return document; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj index 72f72d23..7b4065d3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj @@ -40,7 +40,7 @@ - + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj index 637c5bda..acdcb75a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj @@ -39,7 +39,7 @@ - + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj index 52919fd4..e1ebb1cd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj @@ -18,7 +18,7 @@ - + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj index a92924f5..93a9cd84 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj @@ -16,7 +16,7 @@ - + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs index 76330bac..c7096aed 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs @@ -303,8 +303,12 @@ bool MoveToNextHeader(StreamReaderHelper stream) if (headerType == 0xd9) return false; - // Check for standalone markers. - if (headerType == 0x01 || headerType >= 0xd0 && headerType <= 0xd7) + // 0xff followed by 0x00 is not a valid JPEG tag. Skip this entry. + // If the value 0xFF is ever needed in a JPEG file, it must be escaped by immediately + // following it with 0x00. This is called "byte stuffing". + // Source: https://www.ccoderun.ca/programming/2017-01-31_jpeg/ + // Check for standalone markers 0x01 and 0xd0 through 0xd7. + if (headerType is 0x00 or 0x01 or >= 0xd0 and <= 0xd7) { stream.CurrentOffset += 2; return true; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs index b215cc26..068d8fc5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs @@ -102,6 +102,12 @@ protected override CObject Copy() /// The sequence. public void Add(CSequence sequence) { + if (sequence is CArray array) + { + _items.Add(array); + return; + } + int count = sequence.Count; for (int idx = 0; idx < count; idx++) _items.Add(sequence[idx]); @@ -205,7 +211,12 @@ public override string ToString() var s = new StringBuilder(); for (int idx = 0; idx < _items.Count; idx++) + { + // Add spaces except for first item. + if (s.Length > 0) + s.Append(' '); s.Append(_items[idx]); + } return s.ToString(); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs index aaf29ec9..1f48f8f8 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs @@ -276,6 +276,9 @@ public static byte[] Decode(byte[] data, PdfItem filterItem, PdfItem? decodeParm } #endif + if (decodeParms is not null) + PdfReference.Dereference(ref decodeParms); + if (filterItem is PdfName && decodeParms is null or PdfDictionary) { var filter = GetFilter(filterItem.ToString()!); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionBase.cs index c7e59ef6..0b9964e9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionBase.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionBase.cs @@ -51,7 +51,6 @@ protected bool HandleCryptographicExceptionOnDecryption() "If PDFsharp will load the file and the contents seem to be correct, the file is at least partly not encrypted as expected."); return true; } - return false; } @@ -71,9 +70,9 @@ protected bool HandleCryptographicExceptionOnDecryption() public int? RevisionValue { get; protected set; } - public int? LengthValue { get; protected set; } - - public int? ActualLength { get; protected set; } + // Due to PDF reference length has not always to be set. But the adobe powered PDF viewer extension for edge cannot open files correctly, if length is missing. + // So we always output a length value. + public int LengthValue { get; protected set; } public bool EncryptMetadata { get; protected set; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs index 4b6b1c05..937b0dc1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs @@ -21,7 +21,7 @@ public PdfEncryptionV1To4(PdfStandardSecurityHandler securityHandler) : base(sec /// public void SetEncryptionToV1() { - Initialize(1); + Initialize(1, 40); SecurityHandler.RemoveCryptFilters(); SecurityHandler._document.SetRequiredVersion(12); } @@ -44,8 +44,8 @@ public void SetEncryptionToV2(int length = 40) // ReSharper disable once InconsistentNaming public void SetEncryptionToV4UsingRC4(bool encryptMetadata = true) { - Initialize(4, null, encryptMetadata); - SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToRC4ForV4(ActualLength!.Value); + Initialize(4, 128, encryptMetadata); + SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToRC4ForV4(); SecurityHandler._document.SetRequiredVersion(15); } @@ -55,7 +55,7 @@ public void SetEncryptionToV4UsingRC4(bool encryptMetadata = true) /// The document metadata stream shall be encrypted (default: true). public void SetEncryptionToV4UsingAES(bool encryptMetadata = true) { - Initialize(4, null, encryptMetadata); + Initialize(4, 128, encryptMetadata); SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToAESForV4(); SecurityHandler._document.SetRequiredVersion(16); } @@ -67,9 +67,7 @@ public override void InitializeFromLoadedSecurityHandler() { VersionValue = SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.V); RevisionValue = SecurityHandler.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R); - LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : null; - - UpdateActualLength(); + LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : GetDefaultLength(); EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. @@ -80,7 +78,7 @@ public override void InitializeFromLoadedSecurityHandler() Debug.Assert(calculatedRevision == RevisionValue); } - void Initialize(int versionValue, int? lengthValue = null, bool encryptMetadata = true) + void Initialize(int versionValue, int lengthValue, bool encryptMetadata = true) { CheckVersionAndLength(versionValue, lengthValue); @@ -88,30 +86,28 @@ void Initialize(int versionValue, int? lengthValue = null, bool encryptMetadata RevisionValue = null; // Revision is calculated later in PrepareEncryptionForSaving(). LengthValue = lengthValue; - UpdateActualLength(); - EncryptMetadata = encryptMetadata; } - void UpdateActualLength() + int GetDefaultLength() { - ActualLength = VersionValue switch + return VersionValue switch { - 1 => 40, - 2 => LengthValue ?? 40, // The default for Length value is 40. - 4 => 128, + 1 => 40, // Always 40. + 2 => 40, // Default value. + 4 => 128, // Always 128. _ => throw TH.InvalidOperationException_InvalidVersionValueForEncryptionVersion1To4() }; } - static void CheckVersionAndLength(int? versionValue, int? lengthValue) + static void CheckVersionAndLength(int? versionValue, int lengthValue) { if (!IsVersionSupported(versionValue)) throw TH.InvalidOperationException_InvalidVersionValueForEncryptionVersion1To4(); if (versionValue == 2) // Length is only needed for V2. { - if (lengthValue is null or < 40 or > 128 || lengthValue % 8 > 0) + if (lengthValue is < 40 or > 128 || lengthValue % 8 > 0) throw TH.InvalidOperationException_InvalidKeyLengthForEncryptionVersion2(); } } @@ -135,10 +131,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne CheckVersionAndLength(VersionValue, LengthValue); SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.V, VersionValue!.Value); - if (LengthValue.HasValue) - SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.Length, LengthValue.Value); - else - SecurityHandler.Elements.Remove(PdfSecurityHandler.Keys.Length); + SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.Length, LengthValue); var permissionsValue = SecurityHandler.GetCorrectedPermissionsValue(); SecurityHandler.Elements.SetInteger(PdfStandardSecurityHandler.Keys.P, (int)permissionsValue); @@ -242,7 +235,7 @@ byte[] ComputeOwnerValue(byte[] userPad, byte[] ownerPad) if (RevisionValue >= 3) { // The encryption key length (in bytes) shall depend on the Length value (in bits). - var keyLength = ActualLength!.Value / 8; + var keyLength = LengthValue / 8; // Hash the pad 50 times for (var idx = 0; idx < 50; idx++) @@ -308,7 +301,7 @@ byte[] ComputeUserValueByEncryptionKey(byte[] documentId) userValue[idx] = 0; // Create encryption key with the specified length. - var keyLength = ActualLength!.Value / 8; + var keyLength = LengthValue / 8; Debug.Assert(keyLength == _globalEncryptionKey.Length); var encryptionKey = new Byte[keyLength]; @@ -368,7 +361,7 @@ void ComputeAndStoreEncryptionKey(byte[] documentId, byte[] paddedPassword, byte if (RevisionValue >= 3) { // The encryption and MD5 hashing key length (in bytes) shall depend on the Length value (in bits). - keyLength = ActualLength!.Value / 8; + keyLength = LengthValue / 8; #if !NET6_0_OR_GREATER // We have to call Initialize here for .NET 4.6.2. @@ -405,8 +398,7 @@ void ComputeAndStoreEncryptionKey(byte[] documentId, byte[] paddedPassword, byte public override PasswordValidity ValidatePassword(string inputPassword) { VersionValue = SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.V); - LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : null; - UpdateActualLength(); + LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : GetDefaultLength(); CheckVersionAndLength(VersionValue, LengthValue); RevisionValue = SecurityHandler.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R); @@ -447,7 +439,7 @@ bool ValidateOwnerPassword(byte[] documentId, string inputPassword, byte[] userV if (RevisionValue >= 3) { // The encryption key length (in bytes) shall depend on the Length value (in bits). - var keyLength = ActualLength!.Value / 8; + var keyLength = LengthValue / 8; // Hash the pad 50 times. for (var idx = 0; idx < 50; idx++) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs index ea695f06..f387c904 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs @@ -30,15 +30,18 @@ public void Initialize(bool encryptMetadata = true) { VersionValue = 5; // Always 5 for PdfEncryptionV5. RevisionValue = 6; // Always 6 for PdfEncryptionV5. - LengthValue = null; // Deprecated in PDF 2.0. - - ActualLength = 256; // Always 256 for PdfEncryptionV5. + LengthValue = GetDefaultLength(); // Deprecated in PDF 2.0. But the adobe powered PDF viewer extension for edge cannot open files correctly, if length is missing. EncryptMetadata = encryptMetadata; SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToAESForV5(); } + int GetDefaultLength() + { + return 256; // Always 256 for PdfEncryptionV5. + } + /// /// Initializes the PdfEncryptionV5 with the values that were saved in the security handler. /// @@ -48,7 +51,7 @@ public override void InitializeFromLoadedSecurityHandler() Initialize(encryptMetadata); } - static void CheckValues(int? versionValue, int? revisionValue, int? lengthValue) + static void CheckValues(int? versionValue, int? revisionValue, int lengthValue) { if (versionValue is not 5) throw TH.InvalidOperationException_InvalidVersionValueForEncryptionVersion5(); @@ -56,7 +59,7 @@ static void CheckValues(int? versionValue, int? revisionValue, int? lengthValue) if (revisionValue is not (5 or 6)) throw TH.InvalidOperationException_InvalidRevisionValueForEncryptionVersion5(); - if (lengthValue is not (null or 256)) + if (lengthValue is not 256) throw TH.InvalidOperationException_InvalidLengthValueForEncryptionVersion5(); } @@ -179,7 +182,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.V, VersionValue!.Value); // In PDF reference, Length is marked as deprecated in PDF 2.0, but Adobe Reader cannot read V5 encrypted files if this key is omitted. - SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.Length, ActualLength!.Value); + SecurityHandler.Elements.SetInteger(PdfSecurityHandler.Keys.Length, LengthValue); var permissionsValue = SecurityHandler.GetCorrectedPermissionsValue(); SecurityHandler.Elements.SetInteger(PdfStandardSecurityHandler.Keys.P, (int)permissionsValue); @@ -521,7 +524,7 @@ public override PasswordValidity ValidatePassword(string inputPassword) { VersionValue = SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.V); RevisionValue = SecurityHandler.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R); - LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : null; + LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : GetDefaultLength(); // Ensure properties are set to the only valid values for PdfEncryptionV5. CheckValues(VersionValue, RevisionValue, LengthValue); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs index 1d2261ac..1eb063b4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs @@ -88,7 +88,7 @@ public static class PdfSharpProductVersionInformation /// /// The copyright information. /// - public const string Copyright = "Copyright © 2005-2025 empira Software GmbH."; + public const string Copyright = "Copyright © 2005-2026 empira Software GmbH."; /// /// The trademark of the product. diff --git a/src/foundation/src/shared/src/PdfSharp.Fonts/PdfSharp.Fonts.csproj b/src/foundation/src/shared/src/PdfSharp.Fonts/PdfSharp.Fonts.csproj index a5873475..d9ce4ad0 100644 --- a/src/foundation/src/shared/src/PdfSharp.Fonts/PdfSharp.Fonts.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Fonts/PdfSharp.Fonts.csproj @@ -12,7 +12,7 @@ - + diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/PdfSharp.Shared.csproj b/src/foundation/src/shared/src/PdfSharp.Shared/PdfSharp.Shared.csproj index c8f83720..a7bd61c5 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/PdfSharp.Shared.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Shared/PdfSharp.Shared.csproj @@ -60,7 +60,7 @@ --> - + diff --git a/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj b/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj index ae208583..a280d2b1 100644 --- a/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj +++ b/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj @@ -23,7 +23,7 @@ - + diff --git a/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj b/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj index 5eb1aeba..808424f0 100644 --- a/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj @@ -17,7 +17,7 @@ --> - + From 3112c3d3d2a78cb6397062ea98023b9533dba593 Mon Sep 17 00:00:00 2001 From: PDFsharp-Team Date: Tue, 24 Mar 2026 15:38:10 +0100 Subject: [PATCH 6/7] =?UTF-8?q?=E2=80=8B?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .editorconfig | 6 +- .gitignore | 38 +- .globalconfig | 3 + PdfSharp.sln | 116 +- PdfSharp.sln.DotSettings | 6 + README.md | 15 +- dev/GetAllPDFs.ps1 | 41 + dev/README.md | 6 + dev/_update-local-nuget-packages.ps1 | 78 +- dev/build-local-nuget-packages-release.ps1 | 38 +- dev/del-bin-and-obj.ps1 | 11 +- dev/download-assets.ps1 | 10 +- dev/init-pdfsharp-repository.ps1 | 21 + dev/run-tests.ps1 | 46 +- dev/zip-PDFsharp.ps1 | 38 + docs/Chars.md | 16 - docs/MigraDoc/AboutLineSpacing.md | 33 - docs/MigraDoc/change-log/MD-v6.3.0-log.md | 19 + docs/MigraDoc/change-log/MD-v6.4.0-log.md | 20 + .../change-log/MD-v7.0.0-preview-log.md | 23 + .../design/PDF-A/PDF-A-Fundamentals.md | 7 - .../design/PDF-UA/PDF-UA-Fundamentals.md | 22 - docs/PDFsharp/change-log/PS-v6.3.0-log.md | 46 + docs/PDFsharp/change-log/PS-v6.4.0-log.md | 57 + .../change-log/PS-v7.0.0-preview-log.md | 80 + .../design/FontSeletion/FontResolver.md | 45 - docs/PDFsharp/design/Issues.md | 10 - .../design/PDF-A/PDF-A-Fundamentals.md | 47 - .../design/PdfReader/StringsAndNames.md | 27 - .../design/Signatures/CertificateCreation.md | 53 - docs/PDFsharp/docs/Documentation.md | 14 + docs/change-log/gn-v6.2.0-preview-log.md | 2 +- docs/change-log/gn-v6.3.0-log.md | 33 + docs/change-log/gn-v7.0.0-preview-log.md | 34 + docs/coding/DevNotes.md | 10 +- .../{ => WSL}/DevelopmentWithWSL.md | 7 + .../WSL/launchsettings.template.json | 11 + .../WSL/testenvironments.template.json | 17 + .../{docs-dummy.csproj => docs-public.csproj} | 2 +- docs/publishing/BeforeReleases.md | 128 - docs/publishing/BoilerplateText.md | 82 - docs/publishing/MakeNewReleaseNotes.md | 31 - gitversion-6.x.yml | 50 - gitversion.yml | 46 - src/.gitignore | 5 + src/Directory.Build.props | 125 +- src/Directory.Build.targets | 8 +- src/Directory.Packages.props | 51 +- src/Local.Build.props---template | 17 + .../nuget/src/Directory.Build.props | 5 + .../Dummy-PDFsharp.NuGet-wpf.csproj | 6 +- .../src/MigraDoc.NuGet-gdi/Description.txt | 4 + .../MigraDoc.NuGet-gdi.Fewer.nuspec | 46 + .../MigraDoc.NuGet-gdi.csproj | 5 +- .../MigraDoc.NuGet-gdi.nuspec | 10 +- .../nuget/src/MigraDoc.NuGet-gdi/README.md | 4 - .../src/MigraDoc.NuGet-gdi/ReleaseNotes.txt | 8 +- .../src/MigraDoc.NuGet-wpf/Description.txt | 4 + .../MigraDoc.NuGet-wpf.Fewer.nuspec | 46 + .../MigraDoc.NuGet-wpf.csproj | 5 +- .../MigraDoc.NuGet-wpf.nuspec | 10 +- .../nuget/src/MigraDoc.NuGet-wpf/README.md | 4 - .../src/MigraDoc.NuGet-wpf/ReleaseNotes.txt | 8 +- .../nuget/src/MigraDoc.NuGet/Description.txt | 4 + .../MigraDoc.NuGet.Fewer.nuspec | 45 + .../src/MigraDoc.NuGet/MigraDoc.NuGet.csproj | 5 +- .../src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec | 10 +- .../nuget/src/MigraDoc.NuGet/README.md | 4 - .../nuget/src/MigraDoc.NuGet/ReleaseNotes.txt | 8 +- .../src/PDFsharp.NuGet-gdi/Description.txt | 4 + .../PDFsharp.NuGet-gdi.Fewer.nuspec | 44 + .../PDFsharp.NuGet-gdi.csproj | 5 +- .../PDFsharp.NuGet-gdi.nuspec | 2 +- .../nuget/src/PDFsharp.NuGet-gdi/README.md | 4 - .../src/PDFsharp.NuGet-gdi/ReleaseNotes.txt | 8 +- .../src/PDFsharp.NuGet-wpf/Description.txt | 4 + .../PDFsharp.NuGet-wpf.Fewer.nuspec | 44 + .../PDFsharp.NuGet-wpf.csproj | 5 +- .../PDFsharp.NuGet-wpf.nuspec | 2 +- .../nuget/src/PDFsharp.NuGet-wpf/README.md | 4 - .../src/PDFsharp.NuGet-wpf/ReleaseNotes.txt | 8 +- .../nuget/src/PDFsharp.NuGet/Description.txt | 4 + .../PDFsharp.NuGet.Fewer.nuspec | 50 + .../src/PDFsharp.NuGet/PDFsharp.NuGet.csproj | 5 +- .../src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec | 2 +- .../nuget/src/PDFsharp.NuGet/README.md | 4 - .../nuget/src/PDFsharp.NuGet/ReleaseNotes.txt | 8 +- src/foundation/nuget/src/README.md | 3 + .../MigraDoc.Features.csproj | 2 +- .../features/MigraDoc.Features/Program.cs | 2 +- .../src/MigraDoc/src/Directory.Build.props | 1 + .../DocumentObjectModel.IO/DdlParser.cs | 57 +- .../DocumentObjectModel.IO/DdlScanner.cs | 20 +- .../DocumentObjectModel.IO/enums/TokenType.cs | 2 +- .../ErrorHelpers.cs | 4 +- .../LogMessages.cs | 2 +- .../DocumentObjectModel.Internals/Meta.cs | 4 +- .../MergedCellList.cs | 2 +- .../DocumentObjectModel/Borders.cs | 78 - .../DocumentObjectModel/Character.cs | 2 +- .../DocumentObjectModel/Color.cs | 4 +- .../DocumentObjectModel/Font.cs | 12 +- .../DocumentObjectModel/HeadersFooters.cs | 8 +- .../DocumentObjectModel/Hyperlink.cs | 6 +- .../DocumentObjectModel/ImageHelper.cs | 2 +- .../DocumentObjectModel/Serializer.cs | 4 +- .../DocumentObjectModel/Style.cs | 14 +- .../DocumentObjectModel/Unit.cs | 8 +- .../DocumentObjectModel/enums/SymbolName.cs | 4 +- .../MigraDoc.DocumentObjectModel.csproj | 13 +- .../MigraDocProductVersionInformation.cs | 188 +- .../MigraDoc.DocumentObjectModel/README.md | 2 +- .../MigraDoc.Rendering-gdi.csproj | 213 +- .../Rendering.Forms/DocumentPreview.cs | 19 +- .../MigraDoc.Rendering-wpf.csproj | 23 +- .../MigraDoc.Rendering.csproj | 5 +- .../Properties/GlobalDeclarations.cs | 8 +- .../Properties/GraphicsDeclarations.cs | 57 + .../Rendering.ChartMapper/ChartMapper.cs | 4 +- .../Rendering.ChartMapper/FillFormatMapper.cs | 6 + .../Rendering.ChartMapper/FontMapper.cs | 9 +- .../Rendering.ChartMapper/LineFormatMapper.cs | 9 + .../Rendering.Extensions/UnitConversions.cs | 18 +- .../TestParagraphIterator.cs | 2 +- .../Rendering.UnitTest/TestTable.cs | 2 +- .../Rendering/BordersRenderer.cs | 83 +- .../Rendering/ChartRenderer.cs | 22 +- .../Rendering/ColorHelper.cs | 12 +- .../Rendering/DocumentRenderer.cs | 23 +- .../Rendering/FieldInfos.cs | 2 +- .../Rendering/FillFormatRenderer.cs | 5 +- .../Rendering/FontHandler.cs | 70 +- .../Rendering/FormattedDocument.cs | 4 +- .../Rendering/FormattedHeaderFooter.cs | 2 +- .../Rendering/FormattedTextArea.cs | 4 +- .../Rendering/ImageRenderer.cs | 13 +- .../Rendering/LineFormatRenderer.cs | 8 + .../Rendering/MdPdfMsg.resx | 4 +- .../MigraDoc.Rendering/Rendering/MdPdfMsgs.cs | 6 +- .../Rendering/MdPdfMsgs.resx | 4 +- .../MigraDocRenderingBuildInformation.cs | 51 +- .../Rendering/ParagraphRenderer.cs | 79 +- .../Rendering/PdfDocumentRenderer.cs | 31 +- .../Rendering/ShadingRenderer.cs | 2 + .../Rendering/TableFormatInfo.cs | 2 + .../Rendering/TableRenderer.cs | 25 +- .../Rendering/TextFrameRenderer.cs | 9 + .../MigraDoc.RtfRendering-gdi.csproj | 2 +- .../MigraDoc.RtfRendering-wpf.csproj | 2 +- .../MigraDoc.RtfRendering.csproj | 2 +- .../RtfRendering/ChartRenderer.cs | 14 +- .../RtfRendering/DateFieldRenderer.cs | 2 +- .../RtfRendering/RendererBase.cs | 2 +- .../Bullets.cs | 12 +- .../Helper/DocumentObjectSnapshot.cs | 2 +- .../Images.cs | 8 +- .../MigraDoc.DocumentObjectModel.Tests.csproj | 4 +- .../ParagraphTests.cs | 82 +- .../SerializerTests.cs | 2 +- .../TableTests.cs | 24 +- .../Template.cs | 8 +- .../TextFrames.cs | 8 +- .../valuemodel/BasicTests.cs | 2 +- .../MigraDoc.GBE-Runner.csproj | 2 +- .../MigraDoc.GrammarByExample-GDI.csproj | 8 +- .../MigraDoc.GrammarByExample-WPF.csproj | 2 +- .../MigraDoc.GrammarByExample.csproj | 2 +- .../helper/DdlGbeTestBase.cs | 8 +- .../helper/TestContext.cs | 20 +- .../helper/VisualComparisonTestBase.cs | 4 +- .../MigraDoc.Tests-gdi.csproj | 8 +- .../MigraDoc.Tests-wpf.csproj | 8 +- .../tests/MigraDoc.Tests/ChartTests.cs | 8 +- .../MigraDoc.Tests/CultureAndRegionTests.cs | 26 +- .../Experimental/CreateOnRequest.cs | 16 +- .../MigraDoc.Tests/Extensions/XunitHelper.cs | 8 +- .../Fonts/PredefinedFontsTests.cs | 20 +- .../Helper/SecurityTestHelper.cs | 32 +- .../{ImageFormats.cs => ImageFormatsTests.cs} | 87 +- .../tests/MigraDoc.Tests/MemoryTests.cs | 119 + .../MigraDoc.Tests/MigraDoc.Tests.csproj | 5 +- .../tests/MigraDoc.Tests/MultipleFooters.cs | 6 +- .../tests/MigraDoc.Tests/PageSizeTests.cs | 14 +- .../tests/MigraDoc.Tests/RtfRendererTests.cs | 66 +- .../tests/MigraDoc.Tests/SecurityTests.cs | 192 +- .../tests/MigraDoc.Tests/SpaceBeforeTests.cs | 8 +- .../tests/MigraDoc.Tests/TableTests.cs | 20 +- .../MigraDoc/tests/MigraDoc.Tests/Template.cs | 8 +- .../tests/MigraDoc.Tests/TextTests.cs | 25 +- .../src/PDFsharp/docs/AboutFonts.md | 2 +- src/foundation/src/PDFsharp/docs/Notebook.md | 6 +- .../PDFsharp.Features-gdi.csproj | 3 +- .../PDFsharp.Features-wpf.csproj | 3 +- .../PDFsharp.Features.Runner-gdi.csproj | 2 +- .../PDFsharp.Features.Runner-wpf.csproj | 2 +- .../PdfSharp.Features.Runner.csproj | 2 +- .../PdfSharp.Features.Runner/Program.cs | 14 +- .../PdfSharp.Features/Drawing/lines/Lines1.cs | 1 - .../PdfSharp.Features/Drawing/paths/Paths.cs | 3 +- .../Drawing/shapes/RoundedRectangles.cs | 3 +- .../Drawing/text/AutoFontEncoding.cs | 3 +- .../Drawing/text/NotoSans.cs | 5 +- .../Drawing/text/SurrogateChars.cs | 3 +- .../Drawing/text/SymbolFonts.cs | 3 +- .../PdfSharp.Features/Font/FontResolving.cs | 3 +- .../PdfSharp.Features/Font/FontSelection.cs | 2 +- .../features/PdfSharp.Features/Font/Glyphs.cs | 2 +- .../Font/RenderInstalledFonts.cs | 3 +- .../Font/RotisWinAnsiTester.cs | 10 +- .../Font/encoding/Encodings.cs | 3 +- .../features/PdfSharp.Features/IO/Info.cs | 8 +- .../PdfSharp.Features/IO/LargePdfFiles.cs | 3 +- .../PdfSharp.Features/IO/ObjectStreams.cs | 3 +- .../Pdf/annotations/LinkAnnotations.cs | 3 +- .../PdfSharp.Features/Pdf/pdfa/PDF-A.cs | 7 +- .../PdfSharp.Features.csproj | 3 +- .../PdfSharp.Features/PdfSharpFeatures.cs | 3 +- .../src/PDFsharp/src/Directory.Build.props | 1 + .../src/PdfSharp-gdi/Forms/PagePreview.cs | 28 +- .../PdfSharp-gdi/Forms/PagePreviewCanvas.cs | 2 +- .../src/PdfSharp-gdi/Forms/enums/Zoom.cs | 2 +- .../src/PdfSharp-gdi/PdfSharp-gdi.csproj | 216 +- .../src/PdfSharp-wpf/PdfSharp-wpf.csproj | 205 +- .../PdfSharp.BarCodes-gdi.csproj | 5 +- .../PdfSharp.BarCodes-wpf.csproj | 5 +- .../Drawing.BarCodes/CodeOmr.cs | 8 +- .../Drawing.BarCodes/DataMatrixImage.cs | 10 +- .../Drawing.BarCodes/OmrData.cs | 8 +- .../PdfSharp.BarCodes.csproj | 5 +- .../PdfSharp.Charting-gdi.csproj | 2 +- .../PdfSharp.Charting-gdi.csproj - save | 300 -- .../PdfSharp.Charting-wpf.csproj | 2 +- .../Charting.Renderers/AreaChartRenderer.cs | 4 +- .../AreaPlotAreaRenderer.cs | 9 +- .../Charting.Renderers/AxisRenderer.cs | 6 +- .../Charting.Renderers/AxisTitleRenderer.cs | 5 +- .../BarClusteredLegendRenderer.cs | 24 +- .../BarClusteredPlotAreaRenderer.cs | 11 +- .../BarGridlinesRenderer.cs | 35 +- .../Charting.Renderers/BarPlotAreaRenderer.cs | 15 +- .../BarStackedPlotAreaRenderer.cs | 11 +- .../Charting.Renderers/ChartRenderer.cs | 12 +- .../Charting.Renderers/Colors.cs | 6 +- .../ColumnClusteredPlotAreaRenderer.cs | 11 +- .../ColumnLikeGridlinesRenderer.cs | 35 +- .../ColumnLikeLegendRenderer.cs | 6 +- .../ColumnLikePlotAreaRenderer.cs | 11 +- .../ColumnPlotAreaRenderer.cs | 7 +- .../ColumnStackedPlotAreaRenderer.cs | 14 +- .../Charting.Renderers/Converter.cs | 36 +- .../Charting.Renderers/DataLabelRenderer.cs | 2 +- .../HorizontalXAxisRenderer.cs | 34 +- .../HorizontalYAxisRenderer.cs | 49 +- .../Charting.Renderers/LegendEntryRenderer.cs | 18 +- .../Charting.Renderers/LegendRenderer.cs | 22 +- .../Charting.Renderers/LineChartRenderer.cs | 11 +- .../Charting.Renderers/LineFormatRenderer.cs | 2 +- .../LinePlotAreaRenderer.cs | 7 +- .../Charting.Renderers/MarkerRenderer.cs | 32 +- .../PieClosedPlotAreaRenderer.cs | 8 +- .../PieExplodedPlotAreaRenderer.cs | 18 +- .../Charting.Renderers/PieLegendRenderer.cs | 9 +- .../Charting.Renderers/PiePlotAreaRenderer.cs | 6 +- .../Charting.Renderers/RendererInfo.cs | 25 +- .../Charting.Renderers/RendererParameters.cs | 2 +- .../VerticalXAxisRenderer.cs | 30 +- .../VerticalYAxisRenderer.cs | 59 +- .../PdfSharp.Charting/Charting/ChartFrame.cs | 6 +- .../PdfSharp.Charting/Charting/LineFormat.cs | 3 + .../PdfSharp.Charting.csproj | 2 +- .../Properties/GlobalDeclarations.cs | 7 +- .../Pdf.Signatures/PdfSharpDefaultSigner.cs | 14 +- .../Pdf.Signatures/PsCryptoMsg.cs | 27 +- .../Pdf.Signatures/enum/PsCryptoMsgId.cs | 2 +- .../PdfSharp.Cryptography.csproj | 8 +- .../src/PdfSharp/!internal/Configuration.cs | 48 +- .../src/PdfSharp/!internal/Directives.cs | 32 +- .../PdfSharp/Diagnostics/DebugBreakHelper.cs | 143 + .../src/PdfSharp/Diagnostics/PdfSharpCore.cs | 14 +- .../Drawing.Internal/IImageImporter.cs | 369 --- .../Drawing.Internal/ImageImporter.cs | 85 - .../Drawing.Internal/ImageImporterBmp.cs | 636 ---- .../Drawing.Internal/ImageImporterJpeg.cs | 465 --- .../Drawing.Internal/ImageImporterRoot.cs | 9 - .../PdfSharp/Drawing.Layout/XTextFormatter.cs | 4 +- .../PdfSharp/Drawing.Pdf/PdfGraphicsState.cs | 72 +- .../Drawing.Pdf/XGraphicsPdfRenderer.cs | 192 +- .../src/PdfSharp/Drawing/GeometryHelper.cs | 8 +- .../src/PdfSharp/Drawing/XBitmapEncoder.cs | 1 + .../src/PdfSharp/Drawing/XBitmapImage.cs | 6 +- .../src/PdfSharp/Drawing/XBitmapSource.cs | 11 +- .../PDFsharp/src/PdfSharp/Drawing/XBrush.cs | 4 +- .../PDFsharp/src/PdfSharp/Drawing/XColor.cs | 35 +- .../PDFsharp/src/PdfSharp/Drawing/XFont.cs | 95 +- .../src/PdfSharp/Drawing/XFontFamily.cs | 61 +- .../src/PdfSharp/Drawing/XFontSource.cs | 120 +- .../PDFsharp/src/PdfSharp/Drawing/XForm.cs | 91 +- .../src/PdfSharp/Drawing/XGlyphTypeface.cs | 160 +- .../src/PdfSharp/Drawing/XGraphics.cs | 159 +- .../src/PdfSharp/Drawing/XGraphicsPath.cs | 5 +- .../PDFsharp/src/PdfSharp/Drawing/XImage.cs | 182 +- .../src/PdfSharp/Drawing/XKnownColorTable.cs | 2 +- .../PdfSharp/Drawing/XLinearGradientBrush.cs | 25 +- .../PDFsharp/src/PdfSharp/Drawing/XMatrix.cs | 75 +- .../PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs | 26 +- .../src/PDFsharp/src/PdfSharp/Drawing/XPen.cs | 5 +- .../PDFsharp/src/PdfSharp/Drawing/XPoint.cs | 2 +- .../PdfSharp/Drawing/XRadialGradientBrush.cs | 4 +- .../PDFsharp/src/PdfSharp/Drawing/XRect.cs | 14 +- .../PDFsharp/src/PdfSharp/Drawing/XSize.cs | 1 - .../src/PdfSharp/Drawing/XTypeface.cs | 2 +- .../PDFsharp/src/PdfSharp/Drawing/XVector.cs | 2 +- .../PdfSharp/Drawing/enums/XFontStyleEx.cs | 2 - .../PdfSharp/Drawing/enums/XGraphicsUnit.cs | 2 + .../PdfSharp/Drawing/enums/XSmoothingMode.cs | 2 +- .../src/PdfSharp/Events/DocumentEvents.cs | 139 +- .../src/PdfSharp/Events/PageEvents.cs | 62 + .../src/PdfSharp/Events/PageGraphicsEvents.cs | 57 + .../src/PdfSharp/Events/PdfSharpEventArgs.cs | 8 +- .../src/PdfSharp/Events/RenderEvents.cs | 8 +- .../src/PdfSharp/Fonts.Internal/FontHelper.cs | 152 +- .../Fonts.Internal/FontResolvingOptions.cs | 7 - .../Fonts.Internal/UnicodeHelper.cs-DELETE | 140 + .../PdfSharp/Fonts.OpenType/FontDescriptor.cs | 203 -- .../PdfSharp/Fonts.OpenType/GlyphDataTable.cs | 174 - .../Fonts.OpenType/GlyphTypefaceCache.cs | 73 - .../Fonts.OpenType/OpenTypeFontWriter.cs | 30 - .../Fonts.OpenType/OpenTypeFontfaceCache.cs | 116 - .../PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs | 58 +- .../src/PdfSharp/Fonts/FontDescriptorCache.cs | 73 +- .../src/PdfSharp/Fonts/FontFactory.cs | 294 +- .../src/PdfSharp/Fonts/FontFactoryCaches.cs | 23 - .../src/PdfSharp/Fonts/FontFamilyCache.cs | 52 +- .../src/PdfSharp/Fonts/FontFamilyInternal.cs | 39 +- .../src/PdfSharp/Fonts/FontSourceCache.cs | 200 ++ .../src/PdfSharp/Fonts/GlobalFontSettings.cs | 94 +- .../src/PdfSharp/Fonts/GlyphHelper.cs | 9 +- .../src/PdfSharp/Fonts/GlyphTypefaceCache.cs | 76 + .../PdfSharp/Fonts/PlatformFontResolver.cs | 28 +- .../Fonts/WindowsPlatformFontResolver.cs | 2 +- .../PDFsharp/src/PdfSharp/Internal/Calc.cs | 27 +- .../src/PdfSharp/Internal/Diagnostics.cs | 10 + .../PdfSharp/Internal/DiagnosticsHelper.cs | 4 +- .../src/PdfSharp/Internal/DotNetHelper.cs | 98 +- .../src/PdfSharp/Internal/DoubleUtil.cs | 10 +- .../src/PdfSharp/Internal/ErrorHelpers.cs | 9 +- .../PDFsharp/src/PdfSharp/Internal/Lock.cs | 70 + .../Internal/{Globals.cs => PsGlobals.cs} | 35 +- .../src/PdfSharp/Internal/TokenizerHelper.cs | 10 +- .../Pdf.AcroForms/PdfSignatureField.cs | 177 -- .../src/PdfSharp/Pdf.Actions/PdfAction.cs | 29 +- .../Pdf.Actions/PdfEmbeddedGoToAction.cs | 149 +- .../src/PdfSharp/Pdf.Actions/PdfGoToAction.cs | 28 +- .../Pdf.Actions/PdfRemoteGoToAction.cs | 18 +- .../Pdf.Actions/enums/PdfNamedActionNames.cs | 4 +- .../Pdf.Actions/enums/PdfNamedActionTypes.cs | 116 + .../src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs | 21 +- .../src/PdfSharp/Pdf.Advanced/PdfCatalog.cs | 285 +- .../src/PdfSharp/Pdf.Advanced/PdfContent.cs | 71 +- .../src/PdfSharp/Pdf.Advanced/PdfContents.cs | 40 +- .../Pdf.Advanced/PdfCrossReferenceStream.cs | 15 +- .../Pdf.Advanced/PdfCrossReferenceTable.cs | 121 +- .../PdfDictionaryWithContentStream.cs | 12 +- ...dfInternals.cs => PdfDocumentInternals.cs} | 68 +- .../Pdf.Advanced/PdfEmbeddedFileParameters.cs | 75 + .../Pdf.Advanced/PdfEmbeddedFileStream.cs | 80 +- .../src/PdfSharp/Pdf.Advanced/PdfExtGState.cs | 20 +- .../src/PdfSharp/Pdf.Advanced/PdfFont.cs | 53 +- .../Pdf.Advanced/PdfFontDescriptor.cs | 42 +- .../Pdf.Advanced/PdfFontDescriptorCache.cs | 13 +- .../PdfSharp/Pdf.Advanced/PdfFontProgram.cs | 24 +- .../src/PdfSharp/Pdf.Advanced/PdfFontTable.cs | 6 +- .../PdfSharp/Pdf.Advanced/PdfFormXObject.cs | 91 +- .../Pdf.Advanced/PdfFormXObjectTable.cs | 6 +- .../Pdf.Advanced/PdfGroupAttributes.cs | 11 +- .../Pdf.Advanced/PdfImage.FaxEncode.cs | 88 +- .../src/PdfSharp/Pdf.Advanced/PdfImage.cs | 1061 ++----- .../PdfSharp/Pdf.Advanced/PdfImageTable.cs | 95 +- .../Pdf.Advanced/PdfImportedObjectTable.cs | 2 +- .../Pdf.Advanced/PdfNameDictionary.cs | 79 +- .../PdfNamedDestinationParameters.cs | 3 +- .../Pdf.Advanced/PdfNamedDestinations.cs | 11 +- .../PdfSharp/Pdf.Advanced/PdfObjectStream.cs | 18 +- .../Pdf.Advanced/PdfPageInheritableObjects.cs | 6 +- .../PdfSharp/Pdf.Advanced/PdfPlaceholder.cs | 96 + .../src/PdfSharp/Pdf.Advanced/PdfReference.cs | 200 +- .../PdfSharp/Pdf.Advanced/PdfResourceMap.cs | 22 +- .../src/PdfSharp/Pdf.Advanced/PdfResources.cs | 63 +- .../src/PdfSharp/Pdf.Advanced/PdfShading.cs | 12 +- .../Pdf.Advanced/PdfShadingPattern.cs | 15 +- .../src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs | 10 + .../PdfSharp/Pdf.Advanced/PdfTilingPattern.cs | 12 +- .../PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs | 35 +- .../src/PdfSharp/Pdf.Advanced/PdfTrailer.cs | 190 +- .../PdfTransparencyGroupAttributes.cs | 10 + .../PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs | 40 +- .../src/PdfSharp/Pdf.Advanced/PdfType0Font.cs | 83 +- .../src/PdfSharp/Pdf.Advanced/PdfType1Font.cs | 2 +- .../src/PdfSharp/Pdf.Advanced/PdfXObject.cs | 8 + .../IAnnotationAppearanceHandler.cs | 9 +- .../Pdf.Annotations/Pdf3DAnnotation.cs | 120 + .../PdfSharp/Pdf.Annotations/PdfAnnotation.cs | 629 +++- .../PdfAnnotationAppearance.cs | 120 + .../Pdf.Annotations/PdfAnnotations.cs | 152 +- .../Pdf.Annotations/PdfBorderEffect.cs | 82 + .../Pdf.Annotations/PdfBorderStyle.cs | 103 + .../Pdf.Annotations/PdfCaretAnnotation.cs | 80 + .../PdfFileAttachmentAnnotation.cs | 66 + .../Pdf.Annotations/PdfFreeTextAnnotation.cs | 153 + .../Pdf.Annotations/PdfGenericAnnotation.cs | 33 - .../Pdf.Annotations/PdfInkAnnotation.cs | 82 + .../Pdf.Annotations/PdfLineAnnotation.cs | 166 + .../Pdf.Annotations/PdfLinkAnnotation.cs | 277 +- .../Pdf.Annotations/PdfMarkupAnnotation.cs | 166 + .../Pdf.Annotations/PdfMovieAnnotation.cs | 76 + .../Pdf.Annotations/PdfPolyAnnotation.cs | 188 ++ .../Pdf.Annotations/PdfPopupAnnotation.cs | 69 + .../PdfPrinterMarkAnnotation.cs | 59 + .../PdfProjectionAnnotation.cs | 37 + .../Pdf.Annotations/PdfRedactAnnotation.cs | 119 + .../Pdf.Annotations/PdfRichMediaAnnotation.cs | 71 + .../PdfRubberStampAnnotation.cs | 102 - .../Pdf.Annotations/PdfScreenAnnotation.cs | 80 + .../Pdf.Annotations/PdfShapeAnnotation.cs | 193 ++ .../Pdf.Annotations/PdfSoundAnnotation.cs | 68 + .../Pdf.Annotations/PdfStampAnnotation.cs | 127 + .../Pdf.Annotations/PdfTextAnnotation.cs | 83 +- .../PdfTextMarkupAnnotation.cs | 192 ++ .../Pdf.Annotations/PdfTrapNetAnnotation.cs | 95 + .../Pdf.Annotations/PdfWatermarkAnnotation.cs | 131 + .../Pdf.Annotations/PdfWidgetAnnotation.cs | 378 ++- .../enums/PdfAnnotationFlags.cs | 86 +- .../enums/PdfAnnotationStates.cs | 56 + .../enums/PdfAnnotationTypes.cs | 373 +++ ...tionIcon.cs => PdfStampAnnotationIcons.cs} | 32 +- ...ationIcon.cs => PdfTextAnnotationIcons.cs} | 34 +- .../Pdf.Attachments/EmbeddedFileInfo.cs | 53 + .../Pdf.Attachments/EmbeddedFilesManager.cs | 140 + .../Pdf.Attachments/PdfAFRelationship.cs | 58 + .../Pdf.Attachments/PdfEmbeddedFiles.cs | 132 + .../PdfFileSpecification.cs | 179 +- .../PdfSharp/Pdf.Content.Objects/CObjects.cs | 478 ++- .../PdfSharp/Pdf.Content.Objects/Operators.cs | 47 +- .../Pdf.Content.Objects/enum/OpCodeFlags.cs | 8 +- .../Pdf.Content.Objects/enum/OpCodeName.cs | 39 +- .../src/PdfSharp/Pdf.Content/CLexer.cs | 106 +- .../src/PdfSharp/Pdf.Content/CParser.cs | 80 +- .../src/PdfSharp/Pdf.Content/Chars.cs | 4 +- .../src/PdfSharp/Pdf.Content/ContentReader.cs | 6 + .../Pdf.Content/ContentReaderException.cs | 2 + .../src/PdfSharp/Pdf.Content/ContentWriter.cs | 160 +- .../src/PdfSharp/Pdf.Content/enums/Symbol.cs | 6 +- .../src/PdfSharp/Pdf.Filters/Filter.cs | 2 +- .../src/PdfSharp/Pdf.Filters/Filtering.cs | 31 +- .../src/PdfSharp/Pdf.Filters/FlateDecode.cs | 23 +- .../PDFsharp/src/PdfSharp/Pdf.Forms/!Notes.md | 35 + .../PDFsharp/src/PdfSharp/Pdf.Forms/Design.cs | 1352 ++++++++ .../src/PdfSharp/Pdf.Forms/FieldName.cs | 113 + .../src/PdfSharp/Pdf.Forms/PdfForm.cs | 130 + .../PdfSharp/Pdf.Forms/PdfFormButtonField.cs | 13 + .../Pdf.Forms/PdfFormCertificateSeedValue.cs | 13 + .../Pdf.Forms/PdfFormCheckBoxField.cs | 13 + .../PdfFormCheckBoxOrRadioButtonField.cs | 13 + .../PdfSharp/Pdf.Forms/PdfFormChoiceField.cs | 12 + .../Pdf.Forms/PdfFormComboBoxField.cs | 13 + .../src/PdfSharp/Pdf.Forms/PdfFormField.cs | 13 + .../PdfSharp/Pdf.Forms/PdfFormFieldNode.cs | 46 + .../PdfSharp/Pdf.Forms/PdfFormFieldType.cs | 13 + .../src/PdfSharp/Pdf.Forms/PdfFormFields.cs | 283 ++ .../PdfSharp/Pdf.Forms/PdfFormListBoxField.cs | 13 + .../Pdf.Forms/PdfFormPushButtonField.cs | 13 + .../Pdf.Forms/PdfFormRadioButtonField.cs | 13 + .../Pdf.Forms/PdfFormSignatureField.cs | 13 + .../Pdf.Forms/PdfFormSignatureFieldLock.cs | 13 + .../PdfFormSignatureFieldSeedValue.cs | 13 + .../PdfSharp/Pdf.Forms/PdfFormTextField.cs | 13 + .../Pdf.Forms/PdfFormTextFieldBase.cs | 13 + .../Pdf.Forms/enums/PdfFormFieldFlags.cs | 166 + .../Pdf.Forms/enums/PdfFormSignatureFlags.cs | 34 + .../src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs | 1 + .../src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs | 190 +- .../PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs | 247 +- .../PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs | 91 +- .../src/PdfSharp/Pdf.IO/PdfReaderOptions.cs | 14 +- .../PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs | 648 ++-- .../Pdf.IO/enums/PdfDocumentOpenMode.cs | 2 +- .../PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs | 4 +- .../PdfSharp/Pdf.IO/enums/PdfWriterOptions.cs | 2 - .../src/PdfSharp/Pdf.Internal/AnsiEncoding.cs | 583 ++-- .../Pdf.Internal/GlobalObjectTable.cs | 2 - .../src/PdfSharp/Pdf.Internal/PdfDebugItem.cs | 40 + .../PdfSharp/Pdf.Internal/PdfDebugObject.cs | 40 + .../src/PdfSharp/Pdf.Internal/PdfEncoders.cs | 82 +- .../PdfSharp/Pdf.Internal/PdfRawDictionary.cs | 126 + .../src/PdfSharp/Pdf.Internal/RawEncoding.cs | 14 +- .../Pdf.Internal/ThreadLocalStorage.cs | 2 +- .../Pdf.Metadata/DocumentMetadataInfo.cs | 120 + .../PdfSharp/Pdf.Metadata/MetadataManager.cs | 159 + .../enums/DocumentMetadataStrategy.cs | 32 + .../PdfAcroField.cs | 62 +- .../PdfAcroForm.cs | 37 +- .../PdfButtonField.cs | 8 +- .../PdfCheckBoxField.cs | 23 +- .../PdfChoiceField.cs | 7 +- .../PdfComboBoxField.cs | 16 +- .../PdfGenericField.cs | 11 +- .../PdfListBoxField.cs | 9 +- .../PdfPushButtonField.cs | 11 +- .../PdfRadioButtonField.cs | 59 +- .../Pdf.OldAcroForms/PdfSignatureField.cs | 109 + .../PdfTextField.cs | 51 +- .../enums/PdfAcroFieldFlags.cs | 6 +- .../src/PdfSharp/Pdf.PdfA/PdfAFormat.cs | 55 + .../src/PdfSharp/Pdf.PdfA/PdfAFormats.cs | 74 + .../src/PdfSharp/Pdf.PdfA/PdfAManager.cs | 102 + .../PdfEncryptionV1To4.cs | 30 +- .../PdfEncryptionV5.cs | 46 +- .../PdfSharp/Pdf.Security/CryptFilterBase.cs | 6 +- .../Pdf.Security/IdentityCryptFilter.cs | 17 +- .../src/PdfSharp/Pdf.Security/MD5Managed.cs | 17 +- .../PdfSharp/Pdf.Security/PdfCryptFilter.cs | 22 +- .../PdfSharp/Pdf.Security/PdfCryptFilters.cs | 18 +- .../Pdf.Security/PdfSecurityHandler.cs | 4 + .../Pdf.Security/PdfSecuritySettings.cs | 18 +- .../PdfStandardSecurityHandler.cs | 109 +- .../PdfSharp/Pdf.Security/SecurityManager.cs | 81 + .../enums/PdfUserAccessPermission.cs | 3 - .../DefaultSignatureAppearanceHandler.cs | 8 +- .../Pdf.Signatures/DigitalSignatureHandler.cs | 269 ++ .../Pdf.Signatures/DigitalSignatureOptions.cs | 6 +- .../Pdf.Signatures/PdfPlaceholderObject.cs | 6 +- .../PdfSharp/Pdf.Signatures/PdfSignature.cs | 100 +- .../Pdf.Signatures/PdfSignatureHandler.cs | 242 -- .../PdfSignaturePlaceholderItem.cs | 51 - .../PdfSharp/Pdf.Signatures/RangedStream.cs | 103 +- .../Pdf.Structure/PdfAttributesBase.cs | 12 +- .../Pdf.Structure/PdfLayoutAttributes.cs | 10 + .../Pdf.Structure/PdfMarkInformation.cs | 8 + .../PdfMarkedContentReference.cs | 10 + .../Pdf.Structure/PdfObjectReference.cs | 10 + .../Pdf.Structure/PdfStructureElement.cs | 22 +- .../Pdf.Structure/PdfStructureTreeRoot.cs | 12 +- .../Pdf.Structure/PdfTableAttributes.cs | 10 + .../PdfSharp/Pdf/ArrayOrSingleItemHelper.cs | 88 +- .../PDFsharp/src/PdfSharp/Pdf/ElementsBase.cs | 307 ++ .../src/PdfSharp/Pdf/EntryInfoAttribute.cs | 79 +- .../src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs | 47 +- .../src/PDFsharp/src/PdfSharp/Pdf/Name.cs | 456 +++ .../PDFsharp/src/PdfSharp/Pdf/ParentInfo.cs | 82 + .../src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs | 1575 +++++++-- .../PdfArrayExtensions/PdfArrayExtensions.cs | 35 + .../PdfSharp/Pdf/PdfArrayOfDictionaries.cs | 66 + .../PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs | 6 +- .../src/PdfSharp/Pdf/PdfBooleanObject.cs | 20 +- .../PDFsharp/src/PdfSharp/Pdf/PdfContainer.cs | 75 + .../src/PdfSharp/Pdf/PdfCustomValue.cs | 4 + .../src/PdfSharp/Pdf/PdfCustomValues.cs | 12 +- .../src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs | 104 +- .../src/PdfSharp/Pdf/PdfDictionary.cs | 2806 ++++++++++++----- .../PdfDictionaryExtensions.cs | 70 + .../PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 406 ++- .../PdfSharp/Pdf/PdfDocumentInformation.cs | 142 +- .../src/PdfSharp/Pdf/PdfDocumentOptions.cs | 6 +- .../PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs | 35 +- .../src/PdfSharp/Pdf/PdfIntegerObject.cs | 30 +- .../src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs | 173 +- .../PdfItemExtensions/PdfItemExtensions.cs | 141 + .../PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs | 23 +- .../src/PdfSharp/Pdf/PdfLongInteger.cs | 22 +- .../src/PdfSharp/Pdf/PdfLongIntegerObject.cs | 30 +- .../PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs | 701 ++-- .../src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs | 89 +- .../src/PdfSharp/Pdf/PdfNameObject.cs | 71 +- .../src/PdfSharp/Pdf/PdfNameTreeNode.cs | 385 ++- .../src/PDFsharp/src/PdfSharp/Pdf/PdfNull.cs | 18 +- .../src/PdfSharp/Pdf/PdfNullObject.cs | 20 +- .../PDFsharp/src/PdfSharp/Pdf/PdfNumber.cs | 4 +- .../src/PdfSharp/Pdf/PdfNumberObject.cs | 34 +- .../src/PdfSharp/Pdf/PdfNumberTreeNode.cs | 16 +- .../PDFsharp/src/PdfSharp/Pdf/PdfObject.cs | 287 +- .../PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs | 50 +- .../PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs | 235 +- .../src/PdfSharp/Pdf/PdfOutlineCollection.cs | 6 +- .../src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs | 356 ++- .../src/PdfSharp/Pdf/PdfPageTreeBase.cs | 32 + .../src/PdfSharp/Pdf/PdfPageTreeNode.cs | 187 ++ .../src/PdfSharp/Pdf/PdfPageTreeNodes.cs | 31 + .../src/PDFsharp/src/PdfSharp/Pdf/PdfPages.cs | 493 ++- .../PDFsharp/src/PdfSharp/Pdf/PdfPrimitive.cs | 35 + .../src/PdfSharp/Pdf/PdfPrimitiveObject.cs | 46 + .../src/PDFsharp/src/PdfSharp/Pdf/PdfReal.cs | 20 +- .../src/PdfSharp/Pdf/PdfRealObject.cs | 37 +- .../PDFsharp/src/PdfSharp/Pdf/PdfRectangle.cs | 97 +- .../PDFsharp/src/PdfSharp/Pdf/PdfString.cs | 45 +- .../src/PdfSharp/Pdf/PdfStringObject.cs | 44 +- .../PDFsharp/src/PdfSharp/Pdf/PdfUInteger.cs | 8 +- .../src/PdfSharp/Pdf/PdfViewerPreferences.cs | 14 +- .../PDFsharp/src/PdfSharp/Pdf/TrimMargins.cs | 3 +- .../src/PdfSharp/Pdf/enums/ItemFlags.cs | 82 + .../Pdf/enums/PdfFontColoredGlyphs.cs | 2 +- .../src/PdfSharp/Pdf/enums/PdfVersion.cs | 65 + .../PDFsharp/src/PdfSharp/Pdf/enums/VCF.cs | 35 + .../src/PDFsharp/src/PdfSharp/PdfSharp.csproj | 34 +- .../src/PdfSharp/PdfSharp.csproj.DotSettings | 14 + .../PdfSharp/Properties/GlobalDeclarations.cs | 25 +- .../PdfSharpProductVersionInformation.cs | 209 +- .../XGraphicsExtensions.cs | 2 +- .../StructureBuilder.cs | 14 +- .../UniversalAccessibility/UAManager.cs | 52 +- .../src/PdfSharp/Windows/enums/Zoom.cs | 2 +- .../src/PdfSharp/root/Capabilities.cs | 16 +- .../src/PdfSharp/root/PageSizeConverter.cs | 2 +- .../PdfSharp.Tests-gdi.csproj | 23 +- .../tests/PdfSharp.Tests/Basics/SmokeTests.cs | 171 + .../Build/CSharpFeaturesTests.cs | 238 +- .../Build/CheckPdfDictionaryClassesTests.cs | 113 + .../PdfSharp.Tests/Build/NumericsTests.cs | 78 + .../PdfSharp.Tests/Build/ReleaseBuildTests.cs | 103 +- .../Drawing/images/ImageTests.cs | 69 +- .../PdfSharp.Tests/Drawing/text/TextTests.cs | 32 +- .../Drawing/text/UnicodeHelperTests.cs | 6 +- .../PdfSharp.Tests/Encodings/EncodingTests.cs | 2 +- .../PdfSharp.Tests/Filters/Ascii85Tests.cs | 2 +- .../PdfSharp.Tests/Fonts/FontResolverTests.cs | 123 +- .../PdfSharp.Tests/Helper/XunitHelper.cs | 12 +- .../tests/PdfSharp.Tests/IO/CLexerTests.cs | 59 +- .../PdfSharp.Tests/IO/ContentReaderTests.cs | 75 + .../PdfSharp.Tests/IO/ContentWriterTests.cs | 284 ++ .../tests/PdfSharp.Tests/IO/LexerTests.cs | 2 +- .../tests/PdfSharp.Tests/IO/ReaderTests.cs | 178 +- .../tests/PdfSharp.Tests/IO/WriterTests.cs | 47 +- .../PdfSharp.Tests/Internal/ErrorMessages.cs | 25 + .../Parsers/pdf-files/StringParserTests.cs | 77 + .../tests/PdfSharp.Tests/Pdf.Actions/.gitkeep | 0 .../Pdf.Annotations/AnnotationTestBase.cs | 45 + .../AnnotationTests_old.cs} | 54 +- .../CreateAnnotationTests_old_delete.cs} | 14 +- .../Pdf.Attachments/EmbeddedFilesTests-old.cs | 37 + .../Pdf.Attachments/EmbeddedFilesTests.cs | 118 + .../Pdf.Content/ContentReaderTests.cs | 73 + .../Pdf.Content/ContentWriterTests.cs | 372 +++ .../PdfSharp.Tests/Pdf.Forms/AcroFormStuff.cs | 281 ++ .../Pdf.Metadata/MetadataManagerTests.cs | 238 ++ .../Pdf.Metadata/MetadataTests.cs | 105 + .../Pdf.ObjectModel/Array620Tests.cs | 98 + .../Pdf.ObjectModel/ArrayElementsTests.cs | 995 ++++++ .../ArrayItemTests.cs} | 13 +- .../Pdf.ObjectModel/ArrayPrimitivesTests.cs | 239 ++ .../Pdf.ObjectModel/ArrayTests.cs | 244 ++ .../Pdf.ObjectModel/Dictionary620Tests.cs | 142 + .../DictionaryElementsTests.cs | 986 ++++++ .../Pdf.ObjectModel/DictionaryItemTests.cs | 185 ++ .../DictionaryPrimitivesTest.cs | 512 +++ .../Pdf.ObjectModel/DictionaryTests.cs | 294 ++ .../Pdf.ObjectModel/HybridContainerTests.cs | 148 + .../Pdf.ObjectModel/LayoutTests.cs | 331 ++ .../Pdf.ObjectModel/ObjectModelTests.cs | 295 ++ .../Pdf.ObjectModel/RealNumberTests.cs | 117 + .../Pdf.ObjectModel/model/ObjectModel.cs | 367 +++ .../model/ObjectModelArrayTestsBase.cs | 25 + .../model/ObjectModelDictionaryTestsBase.cs | 23 + .../model/ObjectModelTests-Helper.cs | 101 + .../model/ObjectModelTestsBase.cs | 346 ++ .../PdfSharp.Tests/Pdf.Objects/DateTests.cs | 53 + .../Pdf.Objects/PdfCatalogTests.cs | 39 + .../Pdf.Objects/PdfDateTests.cs | 52 + .../PdfPageTests.cs} | 118 +- .../{Pdf/pdf-a => Pdf.PdfA}/PdfATests.cs | 3 +- .../SecurityTests.cs | 4 +- .../BouncyCastleSignerTests.cs | 20 +- .../DefaultSignerTests.cs | 104 +- .../PdfSharp.Tests/Pdf.Structure/.gitkeep | 0 .../Pdf/creation/CreationTests.cs | 5 +- .../Pdf/objectmodel/DictionaryTests.cs | 23 - .../PdfSharp.Tests/Pdf/streams/StreamTests.cs | 224 ++ .../PdfObjectModel/ObjectModel.cs | 140 + .../PdfObjectModel/ObjectModelMetaTests.cs | 84 + .../PdfObjectModel/ObjectModelTests-Arrays.cs | 78 + .../ObjectModelTests-Dictionaries.cs | 62 + .../PdfObjectModel/ObjectModelTests-Helper.cs | 110 + .../PdfObjectModel/ObjectModelTests.cs | 298 ++ .../PdfSharp.Tests/PdfSharp.Tests.csproj | 27 +- .../PdfSharp.Tests.csproj.DotSettings | 6 + .../Properties/GlobalDeclarations.cs | 2 + .../tests/PdfSharp.Tests/Structs/NameTests.cs | 218 ++ .../tests/PdfSharp.Tests/TestConfig.cs | 14 + .../PdfSharp.tests-wpf.csproj | 22 +- .../PdfSharp.Extensions.csproj | 21 + .../src/PdfSharp.Extensions/README.md | 5 + .../src/PdfSharp.BuildConfig/BuildConfig.json | 4 + .../CreateBuildConfiguration.ps1 | 456 +++ .../PdfSharp.BuildConfig.csproj | 29 + .../src/PdfSharp.BuildConfig/Program.cs | 90 + .../shared/src/PdfSharp.BuildConfig/README.md | 56 + .../src/PdfSharp.BuildConfig/SemVersion.json | 37 + .../SemVersionInformation-substitute.cs | 37 + .../WaitForDownloadAssets.ps1 | 146 + .../src/PdfSharp.Fonts/PdfSharp.Fonts.csproj | 2 +- .../src/PdfSharp.Imaging/BmpImageImporter.cs | 620 ++++ .../shared/src/PdfSharp.Imaging/Imaging.cs | 812 +++++ .../src/PdfSharp.Imaging/JpegImageImporter.cs | 352 +++ .../PdfSharp.Imaging/PdfSharp.Imaging.csproj | 33 + .../PdfSharp.Imaging}/Png/BigGustave/Adam7.cs | 4 +- .../Png/BigGustave/Adler32Checksum.cs | 2 +- .../Png/BigGustave/ChunkHeader.cs | 2 +- .../Png/BigGustave/ColorType.cs | 2 +- .../Png/BigGustave/CompressionMethod.cs | 2 +- .../PdfSharp.Imaging}/Png/BigGustave/Crc32.cs | 2 +- .../Png/BigGustave/Decoder.cs | 4 +- .../Png/BigGustave/FilterMethod.cs | 2 +- .../Png/BigGustave/FilterType.cs | 2 +- .../Png/BigGustave/HeaderValidationResult.cs | 2 +- .../Png/BigGustave/IChunkVisitor.cs | 2 +- .../Png/BigGustave/ImageHeader.cs | 2 +- .../Png/BigGustave/InterlaceMethod.cs | 2 +- .../PdfSharp.Imaging}/Png/BigGustave/LICENSE | 0 .../Png/BigGustave/Palette.cs | 2 +- .../PdfSharp.Imaging}/Png/BigGustave/Pixel.cs | 2 +- .../PdfSharp.Imaging}/Png/BigGustave/Png.cs | 6 +- .../Png/BigGustave/PngBuilder.cs | 30 +- .../Png/BigGustave/PngOpener.cs | 2 +- .../Png/BigGustave/PngOpenerSettings.cs | 2 +- .../Png/BigGustave/PngStreamWriteHelper.cs | 6 +- .../Png/BigGustave/RawPngData.cs | 16 +- .../Png/BigGustave/StreamHelper.cs | 2 +- .../src/PdfSharp.Imaging/PngImageImporter.cs} | 332 +- .../src/shared/src/PdfSharp.Imaging/README.md | 4 + .../src/PdfSharp.OpenType}/CharacterMap.cs | 14 +- .../CodePointGlyphIndexPair.cs | 8 +- .../PdfSharp.OpenType}/GenericFontTable.cs | 4 +- .../src/PdfSharp.OpenType/GlyphDataTable.cs | 370 +++ .../src/PdfSharp.OpenType}/IRefFontTable.cs | 8 +- .../shared/src/PdfSharp.OpenType/KeyHelper.cs | 51 + .../src/PdfSharp.OpenType/MakeItCompile.cs | 102 + .../OpenTypeFontDescriptor.cs} | 455 ++- .../OpenTypeFontFace-Helper.cs | 72 + .../PdfSharp.OpenType/OpenTypeFontFace.cs} | 234 +- .../OpenTypeFontFaceCache.cs | 138 + .../OpenTypeFontFactory-DELETE.cs | 17 + .../PdfSharp.OpenType/OpenTypeFontFamily.cs | 103 + .../OpenTypeFontFamilyCache.cs | 92 + .../PdfSharp.OpenType/OpenTypeFontRegistry.cs | 163 + .../PdfSharp.OpenType/OpenTypeFontSource.cs | 161 + .../OpenTypeFontSourceCache.cs | 244 ++ .../PdfSharp.OpenType}/OpenTypeFontTable.cs | 23 +- .../PdfSharp.OpenType}/OpenTypeFontTables.cs | 706 +++-- .../PdfSharp.OpenType/OpenTypeFontWriter.cs | 169 + .../src/PdfSharp.OpenType/OpenTypeGlobals.cs | 89 + .../PdfSharp.OpenType/OpenTypeGlyphMetrics.cs | 189 ++ .../OpenTypeGlyphTypeface.cs | 83 + .../OpenTypeGlyphTypefaceCache.cs | 103 + .../PdfSharp.OpenType.csproj | 21 + .../Properties/GlobalDeclarations.cs | 15 + .../shared/src/PdfSharp.OpenType/README.md | 7 + .../PdfSharp.OpenType}/TableDirectoryEntry.cs | 8 +- .../Tables}/IndexToLocationTable.cs | 20 +- .../src/PdfSharp.OpenType}/UnicodeHelper.cs | 30 +- .../enums/FontTechnology.cs | 2 +- .../PdfSharp.OpenType}/enums/TableTagNames.cs | 2 +- .../PdfSharp.Quality-gdi.csproj | 15 +- .../PdfSharp.Quality-wpf.csproj | 11 +- .../Properties/Settings.Designer.cs | 4 +- .../PdfSharp.Quality.Ghostscript.csproj | 31 + .../PdfToPngConverter.cs | 195 ++ .../PdfSharp.Quality.Ghostscript/README.md | 22 + .../src/PdfSharp.Quality/AssetsHelper.cs | 3 + .../src/PdfSharp.Quality/CompareHelper.cs | 41 + .../PdfSharp.Quality/FeatureAndSnippetBase.cs | 6 +- .../shared/src/PdfSharp.Quality/FontHelper.cs | 4 +- .../shared/src/PdfSharp.Quality/IOUtility.cs | 129 +- .../src/PdfSharp.Quality/PdfDocUtility.cs | 3 +- .../src/PdfSharp.Quality/PdfFileUtility.cs | 7 + .../src/PdfSharp.Quality/PdfPngComparer.cs | 97 + .../PdfSharp.Quality/PdfSharp.Quality.csproj | 6 +- .../{ => Properties}/GlobalUsings.cs | 0 .../shared/src/PdfSharp.Quality/Snippet.cs | 2 +- .../Testing.TestModel/ObjectModel.cs | 366 +++ .../ObjectModelTestHelper.cs | 181 ++ .../Testing/PdfSharpTestBase.cs | 22 + .../Testing/ReadWriteHelper.cs | 176 ++ .../{ => Testing}/TestClassBase.cs | 12 +- .../PdfSharp.Quality/Testing/TrueOrFalse.cs | 31 + .../src/PdfSharp.Quality/WindowsFonts.cs | 154 + .../fontresolver/UnitTestFontResolver.cs | 6 +- .../src/shared/src/PdfSharp.Shared/.gitignore | 3 + .../PdfSharp.Shared/Internal/BuildVersion.cs | 2 +- .../src/PdfSharp.Shared/Internal/CS13.cs | 56 + .../Internal/ChecksumHelper.cs | 75 + .../src/PdfSharp.Shared/Internal/GfxMsg.cs | 28 + .../src/PdfSharp.Shared/Internal/GfxMsgs.cs | 64 + .../Internal/PdfSharpGitVersionInformation.cs | 56 - .../Internal/SemVersionInformation.cs | 76 + .../src/PdfSharp.Shared/Internal/SyMsg.cs | 40 +- .../src/PdfSharp.Shared/Internal/SyMsgs.cs | 68 +- .../PdfSharp.Shared/Internal/enum/GfxMsgId.cs | 32 + .../PdfSharp.Shared/Internal/enum/SyMsgId.cs | 74 +- .../PdfSharp.Shared/PdfSharp.Shared.csproj | 102 +- .../src/shared/src/PdfSharp.Shared/README.md | 13 +- .../WaitForGitVersionInformation.ps1 | 14 + .../dotnet}/CodeAnalysis.cs | 4 +- .../dotnet/CompilerServices1.cs | 297 ++ .../dotnet/CompilerServices2.cs | 99 + .../dotnet/ExceptionExtensions.cs | 83 + .../dotnet/GetSubArray.cs} | 6 +- .../PdfSharp.Snippets-gdi.csproj | 4 +- .../PdfSharp.Snippets-wpf.csproj | 4 +- .../Drawing/clipping/ClippingMisc.cs | 13 +- .../Drawing/colors/ColorsCmyk.cs | 11 +- .../Drawing/colors/ColorsRgb.cs | 13 +- .../Drawing/graphics/GraphicsFromImage.cs | 13 +- .../Drawing/graphics/GraphicsUnitBase.cs | 21 + .../Drawing/graphics/GraphicsUnitDownwards.cs | 9 + .../Drawing/graphics/GraphicsUnitUpwards.cs | 8 +- .../Drawing/images/ImageHelper.cs | 2 +- .../Font/encoding/FontAnsiEncoding.cs | 3 +- .../fontresolving/ExoticFontsFontResolver.cs | 2 +- .../Font/fontresolving/SegoeWpFontResolver.cs | 8 +- .../selection/DefaultConstructionSnippets.cs | 4 +- .../Fonts.Text/TextSamples.cs | 37 + .../shared/src/PdfSharp.Snippets/Pangrams.cs | 2 +- .../Pdf/annotations/LinkAnnotations.cs | 14 +- .../Pdf/signatures/BouncyCastleSigner.cs | 6 +- .../PdfSharp.Snippets.csproj | 7 +- .../Diagnostics/DebuggerDisplayHelper.cs | 58 + .../BigIntegerExtensions.cs | 58 + .../ConcurrentDictionaryExtensions.cs | 30 + .../DictionaryExtensions.cs | 37 + .../EnumExtensions.cs | 36 + .../StringExtensions.cs | 62 + .../Internal.Threading}/Locks.cs | 45 +- .../PdfSharp.System/Internal/AnsiEncoding.cs | 309 ++ .../PdfSharp.System/Internal/BuildVersion.cs | 2 +- .../PdfSharp.System/Logging/LogMessages.cs | 171 + .../Logging/PdfSharpLogHost.cs | 138 + .../PdfSharp.System/PdfSharp.System.csproj | 8 +- .../PdfSharp.System.csproj.DotSettings | 1 + .../PdfSharp.System/PlugIn/IPdfSharpPlugIn.cs | 28 + .../Properties/FloatOrDouble.cs | 22 + .../Properties/GlobalDeclarations.cs | 5 - .../PdfSharp.System/dotnet/CodeAnalysis.cs | 143 + .../dotnet/CompilerServices2.cs | 99 + .../extensions/SystemStringExtensions.cs | 24 - .../PdfSharp.Testing-gdi.csproj | 2 +- .../PdfSharp.Testing-wpf.csproj | 2 +- .../PdfSharp.Testing/PdfSharp.Testing.csproj | 2 +- .../PdfSharp.WPFonts/PdfSharp.WPFonts.csproj | 3 +- .../PdfSharp.Fonts.TestApp.csproj | 2 +- .../Shared.TestApp}/CompilerServices.cs | 0 .../Shared.TestApp/CompilerServices2.cs | 50 + .../testapps/Shared.TestApp/LogMessages.cs | 1 - .../shared/testapps/Shared.TestApp/Program.cs | 10 - .../Shared.TestApp/Shared.TestApp.csproj | 9 +- .../BasicTests.cs | 0 .../PdfSharp.Fonts.Test.csproj | 2 +- .../README.md | 0 .../FontFaces/BasicTests.cs | 16 + .../PdfSharp.OpenType.Test.csproj | 25 + .../tests/PdfSharp.OpenType.Tests/README.md | 3 + .../PdfSharp.OpenType.Tests/WpfTestBase.cs | 65 + .../shared/tests/Shared.Tests/BasicTests.cs | 23 +- .../Shared.Tests/Quality/IOUtilityTests.cs | 45 +- .../Quality/PdfToPngConverterTests.cs | 30 + .../tests/Shared.Tests/Shared.Tests.csproj | 6 +- .../HelloWorld,MigraDoc-gdi.csproj | 2 +- .../HelloWorld,MigraDoc-wpf.csproj | 2 +- .../src/HelloWorld/HelloWorld,MigraDoc.csproj | 2 +- .../src/MigraDoc/src/HelloWorld/Program.cs | 8 +- .../HelloWorld-gdi,PDFsharp.csproj | 2 +- .../HelloWorld-wpf,PDFsharp.csproj | 2 +- .../src/HelloWorld/HelloWorld,PDFsharp.csproj | 2 +- .../src/PDFsharp/src/HelloWorld/Program.cs | 13 +- src/tools/src/CopyAsLink/CopyAsLink.csproj | 2 +- src/tools/src/CopyAsLink/Program.cs | 14 +- src/tools/src/NRT-Tests/NRT-Tests.csproj | 2 +- .../src/PdfFileViewer/PdfFileViewer.csproj | 2 +- .../PdfSharp.TestHelper-gdi.csproj | 4 +- .../PdfSharp.TestHelper-wpf.csproj | 4 +- .../ContentStream/ContentStreamEnumerator.cs | 34 +- .../ContentStream/ObjectGetterBase.cs | 6 +- .../Analysis/ContentStream/TextGetter.cs | 4 +- .../src/PdfSharp.TestHelper/MemoryLogger.cs | 2 +- .../src/PdfSharp.TestHelper/PdfFileHelper.cs | 9 +- .../PdfSharp.TestHelper.csproj | 4 +- .../PdfSharp.TestHelper/SecurityTestHelper.cs | 29 +- .../TestHelperExtensions.cs | 4 +- 885 files changed, 46582 insertions(+), 14096 deletions(-) create mode 100644 .globalconfig create mode 100644 dev/GetAllPDFs.ps1 create mode 100644 dev/init-pdfsharp-repository.ps1 create mode 100644 dev/zip-PDFsharp.ps1 delete mode 100644 docs/Chars.md delete mode 100644 docs/MigraDoc/AboutLineSpacing.md create mode 100644 docs/MigraDoc/change-log/MD-v6.3.0-log.md create mode 100644 docs/MigraDoc/change-log/MD-v6.4.0-log.md create mode 100644 docs/MigraDoc/change-log/MD-v7.0.0-preview-log.md delete mode 100644 docs/MigraDoc/design/PDF-A/PDF-A-Fundamentals.md delete mode 100644 docs/MigraDoc/design/PDF-UA/PDF-UA-Fundamentals.md create mode 100644 docs/PDFsharp/change-log/PS-v6.3.0-log.md create mode 100644 docs/PDFsharp/change-log/PS-v6.4.0-log.md create mode 100644 docs/PDFsharp/change-log/PS-v7.0.0-preview-log.md delete mode 100644 docs/PDFsharp/design/FontSeletion/FontResolver.md delete mode 100644 docs/PDFsharp/design/Issues.md delete mode 100644 docs/PDFsharp/design/PDF-A/PDF-A-Fundamentals.md delete mode 100644 docs/PDFsharp/design/PdfReader/StringsAndNames.md delete mode 100644 docs/PDFsharp/design/Signatures/CertificateCreation.md create mode 100644 docs/PDFsharp/docs/Documentation.md create mode 100644 docs/change-log/gn-v6.3.0-log.md create mode 100644 docs/change-log/gn-v7.0.0-preview-log.md rename docs/development/{ => WSL}/DevelopmentWithWSL.md (76%) create mode 100644 docs/development/WSL/launchsettings.template.json create mode 100644 docs/development/WSL/testenvironments.template.json rename docs/{docs-dummy.csproj => docs-public.csproj} (81%) delete mode 100644 docs/publishing/BeforeReleases.md delete mode 100644 docs/publishing/BoilerplateText.md delete mode 100644 docs/publishing/MakeNewReleaseNotes.md delete mode 100644 gitversion-6.x.yml delete mode 100644 gitversion.yml create mode 100644 src/.gitignore create mode 100644 src/Local.Build.props---template create mode 100644 src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.Fewer.nuspec create mode 100644 src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.Fewer.nuspec create mode 100644 src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.Fewer.nuspec create mode 100644 src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.Fewer.nuspec create mode 100644 src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.Fewer.nuspec create mode 100644 src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.Fewer.nuspec create mode 100644 src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GraphicsDeclarations.cs rename src/foundation/src/MigraDoc/tests/MigraDoc.Tests/{ImageFormats.cs => ImageFormatsTests.cs} (87%) create mode 100644 src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MemoryTests.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/DebugBreakHelper.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporter.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterBmp.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterRoot.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Events/PageEvents.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Events/PageGraphicsEvents.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs-DELETE delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontWriter.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactoryCaches.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontSourceCache.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphTypefaceCache.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs rename src/foundation/src/PDFsharp/src/PdfSharp/Internal/{Globals.cs => PsGlobals.cs} (61%) delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfSignatureField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionTypes.cs rename src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/{PdfInternals.cs => PdfDocumentInternals.cs} (82%) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileParameters.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPlaceholder.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/Pdf3DAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotationAppearance.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderEffect.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderStyle.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfCaretAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFileAttachmentAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFreeTextAnnotation.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfGenericAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfInkAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLineAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMarkupAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMovieAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPolyAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPopupAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPrinterMarkAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfProjectionAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRedactAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRichMediaAnnotation.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRubberStampAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfScreenAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfShapeAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfSoundAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfStampAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextMarkupAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTrapNetAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWatermarkAnnotation.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationStates.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationTypes.cs rename src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/{PdfRubberStampAnnotationIcon.cs => PdfStampAnnotationIcons.cs} (89%) rename src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/{PdfTextAnnotationIcon.cs => PdfTextAnnotationIcons.cs} (54%) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFileInfo.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFilesManager.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfAFRelationship.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfEmbeddedFiles.cs rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.Advanced => Pdf.Attachments}/PdfFileSpecification.cs (55%) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/!Notes.md create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/Design.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/FieldName.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfForm.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormButtonField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCertificateSeedValue.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxOrRadioButtonField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormChoiceField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormComboBoxField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldNode.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldType.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFields.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormListBoxField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormPushButtonField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormRadioButtonField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldLock.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldSeedValue.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextField.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextFieldBase.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormFieldFlags.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormSignatureFlags.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugItem.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugObject.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfRawDictionary.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/DocumentMetadataInfo.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/MetadataManager.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/enums/DocumentMetadataStrategy.cs rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfAcroField.cs (90%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfAcroForm.cs (81%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfButtonField.cs (91%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfCheckBoxField.cs (95%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfChoiceField.cs (97%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfComboBoxField.cs (81%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfGenericField.cs (72%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfListBoxField.cs (81%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfPushButtonField.cs (72%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfRadioButtonField.cs (55%) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfSignatureField.cs rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/PdfTextField.cs (84%) rename src/foundation/src/PDFsharp/src/PdfSharp/{Pdf.AcroForms => Pdf.OldAcroForms}/enums/PdfAcroFieldFlags.cs (98%) create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormat.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormats.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAManager.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/SecurityManager.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureHandler.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs delete mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignaturePlaceholderItem.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ElementsBase.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/Name.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ParentInfo.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayExtensions/PdfArrayExtensions.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayOfDictionaries.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfContainer.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionaryExtensions/PdfDictionaryExtensions.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItemExtensions/PdfItemExtensions.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeBase.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeNode.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeNodes.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPrimitive.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPrimitiveObject.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/ItemFlags.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/PdfVersion.cs create mode 100644 src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/VCF.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Basics/SmokeTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CheckPdfDictionaryClassesTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/NumericsTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ContentReaderTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ContentWriterTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Internal/ErrorMessages.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Parsers/pdf-files/StringParserTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Actions/.gitkeep create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/AnnotationTestBase.cs rename src/foundation/src/PDFsharp/tests/PdfSharp.Tests/{Pdf/annotations/AnnotationTests.cs => Pdf.Annotations/AnnotationTests_old.cs} (50%) rename src/foundation/src/PDFsharp/tests/PdfSharp.Tests/{Pdf/objectmodel/NameTests.cs => Pdf.Annotations/CreateAnnotationTests_old_delete.cs} (57%) create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Attachments/EmbeddedFilesTests-old.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Attachments/EmbeddedFilesTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Content/ContentReaderTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Content/ContentWriterTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Forms/AcroFormStuff.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Metadata/MetadataManagerTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Metadata/MetadataTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/Array620Tests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayElementsTests.cs rename src/foundation/src/PDFsharp/tests/PdfSharp.Tests/{Pdf/objectmodel/ArrayTests.cs => Pdf.ObjectModel/ArrayItemTests.cs} (50%) create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayPrimitivesTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/Dictionary620Tests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryElementsTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryItemTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryPrimitivesTest.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/HybridContainerTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/LayoutTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ObjectModelTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/RealNumberTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModel.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelArrayTestsBase.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelDictionaryTestsBase.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelTests-Helper.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelTestsBase.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/DateTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfCatalogTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfDateTests.cs rename src/foundation/src/PDFsharp/tests/PdfSharp.Tests/{Pdf/creation/BasicTests.cs => Pdf.Objects/PdfPageTests.cs} (76%) rename src/foundation/src/PDFsharp/tests/PdfSharp.Tests/{Pdf/pdf-a => Pdf.PdfA}/PdfATests.cs (96%) rename src/foundation/src/PDFsharp/tests/PdfSharp.Tests/{Security => Pdf.Security}/SecurityTests.cs (92%) rename src/foundation/src/PDFsharp/tests/PdfSharp.Tests/{Pdf/signatures => Pdf.Signatures}/BouncyCastleSignerTests.cs (87%) rename src/foundation/src/PDFsharp/tests/PdfSharp.Tests/{Pdf/signatures => Pdf.Signatures}/DefaultSignerTests.cs (77%) create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Structure/.gitkeep delete mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/DictionaryTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/streams/StreamTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModel.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelMetaTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Arrays.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Dictionaries.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Helper.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Structs/NameTests.cs create mode 100644 src/foundation/src/PDFsharp/tests/PdfSharp.Tests/TestConfig.cs create mode 100644 src/foundation/src/extensions/src/PdfSharp.Extensions/PdfSharp.Extensions.csproj create mode 100644 src/foundation/src/extensions/src/PdfSharp.Extensions/README.md create mode 100644 src/foundation/src/shared/src/PdfSharp.BuildConfig/BuildConfig.json create mode 100644 src/foundation/src/shared/src/PdfSharp.BuildConfig/CreateBuildConfiguration.ps1 create mode 100644 src/foundation/src/shared/src/PdfSharp.BuildConfig/PdfSharp.BuildConfig.csproj create mode 100644 src/foundation/src/shared/src/PdfSharp.BuildConfig/Program.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.BuildConfig/README.md create mode 100644 src/foundation/src/shared/src/PdfSharp.BuildConfig/SemVersion.json create mode 100644 src/foundation/src/shared/src/PdfSharp.BuildConfig/SemVersionInformation-substitute.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.BuildConfig/WaitForDownloadAssets.ps1 create mode 100644 src/foundation/src/shared/src/PdfSharp.Imaging/BmpImageImporter.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Imaging/Imaging.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Imaging/JpegImageImporter.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Imaging/PdfSharp.Imaging.csproj rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/Adam7.cs (97%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/Adler32Checksum.cs (96%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/ChunkHeader.cs (97%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/ColorType.cs (94%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/CompressionMethod.cs (91%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/Crc32.cs (98%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/Decoder.cs (98%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/FilterMethod.cs (91%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/FilterType.cs (94%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/HeaderValidationResult.cs (97%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/IChunkVisitor.cs (92%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/ImageHeader.cs (98%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/InterlaceMethod.cs (92%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/LICENSE (100%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/Palette.cs (97%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/Pixel.cs (98%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/Png.cs (97%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/PngBuilder.cs (96%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/PngOpener.cs (99%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/PngOpenerSettings.cs (94%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/PngStreamWriteHelper.cs (92%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/RawPngData.cs (94%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.Imaging}/Png/BigGustave/StreamHelper.cs (96%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterPng.cs => shared/src/PdfSharp.Imaging/PngImageImporter.cs} (63%) create mode 100644 src/foundation/src/shared/src/PdfSharp.Imaging/README.md rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType}/CharacterMap.cs (82%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts => shared/src/PdfSharp.OpenType}/CodePointGlyphIndexPair.cs (85%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType}/GenericFontTable.cs (94%) create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/GlyphDataTable.cs rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType}/IRefFontTable.cs (83%) create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/KeyHelper.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/MakeItCompile.cs rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeDescriptor.cs => shared/src/PdfSharp.OpenType/OpenTypeFontDescriptor.cs} (60%) create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFace-Helper.cs rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontface.cs => shared/src/PdfSharp.OpenType/OpenTypeFontFace.cs} (74%) create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFaceCache.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFactory-DELETE.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFamily.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFamilyCache.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontRegistry.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontSource.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontSourceCache.cs rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType}/OpenTypeFontTable.cs (78%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType}/OpenTypeFontTables.cs (59%) create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontWriter.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlobals.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphMetrics.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphTypeface.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphTypefaceCache.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/PdfSharp.OpenType.csproj create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/Properties/GlobalDeclarations.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.OpenType/README.md rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType}/TableDirectoryEntry.cs (92%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType/Tables}/IndexToLocationTable.cs (84%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.Internal => shared/src/PdfSharp.OpenType}/UnicodeHelper.cs (83%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType}/enums/FontTechnology.cs (94%) rename src/foundation/src/{PDFsharp/src/PdfSharp/Fonts.OpenType => shared/src/PdfSharp.OpenType}/enums/TableTagNames.cs (99%) create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/PdfSharp.Quality.Ghostscript.csproj create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/PdfToPngConverter.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/README.md create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality/CompareHelper.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality/PdfPngComparer.cs rename src/foundation/src/shared/src/PdfSharp.Quality/{ => Properties}/GlobalUsings.cs (100%) create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality/Testing.TestModel/ObjectModel.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality/Testing.TestModel/ObjectModelTestHelper.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality/Testing/PdfSharpTestBase.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality/Testing/ReadWriteHelper.cs rename src/foundation/src/shared/src/PdfSharp.Quality/{ => Testing}/TestClassBase.cs (95%) create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality/Testing/TrueOrFalse.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Quality/WindowsFonts.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/.gitignore create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/Internal/CS13.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/Internal/ChecksumHelper.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/Internal/GfxMsg.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/Internal/GfxMsgs.cs delete mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/Internal/SemVersionInformation.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/Internal/enum/GfxMsgId.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/WaitForGitVersionInformation.ps1 rename src/foundation/src/shared/src/{PdfSharp.System/System => PdfSharp.Shared/dotnet}/CodeAnalysis.cs (99%) create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/dotnet/CompilerServices1.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/dotnet/CompilerServices2.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.Shared/dotnet/ExceptionExtensions.cs rename src/foundation/src/shared/src/{PdfSharp.System/System/CompilerServices.cs => PdfSharp.Shared/dotnet/GetSubArray.cs} (89%) create mode 100644 src/foundation/src/shared/src/PdfSharp.Snippets/Fonts.Text/TextSamples.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/Diagnostics/DebuggerDisplayHelper.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/BigIntegerExtensions.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/ConcurrentDictionaryExtensions.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/DictionaryExtensions.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/EnumExtensions.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/StringExtensions.cs rename src/foundation/src/{PDFsharp/src/PdfSharp/Internal => shared/src/PdfSharp.System/Internal.Threading}/Locks.cs (52%) create mode 100644 src/foundation/src/shared/src/PdfSharp.System/Internal/AnsiEncoding.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/Logging/LogMessages.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/Logging/PdfSharpLogHost.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/PlugIn/IPdfSharpPlugIn.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/Properties/FloatOrDouble.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/dotnet/CodeAnalysis.cs create mode 100644 src/foundation/src/shared/src/PdfSharp.System/dotnet/CompilerServices2.cs delete mode 100644 src/foundation/src/shared/src/PdfSharp.System/extensions/SystemStringExtensions.cs rename src/foundation/src/shared/{src/PdfSharp.Shared/System => testapps/Shared.TestApp}/CompilerServices.cs (100%) create mode 100644 src/foundation/src/shared/testapps/Shared.TestApp/CompilerServices2.cs rename src/foundation/src/shared/tests/{PdfSharp.Fonts.Test => PdfSharp.Fonts.Tests}/BasicTests.cs (100%) rename src/foundation/src/shared/tests/{PdfSharp.Fonts.Test => PdfSharp.Fonts.Tests}/PdfSharp.Fonts.Test.csproj (88%) rename src/foundation/src/shared/tests/{PdfSharp.Fonts.Test => PdfSharp.Fonts.Tests}/README.md (100%) create mode 100644 src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/FontFaces/BasicTests.cs create mode 100644 src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/PdfSharp.OpenType.Test.csproj create mode 100644 src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/README.md create mode 100644 src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/WpfTestBase.cs create mode 100644 src/foundation/src/shared/tests/Shared.Tests/Quality/PdfToPngConverterTests.cs diff --git a/.editorconfig b/.editorconfig index 9f785dcd..550e3798 100644 --- a/.editorconfig +++ b/.editorconfig @@ -78,6 +78,7 @@ dotnet_style_namespace_match_folder = true:suggestion dotnet_style_prefer_collection_expression = when_types_loosely_match:suggestion dotnet_style_readonly_field = true:suggestion dotnet_style_require_accessibility_modifiers = for_non_interface_members:silent +dotnet_style_allow_multiple_blank_lines_experimental = true:silent [*.cs] csharp_indent_labels = one_less_than_current @@ -111,4 +112,7 @@ csharp_style_deconstructed_variable_declaration = true:suggestion csharp_style_unused_value_assignment_preference = discard_variable:suggestion csharp_style_unused_value_expression_statement_preference = discard_variable:silent csharp_prefer_static_local_function = true:suggestion -csharp_style_prefer_readonly_struct = true:suggestion \ No newline at end of file +csharp_style_prefer_readonly_struct = true:suggestion +csharp_prefer_system_threading_lock = true:suggestion +csharp_prefer_static_anonymous_function = true:suggestion +csharp_style_prefer_readonly_struct_member = true:suggestion \ No newline at end of file diff --git a/.gitignore b/.gitignore index e136e4a4..82a21a82 100644 --- a/.gitignore +++ b/.gitignore @@ -3,9 +3,7 @@ assets/ TestResults/ localtests/ -.nuget/ .build/ -.testPublish/ *.sln.ide/ _ReSharper.*/ .idea/ @@ -15,41 +13,43 @@ PublishProfiles/ .vs/ .vscode/ .idea/ -bower_components/ -node_modules/ +# bower_components/ +# node_modules/ +zip/ debugSettings.json project.lock.json *.log *.user -*.suo +#*.suo *.cache -*.docstates +#*.docstates +*.zip _ReSharper.* nuget.exe -*net45.csproj -*net451.csproj -*k10.csproj -*.psess +#*net45.csproj +#*net451.csproj +#*k10.csproj +#*.psess *.vsp *.pidb *.userprefs *DS_Store -*.ncrunchsolution -nCrunchTemp*.* -*.*sdf -*.ipch -.settings +#*.ncrunchsolution +#nCrunchTemp*.* +#*.*sdf +#*.ipch +#.settings *.sln.ide node_modules # *launchSettings.json *.orig *.nuget.props *.nuget.targets -/localfeed -oldstate.json -InternalAgentState.json +#/localfeed +#oldstate.json +#InternalAgentState.json /nuget.config *.log -*tempfile.* +#*tempfile.* launchsettings.json testEnvironments.json diff --git a/.globalconfig b/.globalconfig new file mode 100644 index 00000000..e0f231a2 --- /dev/null +++ b/.globalconfig @@ -0,0 +1,3 @@ +is_global = true + +dotnet_diagnostic.IDE0290.severity = suggestion diff --git a/PdfSharp.sln b/PdfSharp.sln index 3c82ce5b..b2b46225 100644 --- a/PdfSharp.sln +++ b/PdfSharp.sln @@ -1,7 +1,7 @@  Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio Version 17 -VisualStudioVersion = 17.0.31912.275 +# Visual Studio Version 18 +VisualStudioVersion = 18.0.11205.157 MinimumVisualStudioVersion = 10.0.40219.1 Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{18632E7D-54DF-4933-A8DF-A7ECE8666E9B}" ProjectSection(SolutionItems) = preProject @@ -9,7 +9,11 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{18632E7D-54D src\Directory.Build.targets = src\Directory.Build.targets src\Directory.Packages.props = src\Directory.Packages.props src\dotnet-test.Build.props = src\dotnet-test.Build.props + src\Local.Build.props = src\Local.Build.props + src\Local.Build.props---template = src\Local.Build.props---template src\nuget.config = src\nuget.config + src\PdfSharpBuildConfig.props = src\PdfSharpBuildConfig.props + src\SemVersion.props = src\SemVersion.props EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PDFsharp", "PDFsharp", "{5912CE0D-0DCE-479F-944A-0DFA4CE95A88}" @@ -30,11 +34,12 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "foundation", "foundation", EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{8770F3E1-3FEE-4611-89D4-AF80DFB3B3EA}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "projects", "projects", "{FD3FEC8C-C8C8-47F1-AF08-F77EC937ACFE}" -EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "samples", "samples", "{EF9C1FA7-24B3-438A-BD21-400C1500E164}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "tests", "tests", "{71BD4587-A8B0-4653-A3B2-93578F71ABB7}" + ProjectSection(SolutionItems) = preProject + src\foundation\src\PDFsharp\tests\Directory.Build.props = src\foundation\src\PDFsharp\tests\Directory.Build.props + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Tests", "src\foundation\src\PDFsharp\tests\PdfSharp.Tests\PdfSharp.Tests.csproj", "{0B1C5283-63AC-4B2C-B0B2-D2FFD4BB3BD2}" EndProject @@ -104,7 +109,6 @@ EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{31FE507C-F342-4DA4-9BE5-B3D2ED93E9CC}" ProjectSection(SolutionItems) = preProject .editorconfig = .editorconfig - gitversion-6.x.yml = gitversion-6.x.yml gitversion.yml = gitversion.yml LICENSE = LICENSE README.md = README.md @@ -118,8 +122,14 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Quality", "src\foundation\src\shared\src\PdfSharp.Quality\PdfSharp.Quality.csproj", "{B0BEB589-A3A7-4412-B045-09621FB1DE25}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Snippets", "src\foundation\src\shared\src\PdfSharp.Snippets\PdfSharp.Snippets.csproj", "{E4D95A70-171A-4027-BFCE-AB2B1907C6C1}" + ProjectSection(ProjectDependencies) = postProject + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.WPFonts", "src\foundation\src\shared\src\PdfSharp.WPFonts\PdfSharp.WPFonts.csproj", "{034B233F-0745-476A-A3DB-8DDF8FA0EF9C}" + ProjectSection(ProjectDependencies) = postProject + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} + EndProjectSection EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "features", "features", "{F6F7E411-5CD0-4507-BF59-70004EDD09DA}" EndProject @@ -128,6 +138,9 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MigraDoc.Features", "src\foundation\src\MigraDoc\features\MigraDoc.Features\MigraDoc.Features.csproj", "{05E1F63A-EC20-4280-86A2-7971691DA964}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Features", "src\foundation\src\PDFsharp\features\PdfSharp.Features\PdfSharp.Features.csproj", "{E42F855D-9A4F-45BE-9950-F20A94169568}" + ProjectSection(ProjectDependencies) = postProject + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp-wpf", "src\foundation\src\PDFsharp\src\PdfSharp-wpf\PdfSharp-wpf.csproj", "{5BB33F6F-0151-4D8F-8530-249A9C3B9B23}" EndProject @@ -140,12 +153,24 @@ EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Quality-gdi", "src\foundation\src\shared\src\PdfSharp.Quality-gdi\PdfSharp.Quality-gdi.csproj", "{B0EE3DF4-34E1-4C69-836E-B8911192D3FE}" EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Snippets-wpf", "src\foundation\src\shared\src\PdfSharp.Snippets-wpf\PdfSharp.Snippets-wpf.csproj", "{8F4002CD-E002-4C61-9E7A-0D78875C4767}" + ProjectSection(ProjectDependencies) = postProject + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Snippets-gdi", "src\foundation\src\shared\src\PdfSharp.Snippets-gdi\PdfSharp.Snippets-gdi.csproj", "{76959A2A-8113-430B-8272-DD37F17F9305}" + ProjectSection(ProjectDependencies) = postProject + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDFsharp.Features-wpf", "src\foundation\src\PDFsharp\features\PDFsharp.Features-wpf\PDFsharp.Features-wpf.csproj", "{E19EF259-4C5F-4967-867C-EF3936B264C4}" + ProjectSection(ProjectDependencies) = postProject + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PDFsharp.Features-gdi", "src\foundation\src\PDFsharp\features\PDFsharp.Features-gdi\PDFsharp.Features-gdi.csproj", "{ECCE20D4-000F-4D15-87B5-0335C7B3163A}" + ProjectSection(ProjectDependencies) = postProject + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} + EndProjectSection EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Charting-wpf", "src\foundation\src\PDFsharp\src\PdfSharp.Charting-wpf\PdfSharp.Charting-wpf.csproj", "{2E3778BB-E153-47C6-AD2C-E69F0B242578}" EndProject @@ -236,8 +261,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Fonts", "src\found EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Fonts.TestApp", "src\foundation\src\shared\testapps\PdfSharp.Fonts.TestApp\PdfSharp.Fonts.TestApp.csproj", "{2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Fonts.Test", "src\foundation\src\shared\tests\PdfSharp.Fonts.Test\PdfSharp.Fonts.Test.csproj", "{D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}" -EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.Testing", "src\foundation\src\shared\src\PdfSharp.Testing\PdfSharp.Testing.csproj", "{D17FA884-98B9-41E2-AB27-2603F79000F5}" EndProject Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dev", "dev", "{BA039CC9-132E-4103-BB47-87C3E3398F82}" @@ -251,6 +274,7 @@ Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "dev", "dev", "{BA039CC9-132 dev\run-tests.ps1 = dev\run-tests.ps1 dev\update-local-nuget-packages-debug.ps1 = dev\update-local-nuget-packages-debug.ps1 dev\update-local-nuget-packages-release.ps1 = dev\update-local-nuget-packages-release.ps1 + dev\zip-PDFsharp.ps1 = dev\zip-PDFsharp.ps1 dev\_update-local-nuget-packages.ps1 = dev\_update-local-nuget-packages.ps1 EndProjectSection EndProject @@ -270,10 +294,30 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.BarCodes-wpf", "sr EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PdfSharp.BarCodes", "src\foundation\src\PDFsharp\src\PdfSharp.BarCodes\PdfSharp.BarCodes.csproj", "{F4AB506C-AD13-4383-9AF9-48D74085ECC1}" EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "docs-dummy", "docs\docs-dummy.csproj", "{76B94284-402D-4951-8DA4-7FFAF15E6C95}" -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("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "extensions", "extensions", "{0E0ABA4E-19EE-4D84-8BD7-734AC1BA5AE0}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{A0D471A6-12D0-4F4E-96BE-32B461547028}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Extensions", "src\foundation\src\extensions\src\PdfSharp.Extensions\PdfSharp.Extensions.csproj", "{2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.BuildConfig", "src\foundation\src\shared\src\PdfSharp.BuildConfig\PdfSharp.BuildConfig.csproj", "{7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Quality.Ghostscript", "src\foundation\src\shared\src\PdfSharp.Quality.Ghostscript\PdfSharp.Quality.Ghostscript.csproj", "{59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Imaging", "src\foundation\src\shared\src\PdfSharp.Imaging\PdfSharp.Imaging.csproj", "{22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.OpenType", "src\foundation\src\shared\src\PdfSharp.OpenType\PdfSharp.OpenType.csproj", "{08CB46DA-52D2-AB40-1E42-A8FC799565F8}" +EndProject +Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "PDFsharpGraphics", "PDFsharpGraphics", "{5854C6A1-5FDB-497C-A5DB-74AF372B58BF}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.Fonts.Test", "src\foundation\src\shared\tests\PdfSharp.Fonts.Tests\PdfSharp.Fonts.Test.csproj", "{2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PdfSharp.OpenType.Test", "src\foundation\src\shared\tests\PdfSharp.OpenType.Tests\PdfSharp.OpenType.Test.csproj", "{A7A57937-E309-9E58-A3B4-6B268E6933E7}" +EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "docs-public", "docs\docs-public.csproj", "{B75C5BBF-4110-E576-C755-66CBF94A4CD9}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -524,10 +568,6 @@ Global {2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776}.Debug|Any CPU.Build.0 = Debug|Any CPU {2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776}.Release|Any CPU.ActiveCfg = Release|Any CPU {2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776}.Release|Any CPU.Build.0 = Release|Any CPU - {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}.Debug|Any CPU.Build.0 = Debug|Any CPU - {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}.Release|Any CPU.ActiveCfg = Release|Any CPU - {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14}.Release|Any CPU.Build.0 = Release|Any CPU {D17FA884-98B9-41E2-AB27-2603F79000F5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {D17FA884-98B9-41E2-AB27-2603F79000F5}.Debug|Any CPU.Build.0 = Debug|Any CPU {D17FA884-98B9-41E2-AB27-2603F79000F5}.Release|Any CPU.ActiveCfg = Release|Any CPU @@ -560,14 +600,42 @@ Global {F4AB506C-AD13-4383-9AF9-48D74085ECC1}.Debug|Any CPU.Build.0 = Debug|Any CPU {F4AB506C-AD13-4383-9AF9-48D74085ECC1}.Release|Any CPU.ActiveCfg = Release|Any CPU {F4AB506C-AD13-4383-9AF9-48D74085ECC1}.Release|Any CPU.Build.0 = Release|Any CPU - {76B94284-402D-4951-8DA4-7FFAF15E6C95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {76B94284-402D-4951-8DA4-7FFAF15E6C95}.Debug|Any CPU.Build.0 = Debug|Any CPU - {76B94284-402D-4951-8DA4-7FFAF15E6C95}.Release|Any CPU.ActiveCfg = Release|Any CPU - {76B94284-402D-4951-8DA4-7FFAF15E6C95}.Release|Any CPU.Build.0 = Release|Any CPU {769ED050-15AF-4EB5-A89F-D7123EE5AA95}.Debug|Any CPU.ActiveCfg = Debug|Any CPU {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 + {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D}.Release|Any CPU.Build.0 = Release|Any CPU + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD}.Release|Any CPU.Build.0 = Release|Any CPU + {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}.Debug|Any CPU.Build.0 = Debug|Any CPU + {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}.Release|Any CPU.ActiveCfg = Release|Any CPU + {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD}.Release|Any CPU.Build.0 = Release|Any CPU + {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}.Debug|Any CPU.Build.0 = Debug|Any CPU + {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}.Release|Any CPU.ActiveCfg = Release|Any CPU + {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958}.Release|Any CPU.Build.0 = Release|Any CPU + {08CB46DA-52D2-AB40-1E42-A8FC799565F8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {08CB46DA-52D2-AB40-1E42-A8FC799565F8}.Debug|Any CPU.Build.0 = Debug|Any CPU + {08CB46DA-52D2-AB40-1E42-A8FC799565F8}.Release|Any CPU.ActiveCfg = Release|Any CPU + {08CB46DA-52D2-AB40-1E42-A8FC799565F8}.Release|Any CPU.Build.0 = Release|Any CPU + {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}.Debug|Any CPU.Build.0 = Debug|Any CPU + {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}.Release|Any CPU.ActiveCfg = Release|Any CPU + {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D}.Release|Any CPU.Build.0 = Release|Any CPU + {A7A57937-E309-9E58-A3B4-6B268E6933E7}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {A7A57937-E309-9E58-A3B4-6B268E6933E7}.Debug|Any CPU.Build.0 = Debug|Any CPU + {A7A57937-E309-9E58-A3B4-6B268E6933E7}.Release|Any CPU.ActiveCfg = Release|Any CPU + {A7A57937-E309-9E58-A3B4-6B268E6933E7}.Release|Any CPU.Build.0 = Release|Any CPU + {B75C5BBF-4110-E576-C755-66CBF94A4CD9}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {B75C5BBF-4110-E576-C755-66CBF94A4CD9}.Debug|Any CPU.Build.0 = Debug|Any CPU + {B75C5BBF-4110-E576-C755-66CBF94A4CD9}.Release|Any CPU.ActiveCfg = Release|Any CPU + {B75C5BBF-4110-E576-C755-66CBF94A4CD9}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -579,7 +647,6 @@ Global {E97F1455-620B-4BF2-B839-08F7AE2FD772} = {E1933982-1D12-497F-B465-67D5E5C74CA1} {0EDABDD2-05D5-43D0-A635-645DD8127201} = {18632E7D-54DF-4933-A8DF-A7ECE8666E9B} {8770F3E1-3FEE-4611-89D4-AF80DFB3B3EA} = {0EDABDD2-05D5-43D0-A635-645DD8127201} - {FD3FEC8C-C8C8-47F1-AF08-F77EC937ACFE} = {18632E7D-54DF-4933-A8DF-A7ECE8666E9B} {EF9C1FA7-24B3-438A-BD21-400C1500E164} = {18632E7D-54DF-4933-A8DF-A7ECE8666E9B} {71BD4587-A8B0-4653-A3B2-93578F71ABB7} = {5912CE0D-0DCE-479F-944A-0DFA4CE95A88} {0B1C5283-63AC-4B2C-B0B2-D2FFD4BB3BD2} = {71BD4587-A8B0-4653-A3B2-93578F71ABB7} @@ -662,7 +729,6 @@ Global {284F0281-F283-4EC3-BD60-DD0CD5A5442F} = {CC13B431-6963-480F-8C21-1F78A220A399} {9EC27C83-FCD6-44B8-9BC6-9F270DD58C8E} = {DE458BB2-5942-4588-A74A-AE9CA86F284C} {2D32A2B9-CE4E-4F0D-9A76-1578FC3F4776} = {9A382224-6874-4ED3-8DF6-1DD3466A02CE} - {D5A4EB68-4E71-4AAE-8A77-AACC46A55A14} = {A1AE894D-9061-44F0-8268-328AC41F403A} {D17FA884-98B9-41E2-AB27-2603F79000F5} = {DE458BB2-5942-4588-A74A-AE9CA86F284C} {BA039CC9-132E-4103-BB47-87C3E3398F82} = {31FE507C-F342-4DA4-9BE5-B3D2ED93E9CC} {C7CCB288-8372-41AD-9F65-6224CE0D6358} = {DE458BB2-5942-4588-A74A-AE9CA86F284C} @@ -673,8 +739,18 @@ Global {E6E3A838-95C6-462D-84E4-FAFF69DA95C0} = {7C753636-7947-46E0-95E0-135EAA7BFEB3} {4CBBAD35-0D01-4BDD-8BDF-B6022497938E} = {7C753636-7947-46E0-95E0-135EAA7BFEB3} {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} + {0E0ABA4E-19EE-4D84-8BD7-734AC1BA5AE0} = {8770F3E1-3FEE-4611-89D4-AF80DFB3B3EA} + {A0D471A6-12D0-4F4E-96BE-32B461547028} = {0E0ABA4E-19EE-4D84-8BD7-734AC1BA5AE0} + {2D10539B-9BEC-4D75-980B-B1D4FDFEAB8D} = {A0D471A6-12D0-4F4E-96BE-32B461547028} + {7680C9FD-3FC5-F4CE-1EE4-5E2DB977FAAD} = {DE458BB2-5942-4588-A74A-AE9CA86F284C} + {59171DE5-B6A9-CE9A-7CCA-976878FDAEBD} = {DE458BB2-5942-4588-A74A-AE9CA86F284C} + {22EFBF71-31C3-ACEE-ACDB-F2D9C5EAF958} = {DE458BB2-5942-4588-A74A-AE9CA86F284C} + {08CB46DA-52D2-AB40-1E42-A8FC799565F8} = {DE458BB2-5942-4588-A74A-AE9CA86F284C} + {5854C6A1-5FDB-497C-A5DB-74AF372B58BF} = {8770F3E1-3FEE-4611-89D4-AF80DFB3B3EA} + {2F7D4DC3-BD6A-F9B0-D972-02784FEF887D} = {A1AE894D-9061-44F0-8268-328AC41F403A} + {A7A57937-E309-9E58-A3B4-6B268E6933E7} = {A1AE894D-9061-44F0-8268-328AC41F403A} + {B75C5BBF-4110-E576-C755-66CBF94A4CD9} = {76BA9372-65AE-479C-AEF7-D50E6B486CEF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {D5FF5562-3C79-434B-B951-B84542D01625} diff --git a/PdfSharp.sln.DotSettings b/PdfSharp.sln.DotSettings index 1415191e..d94e93f8 100644 --- a/PdfSharp.sln.DotSettings +++ b/PdfSharp.sln.DotSettings @@ -6,10 +6,12 @@ BSD CF CR + DA IO IV MD OSX + OT SASL TSA URI @@ -23,6 +25,7 @@ Edit True ID + True True True True @@ -32,6 +35,7 @@ True True True + True True True True @@ -40,6 +44,7 @@ True True True + True True True True @@ -67,6 +72,7 @@ True True True + True True True True diff --git a/README.md b/README.md index 5d069c92..703b5e24 100644 --- a/README.md +++ b/README.md @@ -1,9 +1,9 @@ -# PDFsharp & MigraDoc 6 +# PDFsharp & MigraDoc 7 -Version **6.2.4** -Published **2026-01-06** +Version **7.0.0 Preview 1** +Published **2026-03-24** -This is a final version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 6 with updates for C# 12 and .NET 8, .NET 9, and .NET 10. +This is a preview version of the **PDFsharp** project, the main project of PDFsharp & MigraDoc 7 with updates for C# 12 and .NET 8, .NET 9, and .NET 10. PDFsharp: Copyright (c) 2005-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany MigraDoc: Copyright (c) 2001-2026 empira Software GmbH, Troisdorf (Cologne Area), Germany @@ -15,7 +15,7 @@ For more information see [docs.pdfsharp.net](https://docs.pdfsharp.net/) Project documentation can be found on our DOCS site: . -Note: PowerShell 7 is required to execute the PowerShell scripts that come with PDFsharp. +Note that PowerShell 7 is required to execute the PowerShell scripts that come with PDFsharp. ### Download assets first @@ -34,9 +34,10 @@ Execute `dotnet build` should build the solution without any warnings or errors. * You need the latest .NET SDK version installed -* Please note that you need a git repository with at least one commit in order to build the PDFsharp solution. +* ~~Please note that you need a git repository with at least one commit in order to build the PDFsharp solution. Without a git repository with at least one commit, you will get an error message from `GitVersion.MsBuild` while building the solution. - You can set a tag to define a valid version, e.g.: `git tag v6.2.0` to make it build with a specific version number. Without tag, version 0.1.0 will be used. + You can set a tag to define a valid version, e.g.: `git tag v6.2.0` to make it build with a specific version number. Without tag, version 0.1.0 will be used.~~ + (Is now done automatically by BuildConfig) ### Central package management diff --git a/dev/GetAllPDFs.ps1 b/dev/GetAllPDFs.ps1 new file mode 100644 index 00000000..3029108f --- /dev/null +++ b/dev/GetAllPDFs.ps1 @@ -0,0 +1,41 @@ +# Chat/GPT creates this script for me. That is why comments are German. +# param( +# [Parameter(Mandatory = $true)] +# [string]$SourcePath, + +# [Parameter(Mandatory = $true)] +# [string]$DestinationPath +# ) + +TODO verschieben + +$SourcePath = "D:\repos\emp\PDFsharp.iText\itext.tests" +$DestinationPath = "D:\repos\emp\PDFsharp\assets\private\test-files" + +# Zielverzeichnis erstellen, falls es nicht existiert +if (-not (Test-Path -Path $DestinationPath)) { + New-Item -ItemType Directory -Path $DestinationPath | Out-Null +} + +# Alle PDF-Dateien rekursiv suchen +$files = Get-ChildItem -Path $SourcePath -Recurse -Filter *.pdf + +foreach ($file in $files) { + # Dateiname ermitteln + $destFile = Join-Path $DestinationPath $file.Name + + # Falls Datei schon existiert → eindeutigen Namen erzeugen + if (Test-Path $destFile) { + $baseName = [System.IO.Path]::GetFileNameWithoutExtension($file.Name) + $extension = $file.Extension + $counter = 1 + do { + $newName = "$baseName`_$counter$extension" + $destFile = Join-Path $DestinationPath $newName + $counter++ + } while (Test-Path $destFile) + } + + # Datei kopieren + Copy-Item -Path $file.FullName -Destination $destFile +} diff --git a/dev/README.md b/dev/README.md index 370831f3..af9f5f16 100644 --- a/dev/README.md +++ b/dev/README.md @@ -15,3 +15,9 @@ Builds NuGet packages for release on NuGet.org. ## run-tests Run unit tests on Windows and WSL2. + +## set-en.ps1 +Sets UI language for dotnet build etc. to en-US. + +## zip-PDFsharp.ps1 +Creates a PDFsharp.zip file. diff --git a/dev/_update-local-nuget-packages.ps1 b/dev/_update-local-nuget-packages.ps1 index 943e2410..7121d72b 100644 --- a/dev/_update-local-nuget-packages.ps1 +++ b/dev/_update-local-nuget-packages.ps1 @@ -22,21 +22,70 @@ $b = "`e[94m" $r = "`e[0m" # Set working directory to solution root. -Push-Location $PSScriptRoot -Push-Location .\.. +Push-Location $PSScriptRoot\.. -Write-Host "Copy all `e[93m$($config.ToUpperInvariant())$r NuGet packages to $b$nugetLocal$r." -New-Item -Path $nugetLocal -ItemType directory -Force | Out-Null -$packages = @() -Get-ChildItem -Path . -Filter *.nupkg -Recurse -ErrorAction SilentlyContinue -Force | ForEach-Object { - if ($_.FullName -match "bin\\$config|bin\/$config") { - Copy-Item $_.FullName -Destination ("$nugetLocal\" + $_.Name) - $packages += $_.Name +# Machine local NuGet packages +# Do not copy packages into user profile folder NugetFolder +# if .nuget-local does not exist. +if (Test-Path -Path $nugetLocal) { + # User has a local NuGet directory. +} else { + $nugetLocal = "" +} + +# Project local NuGet packages (if PDFsharp is used as submodule) +# Do not copy packages into .nuget directory if PDFsharp is not used +# as a submodule and therefore .nuget does not exist in project directory. +$nugetProject = "..\..\.nuget" # because of this: YOUR-PROJECT/modules/PDFsharp +if (Test-Path -Path $nugetProject) { + # PDFsharp is a submodule and the outer project has a .nuget directory. + $nugetProject = $nugetProject +} else { + $nugetProject = "" +} + +$hasNugetLocal = $nugetLocal.Length -gt 0 +$hasNugetProject = $nugetProject.Length -gt 0 + +if ($hasNugetLocal -or $hasNugetProject) +{ + if ($hasNugetLocal -and $hasNugetProject) + { + $targetFolders = "$b$nugetLocal$r and $b$nugetProject$r" + } + elseif ($hasNugetLocal) + { + $targetFolders = "$b$nugetLocal$r" } + elseif ($hasNugetProject) + { + $targetFolders = "$b$nugetProject$r" + } + + Write-Output "Copy all `e[93m$($config.ToUpperInvariant())$r NuGet packages to $targetFolders." + # Do not force creation of directory anymore. + # New-Item -Path $nugetLocal -ItemType directory -Force | Out-Null + $packages = @() + Get-ChildItem -Path . -Filter *.nupkg -Recurse -ErrorAction SilentlyContinue -Force | ForEach-Object { + if ($_.FullName -match "bin\\$config|bin\/$config") { + if ($nugetLocal.Length -gt 0) { + Copy-Item $_.FullName -Destination ("$nugetLocal\" + $_.Name) + } + if ($nugetProject.Length -gt 0) { + Copy-Item $_.FullName -Destination ("$nugetProject\" + $_.Name) + } + $packages += $_.Name + } + } + Write-Output " $b$($packages.Count)$r packages copied." } -Write-Host " $b$($packages.Count)$r packages copied." +else +{ + Write-Output "No `e[93m$($config.ToUpperInvariant())$r NuGet packages are copied as there is no .nuget-local and no main module .nuget folder." +} + -Write-Host "Delete all existing old package folders in $b$nuget$r folder." +Write-Output "Delete all existing old package folders in $b$nuget$r folder." $count = 0 $versions = @() $packages | ForEach-Object { @@ -63,10 +112,11 @@ $packages | ForEach-Object { } } } -Write-Host " $b$count$r package $($deleteAllPackageVersions ? '' : 'version ')folders deleted in $b$nuget$r." +Write-Output " $b$count$r package $($deleteAllPackageVersions ? '' : 'version ')folders deleted in $b$nuget$r." if ($packages.Count -gt 0) { - Write-Host " New version number(s) : $b$Versions$r" + # In case you build several times and do not delete the artifacts + # you can have more than one set of NuGet packages + Write-Output " The new version number(s) are: $b$Versions$r" } Pop-Location -Pop-Location diff --git a/dev/build-local-nuget-packages-release.ps1 b/dev/build-local-nuget-packages-release.ps1 index 14be2328..7992933c 100644 --- a/dev/build-local-nuget-packages-release.ps1 +++ b/dev/build-local-nuget-packages-release.ps1 @@ -1,4 +1,16 @@ -# Updates local nuget packages. +<# +.SYNOPSIS + Build PDFsharp with config release and updates the local nuget packages. + +.DESCRIPTION + The script deletes all artifacts and builds a PDFsharp release version. + It first builds the PdfSharp.BuildConfig.csproj to ensure that SemVersion.props and + PDFsharpBuildConfig.props are up-to-date before the build process starts. + The created packages are copied to two locations. + The first is the .nuget-local directory in your user profile directory. + The second is the .nuget directory in the root folder of your project in case + you used PDFsharp as a submodule. +#> #Requires -Version 7 #Requires -PSEdition Core @@ -9,31 +21,35 @@ param ( Push-Location $PSScriptRoot -try { - Write-Host "Delete bin and obj " $deleteBinAndObj +try { + # Write-Output "Delete bin and obj " $deleteBinAndObj if ($deleteBinAndObj) { - Write-Host "Deleting BIN and OBJ" - .\del-bin-and-obj.ps1 - Write-Host "Done deleting bin and obj" + Write-Output "Deleting ‘/bin’ and ‘/obj’..." + .\del-bin-and-obj.ps1 | Out-Null + Write-Output "Done." } Push-Location .. try { - Write-Host "Invoking ‘dotnet build’" - dotnet build -c release + Write-Output "Invoking ‘dotnet build’ for ‘PdfSharp.BuildConfig.csproj’" + # Generate semver infos and PDFsharp build configuration first. + dotnet build .\src\foundation\src\shared\src\PdfSharp.BuildConfig\PdfSharp.BuildConfig.csproj + Write-Output "Invoking ‘dotnet build’ for PDFsharp solution" + dotnet build --configuration release $build = $LASTEXITCODE - Write-Host "‘dotnet build’ has finished" + Write-Output "‘dotnet build’ has finished" } finally { Pop-Location } if ($build -gt 0) { - Write-Host "‘dotnet build’ failed with code " $build + Write-Error "‘dotnet build’ failed with code " $build throw "‘dotnet build’ failed with code " + $build } - .\update-local-nuget-packages-release.ps1 + Write-Output "Invoking ‘update-local-nuget-packages-release.ps1’" + .\update-local-nuget-packages-release.ps1 } finally { Pop-Location diff --git a/dev/del-bin-and-obj.ps1 b/dev/del-bin-and-obj.ps1 index 335f017d..fc9c7ec6 100644 --- a/dev/del-bin-and-obj.ps1 +++ b/dev/del-bin-and-obj.ps1 @@ -4,5 +4,14 @@ #Requires -PSEdition Core Push-Location $PSScriptRoot\..\ -Get-ChildItem .\ -include bin,obj -Recurse | ForEach-Object ($_) { remove-item $_.fullname -Force -Recurse } +if (Test-Path .\src\SemVersion.props) { + Remove-Item .\src\SemVersion.props + & .\src\foundation\src\shared\src\PdfSharp.BuildConfig\CreateBuildConfiguration.ps1 +} +Write-Output "Deleting..." +Get-ChildItem .\ -include bin,obj -Recurse | +ForEach-Object ($_) { + Remove-Item $_.fullname -Force -Recurse | Out-Null +} +Write-Output "Done." Pop-Location diff --git a/dev/download-assets.ps1 b/dev/download-assets.ps1 index 6f9db34b..f9d5dd8b 100644 --- a/dev/download-assets.ps1 +++ b/dev/download-assets.ps1 @@ -23,9 +23,9 @@ $source = "https://assets.pdfsharp.net/" $destination = "$PSScriptRoot/../assets/" if (test-path -PathType container $destination) { - Remove-Item -LiteralPath $destination -Force -Recurse + Remove-Item -LiteralPath $destination -Force -Recurse | Out-Null } -New-Item -ItemType Directory -Path $destination +New-Item -ItemType Directory -Path $destination | Out-Null # Download assets version. $url = $source + ".assets-version" @@ -38,7 +38,7 @@ foreach ($asset in $assetList) { $dest = $destination + $asset $folder = [IO.Path]::GetDirectoryName($dest) - New-Item -ItemType Directory -Path $folder -Force + New-Item -ItemType Directory -Path $folder -Force | Out-Null Invoke-WebRequest $url -OutFile $dest @@ -56,8 +56,8 @@ foreach ($asset in $assetList) { $source = "https://assets.pdfsharp.net/" $destination = "$PSScriptRoot/../assets/fonts/Noto/Noto_Sans/static/" -New-Item -ItemType Directory -Path $destination -New-Item -ItemType Directory -Path "$destination/temp/" +New-Item -ItemType Directory -Path $destination | Out-Null +New-Item -ItemType Directory -Path "$destination/temp/" | Out-Null $url = $source + "fonts/Noto/Noto_Sans.zip" $dest = $destination diff --git a/dev/init-pdfsharp-repository.ps1 b/dev/init-pdfsharp-repository.ps1 new file mode 100644 index 00000000..c950826b --- /dev/null +++ b/dev/init-pdfsharp-repository.ps1 @@ -0,0 +1,21 @@ +# Initialize PDFsharp repository after a fresh git clone. + +#Requires -Version 7 +#Requires -PSEdition Core + +Push-Location $PSScriptRoot/.. + +# Download latest assets. TODO +# ./dev/download-assets.ps1 + ./dev/del-bin-and-obj.ps1 + +# Create correct git version information by producing +# SemVersion.props and SemVersionInformation-generated.cs. +dotnet build .\src\foundation\src\shared\src\PdfSharp.GitVersion\PdfSharp.GitVersion.csproj + +# Build the first time DEBUG and RELEASE +# Compile for testing +dotnet build +dotnet build --configuration release + +Pop-Location diff --git a/dev/run-tests.ps1 b/dev/run-tests.ps1 index 685d0982..5ecbe06e 100644 --- a/dev/run-tests.ps1 +++ b/dev/run-tests.ps1 @@ -4,15 +4,15 @@ .DESCRIPTION The script builds the solution located in the script’s root parent folder and runs 'dotnet test' for all libraries to test, that are found via its projects. - These tests are run in the following environment, as far as available: Windows with NET8 or NET10, Windows with NET462 and Linux/WSL (NET8 or NET10). + These tests are run in the following environment, as far as available: Windows with NET8 or NET6, Windows with NET462 and Linux/WSL (NET8 or NET6). For each environment, libraries not to be run (like WPF in Linux or Linux-targeting DLLs in Windows) are excluded from testing. The test results are displayed in tables per library / code base comparing the test results in the different environments. .PARAMETER Config Specifies the configuration to build and test the solution ("Debug" or "Release"). "Debug" is the default. -.PARAMETER Net10 - Specifies whether NET10 shall be tested instead of NET8. $False is the default. +.PARAMETER Net6 + Specifies whether NET6 shall be tested instead of NET8. $False is the default. .PARAMETER SkipBuild Specifies whether the build of the solution shall be skipped. $False is the default. @@ -68,7 +68,7 @@ BUG: Allow to run all tests, including GBE. param ( [Parameter(Mandatory = $false)] [string]$Config = 'Debug', - [Parameter(Mandatory = $false)] [bool]$Net10 = $false, + [Parameter(Mandatory = $false)] [bool]$Net6 = $false, [Parameter(Mandatory = $false)] [bool]$SkipBuild = $false, [Parameter(Mandatory = $false)] [bool]$RunAllTests = $false ) @@ -77,7 +77,7 @@ $script:SystemNameWindows = "Windows" $script:SystemNameLinux = "Linux" $script:SystemNameWsl = "WSL" $script:NetName462 = "net462" -$script:NetName10 = "net10" +$script:NetName6 = "net6" $script:NetName8 = "net8" @@ -179,9 +179,9 @@ function InitializeScript() } Write-Output "" - if ($script:Net10) + if ($script:Net6) { - Write-Output "NET10 Tests will be run instead of NET8." + Write-Output "NET6 Tests will be run instead of NET8." Write-Output "" } @@ -231,8 +231,8 @@ function CheckNetRuntime($isWsl) $wslOrLocal = "the local machine" } - if ($script:Net10) { - $netMajorVersion = 10; + if ($script:Net6) { + $netMajorVersion = 6; } else { $netMajorVersion = 8; @@ -285,22 +285,20 @@ function LoadTestDllInfos() $testDllInfos = $dllInfos | Where-Object { $_.IsTestDll } - # If Net10 parameter is true, remove net8 DLLs. - if ($script:Net10) + # If Net6 parameter is true, remove net8 DLLs. + if ($script:Net6) { $testDllInfos = $testDllInfos | Where-Object ` { $_.TargetFramework.Contains("net8") -eq $false - $_.TargetFramework.Contains("net9") -eq $false } } - # If Net10 parameter is false, remove net10 DLLs. + # If Net6 parameter is false, remove net6 DLLs. else { $testDllInfos = $testDllInfos | Where-Object ` { - $_.TargetFramework.Contains("net10") -eq $false - $_.TargetFramework.Contains("net9") -eq $false + $_.TargetFramework.Contains("net6") -eq $false } } @@ -599,11 +597,11 @@ function RunTestsForSystem($testDllInfos, $systemName, $isHostedWsl) # Gets the name of the environment for the given system and framework. function GetEnvironmentName($systemName, $targetFramework) { - if ($script:Net10 -and $targetFramework.Contains("net10")) + if ($script:Net6 -and $targetFramework.Contains("net6")) { - $frameworkName = $script:NetName10 + $frameworkName = $script:NetName6 } - elseif ($script:Net10 -eq $false -and $targetFramework.Contains("net8")) + elseif ($script:Net6 -eq $false -and $targetFramework.Contains("net8")) { $frameworkName = $script:NetName8 } @@ -619,13 +617,13 @@ function GetEnvironmentName($systemName, $targetFramework) # HACK: For Linux the frameworkName shall not be shown. if ($systemName -eq $script:SystemNameCurrentLinux) { - if ($script:Net10 -and $frameworkName -ne "net10") + if ($script:Net6 -and $frameworkName -ne "net6") { - Write-Error ("For Linux there’s only one column supported (net10) by test script with Net10 parameter set to true.") + Write-Error ("For Linux there’s only one column supported (net6) by test script with Net6 parameter set to true.") } - elseif ($script:Net10 -eq $false -and $frameworkName -ne "net8") + elseif ($script:Net6 -eq $false -and $frameworkName -ne "net8") { - Write-Error ("For Linux there’s only one column supported (net8) by test script with Net10 parameter set to false.") + Write-Error ("For Linux there’s only one column supported (net8) by test script with Net6 parameter set to false.") } return "$systemName" } @@ -670,9 +668,9 @@ function LoadAndShowTestResults() Write-Output "TestResults" Write-Output "==================================================" - if ($script:Net10) + if ($script:Net6) { - $netNameX = $script:NetName10 + $netNameX = $script:NetName6 } else { diff --git a/dev/zip-PDFsharp.ps1 b/dev/zip-PDFsharp.ps1 new file mode 100644 index 00000000..0e8f414e --- /dev/null +++ b/dev/zip-PDFsharp.ps1 @@ -0,0 +1,38 @@ +# Compresses all files of PDFsharp into a PDFsharp.zip file + +#Requires -Version 7 +#Requires -PSEdition Core + +Push-Location "$PSScriptRoot/.." + +try { + # $root = "$PSScriptRoot/.." + Remove-Item zip -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item PDFsharp.zip -Force -ErrorAction SilentlyContinue + # New-Item -ItemType Directory -Force -Path $Root/PDFsharp.zip + New-Item -ItemType Directory -Force -Path zip + + # I tried an hour or so to do it with Get-Childitem. No way, I finally use robocopy... + # robocopy . $root/zip *.* /dcopy:t /e /xa:h /xd .git /xd "bin" /xd "obj" /xd assets /xd zip + robocopy . zip/PDFsharp *.* /dcopy:t /e /xa:h /xd .git /xd .vs /xd .vscode /xd "bin" /xd "obj" /xd assets /xd zip ` + /xd TestResults /xd localtests ` + /xf debugSettings.json /xf *.user /xf *.userprefs /xf launchsettings.json /xf testEnvironments.json + # robocopy from to *.* /dcopy:t /e /xa:h /xd "System Volume Information" /xd "$RECYCLE*" + + # Delete generated files + Remove-Item zip/PDFsharp/src/SemVersion.props -ErrorAction SilentlyContinue + Remove-Item zip/PDFsharp/src/PdfSharpBuildConfig.props -ErrorAction SilentlyContinue + Remove-Item zip/PDFsharp/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SSemVersionInformation-generated.cs -ErrorAction SilentlyContinue + + # Get-ChildItem zip -Recurse | measure + + Push-Location "zip" + Compress-Archive -Path PDFsharp -DestinationPath ../PDFsharp.zip -CompressionLevel Fastest -Force + Pop-Location + + # Remove-Item $root/zip -Recurse -Force -ErrorAction SilentlyContinue + Remove-Item zip -Recurse -Force -ErrorAction SilentlyContinue +} +finally { + Pop-Location +} diff --git a/docs/Chars.md b/docs/Chars.md deleted file mode 100644 index 03ebbe84..00000000 --- a/docs/Chars.md +++ /dev/null @@ -1,16 +0,0 @@ -# Characters - - “” „“ ’ ‘’ ‚‘ »« ›‹ – - · × ² ³ ½ € † … - ✔ ✘ ↯ ± − × ÷ ⋅ √ ≠ ≤ ≥ ≡ - ® © ← ↑ → ↓ ↔ ↕ ∅ - ✔⇒ - - Shaker´'´`’ás - -# Use of typographic quotation marks - -“English” | „German“ | ‘English’ | ‚German‘ - -Lower left single quotation mark vs comma: ‚ vs , -The glyph looks different e.g. in Segoe UI. diff --git a/docs/MigraDoc/AboutLineSpacing.md b/docs/MigraDoc/AboutLineSpacing.md deleted file mode 100644 index 466c1850..00000000 --- a/docs/MigraDoc/AboutLineSpacing.md +++ /dev/null @@ -1,33 +0,0 @@ -# Line spacing - -This article is about the definition of line spacing in MigraDoc. - -## Line positioning - -The line positioning, depending on the line spacing, should be identical to the positioning done by Microsoft Word. - -The baseline is calculated as follows. - -### Relative line spacings - -For a line’s baseline position in a paragraph using the **Single**, **OnePtFive**, **Double** or **Multiple** line spacing, -the **maximum** of the **paragraph’s baseline** and all **FormattedText’s baselines** is used. - -All baselines used for calculation are the result of the used **font size multiplied with the given line spacing value**. - -### Absolute line spacings - -#### LineSpacingRule.AtLeast - -For a line’s baseline position in a paragraph using the **AtLeast** line spacing, -the **maximum** of the **paragraph’s baseline** and all **FormattedText’s baselines** is used. - -All baselines used for calculation are the **maximum** of the **used font size** and the **paragraph’s line spacing value**. - -#### LineSpacingRule.Exactly - -For a line’s baseline position in a paragraph using the **Exactly** line spacing, -**only the paragraph’s font and line spacing** are considered. -This way, FormattedTexts with a larger font size have no impact on the line’s baseline position. - -The baseline is calculated by using the **paragraph’s font baseline scaled to the paragraph’s line spacing**. diff --git a/docs/MigraDoc/change-log/MD-v6.3.0-log.md b/docs/MigraDoc/change-log/MD-v6.3.0-log.md new file mode 100644 index 00000000..01a8aab3 --- /dev/null +++ b/docs/MigraDoc/change-log/MD-v6.3.0-log.md @@ -0,0 +1,19 @@ +# MigraDoc 6.3.0 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`. + +## What’s new in version 6.3 + +**This file is not yet up-to-date.** + +### Breaking changes + +*No changes in this version.* + +### Features + +*No changes in this version.* + +### Issues + +*No changes in this version.* diff --git a/docs/MigraDoc/change-log/MD-v6.4.0-log.md b/docs/MigraDoc/change-log/MD-v6.4.0-log.md new file mode 100644 index 00000000..526fa482 --- /dev/null +++ b/docs/MigraDoc/change-log/MD-v6.4.0-log.md @@ -0,0 +1,20 @@ +# MigraDoc 6.4.0 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`. + +## What’s new in version 6.4 + +**This file is not yet up-to-date.** + +### Breaking changes + +**PdfDate**??? +{according to PDFsharp 6.4.0 change log} + +### Features + +*No changes in this version.* + +### Issues + +*No changes in this version.* diff --git a/docs/MigraDoc/change-log/MD-v7.0.0-preview-log.md b/docs/MigraDoc/change-log/MD-v7.0.0-preview-log.md new file mode 100644 index 00000000..c0737d05 --- /dev/null +++ b/docs/MigraDoc/change-log/MD-v7.0.0-preview-log.md @@ -0,0 +1,23 @@ +# MigraDoc 7.0.0 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **MigraDoc** `History.md`. + +## What’s new in version 7.0 + +**This file is not yet up-to-date.** + +### Breaking changes + +**PdfDate** +PdfDate is now based on DateTimeOffset. See PDFsharp 7.0.0 change log for details. + +**MigraDocRenderingBuildInformation removed** +Use class **SemVersionInformation** or **PdfSharp.Capabilities.Build** to get most of the values. + +### Features + +*No changes in this version.* + +### Issues + +*No changes in this version.* diff --git a/docs/MigraDoc/design/PDF-A/PDF-A-Fundamentals.md b/docs/MigraDoc/design/PDF-A/PDF-A-Fundamentals.md deleted file mode 100644 index 7bd34ef9..00000000 --- a/docs/MigraDoc/design/PDF-A/PDF-A-Fundamentals.md +++ /dev/null @@ -1,7 +0,0 @@ -# MigraDoc PDF/A fundamentals - -MigraDoc should optionally produce PDF/A-conforming PDF documents. - -## Based on PDFsharp - -Configure Renderer according to PDFsharp. diff --git a/docs/MigraDoc/design/PDF-UA/PDF-UA-Fundamentals.md b/docs/MigraDoc/design/PDF-UA/PDF-UA-Fundamentals.md deleted file mode 100644 index 7baabae7..00000000 --- a/docs/MigraDoc/design/PDF-UA/PDF-UA-Fundamentals.md +++ /dev/null @@ -1,22 +0,0 @@ -# PDF/UA fundamentals - -MigraDoc should optionally produce PDF/UA-conforming PDF documents. - -## Based on PDFsharp - -Configure Renderer according to PDFsharp. - -## DOM - -DOM needs some extensions - -* Language tag for - **Document**, **Paragraph**, **FormattedText** - but not for - **Section**, **Table**, etc. They inherit from **Document**. - -* Alternate text for images - -## Render process - -Because DOM well-defines the structure tree, it should be an easy top-down implementation. diff --git a/docs/PDFsharp/change-log/PS-v6.3.0-log.md b/docs/PDFsharp/change-log/PS-v6.3.0-log.md new file mode 100644 index 00000000..1b7baa9f --- /dev/null +++ b/docs/PDFsharp/change-log/PS-v6.3.0-log.md @@ -0,0 +1,46 @@ +# PDFsharp 6.3.0 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`. + +## What’s new in version 6.3 + +**This file is not yet up-to-date.** + +### Breaking changes + +**PdfDictionary enumerator** +=> PdfItem? -> PdfItem + +**xxxx** +xxxx. + +### Features + +* PDF object model revised +* + +**PDF/A enhancements** +* PdfAManager + +**File embedding** +* **FileManager** + +**XMP metadata** +* xxx + +**PdfDate** +**PdfDate** now based on **DateTimeOffset** internally and corrects minor issues caused by +differences between .NET and .NET Framework. + +**PdfName** +**PdfName** now based on new class **Name** and fixed some minor issues related to UTF-8 encoding, +escaping delimiter characters, and handling of the empty name. + +**PdfDictionary** +Now implements IEnumerable> and not +IEnumerable> anymore. + +### Issues + +**PdfGraphicsState fixes** +Usually not relevant as the error affected only drawing when starting with transparent strokes and fills. (GitHub #281) diff --git a/docs/PDFsharp/change-log/PS-v6.4.0-log.md b/docs/PDFsharp/change-log/PS-v6.4.0-log.md new file mode 100644 index 00000000..701db4ae --- /dev/null +++ b/docs/PDFsharp/change-log/PS-v6.4.0-log.md @@ -0,0 +1,57 @@ +# PDFsharp 6.4.0 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`. + +## What’s new in version 6.4 + +**This file is not yet up-to-date.** + +### Breaking changes + +**PdfDictionary enumerator** +=> PdfItem? -> PdfItem + +**PdfDate** +* PdfDate is now based on DateTimeOffset +* Value is now nullable in getters +* tostring returns "" if null +* Still trailing ‘'’ if not ‘Z’. +* Change in MigraDoc accordingly + +**PdfRectangle** +* GetRectangle is nullable* + +**minor changes** +* Some PDF annotations lost their default constructor because the constructor now requires a PDF document parameter. + + +### Features + +* PDF object model revised +* + +**PDF/A enhancements** +* PdfAManager + +**File embedding** +* **FileManager** + +**XMP metadata** +* preserve, update + +**PdfDate** +**PdfDate** now based on **DateTimeOffset** internally and corrects minor issues caused by +differences between .NET and .NET Framework. Fix in 6.3.0 + +**PdfName** +**PdfName** now based on new class **Name** and fixed some minor issues related to UTF-8 encoding, +escaping delimiter characters, and handling of the empty name. + +**PdfDictionary** +Now implements IEnumerable> and not +IEnumerable> anymore. + +### Issues + +**PdfGraphicsState fixes** +Usually not relevant as the error affected only drawing when starting with transparent strokes and fills. (GitHub #281) diff --git a/docs/PDFsharp/change-log/PS-v7.0.0-preview-log.md b/docs/PDFsharp/change-log/PS-v7.0.0-preview-log.md new file mode 100644 index 00000000..04ecd057 --- /dev/null +++ b/docs/PDFsharp/change-log/PS-v7.0.0-preview-log.md @@ -0,0 +1,80 @@ +# PDFsharp 7.0.0 change log + +A copy of the text below this line is added to `docs.pdfsharp.net` **PDFsharp** `History.md`. + +## What’s new in version 7.0 + +### Breaking changes + +**PdfDictionary enumerator** +Item is no longer nullable, so **PdfItem?** became **PdfItem**. + +**PdfRectangle** changes +**PdfRectangle.Empty** is now obsolete and throws an exception. +Use `new PdfRectangle()` to initialize empty values and to compare if a result of a call is the empty rectangle. + +**MediaBox**, **CropBox**, **BleedBox**, **ArtBox**, **TrimBox** changes +Now obsolete: MediaBoxReadOnly, CropBoxReadOnly, BleedBoxReadOnly, ArtBoxReadOnly, TrimBoxReadOnly. +Use **HasMediaBox**, **HasCropBox**, etc. to check if the boxes have been set. +Use **EffectiveCropBoxReadOnly**, **EffectiveTrimBoxReadOnly**, etc. to get the effective value. + +**PdfDate** +* PdfDate is now based on DateTimeOffset +* Value is now nullable in getters +* ToString returns "" if null +* Still trailing ‘'’ if not ‘Z’ +* Changed in MigraDoc accordingly + +**PdfRectangle** +* GetRectangle is nullable + +**PdfAnnotation** +* Keys were moved from the base class **PdfAnnotation** to the specific class where they belong. + For example, the `/A` key can now be found under `PdfLinkAnnotation.Keys.A`. + Other keys were moved to the **PdfMarkupAnnotation** class. + +**Minor changes** +* Some PDF annotation classes lost their default constructor because the constructor now requires a PDF document parameter. +* Class **PdfRubberStampAnnotation** renamed to **PdfStampAnnotation**. +* Enum **PdfRubberStampAnnotationIcon** renamed to **PdfStampAnnotationIcons**. +* Enum **PdfTextAnnotationIcon** renamed to **PdfTextAnnotationIcons**. + +### Features + +* PDF object model revised. +* PDFsharp is now more forgiving when opening certain non-conforming PDF files. + +**PDF/A enhancements** +* PdfAManager + +**PDF Forms enhancements** +* New classes to support Forms and form elements +* PsX Forms makes using Forms much easier, see https://www.pdfsharp.com/Offers + +**File embedding** +* New **FileManager** + +**PDF Annotations enhancements** +* New classes to support Annotations +* Outlook: PsX Annotations (coming soon) will make using annotations much easier, see https://www.pdfsharp.com/Offers + +**XMP metadata** +* New class MetadataManager +* DocumentMetadataStrategy: KeepExisting, AutoGenerate, UserGenerated, NoMetadata + +**PdfDate** +**PdfDate** now based on **DateTimeOffset** internally and corrects minor issues caused by +differences between .NET and .NET Framework. Fixed in 6.3.0 + +**PdfName** +**PdfName** now based on new class **Name** and fixed some minor issues related to UTF-8 encoding, +escaping delimiter characters, and handling of the empty name. + +**PdfDictionary** +Now implements IEnumerable> and not +IEnumerable> anymore. + +### Issues + +**PdfGraphicsState fixes** +Usually not relevant as the error affected only drawing when starting with transparent strokes and fills. (GitHub #281) diff --git a/docs/PDFsharp/design/FontSeletion/FontResolver.md b/docs/PDFsharp/design/FontSeletion/FontResolver.md deleted file mode 100644 index 801b8aff..00000000 --- a/docs/PDFsharp/design/FontSeletion/FontResolver.md +++ /dev/null @@ -1,45 +0,0 @@ -# Font selection - -Font selection is done by a **FontResolver**. - -## Windows platforms - -The GDI+ and the WPF builds use GDI+ / WPF functionality to select a font -if no Custom FontResolver is set. If a Custom FontResolver is set, it is always used. -The behavior is the same for .NET and .NET Framework. - -The Core build for .NET Framework runs only under Windows and therefore it uses the -WindowsFontResolver (see below) if no Custom FontResolver is set. - -The Core build for .NET executed under Windows uses the -WindowsFontResolver (see below) if no Custom FontResolver is set. - -### The Windows Font - -## Non-Windows platforms - -The Core build for .NET always needs a Custom FontResolver. - -### WSL2 - -PDFsharp prior to version 6.2 uses the `C:\Windows\Fonts` folder. -We removed this behavior. - -### Desktop Linux - -A Linux distribution with UI has fonts installed, but there is no (or at least no easy) -way to locate them on a particular distribution. - -### Mac - -We have no Macs at empira Software. -You must write your own FontResolver. - -### Linux Docker Container - -A Linux distribution for a Docker Container may not provide fonts. -You must write your own FontResolver. - -### Mobile devices, Rasperry Pi, etc. - -You must write your own FontResolver. diff --git a/docs/PDFsharp/design/Issues.md b/docs/PDFsharp/design/Issues.md deleted file mode 100644 index defc067e..00000000 --- a/docs/PDFsharp/design/Issues.md +++ /dev/null @@ -1,10 +0,0 @@ -# Issues - -Design issues in PDFsharp to ponder on. - -## TLS for imported documents - -Importing a PDF document depends on TLS. Thread affinity may be problematic since we -have an execution context. -It may be better to save the weak document handles in the Globals and protect access with -a semaphore. But another global lock may lead to undiscoverable dead-lock situations. diff --git a/docs/PDFsharp/design/PDF-A/PDF-A-Fundamentals.md b/docs/PDFsharp/design/PDF-A/PDF-A-Fundamentals.md deleted file mode 100644 index 871f6b9c..00000000 --- a/docs/PDFsharp/design/PDF-A/PDF-A-Fundamentals.md +++ /dev/null @@ -1,47 +0,0 @@ -# PDF/A fundamentals - -PDFsharp should optionally produce PDF/A-conforming documents. - -## PDF/A, structure tree, and PDF/UA - -Ob der Structure Tree in PDF/A benötigt wird, ist von der gewählten Konformitätsstufe abhängig: -* Stufe A (Accessible) benötigt den Structure Tree erwartungsgemäß. -* Stufe B (Basic) kommt ohne Structure Tree aus. -* Stufe U (Unicode) kommt wahrscheinlich auch ohne Structure Tree aus, habe ich bisher aber nicht überprüft. - -Die Standards und Konformitätsstufen sind unter [PDFA-kompakt-20.pdf](https://pdfa.org/wp-content/uploads/2013/05/PDFA-kompakt-20.pdf) auf Seite 8 gut erklärt. - -In Word kann man unabhängig von PDF/A auswählen, ob das Dokument barrierefrei sein soll: -Bei der obigen Auswahl erzeugt Word ein Dokument, in dem kein StructTreeRoot zu finden ist. In den Metadaten steht 3B und veraPDF validiert es entsprechend mit PDF/A-3B. -Mit zusätzlichem Häkchen bei der Barrierefreiheit erzeugt Word ein Dokument mit StructTreeRoot. In den Metadaten steht 3A und veraPDF validiert es entsprechend mit PDF/A-3A. - -## Proposal - -Mein Vorschlag wäre, dass wir uns hier an Word orientieren und ebenfalls die Varianten PDF/A-3B und PDF/A-3A anbieten. Bei Auswahl von PDF/A-3A würde dann der UAManager genutzt und das PDF auch gleich PDF/UA-konform, bei PDF/A-3B nicht. - -### PDF/A-3B - -Basic level. - -Shall we support - -### PDF/A-3A - -### PDF/A-4* - -Published in 2020 based on PDF 2.0. - -## MigraDoc - -MigraDoc shall use PDFsharp for all PDF/A requirements. -See [MigraDoc PDF/A fundamentals](../../../MigraDoc/design/PDF-A/PDF-A-Fundamentals.md). - -## Links -[PDF/A Wikipedia](https://en.wikipedia.org/wiki/PDF/A) · -[veraPDF](https://verapdf.org/) · -[pdfa.org](https://pdfa.org/archival-pdf/) · -[PDFA-kompakt-20.pdf](https://pdfa.org/wp-content/uploads/2013/05/PDFA-kompakt-20.pdf) · -[PDFA-forever_1b.pdf](https://pdfa.org/wp-content/uploads/2011/08/PDFA-forever_1b.pdf) · -[PDF/A-3 Compliance Issues](https://www.soliddocuments.com/iso-19005-3-compliance.htm) · -[PDF/A-2 Compliance Issues](https://www.soliddocuments.com/iso-19005-2-compliance.htm) · -[PDF/A-1 Compliance Issues](https://www.soliddocuments.com/iso-19005-1-compliance.htm) diff --git a/docs/PDFsharp/design/PdfReader/StringsAndNames.md b/docs/PDFsharp/design/PdfReader/StringsAndNames.md deleted file mode 100644 index 5e63b8f6..00000000 --- a/docs/PDFsharp/design/PdfReader/StringsAndNames.md +++ /dev/null @@ -1,27 +0,0 @@ -# String parsing - -Parsing strings has grown over the years and needs to be reviewed. -Collect issues about strings and names here. - -## Names - -* Spec says a name can contain any character, but parsers generally use e.g. backslash as a delimiter. - -## Strings in PDF objects - -Check the following: - -* Octal characters. Are illegal octal numbers (8 and 9) handled correctly? -* Escape characters. PDF specs states that the reverse solidus must be ignored. -* Nesting of '(' and ')'. Is it always handled correctly? - Should we optimize Writer if parenthesis occurrences are leveled equally? Currently we always escape parenthesis. -* Hex strings <…> - * Is encryption always identified correctly? - * Can it be encoded big endian? Check how Acrobat handles such strings. - * How to decided if it is 8 or 16 bit. Digest is 8 bit, Unicode is 16 bit. - * Can it contain glyph indices outside a content stream? - -## Strings in content streams - -* `(…)` string. Is this ASCII only? -* `<…>` string. Is this Glyph ID only? diff --git a/docs/PDFsharp/design/Signatures/CertificateCreation.md b/docs/PDFsharp/design/Signatures/CertificateCreation.md deleted file mode 100644 index 291f65c4..00000000 --- a/docs/PDFsharp/design/Signatures/CertificateCreation.md +++ /dev/null @@ -1,53 +0,0 @@ -# Certificate creation - -This file introduces how OpenSSL can be used to create self-signed certificates that can be used to sign PDF files. - -## Creating self-signed certificates - -OpenSSL is one option to create certificates for signing. - -### Self-signed RSA certificates - -For testing, you can easily create an RSA certificate using three calls to OpenSSL: - -* Generating a private key -* Generating the self signed certificate -* Create a PFX file containing certificate and private key - -Here is code that creates an RSA certificate with a 2048-bits key: - -```pwsh -openssl genrsa 2048 > private.pem -openssl req -days 32767 -x509 -new -key private.pem -out public.pem -openssl pkcs12 -export -in public.pem -inkey private.pem -out mycert.pfx -``` - -The *-days* parameter specifies how long the certificate will be valid. For some use cases you should not exceed 397 days. - -Note that the three statements above will prompt for input like location, company name, and passwords. - -### Self-signed DSA certificates - -Theoretically, you can create DSA certificates using four calls to OpenSSL. Practically, we did not have success and all DSA certificates we created were rejected by Windows as invalid. - -The code is very similar to the RSA case, but specifying the key length requires am extra OpenSSL call to create a parameter file. - -```pwsh -openssl dsaparam 2048 > dsaparam.txt -openssl gendsa dsaparam.txt > private.pem -openssl req -days 32767 -x509 -new -key private.pem -out public.pem -openssl pkcs12 -export -in public.pem -inkey private.pem -out mycert.pfx -``` - -Please let us know what we were doing wrong if you manage to create DSA certificates using OpenSSL. Thanks in advance. - -### Automization of certificate creation - -OpenSSL will prompt for information while creating the certificate. -To avoid this, the *subj* parameter can be used: - -```txt --subj "/C=US/ST=Washington/L=Seattle/O=John Doe Ltd./OU=PR/CN=John Doe" -``` - -Alternatively, you can write the answers into a text file and redirect input to that file. diff --git a/docs/PDFsharp/docs/Documentation.md b/docs/PDFsharp/docs/Documentation.md new file mode 100644 index 00000000..c6506086 --- /dev/null +++ b/docs/PDFsharp/docs/Documentation.md @@ -0,0 +1,14 @@ +# Notes to source code documentation + +## PDFsharp PDF classes + +// Reference 2.0: 12.5.2 Annotation dictionaries / Page 467 +// Reference 2.0: xxx / Page xxx + +// Reference 2.0: Table 166 — Entries common to all annotation dictionaries / Page 467 +// Reference 2.0: xxx / Page xxx + +// ReSharper disable InconsistentNaming + +// ReSharper restore InconsistentNaming + diff --git a/docs/change-log/gn-v6.2.0-preview-log.md b/docs/change-log/gn-v6.2.0-preview-log.md index 4796d252..c513a61f 100644 --- a/docs/change-log/gn-v6.2.0-preview-log.md +++ b/docs/change-log/gn-v6.2.0-preview-log.md @@ -26,7 +26,7 @@ For better performance, set 'publiclyVisible' to true when creating the MemorySt #### PDFsharp issues **Lexer.ScanNumber and CLexer.ScanNumber** -Based on a bug that crashes PDFsharp if a number in a PDFfile has to much leading zeros, we revised the code of **Lexer.ScanNumber** and **CLexer.ScanNumber**. +Based on a bug that crashes PDFsharp if a number in a PDFfile has too many leading zeros, we revised the code of **Lexer.ScanNumber** and **CLexer.ScanNumber**. **No more commas allowed in XUnit** An old hack allows **XUnit** to be assigned from a string that uses a comma as decimal separator. diff --git a/docs/change-log/gn-v6.3.0-log.md b/docs/change-log/gn-v6.3.0-log.md new file mode 100644 index 00000000..17280c7f --- /dev/null +++ b/docs/change-log/gn-v6.3.0-log.md @@ -0,0 +1,33 @@ +# 6.3.0 change log + +This text is copied to `docs.pdfsharp.net`. + +## What’s new in version 6.3 + +**This file is not yet up-to-date.** + + +### Breaking changes + +* From 6.2 to 6.3 needs a recompile. + +### General features + +* GitVersion build task replaced for better build time. +* Packages for .NET 9. +* 6.4: Implementation for .NET 9 that does not use obsolete APIs. +* DownloadAssets is invoked automatically during building. Requires Internet access. +* 6.4: `PDFsharpBuildFewerFrameworks` build property to speed up development by compiling against .NET 8 only. +* 6.4: `PDFsharpRunSlowTests` build property to include slow unit tests. + +**.NET 9** replaces **.NET 6** +We added .NET 9 (`net9.0`) to ``. +We removed .NET 6 because it is no longer supported by Microsoft. Projects using .NET 6 can still use the current version. + + +#### General issues + +* TODO diff --git a/docs/change-log/gn-v7.0.0-preview-log.md b/docs/change-log/gn-v7.0.0-preview-log.md new file mode 100644 index 00000000..3a2de2e9 --- /dev/null +++ b/docs/change-log/gn-v7.0.0-preview-log.md @@ -0,0 +1,34 @@ +# 7.0.0 change log + +This text is copied to `docs.pdfsharp.net`. + +## What’s new in version 7.0 + +**This file is not yet up-to-date.** + + +### Breaking changes + +* From 6.x to 7.0 needs a recompile. + +### General features + +* GitVersion build task replaced for better build time. +* Packages for .NET 9 and .NET 10. +* 6.4: Implementation for .NET 9 that does not use obsolete APIs. +* DownloadAssets is invoked automatically during building. Requires Internet access. +* 6.4: `PDFsharpBuildFewerFrameworks` build property to speed up development by compiling against .NET 10 only. +* 6.4: `PDFsharpRunSlowTests` build property to include slow unit tests. +* Code review: many classes were refactored to achieve cleaner code with improved maintainability. + +**.NET 9** replaces **.NET 6** +We added .NET 9 (`net9.0`) and .NET 10 to ``. +We removed .NET 6 because it is no longer supported by Microsoft. Projects using .NET 6 can still use the current version. + + +#### General issues + +* TODO diff --git a/docs/coding/DevNotes.md b/docs/coding/DevNotes.md index 03fca80f..4c5f3ae1 100644 --- a/docs/coding/DevNotes.md +++ b/docs/coding/DevNotes.md @@ -6,13 +6,13 @@ Just here before being moved to an appropriate place. ## Temporary tags -Temporary tags are allowed in develop branch, but must be removed in releases. +Temporary tags are allowed in develop branch, but must be removed in (final) releases. Create a unit test that fails if a temporary tag is in the C# code. ### TODO -**Here is something to be done __before the project is released__.** +**Here is something to be done __before the project is finally released__.** ### BUG @@ -55,7 +55,7 @@ documentation or reference purposes and shall not be removed. ### IMPROVE -Here is code that substantially works but has potential for improvements +Here is correct code that substantially works but has potential for improvements for better reliability. **Example** @@ -78,7 +78,9 @@ Here is code that should be covered by (more) unit tests. Remove all this stuff from existing code. * Hack: -* Note: +* Note: + Write "Note that ..." + Do not delete it in text pasted from the PDF Reference. * Magic: * ToDo * UNDONE diff --git a/docs/development/DevelopmentWithWSL.md b/docs/development/WSL/DevelopmentWithWSL.md similarity index 76% rename from docs/development/DevelopmentWithWSL.md rename to docs/development/WSL/DevelopmentWithWSL.md index 6286b060..6d289996 100644 --- a/docs/development/DevelopmentWithWSL.md +++ b/docs/development/WSL/DevelopmentWithWSL.md @@ -27,6 +27,11 @@ It requires you to set up a launchSettings.json in the properties folder of the Afterwards, you can choose the profile when starting the project. +When added for at least one project, Visual Studio may or may not show the WSL launching option for other projects +and add it automatically when choosing WSL. +However, adding the file to a project when needed is recommended. + +You can **copy the launchsettings.template.json** to Properties/launchSettings.json in your project for a quick start. ## Testing in WSL @@ -53,6 +58,8 @@ It requires you to set up a testenvironments.json in the root of the solution, w Afterwards, you can choose the test environment in Test Explorer. +You can **copy the testenvironments.template.json** to testenvironments.json in the solution root folder for a quick start. + ### Run run-tests script With the run-test.ps1 in the dev folder to run all tests in in all available environments, including WSL. diff --git a/docs/development/WSL/launchsettings.template.json b/docs/development/WSL/launchsettings.template.json new file mode 100644 index 00000000..2f189c12 --- /dev/null +++ b/docs/development/WSL/launchsettings.template.json @@ -0,0 +1,11 @@ +{ + "profiles": { + "Windows": { + "commandName": "Project" + }, + "WSL": { + "commandName": "WSL", + "distributionName": "" + } + } +} \ No newline at end of file diff --git a/docs/development/WSL/testenvironments.template.json b/docs/development/WSL/testenvironments.template.json new file mode 100644 index 00000000..65570029 --- /dev/null +++ b/docs/development/WSL/testenvironments.template.json @@ -0,0 +1,17 @@ +{ + "version": "1", + "environments": [ + // See https://aka.ms/remotetesting for more details + // about how to configure remote environments. + { + "name": "WSL", + "type": "wsl", + "wslDistribution": "Ubuntu" + }, + //{ + // "name": "Docker dotnet/sdk", + // "type": "docker", + // "dockerImage": "mcr.microsoft.com/dotnet/sdk" + //} + ] +} \ No newline at end of file diff --git a/docs/docs-dummy.csproj b/docs/docs-public.csproj similarity index 81% rename from docs/docs-dummy.csproj rename to docs/docs-public.csproj index 424fd82e..5152857c 100644 --- a/docs/docs-dummy.csproj +++ b/docs/docs-public.csproj @@ -3,7 +3,7 @@ - net8.0 + net10.0 diff --git a/docs/publishing/BeforeReleases.md b/docs/publishing/BeforeReleases.md deleted file mode 100644 index 24489fdb..00000000 --- a/docs/publishing/BeforeReleases.md +++ /dev/null @@ -1,128 +0,0 @@ -# Before releases - -Before you follow the instructions from the Azure DevOps wiki how to publish PDFsharp, -complete the following tasks. - -## General todos - -### Update the assembly build version - -Set the assembly build version in file `gitversion.yml` to the number of days from -January 1st 2005 to the release day. **This is essential for users that use intaller tools.** - -```yml -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 9999}' -``` - -### Update and sync used NuGet packages - -Update version numbers of NuGet packages used in PDFsharp code (like `MicroSoft.Extensions.Logging`) and -build tools (xunit, gitversion, …). -Do this with `"Manage NuGet Packages for Solution…"` to see what’s new and update the version numbers -manually in `Directory.Packages.props`. - -Sync `Directory.Packages.props` with all other solutions, like `PDFsharp.Samples` etc. - -### Check specific files - -* Search for **`CHECK_BEFORE_RELEASE`** and verify the code at these places. -* Check `Directory.Build.targets` - * `USE_LONG_SIZE` must be defined in a release or preview version. - * `TEST_CODE_xxx` must be 'undefined' by the suffix `_xxx`. - -### Run all tests - -* Run all tests in both RELEASE and DEBUG configurations. -* Run all tests, including all skipped tests, under Windows and WSL2 with `run-tests` (see below). -* TODO: Run all tests in a Linux Docker Container - -#### Run run-test script - -Execute `run-test.ps1` with the following parameters: - -1. -Config \: Run the script once with "Debug" and once with "Release". -2. -Net6 \: Set it to $false to run tests for Net8. -3. -SkipBuild \: Set it to $false to build the solution first. -4. -RunAllTests \: Set it to $true to run even slow tests. - -Run `help .\dev\run-tests.ps1` for more information. - -So this is the call for the DEBUG build using .NET 8: - -```PWSH -.\dev\run-tests.ps1 Debug $false $false $true -``` - -And this is the call for the RELEASE build using .NET 8: - -```PWSH -.\dev\run-tests.ps1 Release $false $false $true -``` - -## Update files and configurations - -### Update .md files - -* Check and update [README.md](../README.md) - -### Update NuGet configuration - -* Check/update BoilerplateText.md file - transfer changes to all NuGet projects -* Check/update referenced projects in NuGet .csproj files -* Check/update referenced NuGet packages in .nuspec files -* Check/update text files -* Check packages with NuGet Package Explorer - -## Test other repositories - -Rebuild both DEBUG and RELEASE and update local NuGet packages. - -### GBE test - -Use ComparePdf (part of PDFsharp.Lab) with GBE to compare the "!!TestResult.pdf" of the last release with the current one: - -```PWSH -.\comparepdfs -f "{PathToFolderOfOldFile}\!!TestResult.pdf" "{PathToFolderOfNewFile}\!!TestResult.pdf" -t "{TargetFolder}" --unique-target -n "{ReportName}" -o -``` - -The folder with the report will be created in `{TargetFolder}`. -Due to `--unique-target`, the current date and time will be appended to the target folder and the PDF filename `{ReportName}`. -The report will be opened after creation, because `-o` is set. - -Run `.\comparepdfs --help` for more information. - -### PDFsharp IssueSubmissionTemplate - -* Build solution -* Run the applications - -### PDFsharp.Samples - -* Build solution -* Run the script that executes all samples - -### PDFsharp.Tests - -* Build solution -* Run the tests - -### PDFsharp.Lab - -* Build solution -* Run the script that executes all samples - -### PDFsharp.MAUI - -* Build solution -* Run the script that executes all samples - -### PDFsharp.Blazor - -* Build solution -* Run the script that executes all samples - -### Test with QBX - -With Qbx.LayouterComparer in AkutQBX.sln you can automatically generate and compare a desired set of the example files with a desired set of layouters with this and a referenced QBX version. -Usually you will run Qbx.LayouterComparer in the updated QBX repository and reference a repository clone with the last QBX commit before the update to the new PDFsharp version as head. -Simply adjust the folders in the Program.cs and comment out not desired files and layouters. diff --git a/docs/publishing/BoilerplateText.md b/docs/publishing/BoilerplateText.md deleted file mode 100644 index 87ab7c65..00000000 --- a/docs/publishing/BoilerplateText.md +++ /dev/null @@ -1,82 +0,0 @@ -# Boilerplate text - -** UNDER CONSTRUCTION ** - -## PDFsharp - -PDFsharp Project - PDFsharp -PDFsharp Library - PDFsharp -MigraDoc Library - Library - - -Source -``` -// PDFsharp - A .NET library for processing PDF -``` - -**Description** -``` -PDFsharp is the Open Source library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents. -``` - - -PDFsharp is a .NET library for creating and manipulating PDF documents. It provides a simple API for generating PDF documents from scratch or modifying existing ones. It can be used in a variety of applications, such as generating invoices, reports, or other types of documents. - - - -Stefan -Describe PDFsharp in 3 sentences -ChatGPT -PDFsharp is a .NET library for creating and manipulating PDF documents. It provides a simple API for generating PDF documents from scratch or modifying existing ones. It can be used in a variety of applications, such as generating invoices, reports, or other types of documents. - -Thomas -Describe PDFsharp in 3 simple sentences. -ChatGPT -PDFsharp is a library for creating and modifying PDF documents using .NET. It has an easy-to-use API that allows developers to generate or modify PDF files programmatically. PDFsharp can be used for various applications, including creating reports, invoices, and other types of documents. - -Stefan -Describe PDFsharp in 3 simple sentences for women. -ChatGPT -PDFsharp is a tool that helps you create and edit PDF documents using .NET. With its simple interface, you can easily generate or modify PDF files for a variety of purposes. Whether you need to create reports, invoices, or other types of documents, PDFsharp can help you get the job done. - -Stefan -Describe PDFsharp in 3 sentences for software developers. -ChatGPT -PDFsharp is a .NET library that provides an easy-to-use API for creating and manipulating PDF documents. It supports a wide range of features such as text, images, graphics, and interactive elements. With PDFsharp, developers can quickly generate or modify PDF files for various purposes in their applications. - - -## MigraDoc - -``` -// MigraDoc - Creating Documents on the Fly -``` - -``` -MigraDoc is a .NET library that allows developers to create documents such as PDF and RTF using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications. -``` - -Stefan -Describe MigraDoc in 3 simple sentences. -ChatGPT -MigraDoc is a .NET library that allows developers to create documents such as PDF, RTF, and HTML using a high-level object model. It provides an intuitive API that simplifies the creation of documents, including text formatting, tables, images, and more. MigraDoc is widely used for generating reports, invoices, and other types of documents in various applications. - - -**Release notes** - -Note: Do not replicate information from the metadata (like "6.0.0" or "prerelease"). - -PDFsharp: -This is a version of PDFsharp compatible with .NET 6 and higher. -The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’. -The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’. -We also publish packages that contain PDFsharp plus MigraDoc. -See the project docs site for further information: -https://docs.pdfsharp.net/link/readme-v6.2.html - - -PDFsharp & MigraDoc: -This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher. -The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net6.0’, ‘net8.0’ and ‘netstandard2.0’. -The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net6.0-windows’, ‘net8.0-windows’ and ‘net462’. -See the project docs site for further information: -https://docs.pdfsharp.net/link/readme-v6.2.html diff --git a/docs/publishing/MakeNewReleaseNotes.md b/docs/publishing/MakeNewReleaseNotes.md deleted file mode 100644 index cafa700c..00000000 --- a/docs/publishing/MakeNewReleaseNotes.md +++ /dev/null @@ -1,31 +0,0 @@ -# Files to check - -[README.md](..\README.md) - -* Test on Linux web server - -## Nuget packages - -[PDFsharp.NuGet README.md](..\src\foundation\nuget\src\PDFsharp.NuGet\README.md) -[PDFsharp.NuGet Description.txt](..\src\foundation\nuget\src\PDFsharp.NuGet\Description.txt) -[PDFsharp.NuGet ReleaseNotes.txt](..\src\foundation\nuget\src\PDFsharp.NuGet\ReleaseNotes.txt) - -[PDFsharp.NuGet-gdi README.md](..\src\foundation\nuget\src\PDFsharp.NuGet-gdi\README.md) -[PDFsharp.NuGet-gdi Description.txt](..\src\foundation\nuget\src\PDFsharp.NuGet-gdi\Description.txt) -[PDFsharp.NuGet-gdi ReleaseNotes.txt](..\src\foundation\nuget\src\PDFsharp.NuGet-gdi\ReleaseNotes.txt) - -[PDFsharp.NuGet-wpf README.md](..\src\foundation\nuget\src\PDFsharp.NuGet-wpf\README.md) -[PDFsharp.NuGet-wpf Description.txt](..\src\foundation\nuget\src\PDFsharp.NuGet-wpf\Description.txt) -[PDFsharp.NuGet-wpf ReleaseNotes.txt](..\src\foundation\nuget\src\PDFsharp.NuGet-wpf\ReleaseNotes.txt) - -[MigraDoc.NuGet README.md](..\src\foundation\nuget\src\MigraDoc.NuGet\README.md) -[MigraDoc.NuGet Description.txt](..\src\foundation\nuget\src\MigraDoc.NuGet\Description.txt) -[MigraDoc.NuGet ReleaseNotes.txt](..\src\foundation\nuget\src\MigraDoc.NuGet\ReleaseNotes.txt) - -[MigraDoc.NuGet-gdi README.md](..\src\foundation\nuget\src\MigraDoc.NuGet-gdi\README.md) -[MigraDoc.NuGet-gdi Description.txt](..\src\foundation\nuget\src\MigraDoc.NuGet-gdi\Description.txt) -[MigraDoc.NuGet-gdi ReleaseNotes.txt](..\src\foundation\nuget\src\MigraDoc.NuGet-gdi\ReleaseNotes.txt) - -[MigraDoc.NuGet-wpf README.md](..\src\foundation\nuget\src\MigraDoc.NuGet-wpf\README.md) -[MigraDoc.NuGet-wpf Description.txt](..\src\foundation\nuget\src\MigraDoc.NuGet-wpf\Description.txt) -[MigraDoc.NuGet-wpf ReleaseNotes.txt](..\src\foundation\nuget\src\MigraDoc.NuGet-wpf\ReleaseNotes.txt) diff --git a/gitversion-6.x.yml b/gitversion-6.x.yml deleted file mode 100644 index ce850d85..00000000 --- a/gitversion-6.x.yml +++ /dev/null @@ -1,50 +0,0 @@ -# Last update: 25-11-17 -assembly-versioning-scheme: MajorMinorPatch -# PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE -# Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; -# C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7675}' -strategies: [Mainline] -assembly-informational-format: '{InformationalVersion}' -branches: - develop: # Current development branch - # branch: develop -- is always 'develop' - # version: {6.0.0}-develop.123 -- taken from git tag plus 'develop' plus commit count - regex: ^develop$ - mode: ContinuousDeployment - increment: None - label: develop - # Investigate this currently undocumented breaking change when switching to v6.x. - # The statement may not be needed. - # Not in v6.x: prevent-increment-of-merged-branch-version: false - # Not working: prevent-increment.of-merged-branch: false - # Not working: prevent-increment.when-branch-merged: false - track-merge-target: true - source-branches: ['feature', 'release'] - release: # Release and preview versions. - # branch: release/6.0.0-preview-3 -- must be same as git tag without leading 'v'. - # version: {6-0-0-preview-3} -- taken from git tag only - regex: ^(release[/-]|master) - # Must not have mode set to be mainline? - increment: None - label: '' - # Not in v6.x: prevent-increment-of-merged-branch-version: true - track-merge-target: true - is-release-branch: true - is-main-branch: true - source-branches: ['develop', 'release', 'feature'] - feature: # Features and bug fixes. - # branch: feature/my-new-feature -- arbitrary name, e.g. a new preview - # version: {6.0.0}-dev-{my-new-feature}.123 -- taken from git tag plus 'dev-' plus branch name plus commit count - regex: ^(user|feature|fix)[/-] - mode: ContinuousDeployment - increment: None - label: 'dev-{BranchName}' - # Not in v6.x: prevent-increment-of-merged-branch-version: true - track-merge-target: true - source-branches: ['develop', 'release', 'feature'] - pull-request: - regex: ^(pull|pull\-requests|pr)[/-] - label: PullRequest - mode: ContinuousDeployment -merge-message-formats: {} diff --git a/gitversion.yml b/gitversion.yml deleted file mode 100644 index caa8c9f2..00000000 --- a/gitversion.yml +++ /dev/null @@ -1,46 +0,0 @@ -# Last update: 25-11-17 -assembly-versioning-scheme: MajorMinorPatch -# PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE -# Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; -# C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); -assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7675}' -mode: Mainline -assembly-informational-format: '{NuGetVersion}' -branches: - develop: # Current development branch - # branch: develop -- is always 'develop' - # version: {6.0.0}-develop.123 -- taken from git tag plus 'develop' plus commit count - regex: ^develop$ - mode: ContinuousDeployment - increment: None - tag: develop - prevent-increment-of-merged-branch-version: false - track-merge-target: true - source-branches: ['feature', 'release'] - release: # Release and preview versions. - # branch: release/6.0.0-preview-3 -- must be same as git tag without leading 'v'. - # version: {6-0-0-preview-3} -- taken from git tag only - regex: ^(release[/-]|master) - # Must not have mode set to be mainline? - increment: None - tag: '' - prevent-increment-of-merged-branch-version: true - track-merge-target: true - is-release-branch: true - is-mainline: true - source-branches: ['develop', 'release', 'feature'] - feature: # Features and bug fixes. - # branch: feature/my-new-feature -- arbitrary name, e.g. a new preview - # version: {6.0.0}-dev-{my-new-feature}.123 -- taken from git tag plus 'dev-' plus branch name plus commit count - regex: ^(user|feature|fix)[/-] - mode: ContinuousDeployment - increment: None - tag: 'dev-{BranchName}' - prevent-increment-of-merged-branch-version: true - track-merge-target: true - source-branches: ['develop', 'release', 'feature'] - pull-request: - regex: ^(pull|pull\-requests|pr)[/-] - tag: PullRequest - mode: ContinuousDeployment -merge-message-formats: {} diff --git a/src/.gitignore b/src/.gitignore new file mode 100644 index 00000000..4381b38b --- /dev/null +++ b/src/.gitignore @@ -0,0 +1,5 @@ +# Ignore generated props file +SemVersion.props +Local.Build.props +PdfSharpBuildConfig.props +PdfSharpBuildConfig.targets diff --git a/src/Directory.Build.props b/src/Directory.Build.props index 3aeeb07c..e253e286 100644 --- a/src/Directory.Build.props +++ b/src/Directory.Build.props @@ -1,49 +1,140 @@ + false true + true enable enable - NU1507 + + + + $(NoWarn);0507;2070;2087 false + + false + false + false + true true - + + false + + + + false + + + - latest *.ncrunchproject;*.DotSettings + + + + + + + + + + + net8.0;net9.0;net10.0;netstandard2.0 + net8.0;net9.0;net10.0 + net8.0;net9.0;net10.0;net462 + net8.0;net9.0;net10.0 + net8.0-windows;net9.0-windows;net10.0-windows;net462 + net8.0-windows;net9.0-windows;net10.0-windows + + + + + net8.0;net9.0;net10.0;net472 + net8.0-windows;net9.0-windows;net10.0-windows;net472 + + + net8.0;net9.0;net10.0;netstandard2.0 + net8.0-windows;net9.0-windows;net10.0-windows;net462 + + + + $(DefineConstants);FEWER_FRAMEWORKS + + + false + + + .Fewer + + + net10.0 + net10.0 + net10.0 + net10.0 + net10.0-windows + net10.0-windows + + + net10.0 + net10.0 + net10.0-windows + + + net10.0 + net10.0-windows + + + + $(DefineConstants);RUN_SLOW_TESTS + + + + PDFsharp empira Software - © 2026 empira + © 2026 empira Software GmbH + © 2005-2026 empira Software GmbH + © 2001-2026 empira Software GmbH + PDFsharp Team empira Software GmbH + + 1032 + 00240000048000009400000006020000002400005253413100040000010001008794e803e566eccc3c9181f52c4f7044e5442cc2ce3cbba9fc11bc4186ba2e446cd31deea20c1a8f499e978417fad2bc74143a4f8398f7cf5c5c0271b0f7fe907c537cff28b9d582da41289d1dae90168a3da2a5ed1115210a18fdae832479d3e639ca4003286ba8b98dc9144615c040ed838981ac816112df3b5a9e7cab4fbb + + + + + + - false + + + $(SemVer) - - true + + + false - - - runtime; build; native; contentfiles; analyzers; buildtransitive - all - - diff --git a/src/Directory.Build.targets b/src/Directory.Build.targets index 3e0c2628..76ac5f3e 100644 --- a/src/Directory.Build.targets +++ b/src/Directory.Build.targets @@ -1,7 +1,11 @@ - - $(DefineConstants);USE_LONG_SIZE;TEST_CODE_xxx + + + + + $(DefineConstants);USE_LONG_SIZE;TEST_CODExxx;PRESERVE_PARSED_VALUESxxx;PDFSHARP_DEBUGxxx + \ No newline at end of file diff --git a/src/Directory.Packages.props b/src/Directory.Packages.props index 91654390..dcb63a2f 100644 --- a/src/Directory.Packages.props +++ b/src/Directory.Packages.props @@ -1,41 +1,44 @@ - 8.0.3 - 8.0.1 - 8.0.1 + 10.0.5 + 10.0.5 + 10.0.5 + 10.0.5 - - + + - - - + + + + + + + + + + - - - - + + + + + - + - - - - - - - - - + + + - + + - \ No newline at end of file diff --git a/src/Local.Build.props---template b/src/Local.Build.props---template new file mode 100644 index 00000000..711e64c0 --- /dev/null +++ b/src/Local.Build.props---template @@ -0,0 +1,17 @@ + + + + + + true + + + + + false + + diff --git a/src/foundation/nuget/src/Directory.Build.props b/src/foundation/nuget/src/Directory.Build.props index 6b12a5c6..79d9d34d 100644 --- a/src/foundation/nuget/src/Directory.Build.props +++ b/src/foundation/nuget/src/Directory.Build.props @@ -29,6 +29,7 @@ + -DEBUG $(NuspecProperties);id=$(NuGetId) $(NuspecProperties);title=$(NuGetTitle) $(NuspecProperties);config=$(Configuration) @@ -39,12 +40,16 @@ $(NuspecProperties);owners=$(Owners) $(NuspecProperties);projectUrl=$(NuGetProjectUrl) $(NuspecProperties);copyright=$(Copyright) + $(NuspecProperties);CopyrightPdfSharp=$(CopyrightPdfSharp) + $(NuspecProperties);CopyrightMigraDoc=$(CopyrightMigraDoc) $(NuspecProperties);releaseNotes=$(NuGetReleaseNotes) $(NuspecProperties);tags=$(NuGetTags) $(NuspecProperties);NetCore_PackageVersion=$(NetCore_PackageVersion) $(NuspecProperties);Logging_Abstractions_PackageVersion=$(Logging_Abstractions_PackageVersion) $(NuspecProperties);Cryptography_PackageVersion=$(Cryptography_PackageVersion) + + $(NuspecProperties);pdfsharpnugetpackagetag=$(PDFsharpNuGetPackageTag) + + false True ..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt index c59d5227..52986d6f 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/Description.txt @@ -3,3 +3,7 @@ This package relies on Windows Forms (GDI+) and can be used under Windows only. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.Fewer.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.Fewer.nuspec new file mode 100644 index 00000000..c6cde112 --- /dev/null +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.Fewer.nuspec @@ -0,0 +1,46 @@ + + + + $id$ + $version$ + $description$ + $summary$ + $authors$ + $owners$ + $projectUrl$ + MIT + images\MigraDoc-128x128.png + README.md + false + $releaseNotes$ + $CopyrightMigraDoc$ + $tags$ + + $title$ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj index a6c95fb0..1314974d 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_NuGet_Window) true false @@ -12,8 +12,9 @@ true true - $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec + $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec PDFsharp-MigraDoc-GDI + $(NuGetId)-DEBUG PDFsharp plus MigraDoc for Windows Forms apps $(DebugBuildMessage)%0D$(NuGetDescription) Creating Documents on the Fly. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec index aaa3f15a..1dc9c55f 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/MigraDoc.NuGet-gdi.nuspec @@ -13,7 +13,7 @@ README.md false $releaseNotes$ - $copyright$ + $CopyrightMigraDoc$ $tags$ $title$ @@ -21,22 +21,22 @@ - + - + - + - + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/README.md b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/README.md index 1b3e37de..b260225d 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/README.md +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/README.md @@ -5,7 +5,3 @@ MigraDoc is a .NET library that allows developers to create documents such as PD This package relies on Windows Forms (GDI+) and can be used under Windows only. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. - -See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt index bdb0512f..4616f1fe 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-gdi/ReleaseNotes.txt @@ -1,5 +1,5 @@ -This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher. -The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. -The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. +This is a version of PDFsharp and MigraDoc compatible with .NET 8 and higher. +The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’. +The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’. See the project docs site for further information: -https://docs.pdfsharp.net/link/readme-v6.2.html +https://docs.pdfsharp.net/link/readme-v7.0.html diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt index ff527946..92f0069a 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/Description.txt @@ -3,3 +3,7 @@ This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.Fewer.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.Fewer.nuspec new file mode 100644 index 00000000..fbc5ba01 --- /dev/null +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.Fewer.nuspec @@ -0,0 +1,46 @@ + + + + $id$ + $version$ + $description$ + $summary$ + $authors$ + $owners$ + $projectUrl$ + MIT + images\MigraDoc-128x128.png + README.md + false + $releaseNotes$ + $CopyrightMigraDoc$ + $tags$ + + $title$ + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj index b4b3a187..7b4dae43 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_NuGet_Window) true false @@ -12,8 +12,9 @@ true true - $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec + $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec PDFsharp-MigraDoc-WPF + $(NuGetId)-DEBUG PDFsharp plus MigraDoc for Windows WPF apps $(DebugBuildMessage)%0D$(NuGetDescription) Creating Documents on the Fly. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec index 3604cad9..01adbf30 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/MigraDoc.NuGet-wpf.nuspec @@ -13,7 +13,7 @@ README.md false $releaseNotes$ - $copyright$ + $CopyrightMigraDoc$ $tags$ $title$ @@ -21,22 +21,22 @@ - + - + - + - + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/README.md b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/README.md index 19de2b70..2aa1a171 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/README.md +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/README.md @@ -5,7 +5,3 @@ MigraDoc is a .NET library that allows developers to create documents such as PD This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. - -See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt index bdb0512f..4616f1fe 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet-wpf/ReleaseNotes.txt @@ -1,5 +1,5 @@ -This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher. -The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. -The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. +This is a version of PDFsharp and MigraDoc compatible with .NET 8 and higher. +The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’. +The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’. See the project docs site for further information: -https://docs.pdfsharp.net/link/readme-v6.2.html +https://docs.pdfsharp.net/link/readme-v7.0.html diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt b/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt index 78d56e99..99d2af4b 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet/Description.txt @@ -3,3 +3,7 @@ This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.Fewer.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.Fewer.nuspec new file mode 100644 index 00000000..3f08bda7 --- /dev/null +++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.Fewer.nuspec @@ -0,0 +1,45 @@ + + + + $id$ + $version$ + $description$ + $summary$ + $authors$ + $owners$ + $projectUrl$ + MIT + images\MigraDoc-128x128.png + README.md + false + $releaseNotes$ + $CopyrightMigraDoc$ + $tags$ + + $title$ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj index ca67c46a..b2a3762f 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj +++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_NuGet) false True @@ -11,8 +11,9 @@ true true - $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec + $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec PDFsharp-MigraDoc + $(NuGetId)-DEBUG PDFsharp plus MigraDoc for Windows and Linux $(DebugBuildMessage)%0D$(NuGetDescription) Creating Documents on the Fly. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec index 679d8cb6..8972a69d 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec +++ b/src/foundation/nuget/src/MigraDoc.NuGet/MigraDoc.NuGet.nuspec @@ -13,7 +13,7 @@ README.md false $releaseNotes$ - $copyright$ + $CopyrightMigraDoc$ $tags$ $title$ @@ -21,22 +21,22 @@ - + - + - + - + diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/README.md b/src/foundation/nuget/src/MigraDoc.NuGet/README.md index be978c7c..096861a0 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/README.md +++ b/src/foundation/nuget/src/MigraDoc.NuGet/README.md @@ -5,7 +5,3 @@ MigraDoc is a .NET library that allows developers to create documents such as PD This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. - -See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt b/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt index bdb0512f..4616f1fe 100644 --- a/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt +++ b/src/foundation/nuget/src/MigraDoc.NuGet/ReleaseNotes.txt @@ -1,5 +1,5 @@ -This is a version of PDFsharp and MigraDoc compatible with .NET 6 and higher. -The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. -The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. +This is a version of PDFsharp and MigraDoc compatible with .NET 8 and higher. +The package ‘PDFsharp-MigraDoc’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’. +The packages ‘PDFsharp-MigraDoc-GDI’ and ‘PDFsharp-MigraDoc-WPF’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’. See the project docs site for further information: -https://docs.pdfsharp.net/link/readme-v6.2.html +https://docs.pdfsharp.net/link/readme-v7.0.html diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt index 4b997b23..be622cff 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/Description.txt @@ -3,3 +3,7 @@ This package relies on Windows Forms (GDI+) and can be used under Windows only. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.Fewer.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.Fewer.nuspec new file mode 100644 index 00000000..c98d5d15 --- /dev/null +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.Fewer.nuspec @@ -0,0 +1,44 @@ + + + + $id$ + $version$ + $description$ + $summary$ + $authors$ + $owners$ + $projectUrl$ + MIT + images\PDFsharp-128x128.png + README.md + false + $releaseNotes$ + $CopyrightPdfSharp$ + $tags$ + + $title$ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj index 5c7f5a49..85ec6c4e 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_NuGet_Window) true false @@ -12,8 +12,9 @@ true true - $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec + $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec PDFsharp-GDI + $(NuGetId)-DEBUG PDFsharp for Windows Forms apps $(DebugBuildMessage)%0D$(NuGetDescription) A .NET library for processing PDF. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec index 1971831a..734e4526 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/PDFsharp.NuGet-gdi.nuspec @@ -13,7 +13,7 @@ README.md false $releaseNotes$ - $copyright$ + $CopyrightPdfSharp$ $tags$ $title$ diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/README.md b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/README.md index 851b3420..b93dbc0d 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/README.md +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/README.md @@ -5,7 +5,3 @@ PDFsharp is the Open Source library for creating and modifying PDF documents usi This package relies on Windows Forms (GDI+) and can be used under Windows only. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. - -See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt index a1689064..f7d8d9c8 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-gdi/ReleaseNotes.txt @@ -1,6 +1,6 @@ -This is a version of PDFsharp compatible with .NET 6 and higher. -The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. -The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. +This is a version of PDFsharp compatible with .NET 8 and higher. +The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’. +The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’. We also publish packages that contain PDFsharp plus MigraDoc. See the project docs site for further information: -https://docs.pdfsharp.net/link/readme-v6.2.html +https://docs.pdfsharp.net/link/readme-v7.0.html diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt index b6028f68..b82a01aa 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/Description.txt @@ -3,3 +3,7 @@ This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.Fewer.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.Fewer.nuspec new file mode 100644 index 00000000..1eef4e64 --- /dev/null +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.Fewer.nuspec @@ -0,0 +1,44 @@ + + + + $id$ + $version$ + $description$ + $summary$ + $authors$ + $owners$ + $projectUrl$ + MIT + images\PDFsharp-128x128.png + README.md + false + $releaseNotes$ + $CopyrightPdfSharp$ + $tags$ + + $title$ + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj index a9c25477..5928ff6c 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_NuGet_Window) true false @@ -12,8 +12,9 @@ true true - $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec + $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec PDFsharp-WPF + $(NuGetId)-DEBUG PDFsharp for Windows WPF apps $(DebugBuildMessage)%0D$(NuGetDescription) A .NET library for processing PDF. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec index 8e242996..2094e3fb 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/PDFsharp.NuGet-wpf.nuspec @@ -13,7 +13,7 @@ README.md false $releaseNotes$ - $copyright$ + $CopyrightPdfSharp$ $tags$ $title$ diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/README.md b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/README.md index 63c45298..23c061b4 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/README.md +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/README.md @@ -5,7 +5,3 @@ PDFsharp is the Open Source library for creating and modifying PDF documents usi This package relies on Windows Presentation Foundation (WPF) and can be used under Windows only. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. - -See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt index a1689064..f7d8d9c8 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet-wpf/ReleaseNotes.txt @@ -1,6 +1,6 @@ -This is a version of PDFsharp compatible with .NET 6 and higher. -The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. -The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. +This is a version of PDFsharp compatible with .NET 8 and higher. +The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’. +The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’. We also publish packages that contain PDFsharp plus MigraDoc. See the project docs site for further information: -https://docs.pdfsharp.net/link/readme-v6.2.html +https://docs.pdfsharp.net/link/readme-v7.0.html diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt b/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt index e18be602..432a269f 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet/Description.txt @@ -3,3 +3,7 @@ This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See https://docs.pdfsharp.net for details. +  +See https://www.pdfsharp.com for professional support offers, premium technical advice, and contract work options. +Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. +Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.Fewer.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.Fewer.nuspec new file mode 100644 index 00000000..2b2ced3e --- /dev/null +++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.Fewer.nuspec @@ -0,0 +1,50 @@ + + + + $id$ + $version$ + $description$ + $summary$ + $authors$ + $owners$ + $projectUrl$ + MIT + images\PDFsharp-128x128.png + README.md + false + $releaseNotes$ + $CopyrightMigraDoc$ + $tags$ + + $title$ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj index a530742a..73410a8b 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj +++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_NuGet) false @@ -12,8 +12,9 @@ true true - $(MSBuildThisFileDirectory)$(MSBuildProjectName).nuspec + $(MSBuildThisFileDirectory)$(MSBuildProjectName)$(NuGetNuspecTag).nuspec PDFsharp + $(NuGetId)-DEBUG PDFsharp for Windows and Linux $(DebugBuildMessage)%0D$(NuGetDescription) A .NET library for processing PDF. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec index 1eeb3b59..5abe25d7 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec +++ b/src/foundation/nuget/src/PDFsharp.NuGet/PDFsharp.NuGet.nuspec @@ -13,7 +13,7 @@ README.md false $releaseNotes$ - $copyright$ + $CopyrightPdfSharp$ $tags$ $title$ diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/README.md b/src/foundation/nuget/src/PDFsharp.NuGet/README.md index 00466f60..4e60f763 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/README.md +++ b/src/foundation/nuget/src/PDFsharp.NuGet/README.md @@ -5,7 +5,3 @@ PDFsharp is the Open Source library for creating and modifying PDF documents usi This package does not depend on Windows and can be used on any .NET compatible platform including Linux and macOS. See [docs.pdfsharp.net](https://docs.pdfsharp.net) for details. - -See [https://www.pdfsharp.com](https://www.pdfsharp.com) for professional support offers, premium technical advice, and contract work options. -Choose a support plan that suits your needs. We offer a variety of options, from small projects to large teams, with flexible response times. -Our team provides PDFsharp expert assistance, including implementation, optimization, and tailored solutions. diff --git a/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt b/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt index a1689064..f7d8d9c8 100644 --- a/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt +++ b/src/foundation/nuget/src/PDFsharp.NuGet/ReleaseNotes.txt @@ -1,6 +1,6 @@ -This is a version of PDFsharp compatible with .NET 6 and higher. -The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’, and ‘netstandard2.0’. -The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’, and ‘net462’. +This is a version of PDFsharp compatible with .NET 8 and higher. +The package ‘PdfSharp’ can be used on any platform including Windows and Linux. The Target Frameworks are ‘net8.0’, ‘net9.0’, ‘net10.0’ and ‘netstandard2.0’. +The packages ‘PDFsharp-gdi’ and ‘PDFsharp-wpf’ can be used under Windows only. The Target Frameworks are ‘net8.0-windows’, ‘net9.0-windows’, ‘net10.0-windows’ and ‘net462’. We also publish packages that contain PDFsharp plus MigraDoc. See the project docs site for further information: -https://docs.pdfsharp.net/link/readme-v6.2.html +https://docs.pdfsharp.net/link/readme-v7.0.html diff --git a/src/foundation/nuget/src/README.md b/src/foundation/nuget/src/README.md index 0d977820..7dd29443 100644 --- a/src/foundation/nuget/src/README.md +++ b/src/foundation/nuget/src/README.md @@ -4,6 +4,9 @@ This folder contains dummy C# projects for the generation of the PDFsharp and Mi ## Developer notes +* Add dependencies for newly referenced NuGet packages to the nuspec files. + Also libraries, that are part of the shared framework, have to be added for all frameworks. + Otherwise, exceptions may occur at runtime when trying to load the respective library. * The description and release notes are single text files read by MSBUILD and put to the nuspec files. This is done because putting the text directly in XML is very cumbersome. * The description and release notes should be worded such that it is not necessary to revise them on every new release. diff --git a/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj b/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj index 93906e8f..7d49c23c 100644 --- a/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj +++ b/src/foundation/src/MigraDoc/features/MigraDoc.Features/MigraDoc.Features.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + $(PDFsharpTargetFrameworks_Exe) diff --git a/src/foundation/src/MigraDoc/features/MigraDoc.Features/Program.cs b/src/foundation/src/MigraDoc/features/MigraDoc.Features/Program.cs index db9c6ae5..72b18d98 100644 --- a/src/foundation/src/MigraDoc/features/MigraDoc.Features/Program.cs +++ b/src/foundation/src/MigraDoc/features/MigraDoc.Features/Program.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using System; diff --git a/src/foundation/src/MigraDoc/src/Directory.Build.props b/src/foundation/src/MigraDoc/src/Directory.Build.props index 7197970b..cf05c780 100644 --- a/src/foundation/src/MigraDoc/src/Directory.Build.props +++ b/src/foundation/src/MigraDoc/src/Directory.Build.props @@ -3,6 +3,7 @@ MigraDoc + $(CopyrightMigraDoc) diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlParser.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlParser.cs index 00a983a7..faa8072b 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlParser.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlParser.cs @@ -150,7 +150,7 @@ Styles ParseStyles(Styles styles) { // StyleName [: BaseStyleName] // { - // ... + // … // } Style? style = null; try @@ -276,7 +276,7 @@ EmbeddedFiles ParseEmbeddedFiles(EmbeddedFiles embeddedFiles) ReadMoreContent: // If a section contains only one paragraph, the paragraph keyword is omitted in MDDDL and the paragraph content is directly inserted into the section. // The IsParagraphContent() check has to be made, before moving to the next token by ReadCode(). - if (IsParagraphContent()) + if (IsParagraphContent()) { // Paragraph content was inserted directly into the section. var paragraph = section.Elements.AddParagraph(); @@ -430,12 +430,12 @@ DocumentElements ParseDocumentElements(DocumentElements elements, Symbol context // All section content will be treated as paragraph content. // // but this is ambiguous: - // \section { \image(...) } + // \section { \image(…) } // It could be an image inside a paragraph or at the section level. // In this case it will be treated as an image on section level. // // If this is not your intention it must be like this: - // \section { \paragraph { \image(...) } } + // \section { \paragraph { \image(…) } } // while (TokenType == TokenType.KeyWord) @@ -1254,9 +1254,9 @@ void ParseImage(Image image, bool paragraphContent) { // Future syntax by example // \image("Name") - // \image("Name")[...] - // \image{base64...} // NYI - // \image[...]{base64...} // NYI + // \image("Name")[…] + // \image{base64…} // NYI + // \image[…]{base64…} // NYI Debug.Assert(image != null); try @@ -1920,33 +1920,30 @@ void ParseAttributeStatement(DocumentObject? doc) ParagraphFormat paragraphFormat = (ParagraphFormat)doc; TabStops tabStops = paragraphFormat.TabStops; - if (true) // HACK_OLD in ParseAttributeStatement // BUG_OLD THHO4STLA Already existed in 2019. - { - bool fAddItem = Symbol == Symbol.PlusAssign; - var tabStop = new TabStop(); + bool fAddItem = Symbol == Symbol.PlusAssign; + var tabStop = new TabStop(); - ReadCode(); + ReadCode(); - if (Symbol == Symbol.BraceLeft) - { - ParseAttributeBlock(tabStop); - } - else if (Symbol is Symbol.StringLiteral or Symbol.RealLiteral or Symbol.IntegerLiteral) - { - // Special hack for tab stops... - Unit unit = Token; - tabStop.SetValue("Position", unit); - - ReadCode(); - } - else - ThrowParserException(MdDomMsgs.UnexpectedSymbol(Token)); + if (Symbol == Symbol.BraceLeft) + { + ParseAttributeBlock(tabStop); + } + else if (Symbol is Symbol.StringLiteral or Symbol.RealLiteral or Symbol.IntegerLiteral) + { + // Special hack for tab stops... + Unit unit = Token; + tabStop.SetValue("Position", unit); - if (fAddItem) - tabStops.AddTabStop(tabStop); - else - tabStops.RemoveTabStop(tabStop.Position); + ReadCode(); } + else + ThrowParserException(MdDomMsgs.UnexpectedSymbol(Token)); + + if (fAddItem) + tabStops.AddTabStop(tabStop); + else + tabStops.RemoveTabStop(tabStop.Position); break; case Symbol.BraceLeft: diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlScanner.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlScanner.cs index d038f718..3a43d6c5 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlScanner.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/DdlScanner.cs @@ -311,7 +311,7 @@ internal Symbol PeekSymbol() int idx = _idx - 1; int length = _ddlLength - idx; - // Move to first non whitespace + // Move to first non-white-space char ch = Char.MinValue; while (length > 0) { @@ -427,7 +427,7 @@ internal Symbol ReadText(bool rootLevel) } /// - /// Returns whether the linebreak should be ignored because the previous symbol is already a whitespace. + /// Returns whether the linebreak should be ignored because the previous symbol is already a white-space. /// bool IgnoreLineBreak() { @@ -580,7 +580,7 @@ internal bool MoveToNextParagraphContentLine(bool rootLevel) ScanNextChar(); while (loop) { - // Scan to next EOL and ignore any white space. + // Scan to next EOL and ignore any white-space. MoveToNonWhiteSpaceOrEol(); switch (_currChar) { @@ -636,9 +636,9 @@ internal bool MoveToNextParagraphContentLine(bool rootLevel) } /// - /// If the current character is not a white space, the function immediately returns it. - /// Otherwise, the DDL cursor is moved forward to the first non-white space or EOF. - /// White spaces are SPACE, HT, VT, CR, and LF.??? + /// If the current character is not a white-space, the function immediately returns it. + /// Otherwise, the DDL cursor is moved forward to the first non-white-space or EOF. + /// White-spaces are SPACE, HT, VT, CR, and LF.??? /// internal char MoveToNonWhiteSpaceOrEol() { @@ -660,9 +660,9 @@ internal char MoveToNonWhiteSpaceOrEol() } /// - /// If the current character is not a white space, the function immediately returns it. - /// Otherwise, the DDL cursor is moved forward to the first non-white space or EOF. - /// White spaces are SPACE, HT, VT, CR, and LF. + /// If the current character is not a white-space, the function immediately returns it. + /// Otherwise, the DDL cursor is moved forward to the first non-white-space or EOF. + /// White-spaces are SPACE, HT, VT, CR, and LF. /// internal char MoveToNonWhiteSpace() { @@ -881,7 +881,7 @@ internal static bool IsOctDigit(char ch) internal static bool IsLetter(char ch) => Char.IsLetter(ch); /// - /// Is character a white space. + /// Is character a white-space. /// internal static bool IsWhiteSpace(char ch) => Char.IsWhiteSpace(ch); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/enums/TokenType.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/enums/TokenType.cs index d63146cd..8d0f5c06 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/enums/TokenType.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.IO/enums/TokenType.cs @@ -9,7 +9,7 @@ namespace MigraDoc.DocumentObjectModel.IO enum TokenType { /// - /// White space or comment. + /// White-space or comment. /// None, diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/ErrorHelpers.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/ErrorHelpers.cs index 606644a8..cac38922 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/ErrorHelpers.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/ErrorHelpers.cs @@ -64,9 +64,9 @@ public static ArgumentException ArgumentException_UndefinedBaseStyle(string styl //public static string InvalidEnumValue(T value) where T : unmanaged // struct, Enum //{ - // // ... where T : enum + // // … where T : enum // // -or- - // // ... where T : Enum + // // … where T : Enum // // is not implemented in C# because nobody has done this. // // See Eric Lippert on this topic: http://stackoverflow.com/questions/1331739/enum-type-constraints-in-c-sharp // // UPDATE: Enum constraint comes with C# 7.3, see: https://devblogs.microsoft.com/premier-developer/dissecting-new-generics-constraints-in-c-7-3/ diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/LogMessages.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/LogMessages.cs index 0fbdc31c..dd756dd3 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/LogMessages.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/LogMessages.cs @@ -22,7 +22,7 @@ public static partial void ArgbValueIsConsideredEmptyColor( // What to log? - // Image not found (not loadable, not found, ...) + // Image not found (not loadable, not found, …) // Font not found under Linux (use Linux substitution) // Information/Trace-level for e.g. TabStop inheritance, style inheritance // Differences in RTF vs Word (decimal tab) diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/Meta.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/Meta.cs index 6f736716..b966d5b3 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/Meta.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Internals/Meta.cs @@ -97,7 +97,7 @@ public bool HasValue(string name) { // BUG_OLD: HasValue("a.b") not handled if (name.Contains('.')) - throw new NotImplementedException($"'{name}' contains a dot."); + throw new NotSupportedException($"'{name}' contains a dot."); return ValueDescriptors.HasName(name); } @@ -109,7 +109,7 @@ public bool HasValue(string name) public void SetNull(DocumentObject dom, string name) { if (name.Contains('.')) - throw new NotImplementedException($"'{name}' contains a dot."); + throw new NotSupportedException($"'{name}' contains a dot."); var vd = ValueDescriptors[name]; if (vd == null) diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs index 22db1b49..fa72795e 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel.Visitors/MergedCellList.cs @@ -39,7 +39,7 @@ void Init(Table table) #if true int rows = table.Rows.Count; int columns = table.Columns.Count; - var flags = new Boolean[rows, columns]; + var flags = new bool[rows, columns]; for (int rwIdx = 0; rwIdx < rows; ++rwIdx) { for (int clmIdx = 0; clmIdx < columns; ++clmIdx) diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Borders.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Borders.cs index 9215eb31..8c3c4d61 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Borders.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Borders.cs @@ -121,24 +121,6 @@ protected override object DeepCopy() return borders; } - ///// - ///// Gets an enumerator for the borders object. - ///// - //public IEnumerator GetEnumerator() - //{ - // throw new NotImplementedException(); - // //var ht = new Dictionary - // //{ - // // { "Top", Values.Top }, - // // { "Left", Values.Left }, - // // { "Bottom", Values.Bottom }, - // // { "Right", Values.Right }, - // // { "DiagonalUp", Values.DiagonalUp }, - // // { "DiagonalDown", Values.DiagonalDown } - // //}; - // //return new BorderEnumerator(ht); - //} - /// /// Clears all Border objects from the collection. Additionally, 'Borders = null' /// is written to the DDL stream when serialized. @@ -420,60 +402,6 @@ internal void Serialize(Serializer serializer, Borders? refBorders) return null; } - // /// - // /// Returns an enumerator that can iterate through the Borders. - // /// - // public class BorderEnumerator : IEnumerator - // { - //#war/ning This class must be checked with a unit test. - // /// - // /// Creates a new BorderEnumerator. - // /// - // public BorderEnumerator(Dictionary ht) - // { - // _ht = ht; - // _index = -1; - // } - - // public void Dispose() - // => throw new NotImplementedException(); - - // /// - // /// Sets the enumerator to its initial position, which is before the first element in the border collection. - // /// - // public void Reset() => _index = -1; - - // object IEnumerator.Current => Current; - - // /// - // /// Gets the current element in the border collection. - // /// - // public Border Current - // { - // get - // { - // IEnumerator enumerator = _ht.GetEnumerator(); - // enumerator.Reset(); - // for (int idx = 0; idx < _index + 1; idx++) - // enumerator.MoveNext(); - // // return (((DictionaryEntry)enumerator.Current).Value as Border)!; // B_UG: May return null - // return (((KeyValuePair)enumerator.Current).Value as Border)!; - // } - // } - - // /// - // /// Advances the enumerator to the next element of the border collection. - // /// - // public bool MoveNext() - // { - // _index++; - // return (_index < _ht.Count); - // } - - // int _index; - // readonly Dictionary _ht; - // } - /// /// Returns the metaobject of this instance. /// @@ -589,11 +517,5 @@ public Color? Color /// public bool? BordersCleared { get; set; } } - - //IEnumerator IEnumerable.GetEnumerator() - //{ - // throw new NotImplementedException(); - // //return GetEnumerator(); - //} } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Character.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Character.cs index 9ff8eb76..d64d7290 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Character.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Character.cs @@ -171,7 +171,7 @@ internal override void Serialize(Serializer serializer) { if (SymbolName == SymbolName.Blank) { - // Note: Don’t try to optimize it by leaving away the braces in case a single space is added. + // Do not try to optimize it by leaving away the braces in case a single space is added. // This would lead to confusion with '(' in directly following text. text = Invariant($@"\\space({Count})"); } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Color.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Color.cs index 4d1a106f..936898de 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Color.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Color.cs @@ -52,7 +52,7 @@ static Color() // Aqua == Cyan // Fuchsia == Magenta //var key = new Style(); ??? -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER StandardColors.TryAdd(d, c); #else if (!StandardColors.ContainsKey(d)) @@ -307,7 +307,7 @@ public static Color Parse(string color) // // While comparing a string to "" may seem like it’s checking for an empty string, it’s actually checking // for a specific value of an empty string. This can be a problem if the empty string is represented by - // something other than "" in the code, such as null or whitespace. + // something other than "" in the code, such as null or white-space. // // On the other hand, comparing the length of a string to 0 is always checking for an empty string, // regardless of how it’s represented in the code. However, this operation can be less efficient than comparing diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Font.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Font.cs index f417f857..b0cff74d 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Font.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Font.cs @@ -302,17 +302,12 @@ internal void Serialize(Serializer serializer, Font? font) else fontStyle = "(\"" + ((FormattedText)Parent).Style + "\")"; - //bool needBlank = false; // nice, but later... + //bool needBlank = false; // nice, but later… serializer.Write("\\font" + fontStyle + "["); if (!String.IsNullOrEmpty(Values.Name)) serializer.WriteSimpleAttribute("Name", Name); -#if DEBUG_ // Test - if (Size != Unit.Empty && Size != 0 && Size.Point == 0) - _ = typeof(int); -#endif - if (!Values.Size.IsValueNullOrEmpty()) serializer.WriteSimpleAttribute("Size", Size); @@ -348,11 +343,6 @@ internal void Serialize(Serializer serializer, Font? font) serializer.WriteSimpleAttribute("Name", Name); } -#if DEBUG_ - // Test - if (!_size.IsNull && Size != 0 && Size.Point == 0) - _ = typeof(int); -#endif if (!Values.Size.IsValueNullOrEmpty() && (font == null || Size != font.Size)) serializer.WriteSimpleAttribute("Size", Size); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/HeadersFooters.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/HeadersFooters.cs index dad5ad08..5e8aef94 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/HeadersFooters.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/HeadersFooters.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using MigraDoc.DocumentObjectModel.Visitors; @@ -136,15 +136,15 @@ internal override void Serialize(Serializer serializer) bool hasEvenPage = HasHeaderFooter(Values.EvenPage); bool hasFirstPage = HasHeaderFooter(Values.FirstPage); - // \primary... + // \primary… if (hasPrimary) Primary.Serialize(serializer, "primary"); - // \even... + // \even… if (hasEvenPage) EvenPage.Serialize(serializer, "evenpage"); - // \firstpage... + // \firstpage… if (hasFirstPage) FirstPage.Serialize(serializer, "firstpage"); } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Hyperlink.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Hyperlink.cs index 531e936d..2f5e97b7 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Hyperlink.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Hyperlink.cs @@ -457,14 +457,14 @@ public Font Font public bool NoHyperlinkStyle { // This Property is not part of Word’s Automation Model. With this property Hyperlinks can be used without implicitly setting the Hyperlink style. - // This way they occur like linked cross-references in Word, which are not implemented as an own DocumentObject type in MigraDoc. + // This way they occur like linked cross references in Word, which are not implemented as an own DocumentObject type in MigraDoc. get => Values.NoHyperlinkStyle ?? false; set => Values.NoHyperlinkStyle = value; } /// /// For HyperlinkType Local/Bookmark: Gets or sets the target bookmark name of the Hyperlink. - /// For HyperlinkTypes File and Url/Web: Gets or sets the target filename of the Hyperlink, e.g. a path to a file or a URL. + /// For HyperlinkTypes File and Url/Web: Gets or sets the target filename of the Hyperlink, e.g. a path to a file or an URL. /// For HyperlinkType ExternalBookmark: Not valid - throws Exception. /// This property is retained due to compatibility reasons. /// @@ -503,7 +503,7 @@ public string Name } /// - /// Gets or sets the target filename of the Hyperlink, e.g. a path to a file or a URL. + /// Gets or sets the target filename of the Hyperlink, e.g. a path to a file or an URL. /// Used for HyperlinkTypes ExternalBookmark, File and Url/Web. /// public string Filename diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/ImageHelper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/ImageHelper.cs index 3a33a8e4..00889c7e 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/ImageHelper.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/ImageHelper.cs @@ -53,7 +53,7 @@ public static bool InSubfolder(string root, string filename, string imagePath, s /// public static string ExtractPageNumber(string path, out int pageNumber) { - // Note: duplicated from class XPdfForm. + // This code is duplicated from class XPdfForm. if (path == null) throw new ArgumentNullException(nameof(path)); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs index a3ed8f10..b2e181d9 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Serializer.cs @@ -86,7 +86,7 @@ void WriteStamp() if (_fWriteStamp) { WriteComment("Created by empira MigraDoc Document Object Model"); - WriteComment(String.Format("generated file created {0:d} at {0:t}", DateTime.Now)); + WriteComment(String.Format("generated file created {0:d} at {0:t}", DateTimeOffset.Now)); } } @@ -297,7 +297,7 @@ void WriteLineToStream(string text) ///// ///// Mighty function to figure out if a blank is required as separator. - ///// // Does not work without context... + ///// // Does not work without context… ///// //bool IsBlankRequired(char left, char right) //{ diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Style.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Style.cs index a45c9b3f..7bdc60f0 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Style.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Style.cs @@ -141,7 +141,7 @@ public string BaseStyle // Self assignment is allowed. if (String.Compare(Values.BaseStyle, value, StringComparison.OrdinalIgnoreCase) == 0) { - Values.BaseStyle = value; // character case may change... + Values.BaseStyle = value; // character case may change… return; } @@ -161,7 +161,7 @@ public string BaseStyle throw new ArgumentException(msg); } - if (idxBaseStyle >= 0) // BUG_OLD THHO4STLA Was "idxBaseStyle > 1". + if (idxBaseStyle >= 0) { // styles cannot be null if idxBaseStyle >= 0. Debug.Assert(styles != null, nameof(styles) + " != null"); @@ -232,7 +232,7 @@ public StyleType Type if (Values.BaseStyle == "") throw new ArgumentException("User-defined Style defined without a BaseStyle"); - // REVIEW KlPo4StLa Special treatment for DefaultParagraphFont faulty (DefaultParagraphFont not returned via styles["name"]). + // Special treatment for DefaultParagraphFont faulty (DefaultParagraphFont not returned via styles["name"]). if (Values.BaseStyle == DefaultParagraphFontName) return styles[0]; @@ -307,14 +307,14 @@ internal override void Serialize(Serializer serializer) // case: built-in style with unmodified base style name string name = DdlEncoder.QuoteIfNameContainsBlanks(Name); serializer.WriteLineNoCommit(name); - // It’s fine if we have the predefined base style, but... - // ...the base style may have been modified or may even have a modified base style. + // It’s fine if we have the predefined base style, but… + // … the base style may have been modified or may even have a modified base style. // Methinks it’s wrong to compare with the built-in style, so let’s compare with the // real base style: refStyle = Document.Styles[Document.Styles.GetIndex(Values.BaseStyle!)]; // BUG_OLD: Base style can be null refFormat = refStyle.ParagraphFormat; //refFont = refFormat.Font; - // Note: we must write "Underline = none" if the base style has "Underline = single" - we cannot + // We must write "Underline = none" if the base style has "Underline = single" - we cannot // detect this if we compare with the built-in style that has no underline. // Known problem: Default values like "OutlineLevel = Level1" will now be serialized. // TODO_OLD: Optimize DDL output, remove redundant default values. @@ -366,7 +366,7 @@ internal override void Serialize(Serializer serializer) /// void Optimize() { - // Just here as a reminder to do it... + // Just here as a reminder to do it… } /// diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Unit.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Unit.cs index 4c3bc80c..3392cb41 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Unit.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/Unit.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. namespace MigraDoc.DocumentObjectModel @@ -58,7 +58,7 @@ void INullableValue.SetValue(object? value) /// /// Resets this instance, - /// i.e. IsNull() will return true afterwards. + /// i.e. IsNull() will return true afterward. /// void INullableValue.SetNull() { @@ -443,8 +443,8 @@ public static implicit operator Unit(string? value) var unit = Zero; value = value.Trim(); - // For Germans... - value = value.Replace(',', '.'); + // For Germans… + value = value.Replace(',', '.'); // TODO: Remove, but this is a breaking change. int count = value.Length; int valLen = 0; diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/enums/SymbolName.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/enums/SymbolName.cs index a75ccbce..0d37e03f 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/enums/SymbolName.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/DocumentObjectModel/enums/SymbolName.cs @@ -8,7 +8,7 @@ namespace MigraDoc.DocumentObjectModel /// public enum SymbolName : uint { - // ===== \space(...) ===== + // ===== \space(…) ===== /// /// A regular blank. /// @@ -52,7 +52,7 @@ public enum SymbolName : uint ParaBreak = 0xF4000007, //MarginBreak = 0xF4000002, - // ===== \symbol(...) ===== + // ===== \symbol(…) ===== /// /// The Euro symbol €. /// diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj index e7f47f6d..48eb21e9 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/MigraDoc.DocumentObjectModel.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;net462;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) MigraDoc True ..\..\..\..\..\StrongnameKey.snk @@ -12,9 +12,9 @@ true - - ; - + 0 @@ -33,7 +33,8 @@ - + + @@ -42,6 +43,6 @@ - + diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs index 91d45086..4cc1871b 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/Properties/MigraDocProductVersionInformation.cs @@ -3,7 +3,9 @@ using PdfSharp.Internal; +#pragma warning disable IDE0130 // Namespace does not match folder structure namespace MigraDoc +#pragma warning restore IDE0130 // Namespace does not match folder structure { /// /// Version information for all MigraDoc related assemblies. @@ -11,7 +13,6 @@ namespace MigraDoc // TODO_OLD: These literals are not completely in sync with the NuGet metadata. Should be reviewed and fixed. public static class MigraDocProductVersionInformation { - // Cannot use const string anymore because GitVersionInformation used static string. // The fields are reordered to take initialization chronology into account. /// @@ -27,38 +28,43 @@ public static class MigraDocProductVersionInformation /// /// The major version number of the product. /// - public static readonly string VersionMajor = PdfSharpGitVersionInformation.Major; + public static readonly string VersionMajor = SemVersionInformation.Major; /// /// The minor version number of the product. /// - public static readonly string VersionMinor = PdfSharpGitVersionInformation.Minor; + public static readonly string VersionMinor = SemVersionInformation.Minor; /// /// The patch number of the product. /// - public static readonly string VersionPatch = PdfSharpGitVersionInformation.Patch; + public static readonly string VersionPatch = SemVersionInformation.Patch; /// /// The Version pre-release string for NuGet. /// - public static readonly string VersionPreRelease = PdfSharpGitVersionInformation.PreReleaseLabel; + public static readonly string VersionPreRelease = SemVersionInformation.PreReleaseLabel; /// /// The PDF creator application information string. /// The PDF producer (created by) is PDFsharp anyway. /// - public static readonly string Creator = $"{Title} {PdfSharpGitVersionInformation.InformationalVersion} ({Url})"; + public static readonly string Creator = $"{Title} {SemVersionInformation.InformationalVersion} ({Url})"; /// /// The full version number. /// - public static readonly string Version = PdfSharpGitVersionInformation.MajorMinorPatch; + public static readonly string Version = SemVersionInformation.Version; /// - /// The full semantic version number created by GitVersion. + /// The full file version number. /// - public static readonly string SemanticVersion = PdfSharpGitVersionInformation.SemVer; + public static readonly string FileVersion = SemVersionInformation.FileVersion; + + /// + /// The full semantic version number. + /// + public static readonly string SemanticVersion = SemVersionInformation.InformationalVersion; /// /// The home page of this product. @@ -83,7 +89,7 @@ public static class MigraDocProductVersionInformation /// /// The copyright information. /// - public const string Copyright = "Copyright © 2001-2026 empira Software GmbH."; // Also used as NuGet Copyright. + public const string Copyright = "Copyright © 2001-2026 empira Software GmbH."; // Not used as NuGet Copyright. See Directory.Build.Props. /// /// The trademark of the product. @@ -96,90 +102,84 @@ public static class MigraDocProductVersionInformation public const string Culture = ""; // Build = days since 2001-07-04 - change values ONLY here -#if DEBUG_ - // ReSharper disable RedundantNameQualifier - //public static int BuildNumber = (System.DateTime.Now - new System.DateTime(2001, 7, 4)).Days; - public static int BuildNumber = (System.DateTime.Now - new System.DateTime(2005, 1, 1)).Days; // Same BuildNumber like PDFsharp - // ReSharper restore RedundantNameQualifier -#endif -//#if Not_used_anymore // #DELETE 25-12-31 - -// /// -// /// E.g. "2005-01-01", for use in NuGet Script -// /// -// public const string VersionReferenceDate = "2005-01-01"; - -// /// -// /// Use _ instead of blanks and special characters. Can be complemented with a suffix in the NuGet Script. -// /// Nuspec Doc: The unique identifier for the package. This is the package name that is shown when packages are listed using the Package Manager Console. -// /// These are also used when installing a package using the Install-Package command within the Package Manager Console. -// /// Package IDs may not contain any spaces or characters that are invalid in a URL. In general, they follow the same rules as .NET namespaces do. -// /// So Foo.Bar is a valid ID, Foo! and Foo Bar are not. -// /// -// public const string NuGetID = "PDFsharp-MigraDoc"; - -// /// -// /// Nuspec Doc: The human-friendly title of the package displayed in the Manage NuGet Packages dialog. If none is specified, the ID is used instead. -// /// -// public const string NuGetTitle = "PDFsharp + MigraDoc"; - -// /// -// /// Nuspec Doc: A comma-separated list of authors of the package code. -// /// -// public const string NuGetAuthors = "empira Software GmbH"; - -// /// -// /// Nuspec Doc: A comma-separated list of the package creators. This is often the same list as in authors. This is ignored when uploading the package to the NuGet.org Gallery. -// /// -// public const string NuGetOwners = "empira Software GmbH"; - -// /// -// /// Nuspec Doc: A long description of the package. This shows up in the right pane of the Add Package Dialog as well as in the Package Manager Console when listing packages using the Get-Package command. -// /// -// public const string NuGetDescription = "MigraDoc - the Open Source .NET library that easily creates documents based on an object model with paragraphs, tables, styles, etc. and renders them into PDF or RTF."; - -// /// -// /// Nuspec Doc: A description of the changes made in each release of the package. This field only shows up when the _Updates_ tab is selected and the package is an update to a previously installed package. -// /// It is displayed where the Description would normally be displayed. -// /// -// public const string NuGetReleaseNotes = ""; - -// /// -// /// Nuspec Doc: A short description of the package. If specified, this shows up in the middle pane of the Add Package Dialog. If not specified, a truncated version of the description is used instead. -// /// -// public const string NuGetSummary = "Creating Documents on the Fly."; - -// /// -// /// Nuspec Doc: The locale ID for the package, such as en-us. -// /// -// public const string NuGetLanguage = ""; - -// /// -// /// Nuspec Doc: A URL for the home page of the package. -// /// -// public const string NuGetProjectUrl = "http://www.pdfsharp.com/"; - -// /// -// /// Nuspec Doc: A URL for the image to use as the icon for the package in the Manage NuGet Packages dialog box. This should be a 32x32-pixel .png file that has a transparent background. -// /// -// public const string NuGetIconUrl = "http://www.pdf/sharp.net/resources/MigraDoc-Logo-32x32.png"; - -// /// -// /// Nuspec Doc: A link to the license that the package is under. -// /// -// public const string NuGetLicenseUrl = "http://www.pdf/sharp.net/MigraDoc_License.ashx"; - -// /// -// /// Nuspec Doc: A Boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed. -// /// -// public const bool NuGetRequireLicenseAcceptance = false; - -// /// -// /// Nuspec Doc: A space-delimited list of tags and keywords that describe the package. This information is used to help make sure users can find the package using -// /// searches in the Add Package Reference dialog box or filtering in the Package Manager Console window. -// /// -// public const string NuGetTags = "MigraDoc PdfSharp Pdf Document Generation"; -//#endif +#if Not_used_anymore // #DELETE 25-12-31 + + /// + /// E.g. "2005-01-01", for use in NuGet Script + /// + public const string VersionReferenceDate = "2005-01-01"; + + /// + /// Use _ instead of blanks and special characters. Can be complemented with a suffix in the NuGet Script. + /// Nuspec Doc: The unique identifier for the package. This is the package name that is shown when packages are listed using the Package Manager Console. + /// These are also used when installing a package using the Install-Package command within the Package Manager Console. + /// Package IDs may not contain any spaces or characters that are invalid in an URL. In general, they follow the same rules as .NET namespaces do. + /// So Foo.Bar is a valid ID, Foo! and Foo Bar are not. + /// + public const string NuGetID = "PDFsharp-MigraDoc"; + + /// + /// Nuspec Doc: The human-friendly title of the package displayed in the Manage NuGet Packages dialog. If none is specified, the ID is used instead. + /// + public const string NuGetTitle = "PDFsharp + MigraDoc"; + + /// + /// Nuspec Doc: A comma-separated list of authors of the package code. + /// + public const string NuGetAuthors = "empira Software GmbH"; + + /// + /// Nuspec Doc: A comma-separated list of the package creators. This is often the same list as in authors. This is ignored when uploading the package to the NuGet.org Gallery. + /// + public const string NuGetOwners = "empira Software GmbH"; + + /// + /// Nuspec Doc: A long description of the package. This shows up in the right pane of the Add Package Dialog as well as in the Package Manager Console when listing packages using the Get-Package command. + /// + public const string NuGetDescription = "MigraDoc - the Open Source .NET library that easily creates documents based on an object model with paragraphs, tables, styles, etc. and renders them into PDF or RTF."; + + /// + /// Nuspec Doc: A description of the changes made in each release of the package. This field only shows up when the _Updates_ tab is selected and the package is an update to a previously installed package. + /// It is displayed where the Description would normally be displayed. + /// + public const string NuGetReleaseNotes = ""; + + /// + /// Nuspec Doc: A short description of the package. If specified, this shows up in the middle pane of the Add Package Dialog. If not specified, a truncated version of the description is used instead. + /// + public const string NuGetSummary = "Creating Documents on the Fly."; + + /// + /// Nuspec Doc: The locale ID for the package, such as en-us. + /// + public const string NuGetLanguage = ""; + + /// + /// Nuspec Doc: A URL for the home page of the package. + /// + public const string NuGetProjectUrl = "http://www.pdfsharp.net/"; + + /// + /// Nuspec Doc: A URL for the image to use as the icon for the package in the Manage NuGet Packages dialog box. This should be a 32x32-pixel .png file that has a transparent background. + /// + public const string NuGetIconUrl = "http://www.pdfsharp.net/resources/MigraDoc-Logo-32x32.png"; + + /// + /// Nuspec Doc: A link to the license that the package is under. + /// + public const string NuGetLicenseUrl = "http://www.pdfsharp.net/MigraDoc_License.ashx"; + + /// + /// Nuspec Doc: A boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed. + /// + public const bool NuGetRequireLicenseAcceptance = false; + + /// + /// Nuspec Doc: A space-delimited list of tags and keywords that describe the package. This information is used to help make sure users can find the package using + /// searches in the Add Package Reference dialog box or filtering in the Package Manager Console window. + /// + public const string NuGetTags = "MigraDoc PdfSharp Pdf Document Generation"; +#endif } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md index aff137a8..677ae36e 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md +++ b/src/foundation/src/MigraDoc/src/MigraDoc.DocumentObjectModel/README.md @@ -3,5 +3,5 @@ One single build with all target frameworks: ```xml -net8.0;net9.0;net10.0;net462;netstandard2.0 +net8.0;net462;netstandard2.0 ``` diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj index 2a586f0c..71105ccd 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/MigraDoc.Rendering-gdi.csproj @@ -1,12 +1,13 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true MigraDoc + $(DefineConstants);GDI true - GDI + True ..\..\..\..\..\StrongnameKey.snk true @@ -25,13 +26,14 @@ - + + @@ -109,209 +111,10 @@ - - - + + + diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs index 301d4818..4ebff5bd 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-gdi/Rendering.Forms/DocumentPreview.cs @@ -1,7 +1,6 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System; using System.ComponentModel; using System.Drawing; using System.Windows.Forms; @@ -19,9 +18,6 @@ namespace MigraDoc.Rendering.Forms /// public class DocumentPreview : UserControl { - private PdfSharp.Forms.PagePreview _preview; - private readonly Container _components = null!; - /// /// Initializes a new instance of the class. /// @@ -47,11 +43,11 @@ protected override void Dispose(bool disposing) Zoom GetNewZoomFactor(int currentZoom, bool larger) { - int[] values = new int[] - { - 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, - 250, 300, 350, 400, 450, 500, 600, 700, 800 - }; + int[] values = + [ + 10, 20, 30, 40, 50, 60, 70, 80, 90, 100, 120, 140, 160, 180, 200, + 250, 300, 350, 400, 450, 500, 600, 700, 800 + ]; if (currentZoom <= 10 && !larger) return Zoom.Percent10; @@ -513,9 +509,12 @@ protected override void OnMouseWheel(MouseEventArgs e) NextPage(); } - private void PreviewZoomChanged(object? sender, EventArgs e) + void PreviewZoomChanged(object? sender, EventArgs e) { OnZoomChanged(e); } + + PdfSharp.Forms.PagePreview _preview; + readonly Container _components = null!; } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj index c98ca20c..420f2ede 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering-wpf/MigraDoc.Rendering-wpf.csproj @@ -1,18 +1,19 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 - true - MigraDoc - true - - WPF - True - ..\..\..\..\..\StrongnameKey.snk + $(PDFsharpTargetFrameworks_Windows) + true + MigraDoc + $(DefineConstants);WPF + true + + + True + ..\..\..\..\..\StrongnameKey.snk - true + true @@ -24,11 +25,13 @@ - + + + diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj index 6775c2e9..4e37c636 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/MigraDoc.Rendering.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) MigraDoc True ..\..\..\..\..\StrongnameKey.snk @@ -40,7 +40,8 @@ - + + diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GlobalDeclarations.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GlobalDeclarations.cs index 2d7824e7..7e417fdf 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GlobalDeclarations.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GlobalDeclarations.cs @@ -2,9 +2,11 @@ // See the LICENSE file in the solution root for more information. global using System.IO; +global using System.Diagnostics; global using PdfSharp.Diagnostics; global using static System.FormattableString; +#if PSGFX +global using PdfSharp.Graphics.XGfx; +#endif +//[assembly: ComVisible(false)] -using System.Runtime.InteropServices; - -[assembly: ComVisible(false)] diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GraphicsDeclarations.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GraphicsDeclarations.cs new file mode 100644 index 00000000..69889197 --- /dev/null +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Properties/GraphicsDeclarations.cs @@ -0,0 +1,57 @@ +// MigraDoc - Creating Documents on the Fly +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 + +#if PSGFX +global using float_ = float; +global using FLOAT_ = float; +global using XPoint = PdfSharp.Graphics.Point; +global using XRect = PdfSharp.Graphics.Rect; +global using XSize = PdfSharp.Graphics.Size; +global using XMatrix = PdfSharp.Graphics.Media.Matrix; +global using XColor = PdfSharp.Graphics.Color; +global using XColors = PdfSharp.Graphics.Colors; +global using XPen = PdfSharp.Graphics.Media.Pen; +global using XPens = PdfSharp.Graphics.Media.Pens; +global using XLineJoin = PdfSharp.Graphics.Media.PenLineJoin; +global using XDashStyle = PdfSharp.Graphics.Media.DashStyle; +global using XDashStyles = PdfSharp.Graphics.Media.DashStyles; +global using XBrush = PdfSharp.Graphics.Media.Brush; +global using XBrushes = PdfSharp.Graphics.Media.Brushes; +global using XSolidBrush = PdfSharp.Graphics.Media.SolidColorBrush; +global using XLinearGradientBrush = PdfSharp.Graphics.Media.SolidColorBrush; +global using XUnit = PdfSharp.Graphics.Unit; +global using XUnitPt = PdfSharp.Graphics.UnitPt; +global using XGraphicsState = int; +global using XGraphics = PdfSharp.Graphics.Media.DrawingContext; +//global using XFont = PdfSharp.Graphics.Text.FontFace; +global using XFont = PdfSharp.Graphics.XGfx.XFontGfx; +//global using XFontStyleEx = PdfSharp.Graphics.XGfx.XFontStyleEx; +#else +global using float_ = double; +global using FLOAT_ = double; +using System.Runtime.CompilerServices; +#endif + +using System.Runtime.InteropServices; +using PdfSharp.Drawing; + +#if PSGFX +using PdfSharp.Graphics; +using PdfSharp.Pdf; +using PdfSharp.Fonts; +#else +public static class IdentityExtensions +{ + extension(XRect rect) + { + public PdfSharp.Drawing.XRect AsXRect => rect; + } + + extension(XPoint point) + { + public PdfSharp.Drawing.XPoint AsXPoint => point; + } +} +#endif diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/ChartMapper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/ChartMapper.cs index ab85bf45..897c8d1f 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/ChartMapper.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/ChartMapper.cs @@ -17,8 +17,8 @@ ChartFrame MapObject(DocumentObjectModel.Shapes.Charts.Chart domChart) { var chartFrame = new ChartFrame { - Size = new XSize(domChart.Width.Point, domChart.Height.Point), - Location = new XPoint(domChart.Left.Position.Point, domChart.Top.Position.Point) + Size = new XSize((float_)domChart.Width.Point, (float_)domChart.Height.Point), + Location = new XPoint((float_)domChart.Left.Position.Point, (float_)domChart.Top.Position.Point) }; var chart = new Chart((ChartType)domChart.Type); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FillFormatMapper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FillFormatMapper.cs index 54cae6df..d42208d1 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FillFormatMapper.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FillFormatMapper.cs @@ -12,7 +12,13 @@ class FillFormatMapper void MapObject(FillFormat fillFormat, DocumentObjectModel.Shapes.FillFormat domFillFormat) { if (domFillFormat.Color.IsEmpty) + { +#if PSGFX + fillFormat.Color = PdfSharp.Graphics.Color.FromArgb(0, 0, 0, 0); +#else fillFormat.Color = XColor.Empty; +#endif + } else { #if noCMYK diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FontMapper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FontMapper.cs index fe62295b..3c876973 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FontMapper.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/FontMapper.cs @@ -16,8 +16,15 @@ void MapObject(Font font, DocumentObjectModel.Font domFont) { font.Bold = domFont.Bold; if (domFont.Color.IsEmpty) - font.Color = XColor.Empty; + { +#if PSGFX + font.Color = new XColor(); +#else + font.Color = PdfSharp.Drawing.XColor.Empty; +#endif + } else + { #if noCMYK font.Color = XColor.FromArgb((int)domFont.Color.Argb); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/LineFormatMapper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/LineFormatMapper.cs index 944aebe0..6edddafa 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/LineFormatMapper.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.ChartMapper/LineFormatMapper.cs @@ -29,12 +29,21 @@ void MapObject(LineFormat lineFormat, DocumentObjectModel.Shapes.LineFormat domL lineFormat.DashStyle = domLineFormat.DashStyle switch { +#if PSGFX + DocumentObjectModel.Shapes.DashStyle.Dash => XDashStyles.Dash, + DocumentObjectModel.Shapes.DashStyle.DashDot => XDashStyles.DashDot, + DocumentObjectModel.Shapes.DashStyle.DashDotDot => XDashStyles.DashDotDot, + DocumentObjectModel.Shapes.DashStyle.Solid => XDashStyles.Solid, + DocumentObjectModel.Shapes.DashStyle.SquareDot => XDashStyles.Dot, + _ => XDashStyles.Solid +#else DocumentObjectModel.Shapes.DashStyle.Dash => XDashStyle.Dash, DocumentObjectModel.Shapes.DashStyle.DashDot => XDashStyle.DashDot, DocumentObjectModel.Shapes.DashStyle.DashDotDot => XDashStyle.DashDotDot, DocumentObjectModel.Shapes.DashStyle.Solid => XDashStyle.Solid, DocumentObjectModel.Shapes.DashStyle.SquareDot => XDashStyle.Dot, _ => XDashStyle.Solid +#endif }; lineFormat.Style = domLineFormat.Style switch { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.Extensions/UnitConversions.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.Extensions/UnitConversions.cs index 8f7d3284..25ca61a7 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.Extensions/UnitConversions.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.Extensions/UnitConversions.cs @@ -3,6 +3,10 @@ using MigraDoc.DocumentObjectModel; using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics; +#endif +using Unit = MigraDoc.DocumentObjectModel.Unit; namespace MigraDoc.Rendering.Extensions { @@ -18,9 +22,14 @@ public static XUnit ToXUnit(this Unit unit) { var xGraphicsUnit = unit.Type.TryGetAsXGraphicsUnit(); if (xGraphicsUnit != null) - return new XUnit(unit.Value, xGraphicsUnit.Value); - - return XUnit.FromPoint(unit.Point); + { +#if PSGFX + return new XUnit((float_)unit.Value, (GraphicsUnit)xGraphicsUnit.Value); +#else + return new XUnit((float_)unit.Value, xGraphicsUnit.Value); +#endif + } + return XUnit.FromPoint((float_)unit.Point); } static XGraphicsUnit? TryGetAsXGraphicsUnit(this UnitType unitType) @@ -41,10 +50,11 @@ public static XUnit ToXUnit(this Unit unit) /// public static Unit ToUnit(this XUnit xUnit) { +#if !PSGFX var unitType = xUnit.Type.TryGetAsUnitType(); if (unitType != null) return new Unit(xUnit.Value, unitType.Value); - +#endif return Unit.FromPoint(xUnit.Point); } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestParagraphIterator.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestParagraphIterator.cs index fe4c6bfd..5c41f00d 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestParagraphIterator.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestParagraphIterator.cs @@ -3,7 +3,7 @@ using MigraDoc.DocumentObjectModel; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class namespace MigraDoc.Rendering.UnitTest { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestTable.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestTable.cs index 0023c2f2..70167e27 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestTable.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering.UnitTest/TestTable.cs @@ -4,7 +4,7 @@ using MigraDoc.DocumentObjectModel; using MigraDoc.DocumentObjectModel.Tables; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class namespace MigraDoc.Rendering.UnitTest { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/BordersRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/BordersRenderer.cs index 2827e20f..9af022c0 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/BordersRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/BordersRenderer.cs @@ -1,10 +1,11 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System.Diagnostics; using MigraDoc.DocumentObjectModel; using MigraDoc.DocumentObjectModel.Tables; +#if !PSXGRA using PdfSharp.Drawing; +#endif namespace MigraDoc.Rendering { @@ -80,7 +81,7 @@ internal XUnitPt GetWidth(BorderType type) //if (!border._width.IsNull) //if (border.Values.Width is not null) if (!values.Width.IsValueNullOrEmpty()) - return values.Width!.Value.Point; + return (float_)values.Width!.Value.Point; //if (!border._color.IsNull || !border._style.IsNull || border.Visible) //if (border.Values.Color is not null || border.Values.Style is not null || border.Visible) @@ -89,9 +90,9 @@ internal XUnitPt GetWidth(BorderType type) //if (!_borders._width.IsNull) //if (_borders.Values.Width is not null) if (!_borders.Values.Width.IsValueNullOrEmpty()) - return _borders.Values.Width!.Value.Point; + return (float_)(_borders.Values.Width!.Value.Point); - return 0.5; + return 0.5f; } } else if (!(type == BorderType.DiagonalDown || type == BorderType.DiagonalUp)) @@ -104,12 +105,12 @@ internal XUnitPt GetWidth(BorderType type) //if (!_borders._width.IsNull) //if (_borders.Values.Width is not null) if (!values.Width.IsValueNullOrEmpty()) - return values.Width!.Value.Point; + return (float_)(values.Width!.Value.Point); //if (!_borders._color.IsNull || !_borders._style.IsNull || _borders.Visible) //if (_borders.Values.Color is not null || _borders.Values.Style is not null || _borders.Visible) if (!values.Color.IsValueNullOrEmpty() || values.Style is not null || _borders.Visible) - return 0.5; + return 0.5f; } return 0; } @@ -130,7 +131,11 @@ internal void RenderVertically(BorderType type, XUnitPt left, XUnitPt top, XUnit left += borderWidth / 2; var pen = GetPen(type); if (pen != null) +#if PSGFX + _gfx.DrawLine(pen, new(left, top + height), new(left, top)); +#else _gfx.DrawLine(pen, left, top + height, left, top); +#endif } /// @@ -149,7 +154,11 @@ internal void RenderHorizontally(BorderType type, XUnitPt left, XUnitPt top, XUn top += borderWidth / 2; var pen = GetPen(type); if (pen != null) +#if PSGFX + _gfx.DrawLine(pen, new(left + width, top), new(left, top)); +#else _gfx.DrawLine(pen, left + width, top, left, top); +#endif } internal void RenderDiagonally(BorderType type, XUnitPt left, XUnitPt top, XUnitPt width, XUnitPt height) @@ -158,6 +167,25 @@ internal void RenderDiagonally(BorderType type, XUnitPt left, XUnitPt top, XUnit if (borderWidth == 0) return; +#if PSGFX + //XGraphicsState state = _gfx.Save(); + //_gfx.IntersectClip(new XRect(left, top, width, height)); + + //if (type == BorderType.DiagonalDown) + //{ + // var pen = GetPen(type); + // if (pen != null) + // _gfx.DrawLine(pen, left, top, left + width, top + height); + //} + //else if (type == BorderType.DiagonalUp) + //{ + // var pen = GetPen(type); + // if (pen != null) + // _gfx.DrawLine(pen, left, top + height, left + width, top); + //} + + //_gfx.Restore(state); +#else XGraphicsState state = _gfx.Save(); _gfx.IntersectClip(new XRect(left, top, width, height)); @@ -175,6 +203,7 @@ internal void RenderDiagonally(BorderType type, XUnitPt left, XUnitPt top, XUnit } _gfx.Restore(state); +#endif } internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, XUnitPt width, XUnitPt height) @@ -203,6 +232,20 @@ internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, X switch (roundedCorner) { +#if PSGFX + case RoundedCorner.TopLeft: + //_gfx.DrawArc(borderPen, new XRect(x, y, ellipseWidth, ellipseHeight), 180, 90); + break; + case RoundedCorner.TopRight: + //_gfx.DrawArc(borderPen, new XRect(x - width, y, ellipseWidth, ellipseHeight), 270, 90); + break; + case RoundedCorner.BottomRight: + //_gfx.DrawArc(borderPen, new XRect(x - width, y - height, ellipseWidth, ellipseHeight), 0, 90); + break; + case RoundedCorner.BottomLeft: + //_gfx.DrawArc(borderPen, new XRect(x, y - height, ellipseWidth, ellipseHeight), 90, 90); + break; +#else case RoundedCorner.TopLeft: _gfx.DrawArc(borderPen, new XRect(x, y, ellipseWidth, ellipseHeight), 180, 90); break; @@ -215,6 +258,7 @@ internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, X case RoundedCorner.BottomLeft: _gfx.DrawArc(borderPen, new XRect(x, y - height, ellipseWidth, ellipseHeight), 90, 90); break; +#endif } } @@ -228,6 +272,32 @@ internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, X var style = GetStyle(type); switch (style) { +#if PSGFX + case BorderStyle.DashDot: + //pen.DashStyle = XDashStyle.DashDot; + break; + + case BorderStyle.DashDotDot: + //pen.DashStyle = XDashStyle.DashDotDot; + break; + + case BorderStyle.DashLargeGap: + //pen.DashPattern = [3, 3]; + break; + + case BorderStyle.DashSmallGap: + //pen.DashPattern = [5, 1]; + break; + + case BorderStyle.Dot: + //pen.DashStyle = XDashStyle.Dot; + break; + + case BorderStyle.Single: + default: + //pen.DashStyle = XDashStyle.Solid; + break; +#else case BorderStyle.DashDot: pen.DashStyle = XDashStyle.DashDot; break; @@ -252,6 +322,7 @@ internal void RenderRounded(RoundedCorner roundedCorner, XUnitPt x, XUnitPt y, X default: pen.DashStyle = XDashStyle.Solid; break; +#endif } return pen; } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ChartRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ChartRenderer.cs index 3047caed..ceb23069 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ChartRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ChartRenderer.cs @@ -46,7 +46,7 @@ internal ChartRenderer(XGraphics gfx, RenderInfo renderInfo, FieldInfos? fieldIn FormattedTextArea? GetFormattedTextArea(TextArea? area) { - return GetFormattedTextArea(area, Double.NaN); + return GetFormattedTextArea(area, FLOAT_.NaN); } void GetLeftRightVerticalPosition(out XUnitPt top, out XUnitPt bottom) @@ -185,10 +185,10 @@ internal override void Format(Area area, FormatInfo? previousFormatInfo) var formatInfo = (ChartFormatInfo)_renderInfo.FormatInfo; var textArea = _chart.Values.HeaderArea; - formatInfo.FormattedHeader = GetFormattedTextArea(textArea, _chart.Width.Point); + formatInfo.FormattedHeader = GetFormattedTextArea(textArea, (float)_chart.Width.Point); textArea = _chart.Values.FooterArea; - formatInfo.FormattedFooter = GetFormattedTextArea(textArea, _chart.Width.Point); + formatInfo.FormattedFooter = GetFormattedTextArea(textArea, (float)_chart.Width.Point); textArea = _chart.Values.LeftArea; formatInfo.FormattedLeft = GetFormattedTextArea(textArea); @@ -232,7 +232,7 @@ XUnitPt AlignVertically(VerticalAlignment vAlign, XUnitPt top, XUnitPt bottom, X XUnitPt GetTopBottomWidth() { ChartFormatInfo formatInfo = (ChartFormatInfo)_renderInfo.FormatInfo; - XUnitPt width = _chart.Width.Point; + XUnitPt width = (float)_chart.Width.Point; if (formatInfo.FormattedRight != null) width -= formatInfo.FormattedRight.InnerWidth; if (formatInfo.FormattedLeft != null) @@ -270,13 +270,13 @@ void RenderArea(FormattedTextArea? area, Rectangle rect) fillFormatRenderer.Render(rect.X, rect.Y, rect.Width, rect.Height); XUnitPt top = rect.Y; - top += textArea.TopPadding.Point; + top += (float)textArea.TopPadding.Point; XUnitPt bottom = rect.Y + rect.Height; - bottom -= textArea.BottomPadding.Point; + bottom -= (float)textArea.BottomPadding.Point; top = AlignVertically(textArea.VerticalAlignment, top, bottom, area.ContentHeight); XUnitPt left = rect.X; - left += textArea.LeftPadding.Point; + left += (float)textArea.LeftPadding.Point; RenderInfo[] renderInfos = area.GetRenderInfos(); RenderByInfos(left, top, renderInfos); @@ -321,16 +321,16 @@ void RenderPlotArea(PlotArea area, Rectangle rect) var chartFrame = ((ChartFormatInfo)_renderInfo.FormatInfo).ChartFrame ?? NRT.ThrowOnNull("BUG_OLD"); XUnitPt top = rect.Y; - top += area.TopPadding.Point; + top += (float)area.TopPadding.Point; XUnitPt bottom = rect.Y + rect.Height; - bottom -= area.BottomPadding.Point; + bottom -= (float)area.BottomPadding.Point; XUnitPt left = rect.X; - left += area.LeftPadding.Point; + left += (float)area.LeftPadding.Point; XUnitPt right = rect.X + rect.Width; - right -= area.RightPadding.Point; + right -= (float)area.RightPadding.Point; chartFrame.Location = new XPoint(left, top); chartFrame.Size = new XSize(right - left, bottom - top); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ColorHelper.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ColorHelper.cs index 638256b0..20a003ad 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ColorHelper.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ColorHelper.cs @@ -1,7 +1,10 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. +#if PSGFX +#else using PdfSharp.Drawing; +#endif using MigraDoc.DocumentObjectModel; namespace MigraDoc.Rendering @@ -11,6 +14,12 @@ static class ColorHelper /// /// Converts Color to XColor. /// +#if PSGFX + public static XColor ToXColor(Color color, bool cmyk) + { + return XColor.FromArgb((byte)color.A, (byte)color.R, (byte)color.G, (byte)color.B); + } +#else public static XColor ToXColor(Color color, bool cmyk) { if (color.IsEmpty) @@ -18,7 +27,8 @@ public static XColor ToXColor(Color color, bool cmyk) if (cmyk) return XColor.FromCmyk(color.Alpha / 100.0, color.C / 100.0, color.M / 100.0, color.Y / 100.0, color.K / 100.0); - return XColor.FromArgb((int)color.Argb); + return XColor.FromArgb((uint)color.Argb); } +#endif } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/DocumentRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/DocumentRenderer.cs index 9dd9177f..bcf9e650 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/DocumentRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/DocumentRenderer.cs @@ -1,16 +1,18 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System.Diagnostics; using System.Diagnostics.CodeAnalysis; using PdfSharp.Events; using PdfSharp.Pdf; +using PdfSharp.Internal; +#if PSXGRA using PdfSharp.Drawing; +#endif using MigraDoc.DocumentObjectModel; using MigraDoc.DocumentObjectModel.Visitors; using MigraDoc.DocumentObjectModel.Shapes; using MigraDoc.DocumentObjectModel.Tables; -using PdfSharp.Internal; +using PdfSharp.Drawing; namespace MigraDoc.Rendering { @@ -38,9 +40,11 @@ public void PrepareDocument(RenderEvents? renderEvents = null) _previousListNumbers[ListType.NumberList2] = 0; _previousListNumbers[ListType.NumberList3] = 0; _formattedDocument = new FormattedDocument(_document, this); - +#if PSGFX + var gfx = (XGraphics)null!; //XGraphics.CreateMeasureContext(new XSize(2000, 2000), XGraphicsUnit.Point, XPageDirection.Downwards, renderEvents); +#else var gfx = XGraphics.CreateMeasureContext(new XSize(2000, 2000), XGraphicsUnit.Point, XPageDirection.Downwards, renderEvents); - +#endif _previousListInfo = null; _formattedDocument.Format(gfx); } @@ -96,7 +100,7 @@ public void RenderPage(XGraphics gfx, int page, PageRenderOptions options) var fieldInfos = _formattedDocument.GetFieldInfos(page); - fieldInfos.Date = PrintDate != DateTime.MinValue ? PrintDate : DateTime.Now; + fieldInfos.Date = PrintDate ?? DateTimeOffset.Now; if ((options & PageRenderOptions.RenderHeader) == PageRenderOptions.RenderHeader) RenderHeader(gfx, page); @@ -164,7 +168,7 @@ documentObject is not Table && throw new ArgumentException(MdPdfMsgs.ObjectNotRenderable(documentObject.GetType().Name).Message); var renderer = Renderer.Create(graphics, this, documentObject, null); - renderer!.Format(new Rectangle(xPosition, yPosition, width, double.MaxValue), null); + renderer!.Format(new Rectangle(xPosition, yPosition, width, Single.MaxValue), null); RenderInfo renderInfo = renderer.RenderInfo; renderInfo.LayoutInfo.ContentArea.X = xPosition; @@ -300,7 +304,7 @@ internal int NextListNumber(ListInfo listInfo) /// /// Gets or sets the print date, i.e. the rendering date. /// - public DateTime PrintDate { get; set; } = DateTime.MinValue; + public DateTimeOffset? PrintDate { get; set; } = null; // TODO: Breaking change, is now 'DateTimeOffset?'. internal PredefinedFontsAndChars FontsAndChars => _fontsAndChars ??= new (); PredefinedFontsAndChars? _fontsAndChars; @@ -321,7 +325,12 @@ static XFont CreateFont(string familyName, double emSize, XFontStyleEx style, st { try { +#if PSGFX + //return new(familyName, emSize, style); + return null!; +#else return new(familyName, emSize, style); +#endif } catch (Exception ex) { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FieldInfos.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FieldInfos.cs index e29172e5..219d34bd 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FieldInfos.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FieldInfos.cs @@ -61,6 +61,6 @@ internal BookmarkInfo(int physicalPageNumber, int displayPageNumber) internal int Section; internal int SectionPages; internal int NumPages; - internal DateTime Date; + internal DateTimeOffset Date; } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FillFormatRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FillFormatRenderer.cs index 60f31199..b549d3f5 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FillFormatRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FillFormatRenderer.cs @@ -1,10 +1,11 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System.Diagnostics; -using PdfSharp.Drawing; using MigraDoc.DocumentObjectModel; using MigraDoc.DocumentObjectModel.Shapes; +#if !PSXGRA +using PdfSharp.Drawing; +#endif namespace MigraDoc.Rendering { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs index 986f8bd6..075e4e82 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FontHandler.cs @@ -3,21 +3,21 @@ #define CACHE_FONTS_ -using System.Diagnostics; using PdfSharp.Drawing; using MigraDoc.DocumentObjectModel; +// v7.0.0 REVIEW PSG + namespace MigraDoc.Rendering { /// /// Helps to measure and handle fonts. /// - class FontHandler + static class FontHandler { #if DEBUG_ internal static int CreateFontCounter; #endif - /// /// Converts a DOM Font to an XFont. /// @@ -30,7 +30,6 @@ internal static XFont FontToXFont(Font font) return lastXFont; XFontStyleEx style = GetXStyle(font); - #if DEBUG_ if (StringComparer.OrdinalIgnoreCase.Compare(font.Name, "Segoe UI Semilight") == 0 && (style & XFontStyleEx.BoldItalic) == XFontStyleEx.Italic) @@ -67,64 +66,87 @@ internal static XFontStyleEx GetXStyle(Font font) internal static XUnitPt GetDescent(XFont font) { +#if PSGFX + var descent = (font.FontFace.Height - font.FontFace.Baseline) * font.Size; + return new(descent); +#else XUnitPt descent = font.Metrics.Descent; descent *= font.Size; descent /= font.FontFamily.GetEmHeight(font.Style); return descent; +#endif } internal static XUnitPt GetAscent(XFont font) { +#if PSGFX + var descent = font.FontFace.Baseline * font.Size; + return new(descent); +#else XUnitPt ascent = font.Metrics.Ascent; ascent *= font.Size; ascent /= font.FontFamily.GetEmHeight(font.Style); return ascent; +#endif } internal static double GetSubSuperScaling(XFont font) { +#if PSGFX + return 0.8 * font.Size; // TODO #PSG +#else return 0.8 * GetAscent(font) / font.GetHeight(); +#endif } internal static XFont ToSubSuperFont(XFont font) { +#if PSGFX double size = font.Size * GetSubSuperScaling(font); - - return new XFont(font.Name2, size, font.Style, font.PdfOptions); + return new XFont(font.FontFace.FamilyName, size, font.Style/*, font.PdfOptions*/); // TODO #PSG +#else + double size = font.Size * GetSubSuperScaling(font); + return new XFont(font.Name, size, font.Style, font.PdfOptions); +#endif } internal static XBrush FontColorToXBrush(Font font) { +#if PSGFX + Debug.Assert(font.Document != null, "font.Document != null"); + return new XSolidBrush(ColorHelper.ToXColor(font.Color, font.Document.UseCmykColor)); +#else #if noCMYK return new XSolidBrush(XColor.FromArgb((int)font.Color.A, (int)font.Color.R, (int)font.Color.G, (int)font.Color.B)); #else Debug.Assert(font.Document != null, "font.Document != null"); return new XSolidBrush(ColorHelper.ToXColor(font.Color, font.Document.UseCmykColor)); +#endif #endif } -#if CACHE_FONTS - static XFont XFontFromCache(Font font, bool unicode, PdfFontEmbedding fontEmbedding) - { - XFont xFont = null; +#if CACHE_FONTS || true_ + static XFont XFontFromCache(Font font, bool unicode, PdfFontEmbedding fontEmbedding) + { + XFont xFont = null; - XPdfFontOptions options = null; - options = new XPdfFontOptions(fontEmbedding, unicode); - XFontStyleEx style = GetXStyle(font); - xFont = new XFont(font.Name, font.Size, style, options); + XPdfFontOptions options = null; + options = new XPdfFontOptions(fontEmbedding, unicode); + XFontStyleEx style = GetXStyle(font); + xFont = new XFont(font.Name, font.Size, style, options); - return xFont; - } + return xFont; + } - static string BuildSignature(Font font, bool unicode, PdfFontEmbedding fontEmbedding) - { - StringBuilder signature = new StringBuilder(128); - signature.Append(font.Name.ToLower()); - signature.Append(font.Size.Point.ToString("##0.0")); - return signature.ToString(); - } + static string BuildSignature(Font font, bool unicode, PdfFontEmbedding fontEmbedding) + { + StringBuilder signature = new StringBuilder(128); + signature.Append(font.Name.ToLower()); + signature.Append(font.Size.Point.ToString("##0.0")); + return signature.ToString(); + } - static Hash_table fontCache = new Hash_table(); + static Hash_table fontCache = new Hash_table(); #endif } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedDocument.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedDocument.cs index b9239d0e..3c9c8b24 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedDocument.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedDocument.cs @@ -190,7 +190,7 @@ Rectangle CalcContentRect(int page) width -= pageSetup.RightMargin.Point; width -= pageSetup.LeftMargin.Point; - + XUnitPt height = pageSetup.PageHeight.Point; height -= pageSetup.TopMargin.Point; @@ -423,7 +423,7 @@ PageOrientation CalcPageOrientation(PageSetup _) } XSize CalcPageSize(PageSetup pageSetup) - => new(pageSetup.PageWidth.Point, pageSetup.PageHeight.Point); + => new((float_)pageSetup.PageWidth.Point, (float_)pageSetup.PageHeight.Point); bool IAreaProvider.PositionHorizontally(LayoutInfo layoutInfo) { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedHeaderFooter.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedHeaderFooter.cs index 4eb45812..64940be9 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedHeaderFooter.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedHeaderFooter.cs @@ -30,7 +30,7 @@ internal void Format(XGraphics gfx) Area? IAreaProvider.GetNextArea() { if (_isFirstArea) - return new Rectangle(ContentRect.X, ContentRect.Y, ContentRect.Width, double.MaxValue); + return new Rectangle(ContentRect.X, ContentRect.Y, ContentRect.Width, FLOAT_.MaxValue); return null; } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedTextArea.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedTextArea.cs index 74cd37e9..eaa49d82 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedTextArea.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/FormattedTextArea.cs @@ -70,8 +70,8 @@ XUnitPt CalcInherentWidth() inherentWidth = Math.Max(renderer.RenderInfo.LayoutInfo.MinWidth, inherentWidth); } } - inherentWidth.Point += TextArea.LeftPadding.Point; - inherentWidth.Point += TextArea.RightPadding.Point; + inherentWidth.Point += (float_)TextArea.LeftPadding.Point; + inherentWidth.Point += (float_)TextArea.RightPadding.Point; return inherentWidth; } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs index 7e715b29..c2bbb255 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ImageRenderer.cs @@ -1,7 +1,6 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System.Diagnostics; using Microsoft.Extensions.Logging; using PdfSharp.Drawing; using MigraDoc.DocumentObjectModel.Shapes; @@ -187,9 +186,9 @@ void CalculateImageDimensions() } // ReSharper restore CompareOfFloatsByEqualityOperator - XUnitPt inherentWidth = XUnitPt.FromInch(xPixels / horzRes); + XUnitPt inherentWidth = XUnitPt.FromInch((float_)(xPixels / horzRes)); double yPixels = xImage.PixelHeight; - XUnitPt inherentHeight = XUnitPt.FromInch(yPixels / vertRes); + XUnitPt inherentHeight = XUnitPt.FromInch((float_)(yPixels / vertRes)); //bool lockRatio = _image.IsNull("LockAspectRatio") ? true : _image.LockAspectRatio; bool lockRatio = _image.Values.LockAspectRatio is null || _image.LockAspectRatio; @@ -274,8 +273,8 @@ void CalculateImageDimensions() } if (resultHeight <= 0 || resultWidth <= 0) { - formatInfo.Width = XUnitPt.FromCentimeter(2.5); - formatInfo.Height = XUnitPt.FromCentimeter(2.5); + formatInfo.Width = XUnitPt.FromCentimeter(2.5f); + formatInfo.Height = XUnitPt.FromCentimeter(2.5f); //Debug.WriteLine(Messages2.EmptyImageSize); MigraDocLogHost.PdfRenderingLogger.LogError(MdPdfMsgs.EmptyImageSize.Message); _failure = ImageFailure.EmptySize; @@ -304,13 +303,13 @@ void CalculateImageDimensions() if (!_image.Values.Width.IsValueNullOrEmpty()) formatInfo.Width = _image.Width.Point; else - formatInfo.Width = XUnitPt.FromCentimeter(2.5); + formatInfo.Width = XUnitPt.FromCentimeter(2.5f); //if (_image.Values.Height is not null) if (!_image.Values.Height.IsValueNullOrEmpty()) formatInfo.Height = _image.Height.Point; else - formatInfo.Height = XUnitPt.FromCentimeter(2.5); + formatInfo.Height = XUnitPt.FromCentimeter(2.5f); } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/LineFormatRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/LineFormatRenderer.cs index a50d57f6..925a5efb 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/LineFormatRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/LineFormatRenderer.cs @@ -72,11 +72,19 @@ internal void Render(XUnitPt xPosition, XUnitPt yPosition, XUnitPt width, XUnitP var pen = new XPen(GetColor(), width); pen.DashStyle = (_lineFormat?.DashStyle ?? NRT.ThrowOnNull()) switch { +#if PSGFX + DashStyle.Dash => XDashStyles.Dash, + DashStyle.DashDot => XDashStyles.DashDot, + DashStyle.DashDotDot => XDashStyles.DashDotDot, + DashStyle.Solid => XDashStyles.Solid, + DashStyle.SquareDot => XDashStyles.Dot, +#else DashStyle.Dash => XDashStyle.Dash, DashStyle.DashDot => XDashStyle.DashDot, DashStyle.DashDotDot => XDashStyle.DashDotDot, DashStyle.Solid => XDashStyle.Solid, DashStyle.SquareDot => XDashStyle.Dot, +#endif _ => pen.DashStyle }; return pen; diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsg.resx b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsg.resx index 614232cc..5cc5f3da 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsg.resx +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsg.resx @@ -127,7 +127,7 @@ Image could not be read. - The number {0} is to large to be displayed as letters. + The number {0} is too large to be displayed as letters. Bookmark '{0}' is not defined within the document. @@ -136,7 +136,7 @@ Invalid image type: '{0}'. - The number {0} is to large to be displayed as roman. + The number {0} is too large to be displayed as roman. Image '{0}' not found. diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.cs index dd207cc1..60ebc9cd 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.cs @@ -6,7 +6,7 @@ namespace MigraDoc.Rendering { /// - /// MigraDoc PDF renderer message id. + /// MigraDoc PDF renderer message ID. /// // ReSharper disable InconsistentNaming enum MdPdfMsgId @@ -36,10 +36,10 @@ enum MdPdfMsgId static class MdPdfMsgs { internal static MdPdfMsg NumberTooLargeForRoman(int number) - => new(MdPdfMsgId.NumberTooLargeForRoman, Invariant($"The number {number} is to large to be displayed as roman number.")); + => new(MdPdfMsgId.NumberTooLargeForRoman, Invariant($"The number {number} is too large to be displayed as roman number.")); internal static MdPdfMsg NumberTooLargeForLetters(int number) - => new(MdPdfMsgId.NumberTooLargeForLetters, $"The number {number} is to large to be displayed as letters."); + => new(MdPdfMsgId.NumberTooLargeForLetters, $"The number {number} is too large to be displayed as letters."); internal static MdPdfMsg DisplayEmptyImageSize => new(MdPdfMsgId.DisplayEmptyImageSize, "Image has empty size."); diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.resx b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.resx index 614232cc..5cc5f3da 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.resx +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MdPdfMsgs.resx @@ -127,7 +127,7 @@ Image could not be read. - The number {0} is to large to be displayed as letters. + The number {0} is too large to be displayed as letters. Bookmark '{0}' is not defined within the document. @@ -136,7 +136,7 @@ Invalid image type: '{0}'. - The number {0} is to large to be displayed as roman. + The number {0} is too large to be displayed as roman. Image '{0}' not found. diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MigraDocRenderingBuildInformation.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MigraDocRenderingBuildInformation.cs index 32e34786..8e0e04c7 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MigraDocRenderingBuildInformation.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/MigraDocRenderingBuildInformation.cs @@ -1,63 +1,40 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System.Reflection; -using System.Runtime.Versioning; - -#pragma warning disable 0436 - -namespace MigraDoc.Rendering +namespace MigraDoc.Rendering // DELETE { /// - /// Information about the build of the renderer. + /// Use MigraDocProductVersionInformation instead. /// + [Obsolete("Do not use this class anymore. It is not useful and will be removed.")] public static class MigraDocRenderingBuildInformation { /// - /// Gets the git semantic version number created by GitVersionTask. + /// Gets the git semantic version number. /// - public static string GitSemVer => global::GitVersionInformation.SemVer; + [Obsolete("Do not use this class anymore. Use 'PdfSharp.Internal.SemVersionInformation.Version' instead.")] + public static string GitSemVer => "do not use this anymore"; /// - /// Gets the name of the branch created by GitVersionTask. + /// Gets the name of the branch. /// - public static string BranchName => global::GitVersionInformation.BranchName; + [Obsolete("Do not use this class anymore. Use 'PdfSharp.Internal.SemVersionInformation.BranchName' instead.")] + public static string BranchName => "do not use this anymore"; /// - /// Gets the commit date created by GitVersionTask. + /// Gets the commit date. /// - public static string CommitDate => global::GitVersionInformation.CommitDate; + [Obsolete("Do not use this class anymore. Use 'PdfSharp.Internal.SemVersionInformation.CommitDate' instead.")] + public static string CommitDate => "do not use this anymore"; /// /// Gets the assembly title attribute value. /// - public static string AssemblyTitle - => ((AssemblyTitleAttribute)Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(AssemblyTitleAttribute), false)[0]).Title; + public static string AssemblyTitle => "do not use this anymore"; -#if !NET6_0_OR_GREATER - /// - /// Gets the target platform attribute value. - /// - public static string TargetPlatform - { - get - { - // TargetPlatformAttribute is not available under .NET Framework. - return "Unknown Target Platform"; - } - } -#else /// /// Gets the target platform attribute value. /// - public static string TargetPlatform - { - get - { - var attribute = Assembly.GetExecutingAssembly().GetCustomAttributes(typeof(TargetPlatformAttribute), false); - return attribute.Length == 1 ? ((TargetPlatformAttribute)attribute[0]).PlatformName : "Unknown Target Platform"; - } - } -#endif + public static string TargetPlatform => "do not use this anymore"; } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs index e6d9ec52..5bf7b721 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ParagraphRenderer.cs @@ -1,12 +1,11 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System.Diagnostics; using System.Text; -using MigraDoc.DocumentObjectModel; using PdfSharp.Pdf; using PdfSharp.Pdf.Advanced; using PdfSharp.Drawing; +using MigraDoc.DocumentObjectModel; using MigraDoc.DocumentObjectModel.Fields; using MigraDoc.DocumentObjectModel.Shapes; using MigraDoc.Rendering.Extensions; @@ -115,7 +114,7 @@ internal override void Render() for (int idx = 0; idx < parFormatInfo.LineCount; idx++) { LineInfo lineInfo = parFormatInfo.GetLineInfo(idx); - _isLastLine = (idx == parFormatInfo.LineCount - 1); + _isLastLine = idx == parFormatInfo.LineCount - 1; _lastTabPosition = 0; if (lineInfo.ReMeasureLine) @@ -179,9 +178,9 @@ string GetFieldValue(DocumentObject field) { if (field is DateField dateField) { - var dt = _fieldInfos?.Date ?? NRT.ThrowOnNull(); - if (dt == DateTime.MinValue) - dt = DateTime.Now; + var dt = _fieldInfos?.Date ?? NRT.ThrowOnNull(); + if (dt == DateTimeOffset.MinValue) + dt = DateTimeOffset.Now; return FormatDateTimeForField(dt, dateField); } @@ -194,7 +193,7 @@ string GetFieldValue(DocumentObject field) return ""; } - static String FormatDateTimeForField(DateTime dateTime, DateField dateField) + static String FormatDateTimeForField(DateTimeOffset dateTime, DateField dateField) { var culture = dateField.Document!.EffectiveCulture; var dtfInfo = culture.DateTimeFormat; @@ -361,7 +360,7 @@ static bool IsSoftHyphen(DocumentObject docObj) XUnitPt ProbeAfterLeftAlignedTab(XUnitPt tabStopPosition, out bool notFitting) { //--- Save --------------------------------- - SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, + SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, out var lineEndsWithLineBreak); //------------------------------------------ @@ -387,7 +386,7 @@ XUnitPt ProbeAfterLeftAlignedTab(XUnitPt tabStopPosition, out bool notFitting) XUnitPt ProbeAfterRightAlignedTab(XUnitPt tabStopPosition, out bool notFitting) { //--- Save --------------------------------- - SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, + SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, out var lineEndsWithLineBreak); //------------------------------------------ @@ -431,7 +430,7 @@ XUnitPt ProbeAfterRightAlignedTab(XUnitPt tabStopPosition, out bool notFitting) XUnitPt ProbeAfterCenterAlignedTab(XUnitPt tabStopPosition, out bool notFitting) { //--- Save --------------------------------- - SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, + SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, out var lineEndsWithLineBreak); //------------------------------------------ @@ -475,7 +474,7 @@ XUnitPt ProbeAfterDecimalAlignedTab(XUnitPt tabStopPosition, out bool notFitting notFitting = false; //--- Save --------------------------------- - SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, + SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, out var lineEndsWithLineBreak); //------------------------------------------ @@ -571,7 +570,7 @@ XUnitPt ProbeAfterDecimalAlignedTab(XUnitPt tabStopPosition, out bool notFitting return ProbeAfterRightAlignedTab(tabStopPosition, out notFitting); } - void SaveBeforeProbing(out ParagraphIterator? paragraphIter, out int blankCount, out XUnitPt wordsWidth, out XUnitPt xPosition, out XUnitPt lineWidth, out XUnitPt blankWidth, + void SaveBeforeProbing(out ParagraphIterator? paragraphIter, out int blankCount, out XUnitPt wordsWidth, out XUnitPt xPosition, out XUnitPt lineWidth, out XUnitPt blankWidth, out bool lineEndsWithLineBreak) { paragraphIter = _currentLeaf; @@ -583,7 +582,7 @@ void SaveBeforeProbing(out ParagraphIterator? paragraphIter, out int blankCount, lineEndsWithLineBreak = _currentLineEndsWithLineBreak; } - void RestoreAfterProbing(ParagraphIterator? paragraphIter, int blankCount, XUnitPt wordsWidth, XUnitPt xPosition, XUnitPt lineWidth, XUnitPt blankWidth, + void RestoreAfterProbing(ParagraphIterator? paragraphIter, int blankCount, XUnitPt wordsWidth, XUnitPt xPosition, XUnitPt lineWidth, XUnitPt blankWidth, bool lineEndsWithLineBreak) { _currentLeaf = paragraphIter; @@ -606,7 +605,7 @@ bool ProbeAfterTab() _currentBlankCount = 0; // Extra for auto tab after list symbol. - //TODO_OLD: KLPO4KLPO: Check if this conditional statement is still required. + // Check if this conditional statement is still required. if (_currentLeaf != null && IsTab(_currentLeaf.Current)) _currentLeaf = _currentLeaf.GetNextLeaf(); @@ -820,7 +819,7 @@ void RenderLine(LineInfo lineInfo) void ReMeasureLine(ref LineInfo lineInfo) { //--- Save --------------------------------- - SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, + SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, out var lineEndsWithLineBreak); bool origLastTabPassed = _lastTabPassed; //------------------------------------------ @@ -957,7 +956,7 @@ void RenderElement(DocumentObject docObj) break; default: - throw new NotImplementedException(docObj.GetType().Name + " is not implemented."); + throw new NotSupportedException(docObj.GetType().Name + " is not implemented."); } } @@ -1021,7 +1020,7 @@ void RenderBookmarkField(BookmarkField bookmarkField) var destinationName = bookmarkField.Name; var position = GetDestinationPosition(); - pdfDocument.AddNamedDestination(destinationName, pageNr, PdfNamedDestinationParameters.CreatePosition(position)); + pdfDocument.AddNamedDestination(destinationName, pageNr, PdfNamedDestinationParameters.CreatePosition(position.AsXPoint)); } RenderUnderline(0, false); @@ -1043,7 +1042,7 @@ XPoint GetDestinationPosition() return pdfPosition; } // ReSharper disable once InconsistentNaming - static readonly XUnitPt _margin = XUnitPt.FromCentimeter(0.5); + static readonly XUnitPt _margin = XUnitPt.FromCentimeter(0.5f); void RenderPageRefField(PageRefField pageRefField) { @@ -1268,31 +1267,31 @@ void EndHyperlink(Hyperlink hyperlink, XUnitPt right, XUnitPt bottom) var pdfDocument = _gfx.PdfPage?.Owner; if (pdfDocument != null) { - page.AddDocumentLink(new PdfRectangle(rect), hyperlink.BookmarkName); + page.AddDocumentLink(new PdfRectangle(rect.AsXRect), hyperlink.BookmarkName); } // Otherwise use page from bookmark’s fieldInfo. else { var pageRef = _fieldInfos?.GetPhysicalPageNumber(hyperlink.BookmarkName) ?? NRT.ThrowOnNull(); if (pageRef > 0) - page.AddDocumentLink(new PdfRectangle(rect), pageRef); + page.AddDocumentLink(new PdfRectangle(rect.AsXRect), pageRef); } break; case HyperlinkType.ExternalBookmark: - page.AddDocumentLink(new PdfRectangle(rect), hyperlink.Filename, hyperlink.BookmarkName, ConvertHyperlinkTargetWindow(hyperlink.NewWindow)); + page.AddDocumentLink(new PdfRectangle(rect.AsXRect), hyperlink.Filename, hyperlink.BookmarkName, ConvertHyperlinkTargetWindow(hyperlink.NewWindow)); break; case HyperlinkType.EmbeddedDocument: - page.AddEmbeddedDocumentLink(new PdfRectangle(rect), hyperlink.Filename, hyperlink.BookmarkName, ConvertHyperlinkTargetWindow(hyperlink.NewWindow)); + page.AddEmbeddedDocumentLink(new PdfRectangle(rect.AsXRect), hyperlink.Filename, hyperlink.BookmarkName, ConvertHyperlinkTargetWindow(hyperlink.NewWindow)); break; case HyperlinkType.Web: - page.AddWebLink(new PdfRectangle(rect), hyperlink.Filename); + page.AddWebLink(new PdfRectangle(rect.AsXRect), hyperlink.Filename); break; case HyperlinkType.File: - page.AddFileLink(new PdfRectangle(rect), hyperlink.Filename); + page.AddFileLink(new PdfRectangle(rect.AsXRect), hyperlink.Filename); break; } _hyperlinkRect = new XRect(); @@ -1905,7 +1904,9 @@ FormatResult FormatAsWord(XUnitPt width, Rectangle fittingRect) FormatResult FormatDateField(DateField dateField, Rectangle fittingRect) { _reMeasureLine = true; - var estimatedFieldValue = FormatDateTimeForField(DateTime.Now, dateField); + // Use a fixed date to measure the dimensions required. + var estimatedFieldValue = FormatDateTimeForField(new DateTimeOffset(2999, 08, 28, 22, 28, 28, TimeSpan.Zero), dateField); + //var estimatedFieldValue = FormatDateTimeForField(DateTimeOffset.Now, dateField); return FormatWord(estimatedFieldValue, fittingRect); } @@ -2221,7 +2222,7 @@ FormatResult FormatSoftHyphen() return FormatResult.Continue; //--- Save --------------------------------- - SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, + SaveBeforeProbing(out var iter, out var blankCount, out var wordsWidth, out var xPosition, out var lineWidth, out var blankWidth, out var lineEndsWithLineBreak); //------------------------------------------ _currentLeaf = nextIter; @@ -2367,7 +2368,7 @@ void StoreLineInformation() if (_startLeaf != null && _startLeaf == _currentLeaf) HandleNonFittingLine(); #endif - + lineInfo.LastTab = _lastTab; _renderInfo.LayoutInfo.ContentArea = contentArea ?? NRT.ThrowOnNull(); @@ -2511,7 +2512,7 @@ VerticalLineInfo CalcVerticalInfo(XFont font, bool isListSymbol = false) lineHeight = singleLineSpace * factor; lineHeight = Math.Max(lineHeight, _currentVerticalInfo.Height); } - + XUnitPt descent; if (lineSpacingRule == LineSpacingRule.Exactly) { @@ -2541,7 +2542,7 @@ VerticalLineInfo CalcVerticalInfo(XFont font, bool isListSymbol = false) return new(lineHeight, descent, inherentLineSpace); } - + /// /// The font used for the current paragraph element. /// @@ -2676,7 +2677,11 @@ void EndUnderline(XPen pen, XUnitPt xPosition) { XUnitPt yPosition = CurrentBaselinePosition; yPosition += 0.33 * _currentVerticalInfo.Descent; +#if PSGFX + _gfx.DrawLine(pen, new(_underlineStartPos, yPosition), new(xPosition, yPosition)); +#else _gfx.DrawLine(pen, _underlineStartPos, yPosition, xPosition, yPosition); +#endif } XPen? _currentUnderlinePen; @@ -2739,16 +2744,30 @@ bool UnderlinePenChanged(XPen? pen) XPen pen = new XPen(XColor.FromArgb(font.Color.Argb), font.Size / 16); #else Debug.Assert(_paragraph.Document != null, "_paragraph.Document != null"); +#if PSGFX + var xxx = ColorHelper.ToXColor(font.Color, _paragraph.Document.UseCmykColor); + var pen = new XPen(new XSolidBrush(xxx), (float_)font.Size.Point / 16); +#else var pen = new XPen(ColorHelper.ToXColor(font.Color, _paragraph.Document.UseCmykColor), font.Size.Point / 16); +#endif #endif pen.DashStyle = font.Underline switch { +#if PSGFX + Underline.DotDash => XDashStyles.DashDot, + Underline.DotDotDash => XDashStyles.DashDotDot, + Underline.Dash => XDashStyles.Dash, + Underline.Dotted => XDashStyles.Dot, + Underline.Single => XDashStyles.Solid, + _ => XDashStyles.Solid +#else Underline.DotDash => XDashStyle.DashDot, Underline.DotDotDash => XDashStyle.DashDotDot, Underline.Dash => XDashStyle.Dash, Underline.Dotted => XDashStyle.Dot, Underline.Single => XDashStyle.Solid, _ => XDashStyle.Solid +#endif }; return pen; } @@ -2769,7 +2788,7 @@ bool UnderlinePenChanged(XPen? pen) bool _isFirstLine; bool _isLastLine; VerticalLineInfo _currentVerticalInfo; - Area _formattingArea = default!; + Area _formattingArea = null!; XUnitPt _currentYPosition; XUnitPt _currentXPosition; ParagraphIterator? _currentLeaf; diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/PdfDocumentRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/PdfDocumentRenderer.cs index d0d6c4f2..c3cc1337 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/PdfDocumentRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/PdfDocumentRenderer.cs @@ -1,12 +1,17 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System.Diagnostics; using System.Reflection; using PdfSharp.Pdf; using PdfSharp.Drawing; using MigraDoc.DocumentObjectModel; using MigraDoc.Rendering.Internals; +#if PSGFX +using PdfSharp.Graphics.Pdf; +using PdfSharp.Graphics.Pdf.Media; + +#else +#endif namespace MigraDoc.Rendering { @@ -143,10 +148,12 @@ public void PrepareRenderPages() var pdfDocument = PdfDocument; foreach (var item in _document?.EmbeddedFiles ?? NRT.ThrowOnNull()) { - if (item as EmbeddedFile is { } embeddedFile) - pdfDocument.AddEmbeddedFile(embeddedFile.Name, embeddedFile.Path); + if (item as DocumentObjectModel.EmbeddedFile is { } embeddedFile) +#pragma warning disable CS0618 // Type or member is obsolete + pdfDocument.AddEmbeddedFile(embeddedFile.Name, embeddedFile.Path); // TODO: Do not use this function anymore. +#pragma warning restore CS0618 // Type or member is obsolete else - NRT.ThrowOnNull(); + NRT.ThrowOnNull(); } WriteDocumentInformation(); @@ -200,17 +207,31 @@ public void RenderPages(int startPage, int endPage) _pdfDocument ??= CreatePdfDocument(); - DocumentRenderer.PrintDate = DateTime.Now; + DocumentRenderer.PrintDate = DateTimeOffset.Now; for (int pageNr = startPage; pageNr <= endPage; ++pageNr) { var pdfPage = _pdfDocument.AddPage(); var pageInfo = DocumentRenderer.FormattedDocument.GetPageInfo(pageNr); +#if PSGFX + pdfPage.Width = PdfSharp.Drawing.XUnit.FromPoint(pageInfo.Width); + pdfPage.Height = PdfSharp.Drawing.XUnit.FromPoint(pageInfo.Height); +#else pdfPage.Width = pageInfo.Width; pdfPage.Height = pageInfo.Height; +#endif pdfPage.Orientation = pageInfo.Orientation; +#if PSGFX + var pageVisual = PdfPageVisual.FromPdfPage(pdfPage); + var gfx = pageVisual.RenderOpen(); +#else using var gfx = XGraphics.FromPdfPage(pdfPage); +#endif DocumentRenderer.RenderPage(gfx, pageNr); +#if PSGFX + // DrawingContext must be closed. + gfx.Close(); +#endif } } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ShadingRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ShadingRenderer.cs index 08f32f85..d18dc727 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ShadingRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/ShadingRenderer.cs @@ -42,6 +42,7 @@ internal void Render(XUnitPt x, XUnitPt y, XUnitPt width, XUnitPt height, Rounde var path = new XGraphicsPath(); +#if !PSGFX switch (roundedCorner) { case RoundedCorner.TopLeft: @@ -61,6 +62,7 @@ internal void Render(XUnitPt x, XUnitPt y, XUnitPt width, XUnitPt height, Rounde path.AddLine(new XPoint(x, y), new XPoint(x + width, y)); break; } +#endif path.CloseFigure(); _gfx.DrawPath(_brush, path); } diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableFormatInfo.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableFormatInfo.cs index 227e97ef..3a0cdd50 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableFormatInfo.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableFormatInfo.cs @@ -3,7 +3,9 @@ using MigraDoc.DocumentObjectModel.Tables; using MigraDoc.DocumentObjectModel.Visitors; +#if !PSXGRA using PdfSharp.Drawing; +#endif namespace MigraDoc.Rendering { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableRenderer.cs index a28b6406..9e780de2 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TableRenderer.cs @@ -1,7 +1,6 @@ // MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. -using System.Diagnostics; using MigraDoc.DocumentObjectModel.Internals; using PdfSharp.Drawing; using MigraDoc.DocumentObjectModel; @@ -216,25 +215,25 @@ void RenderContent(Cell cell, Rectangle innerRect) XUnitPt contentHeight = formattedCell.ContentHeight; XUnitPt innerHeight = innerRect.Height; Debug.Assert(cell.Column != null, "cell.Column != null"); - XUnitPt targetX = innerRect.X + cell.Column.LeftPadding.Point; + XUnitPt targetX = (float_)(innerRect.X + cell.Column.LeftPadding.Point); XUnitPt targetY; Debug.Assert(cell.Row != null, "cell.Row != null"); if (verticalAlignment == VerticalAlignment.Bottom) { targetY = innerRect.Y + innerRect.Height; - targetY -= cell.Row.BottomPadding.Point; + targetY -= (float_)cell.Row.BottomPadding.Point; targetY -= contentHeight; } else if (verticalAlignment == VerticalAlignment.Center) { - targetY = innerRect.Y + cell.Row.TopPadding.Point; - targetY += innerRect.Y + innerRect.Height - cell.Row.BottomPadding.Point; + targetY = (float_)(innerRect.Y + cell.Row.TopPadding.Point); + targetY += (float_)(innerRect.Y + innerRect.Height - cell.Row.BottomPadding.Point); targetY -= contentHeight; targetY /= 2; } else - targetY = innerRect.Y + cell.Row.TopPadding.Point; + targetY = (float_)(innerRect.Y + cell.Row.TopPadding.Point); RenderByInfos(targetX, targetY, renderInfos); } @@ -283,7 +282,7 @@ Rectangle GetInnerRect(XUnitPt startingHeight, Cell cell) int cellColIndex = cell.Column.Index; // Cache property result. for (int clmIdx = 0; clmIdx < cellColIndex; ++clmIdx) { - x += _table.Columns[clmIdx]?.Width.Point ?? NRT.ThrowOnNull(); + x += (float_)(_table.Columns[clmIdx]?.Width.Point ?? NRT.ThrowOnNull()); } x += LeftBorderOffset; @@ -491,7 +490,7 @@ void FinishLayoutInfo(Area area, XUnitPt currentHeight, XUnitPt startingHeight) //foreach (Column clm in _table.Columns) foreach (var clm in _table.Columns.Cast()) { - width += clm.Width.Point; + width += (float_)(clm.Width.Point); } layoutInfo.ContentArea.Width = width; } @@ -499,12 +498,12 @@ void FinishLayoutInfo(Area area, XUnitPt currentHeight, XUnitPt startingHeight) //if (_table.Rows.Values.LeftIndent is not null) if (!_table.Rows.Values.LeftIndent.IsValueNullOrEmpty()) - layoutInfo.Left = _table.Rows.LeftIndent.Point; + layoutInfo.Left = (float_)_table.Rows.LeftIndent.Point; else if (_table.Rows.Alignment == RowAlignment.Left) { XUnitPt leftOffset = LeftBorderOffset; - leftOffset += _table.Columns[0].LeftPadding.Point; + leftOffset += (float_)_table.Columns[0].LeftPadding.Point; layoutInfo.Left = -leftOffset; } @@ -590,7 +589,7 @@ void CalcLastHeaderRow() _lastHeaderRow = row.Index; else break; } - // Note: Do not use _connectedRowsMap here, it was not yet initialized. + // Do not use _connectedRowsMap here, it was not yet initialized. if (_lastHeaderRow >= 0) _lastHeaderRow = CalcLastConnectedRowDirect(_lastHeaderRow); @@ -876,7 +875,7 @@ Cell GetMinMergedCell(int row) { #if DEBUG // Comparing the result of the new implementation with the old version. - // Note: It does not matter which cell is actually returned. Just the bottom margin of the cell matters. + // It does not matter which cell is actually returned. Just the bottom margin of the cell matters. var originalResult = GetMinMergedCellOriginal(row); #endif @@ -896,7 +895,7 @@ Cell GetMinMergedCell(int row) // Does it matter which cell we return??? 2023-03-08 Yes, we need a result that is in _mergedCells. Debug.Assert(originalResult == cell); - // Note: It does not matter which cell is actually returned. Just the bottom margin of the cell matters. + // It does not matter which cell is actually returned. Just the bottom margin of the cell matters. Debug.Assert(originalResult.Row!.Index + originalResult.MergeDown == cell.Row!.Index + cell.MergeDown); #endif diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TextFrameRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TextFrameRenderer.cs index b254fbdb..4cd2f44a 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TextFrameRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.Rendering/Rendering/TextFrameRenderer.cs @@ -60,7 +60,11 @@ void RenderContent() XGraphicsState Transform() { Area frameContentArea = _renderInfo.LayoutInfo.ContentArea; +#if PSGFX + var state = _gfx.Save(); +#else XGraphicsState state = _gfx.Save(); +#endif XUnitPt xPosition; XUnitPt yPosition; switch (_textFrame.Orientation) @@ -93,8 +97,13 @@ XGraphicsState Transform() void ResetTransform(XGraphicsState? state) { +#if PSGFX + if (state != null) + _gfx.Restore(state.Value); +#else if (state != null) _gfx.Restore(state); +#endif } readonly TextFrame _textFrame; diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj index 248fca94..262cfd21 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-gdi/MigraDoc.RtfRendering-gdi.csproj @@ -1,6 +1,6 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true MigraDoc GDI diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj index f9d150e8..ff82545c 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering-wpf/MigraDoc.RtfRendering-wpf.csproj @@ -1,6 +1,6 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true MigraDoc WPF diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj index 93dcc6ce..317a7aba 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/MigraDoc.RtfRendering.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) MigraDoc ..\..\..\..\..\StrongnameKey.snk True diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/ChartRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/ChartRenderer.cs index 1de31cc2..c64b5e29 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/ChartRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/ChartRenderer.cs @@ -182,7 +182,7 @@ bool StoreTempImage(string fileName) // throw new InvalidOperationException("This version of MigraDoc cannot render charts to RTF. Use the WPF build under Windows to create RTF files with charts."); return false; -#else +#else // GDI case here. try { const float resolution = 96; @@ -192,18 +192,8 @@ bool StoreTempImage(string fileName) XGraphics gfx = XGraphics.CreateMeasureContext(new XSize(horzPixels, vertPixels), XGraphicsUnit.Point, XPageDirection.Downwards, new RenderEvents()); #else -#if GDI Bitmap bmp = new Bitmap(horzPixels, vertPixels); XGraphics gfx = XGraphics.FromGraphics(Graphics.FromImage(bmp), new XSize(horzPixels, vertPixels), new RenderEvents()); -#else - // TODOWPF - // TODOCORE - return false; -#endif -#if WPF - // TODOWPF - XGraphics gfx = null; //XGraphics.FromGraphics(Graphics.FromImage(bmp), new XSize(horzPixels, vertPixels)); -#endif #endif //REM: Should not be necessary: gfx.ScaleTransform(resolution / 72); @@ -211,10 +201,8 @@ bool StoreTempImage(string fileName) DocumentRenderer renderer = new MigraDoc.Rendering.DocumentRenderer(_chart.Document!); renderer.RenderObject(gfx, 0, 0, GetShapeWidth().Point, _chart); -#if GDI bmp.SetResolution(resolution, resolution); bmp.Save(fileName, System.Drawing.Imaging.ImageFormat.Png); -#endif } catch (Exception) { diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/DateFieldRenderer.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/DateFieldRenderer.cs index 87ac24c0..70e0099d 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/DateFieldRenderer.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/DateFieldRenderer.cs @@ -247,7 +247,7 @@ string TranslateCustomFormatChar(char ch) /// protected override string GetFieldResult() { - return DateTime.Now.ToString(GetEffectiveFormat(_dateField, out _)); + return DateTimeOffset.Now.ToString(GetEffectiveFormat(_dateField, out _)); } readonly DateField _dateField; diff --git a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs index 7994c802..5230386f 100644 --- a/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs +++ b/src/foundation/src/MigraDoc/src/MigraDoc.RtfRendering/RtfRendering/RendererBase.cs @@ -287,7 +287,7 @@ protected void Translate(string valueName, string rtfCtrl) } /// - /// Translates a value named 'valueName' to a Rtf Control word that specifies a Boolean and divides in two control words. + /// Translates a value named 'valueName' to a Rtf Control word that specifies a boolean and divides in two control words. /// If the control word in false case is simply left away, you can also use the Translate function as well. /// protected void TranslateBool(string valueName, string rtfTrueCtrl, string rtfFalseCtrl, bool withStar) diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Bullets.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Bullets.cs index 0af08041..ea72cb54 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Bullets.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Bullets.cs @@ -58,10 +58,10 @@ public void Create_Bullets() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("HelloWorld"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/bullets/HelloWorld"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -264,10 +264,10 @@ public void BulletLineSpacingTest() var pdfDocument = pdfRenderer.PdfDocument; pdfDocument.Options.CompressContentStreams = false; - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("BulletLineSpacingTest"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/bullets/BulletLineSpacingTest"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Helper/DocumentObjectSnapshot.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Helper/DocumentObjectSnapshot.cs index 194c0bcf..ccb34886 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Helper/DocumentObjectSnapshot.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Helper/DocumentObjectSnapshot.cs @@ -121,7 +121,7 @@ public DocumentObjectSnapshot(DocumentObject docObj, Dictionary - net8.0;net9.0;net10.0;net462 + $(PDFsharpTargetFrameworks_Tests_Exe) - CS1685 + $(NoWarn);1685 True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/ParagraphTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/ParagraphTests.cs index dd1f8fbb..b3f4260a 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/ParagraphTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/ParagraphTests.cs @@ -44,7 +44,7 @@ public void Test_Empty_Paragraph() var pdfRenderer = new PdfDocumentRenderer { Document = document }; pdfRenderer.RenderDocument(); - var filename = PdfFileUtility.GetTempPdfFileName("Test_Empty_Paragraph"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Empty_Paragraph"); pdfRenderer.PdfDocument.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -62,7 +62,7 @@ public void Test_Empty_FormattedText() var pdfRenderer = new PdfDocumentRenderer { Document = document }; pdfRenderer.RenderDocument(); - var filename = PdfFileUtility.GetTempPdfFileName("Test_Empty_FormattedText"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Empty_FormattedText"); pdfRenderer.PdfDocument.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -215,7 +215,7 @@ public void Test_Multiline_Border_Paragraph_PageBreaks() } // Save sumDoc. - var filename = PdfFileUtility.GetTempPdfFileName("Test_Multiline_Border_Paragraph_PageBreaks"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Multiline_Border_Paragraph_PageBreaks"); sumDoc.PageLayout = PdfPageLayout.TwoPageLeft; sumDoc.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); @@ -373,7 +373,7 @@ public void Test_Trailing_Objects_Border_Paragraph_PageBreak() } // Save sumDoc. - var filename = PdfFileUtility.GetTempPdfFileName("Test_Trailing_Objects_Border_Paragraph_PageBreak"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Trailing_Objects_Border_Paragraph_PageBreak"); sumDoc.PageLayout = PdfPageLayout.TwoPageLeft; sumDoc.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); @@ -469,10 +469,10 @@ public void Test_LineSpacingRule() var pdfDocument = pdfRenderer.PdfDocument; pdfDocument.Options.CompressContentStreams = false; - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Test_LineSpacingRule"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_LineSpacingRule"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); @@ -654,10 +654,10 @@ public void Test_PageBreak_And_Fitting_Line_Height() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Test_PageBreak_And_Fitting_Line_Height"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_PageBreak_And_Fitting_Line_Height"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -696,10 +696,66 @@ public void Test_Line_And_PageBreak_Big_Words() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Test_Line_And_PageBreak_Big_Words"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_Line_And_PageBreak_Big_Words"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(filename); + } + + [Fact] + public void Test_New_Font() + { + // In this test the font of style "Normal" is replaced by a new font instance without a color been set. + // For this reason the ultimately inherited font color is Font.Empty and no text and underline is visible. + + // In the future, a warning could be shown if an empty color is used for drawing an object. + // Alternatively or additionally, Colors.Black could automatically been used, if the color is still Colors.Empty after flattening. + + // This test helped to find bugs in PDFsharp 6.2.1, as PdfGraphicsState was not implemented correctly to start with drawing + // strokes and fills with alpha channel set to 0, like Color.Empty. The bugs resulted in drawing the content partly in black: + // The text was completely visible and the underline until drawing the border. So this test also helps to verify the bugfix. + + var document = new Document(); + + // Due to this line the ultimately inherited font color is Font.Empty and no text and underline is visible. + document.Styles.Normal.Font = new Font("Arial", 9); + document.Styles.Normal.Font.Underline = Underline.Single; + + var section = document.AddSection(); + section.AddParagraph("Paragraph - underline visible in PDFsharp 6.2.1"); + + var tbl1 = section.AddTable(); + tbl1.AddColumn(Unit.FromCentimeter(10)); + var row = tbl1.AddRow(); + row.Cells[0].AddParagraph("Table 1 - underline visible in PDFsharp 6.2.1"); + + var tbl2 = section.AddTable(); + tbl2.AddColumn(Unit.FromCentimeter(10)); + row = tbl2.AddRow(); + + row.Borders.Left = new Border { Width = 0.75, Color = Colors.Black }; + + row.Cells[0].AddParagraph("Table 2 With Border - underline still visible in PDFsharp 6.2.1"); + + var tbl3 = section.AddTable(); + tbl3.AddColumn(Unit.FromCentimeter(10)); + row = tbl3.AddRow(); + row.Cells[0].AddParagraph("Table 3 - underline not visible in PDFsharp 6.2.1 from now on"); + + section.AddParagraph("Paragraph after table - underline not visible"); + + + // Create a renderer for the MigraDoc document. + var pdfRenderer = new PdfDocumentRenderer { Document = document }; + + // Layout and render document to PDF. + pdfRenderer.RenderDocument(); + + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Test_New_Font"); + pdfRenderer.PdfDocument.Save(filename); + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/SerializerTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/SerializerTests.cs index ade87051..97512ffa 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/SerializerTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/SerializerTests.cs @@ -804,7 +804,7 @@ public void Test_WriteAndReadMdddl_Enum() var style = doc.Styles[styleName]; style.Should().NotBeNull(); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var desiredValue in Enum.GetValues()) #else foreach (ParagraphAlignment desiredValue in Enum.GetValues(typeof(ParagraphAlignment))) diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TableTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TableTests.cs index 902bc046..84ab9189 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TableTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TableTests.cs @@ -56,10 +56,10 @@ public void Create_Hello_World_TableTests() rendering.Should().Throw(); - //// Save the document... + //// Save the document… //var filename = PdfFileUtility.GetTempPdfFileName("HelloWorld"); //pdfRenderer.PdfDocument.Save(filename); - //// ...and start a viewer. + //// … and start a viewer. //PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -126,7 +126,7 @@ public void Test_MergeDown_Simple() var pdfRenderer = CreateReadablePdfDocumentRenderer(document); pdfRenderer.RenderDocument(); - var filename = PdfFileUtility.GetTempPdfFileName("Test_MergeDown"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_MergeDown"); pdfRenderer.PdfDocument.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -169,7 +169,7 @@ public void Test_KeepWith_MergeDown_PageBreak() var pdfRenderer = CreateReadablePdfDocumentRenderer(document); pdfRenderer.RenderDocument(); - var filename = PdfFileUtility.GetTempPdfFileName("Test_MergeDown_PageBreak"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_MergeDown_PageBreak"); pdfRenderer.PdfDocument.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -207,7 +207,7 @@ public void Test_MergeDown_LineBreak_RowHeight() var pdfRenderer = CreateReadablePdfDocumentRenderer(document); pdfRenderer.RenderDocument(); - var filename = PdfFileUtility.GetTempPdfFileName("Test_MergeDown_LineBreak_RowHeight"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_MergeDown_LineBreak_RowHeight"); pdfRenderer.PdfDocument.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); @@ -245,7 +245,7 @@ public void Test_MergeDown_LineBreak_RowHeight() // Find "ID#2" text object. var id2Found = streamEnumerator.Text.MoveAndGetNext(x => x.Text == "ID#2", true, out _); id2Found.Should().BeTrue("text object \"ID#2\" shall be found"); - + // Check the following lines drawing the borders for the correct values. streamEnumerator.MoveNext().Should().BeTrue(); streamEnumerator.Current.Should().Be("ET", "\"ID#1\" shall be the last text of the cell"); @@ -303,7 +303,7 @@ public void Test_Border_Inheritance() var pdfRenderer = CreateReadablePdfDocumentRenderer(document); pdfRenderer.RenderDocument(); - var filename = PdfFileUtility.GetTempPdfFileName("Test_Border_Inheritance"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_Border_Inheritance"); pdfRenderer.PdfDocument.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); @@ -311,7 +311,7 @@ public void Test_Border_Inheritance() var page = pdfRenderer.PdfDocument.Pages[0]; var contentReference = (PdfReference)page.Contents.Elements.Items[0]; var content = (PdfDictionary)contentReference.Value; - var contentStream = content.Stream.ToString(); + var contentStream = content.Stream?.ToString() ?? ""; var contentLines = contentStream.Split('\n'); // 1.5 is the desired border width. It shall be set only once. @@ -388,7 +388,7 @@ public void Test_Huge_MergeDown_Cell() var pdfRenderer = CreateReadablePdfDocumentRenderer(document); pdfRenderer.RenderDocument(); - var filename = PdfFileUtility.GetTempPdfFileName("Test_Huge_MergeDown_Cell"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_Huge_MergeDown_Cell"); pdfRenderer.PdfDocument.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -432,7 +432,7 @@ public void Test_Repeated_Heading_Border() var pdfRenderer = CreateReadablePdfDocumentRenderer(document); pdfRenderer.RenderDocument(); - var filename = PdfFileUtility.GetTempPdfFileName("Test_Repeated_Heading_Border"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/Test_Repeated_Heading_Border"); pdfRenderer.PdfDocument.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); @@ -443,7 +443,7 @@ public void Test_Repeated_Heading_Border() { var contentStream = PdfFileHelper.GetPageContentStream(pdfRenderer.PdfDocument, pageIdx); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER // Split ContentStream where the "Row" text is rendered. var contentByRows = contentStream.Split("(Row) Tj"); contentByRows.Length.Should().Be(3, "as \"Row\" occurs twice per page, the stream should be split into 3 parts"); @@ -474,7 +474,7 @@ public void Test_Repeated_Heading_Border() bottomBorderDrawLinePartLines.Should().NotContain(contentStreamBottomWidth, "heading bottom border should not be of content bottom border width"); bottomBorderDrawLinePartLines.Should().NotContain(contentStreamBottomColor, "heading bottom border should not be of content bottom border color"); - + // Row 1. contentRowDrawLineParts = rowsByDrawLinesByLines[1]; contentRowDrawLineParts.Length.Should().Be(3, "for the content rows one bottom and one top border should split the content into 3 parts"); diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Template.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Template.cs index cba7410f..23fef44b 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Template.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/Template.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using MigraDoc.DocumentObjectModel.Fields; @@ -52,10 +52,10 @@ public void Create_Hello_World_TemplateDOM() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("HelloWorld"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/HelloWorld"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TextFrames.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TextFrames.cs index e68470e2..e392b9f9 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TextFrames.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/TextFrames.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using PdfSharp.Fonts; @@ -56,10 +56,10 @@ public void Create_TextFrames() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Create_TextFrames"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/Create_TextFrames"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/valuemodel/BasicTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/valuemodel/BasicTests.cs index e200fd1b..f9b83d91 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/valuemodel/BasicTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.DocumentObjectModel.Tests/valuemodel/BasicTests.cs @@ -19,7 +19,7 @@ public Test1Object() } internal override void Serialize(Serializer serializer) - => throw new NotImplementedException(); + => throw new NotSupportedException(); internal override Meta Meta => TheMeta; diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj index 91eaa028..7d71bf5f 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GBE-Runner/MigraDoc.GBE-Runner.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net9.0;net10.0;net462 + $(PDFsharpTargetFrameworks_Tests_Exe) MigraDoc.GBE_Runner MigraDoc.GBE_Runner.Program 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..e1590221 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 @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Tests_Windows) MigraDoc.GrammarByExample_GDI true true @@ -92,10 +92,4 @@ - - - all - runtime; build; native; contentfiles; analyzers; buildtransitive - - diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj index 8afb911c..b47688c0 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample-WPF/MigraDoc.GrammarByExample-WPF.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Tests_Windows) MigraDoc.GrammarByExample_WPF true true diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj index 317b2cd8..da8c0f2c 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/MigraDoc.GrammarByExample.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;net462 + $(PDFsharpTargetFrameworks_Tests_Exe) false diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/DdlGbeTestBase.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/DdlGbeTestBase.cs index 01eff602..d87263cc 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/DdlGbeTestBase.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/DdlGbeTestBase.cs @@ -3,7 +3,7 @@ using System; using System.Diagnostics; -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER using Microsoft.VisualStudio.TestPlatform.ObjectModel; #endif using MigraDoc.DocumentObjectModel; @@ -106,7 +106,7 @@ void CompatibilityPatchCallback(Document document) style!.Font.Name = "Verdana"; #endif #if CORE - // Note: Core uses SnippetsFontResolver and all required fonts should be available. + // Core uses SnippetsFontResolver and all required fonts should be available. var style = document.Styles[Style.DefaultParagraphName]; Debug.Assert(style != null, nameof(style) + " != null"); // Since all reference documents created with PDFsharp 1.40 or earlier use Verdana, we change the default to Verdana here for all DLL snippets. @@ -116,7 +116,7 @@ void CompatibilityPatchCallback(Document document) internal static string WslPathHack(string path) { -#if !NET6_0_OR_GREATER +#if !NET8_0_OR_GREATER // .NET 4.6.2 or .NETStandard 2.0, for Windows only. return path; #else @@ -130,7 +130,7 @@ internal static string WslPathHack(string path) return path.Replace(@"D:\", "/mnt/c/").Replace('\\', '/'); } - throw new NotImplementedException($"Platform {Environment.OSVersion} not yet supported."); + throw new NotSupportedException($"Platform {Environment.OSVersion} not yet supported."); #endif } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/TestContext.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/TestContext.cs index d348e33e..f8dfbdb6 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/TestContext.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/TestContext.cs @@ -21,7 +21,7 @@ public TestContext() var folder0 = @"D:\GBE-Output\"; Directory.CreateDirectory(DdlGbeTestBase.WslPathHack(folder0)); - var now = DateTime.Now; + var now = DateTimeOffset.Now; //Console.WriteLine($"Now {now})"); #if GBE const string tag = "GBE"; @@ -32,12 +32,24 @@ public TestContext() #elif WPF const string tag = "WPF"; #endif -#if NET8_0 +#if NET462 + const string tag2 = "NET462"; +#elif NET12_0 + const string tag2 = "NET120"; +#elif NET11_0 + const string tag2 = "NET110"; +#elif NET10_0 + const string tag2 = "NET100"; +#elif NET9_0 + const string tag2 = "NET90"; +#elif NET8_0 const string tag2 = "NET80"; +#elif NET7_0 + const string tag2 = "NET70"; #elif NET6_0 const string tag2 = "NET60"; -#elif NET462 - const string tag2 = "NET462"; +#elif NET5_0 + const string tag2 = "NET50"; #elif NETSTANDARD2_0 const string tag2 = "NETstandard20"; #else diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/VisualComparisonTestBase.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/VisualComparisonTestBase.cs index f632bd75..785f8ce7 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/VisualComparisonTestBase.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.GrammarByExample/helper/VisualComparisonTestBase.cs @@ -68,7 +68,7 @@ public string CreatePdfFromMdddlFile(string pdfFile, string mdddlPath, string te var document = DdlReaderDocumentFromFile(file); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER #if CORE document.Info.Author += "[.NET 6.0+ Core build]"; #elif GDI @@ -102,7 +102,7 @@ public string CreatePdfFromDdlString(string pdfFile, string ddlString, string te Document DdlReaderDocumentFromFile(string file) { - //#if NET6_0_OR_GREATER + //#if NET8_0_OR_GREATER // var ansiEncoding = CodePagesEncodingProvider.Instance.GetEncoding(1252)!; //#else // var ansiEncoding = Encoding.GetEncoding(1252); diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj index 72425076..842b3c0e 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-gdi/MigraDoc.Tests-gdi.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Tests_Windows) true True ..\..\..\..\..\StrongnameKey.snk @@ -22,7 +22,8 @@ - + + @@ -32,7 +33,8 @@ - + + diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj index 46e32c27..a4587e2a 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests-wpf/MigraDoc.Tests-wpf.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Tests_Windows) true true True @@ -23,7 +23,8 @@ - + + @@ -33,7 +34,8 @@ - + + diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ChartTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ChartTests.cs index 71835899..1a77faa5 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ChartTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ChartTests.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using PdfSharp.TestHelper; @@ -55,10 +55,10 @@ public void Create_MigraDoc_Chart_Test() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("ChartTests"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/charts/ChartTests"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); #if DEBUG___ diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs index 70b1adf8..2916ca7d 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/CultureAndRegionTests.cs @@ -53,7 +53,7 @@ public void DateTimeTest() var germanMonths = new[] { "Januar", "Februar", "März", "April", "Mai", "Juni", "Juli", "August", "September", "Oktober", "November", "Dezember" }; // filenamePattern with placeholder for cultureInfo. - var filenamePattern = IOUtility.GetTempFileName("DateTime{0}", "pdf"); + var filenamePattern = IOUtility.GetTempFullFileName("DateTime{0}", "pdf"); // Create one result file per cultureInfo. var cultureInfos = new[] { null, CultureInfo.GetCultureInfo("en-us"), CultureInfo.GetCultureInfo("de-de") }; @@ -63,9 +63,9 @@ public void DateTimeTest() var section = document.AddSection(); - section.AddParagraph($"123456: {new DateTime(1, 2, 3, 4, 5, 6).ToString(cultureInfo)}"); + section.AddParagraph($"123456: {new DateTimeOffset(1, 2, 3, 4, 5, 6,TimeSpan.Zero).ToString(cultureInfo)}"); - section.AddParagraph($"DateTime.Now: {DateTime.Now.ToString(cultureInfo)}"); + section.AddParagraph($"DateTimeOffset.Now: {DateTimeOffset.Now.ToString(cultureInfo)}"); var p = section.AddParagraph("p.AddDateField(): "); p.AddDateField(); @@ -86,7 +86,7 @@ public void DateTimeTest() // Check PDF file content. var streamEnumerator = PdfFileHelper.GetPageContentStreamEnumerator(pdfDocument, 0); - // Get all text content split by whitespace. + // Get all text content split by white-space. var texts = new List(); while (streamEnumerator.Text.MoveAndGetNext(true, out var textInfo)) texts.AddRange(textInfo!.Text.Split(' ')); @@ -98,19 +98,21 @@ public void DateTimeTest() if (isEnUs) { - texts.Count.Should().Be(19); + texts.Count.Should().Be(21); - // 123456: 2/3/0001 4:05:06 AM + // 123456: 2/3/0001 4:05:06 AM +00:00 texts[++idx].Should().Be("123456:"); Regex.IsMatch(texts[++idx], "[\\d]{1,2}/[\\d]{1,2}/[\\d]{4}").Should().BeTrue(); Regex.IsMatch(texts[++idx], "[\\d]{1,2}:[\\d]{2}:[\\d]{2}").Should().BeTrue(); amPm.Should().Contain(texts[++idx]); + Regex.IsMatch(texts[++idx], "[+-][\\d]{2}:[\\d]{2}").Should().BeTrue(); - // DateTime.Now: 10/14/2024 10:40:50 AM - texts[++idx].Should().Be("DateTime.Now:"); + // DateTimeOffset.Now: 10/14/2024 10:40:50 AM +02:00 + texts[++idx].Should().Be("DateTimeOffset.Now:"); Regex.IsMatch(texts[++idx], "[\\d]{1,2}/[\\d]{1,2}/[\\d]{4}").Should().BeTrue(); Regex.IsMatch(texts[++idx], "[\\d]{1,2}:[\\d]{2}:[\\d]{2}").Should().BeTrue(); amPm.Should().Contain(texts[++idx]); + Regex.IsMatch(texts[++idx], "[+-][\\d]{2}:[\\d]{2}").Should().BeTrue(); // p.AddDateField(): 10/14/2024 10:40:50 AM texts[++idx].Should().Be("p.AddDateField\\(\\):"); @@ -129,17 +131,19 @@ public void DateTimeTest() } else if (isDeDe) { - texts.Count.Should().Be(15); + texts.Count.Should().Be(17); // 123456: 03.02.0001 04:05:06 texts[++idx].Should().Be("123456:"); Regex.IsMatch(texts[++idx], "[\\d]{2}.[\\d]{2}.[\\d]{4}").Should().BeTrue(); Regex.IsMatch(texts[++idx], "[\\d]{2}:[\\d]{2}:[\\d]{2}").Should().BeTrue(); + Regex.IsMatch(texts[++idx], "[+-][\\d]{2}:[\\d]{2}").Should().BeTrue(); - // DateTime.Now: 14.10.2024 10:40:50 - texts[++idx].Should().Be("DateTime.Now:"); + // DateTimeOffset.Now: 14.10.2024 10:40:50 + texts[++idx].Should().Be("DateTimeOffset.Now:"); Regex.IsMatch(texts[++idx], "[\\d]{2}.[\\d]{2}.[\\d]{4}").Should().BeTrue(); Regex.IsMatch(texts[++idx], "[\\d]{2}:[\\d]{2}:[\\d]{2}").Should().BeTrue(); + Regex.IsMatch(texts[++idx], "[+-][\\d]{2}:[\\d]{2}").Should().BeTrue(); // p.AddDateField(): 14.10.2024 10:40:50 texts[++idx].Should().Be("p.AddDateField\\(\\):"); diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Experimental/CreateOnRequest.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Experimental/CreateOnRequest.cs index 90bb48b5..811a4fa7 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Experimental/CreateOnRequest.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Experimental/CreateOnRequest.cs @@ -87,12 +87,12 @@ public override bool Equals(object? o) return false; } - static Boolean ISectionInterface
.operator ==(Section? c1, Section? c2) + static bool ISectionInterface
.operator ==(Section? c1, Section? c2) { return c2 is null; } - static Boolean ISectionInterface
.operator !=(Section? c1, Section? c2) + static bool ISectionInterface
.operator !=(Section? c1, Section? c2) { return true; } @@ -172,36 +172,36 @@ public override string ToString() return stringBuilder.ToString(); } - static Boolean ISectionInterface
.operator ==(Section? c1, Section? c2) + static bool ISectionInterface
.operator ==(Section? c1, Section? c2) { Console.WriteLine("=="); return true; } - static Boolean ISectionInterface
.operator !=(Section? c1, Section? c2) + static bool ISectionInterface
.operator !=(Section? c1, Section? c2) { Console.WriteLine("!="); return true; } - public static Boolean operator ==(Section? c1, Section? c2) + public static bool operator ==(Section? c1, Section? c2) { return true; } - public static Boolean operator !=(Section? c1, Section? c2) + public static bool operator !=(Section? c1, Section? c2) { return true; } - //static Boolean SectionInterface.operator ==(SectionInterface c1, SectionInterface c2) + //static bool SectionInterface.operator ==(SectionInterface c1, SectionInterface c2) //{ // if (c2 is null) // return true; // return false; //} - //static Boolean SectionInterface.operator !=(SectionInterface c1, SectionInterface c2) + //static bool SectionInterface.operator !=(SectionInterface c1, SectionInterface c2) //{ // return !(c1 == c2); //} diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs index a51ac72d..8e7bc4aa 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Extensions/XunitHelper.cs @@ -21,8 +21,14 @@ public static class SkippableTests /// True if slow tests should be skipped. public static bool SkipSlowTests() { +#if RUN_SLOW_TESTS + return false; +#else var env = Environment.GetEnvironmentVariable("PDFsharpTests"); + //#/warning slow tests + // env = "x"; return String.IsNullOrEmpty(env); +#endif } /// @@ -32,7 +38,7 @@ public static bool SkipSlowTests() /// True if slow tests should be skipped. public static bool SkipSlowTestsUnderDotNetFramework() { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER return false; #else return SkipSlowTests(); diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs index 29571259..93fab501 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Fonts/PredefinedFontsTests.cs @@ -39,14 +39,14 @@ public void Use_default_error_font() // In GDI and WPF builds rendering a document must succeed if the error font is available - even is an error message is rendered. var document = CreateDocumentWithErrorMessage(); - var filename = PdfFileUtility.GetTempPdfFileName("Use_default_error_font"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/fonts/Use_default_error_font"); // Even with error message the document must be successfully created. var pdfDocument = RenderDocumentShouldSucceed(document, filename); CheckErrorFont(pdfDocument, "Courier New"); #else - throw new NotImplementedException("Unknown flavor."); + throw new NotSupportedException("Unknown flavor."); #endif } finally @@ -77,14 +77,14 @@ public void Use_alternate_error_font() // In GDI and WPF builds rendering a document must succeed if the error font is available - even is an error message is rendered. var document = CreateDocumentWithErrorMessage(); - var filename = PdfFileUtility.GetTempPdfFileName("Use_alternate_error_font"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/fonts/Use_alternate_error_font"); // Even with error message the document must be successfully created. var pdfDocument = RenderDocumentShouldSucceed(document, filename); CheckErrorFont(pdfDocument, "Segoe UI"); #else - throw new NotImplementedException("Unknown flavor."); + throw new NotSupportedException("Unknown flavor."); #endif } finally @@ -137,14 +137,14 @@ public void Use_default_bullets_font() // In GDI and WPF builds rendering a document with bullet lists must succeed if the bullet fonts are available. var document = CreateDocumentWithBulletList(); - var filename = PdfFileUtility.GetTempPdfFileName("Use_default_bullets_font"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/fonts/Use_default_bullets_font"); // The document must be successfully created. var pdfDocument = RenderDocumentShouldSucceed(document, filename); CheckBulletFonts(pdfDocument, "Courier New", "Courier New", "Courier New"); #else - throw new NotImplementedException("Unknown flavor."); + throw new NotSupportedException("Unknown flavor."); #endif } finally @@ -175,14 +175,14 @@ public void Use_alternate_bullets_font() // In GDI and WPF builds rendering a document with bullet lists must succeed if the bullet fonts are available. var document = CreateDocumentWithBulletList(); - var filename = PdfFileUtility.GetTempPdfFileName("Use_alternate_bullets_font"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/fonts/Use_alternate_bullets_font"); // The document must be successfully created. var pdfDocument = RenderDocumentShouldSucceed(document, filename); CheckBulletFonts(pdfDocument, "Arial", "Times new roman", "Verdana"); #else - throw new NotImplementedException("Unknown flavor."); + throw new NotSupportedException("Unknown flavor."); #endif } finally @@ -254,9 +254,9 @@ PdfDocument RenderDocumentShouldSucceed(Document document, string filename) var pdfDocument = pdfRenderer.PdfDocument; - // Save the document... + // Save the document… pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); return pdfDocument; diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Helper/SecurityTestHelper.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Helper/SecurityTestHelper.cs index aa1f3bae..177a26c3 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Helper/SecurityTestHelper.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Helper/SecurityTestHelper.cs @@ -3,6 +3,8 @@ using MigraDoc.DocumentObjectModel; using MigraDoc.Rendering; +using PdfSharp.Quality; +using System.IO; using static PdfSharp.TestHelper.SecurityTestHelper; namespace MigraDoc.Tests.Helper @@ -52,39 +54,41 @@ public static PdfDocumentRenderer RenderSecuredStandardTestDocument(TestOptions return RenderSecuredDocument(CreateStandardTestDocument(), options); } - public static void WriteStandardTestDocument(string filename) + public static void WriteStandardTestDocument(Stream stream) { var pdfRenderer = RenderDocument(CreateStandardTestDocument()); - pdfRenderer.Save(filename); + pdfRenderer.Save(stream, false); } - public static string GetStandardTestDocument() + public static Stream GetStandardTestDocument() { - const string filename = "temp.pdf"; - WriteStandardTestDocument(filename); - return filename; + var stream = CreateTempStream("unittests/SecurityTests/StandardTestDocument"); + WriteStandardTestDocument(stream); + return stream; } - public static void WriteSecuredStandardTestDocument(string filename, TestOptions options) + public static void WriteSecuredStandardTestDocument(Stream stream, TestOptions options) { var pdfRenderer = RenderSecuredStandardTestDocument(options); - pdfRenderer.Save(filename); + pdfRenderer.Save(stream, false); } - public static string GetSecuredStandardTestDocument(TestOptions options) + public static Stream GetSecuredStandardTestDocument(TestOptions options) { - string filename; + Stream stream; if (options.Encryption == TestEncryptions.V5R5ReadOnly) { - GetAssetsTestFile(options, out filename); + // Open file from Assets. + GetAssetsTestFile(options, out var filename); + stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read); } else { // Render secured test document. - filename = "temp.pdf"; - WriteSecuredStandardTestDocument(filename, options); + stream = CreateTempStream("unittests/SecurityTests/SecuredStandardTestDocument"); + WriteSecuredStandardTestDocument(stream, options); } - return filename; + return stream; } } } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormats.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormatsTests.cs similarity index 87% rename from src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormats.cs rename to src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormatsTests.cs index 437bafc7..28546fc6 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormats.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/ImageFormatsTests.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using System.Runtime.InteropServices; @@ -24,9 +24,9 @@ namespace MigraDoc.Tests { [Collection("PDFsharp")] - public class ImageFormats : IDisposable + public class ImageFormatsTests : IDisposable { - public ImageFormats() + public ImageFormatsTests() { PdfSharpCore.ResetAll(); #if CORE @@ -61,10 +61,10 @@ public void Test_Image_Formats() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Image_Formats"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/images/Image_Formats"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -100,10 +100,10 @@ public void Test_Image_BASE64() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Image_Base64"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/images/Image_Base64"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -185,10 +185,10 @@ public void Test_Image_Interpolate() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Image_Interpolate"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/images/Image_Interpolate"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -308,8 +308,70 @@ struct TestImage public Unit? Width { get; set; } } +#if true_ +#warning Deactivate this before publishing. + + readonly TestImage[] _testImages = + [ + // Test individual images. + + //// JPEG + //new() { Path = @"jpeg\windows7problem.jpg", Comment = "JPEG image", Width = "12cm" }, + //new() { Path = @"jpeg\TruecolorNoAlpha.jpg", Comment = "JPEG image" }, + //new() { Path = @"jpeg\truecolorA.jpg", Comment = "JPEG image" }, + //new() { Path = @"jpeg\PowerBooks_CMYK.jpg", Comment = "JPEG with EXIF header" }, + //new() { Path = @"jpeg\indexedmonoA.jpg", Comment = "JPEG image" }, + //new() { Path = @"jpeg\grayscaleNoAlpha.jpg", Comment = "JPEG image" }, + //new() { Path = @"jpeg\grayscaleA.jpg", Comment = "JPEG image" }, + //new() { Path = @"jpeg\color8A.jpg", Comment = "JPEG image" }, + //new() { Path = @"jpeg\color4A.jpg", Comment = "JPEG image" }, + //new() { Path = @"jpeg\blackwhiteA.jpg", Comment = "JPEG image" }, + //new() { Path = @"jpeg\Balloons_CMYK.jpg", Comment = "CMYK" }, + //new() { Path = @"jpeg\Balloons_CMYK - Copy.jpg", Comment = "CMYK?" }, + + //// BMP + //new() { Path = @"bmp\BlackwhiteA.bmp", Comment = "BMP image" }, + //new() { Path = @"bmp\BlackwhiteA2.bmp", Comment = "BMP image" }, + //new() { Path = @"bmp\BlackwhiteTXT.bmp", Comment = "BMP image", Width = "8cm" }, + new() { Path = @"bmp\Color4A.bmp", Comment = "BMP image" }, + //new() { Path = @"bmp\Color8A.bmp", Comment = "BMP image" }, + //new() { Path = @"bmp\GrayscaleA.bmp", Comment = "BMP image" }, + //new() { Path = @"bmp\IndexedmonoA.bmp", Comment = "BMP image" }, + //new() { Path = @"bmp\Test_OS2.bmp", Comment = "OS/2 bitmap, not supported by Core build" }, + //new() { Path = @"bmp\TruecolorA.bmp", Comment = "BMP image" }, + //new() { Path = @"bmp\TruecolorMSPaint.bmp", Comment = "BMP image" }, + + //// PNG + //new() { Path = @"png\windows7problem.png", Comment = "PNG", Width = "12cm" }, + //new() { Path = @"png\truecolorAlpha.png", Comment = "PNG" }, + //new() { Path = @"png\truecolorA.png", Comment = "PNG" }, + //new() { Path = @"png\indexedmonoA.png", Comment = "PNG" }, + //new() { Path = @"png\grayscaleAlpha.png", Comment = "PNG" }, + //new() { Path = @"png\grayscaleA.png", Comment = "PNG" }, + //new() { Path = @"png\color8A.png", Comment = "PNG" }, + //new() { Path = @"png\color4A.png", Comment = "PNG" }, + //new() { Path = @"png\blackwhiteA.png", Comment = "PNG" }, + + //// BMP & PNG + //new() { Path = @"MigraDoc.bmp", Comment = "BMP image", Width = "8cm" }, + //new() { Path = @"Logo landscape.bmp", Comment = "BMP", Width = "12cm" }, + //new() { Path = @"Logo landscape MS Paint.bmp", Comment = "BMP", Width = "12cm" }, + //new() { Path = @"Logo landscape 256.bmp", Comment = "BMP image", Width = "12cm" }, + //new() { Path = @"MigraDoc.png", Comment = "PNG image", Width = "8cm" }, + //new() { Path = @"Logo landscape.png", Comment = "PNG image", Width = "12cm" }, + //new() { Path = @"Logo landscape 256.png", Comment = "PNG image", Width = "12cm" }, + + //// GIF, TIFF, PNG + //new() { Path = @"misc\image009.gif", Comment = "GIF, not supported by Core build" }, + //new() { Path = @"misc\Rose (RGB 8).tif", Comment = "TIFF, not supported by Core build" }, + //new() { Path = @"misc\Test.gif", Comment = "GIF, not supported by Core build" }, + //new() { Path = @"misc\Test.png", Comment = "PNG image" } + ]; +#else readonly TestImage[] _testImages = [ + // Test all images. + // JPEG new() { Path = @"jpeg\windows7problem.jpg", Comment = "JPEG image", Width = "12cm" }, new() { Path = @"jpeg\TruecolorNoAlpha.jpg", Comment = "JPEG image" }, @@ -362,5 +424,6 @@ struct TestImage new() { Path = @"misc\Test.gif", Comment = "GIF, not supported by Core build" }, new() { Path = @"misc\Test.png", Comment = "PNG image" } ]; +#endif } } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MemoryTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MemoryTests.cs new file mode 100644 index 00000000..381226de --- /dev/null +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MemoryTests.cs @@ -0,0 +1,119 @@ +// MigraDoc - Creating Documents on the Fly +// See the LICENSE file in the solution root for more information. + +using MigraDoc.DocumentObjectModel; +using MigraDoc.DocumentObjectModel.Fields; +using MigraDoc.Rendering; +using PdfSharp.Diagnostics; +#if CORE +using PdfSharp.Fonts; +#endif +using PdfSharp.Pdf; +using PdfSharp.Quality; +using Xunit; +using FluentAssertions; + +namespace MigraDoc.Tests +{ + [Collection("PDFsharp")] + public class MemoryTests : IDisposable + { + public MemoryTests() + { + PdfSharpCore.ResetAll(); +#if CORE + GlobalFontSettings.FontResolver = new UnitTestFontResolver(); +#endif + } + + public void Dispose() + { + PdfSharpCore.ResetAll(); + } + + [Fact] + public void MigraDoc_MemoryLeak_Test() + { + // Background information: + // We had a bug that a variable used for caching caused + // a MigraDoc Document object to remain in memory. + // This test creates a MigraDoc Document, renders a PDF, + // and finally assures that Document, PdfRenderer, and PdfDocument + // are disposed after a garbage collection. + + // Create weak references for testing. + WeakReference documentReference; + WeakReference pdfDocumentReference; + WeakReference pdfDocumentRendererReference; + + // Create a PDF file and assign weak references. + MyCreateDocument(); + + // All weak references refer to objects that are out of scope here. + + // Wait for Garbage Collection. + //GCSettings.LargeObjectHeapCompactionMode = GCLargeObjectHeapCompactionMode.CompactOnce; + GC.WaitForPendingFinalizers(); + GC.Collect(); + GC.WaitForFullGCComplete(); + + // Check that all weak references are invalid now. + documentReference.TryGetTarget(out _).Should().BeFalse(); + pdfDocumentReference.TryGetTarget(out _).Should().BeFalse(); + pdfDocumentRendererReference.TryGetTarget(out _).Should().BeFalse(); + return; + + // Create a PDF file and assign weak references. + void MyCreateDocument() + { + // Create a MigraDoc document. + var document = new Document(); + documentReference = new(document); + + // Add a section to the document. + var section = document.AddSection(); + + // Add a paragraph to the section. + var paragraph = section.AddParagraph(); + + // Set font color. + //paragraph.Format.Font.Color = Color.FromCmyk(100, 30, 20, 50); + paragraph.Format.Font.Color = Colors.DarkBlue; + + // Add some text to the paragraph. + paragraph.AddFormattedText("Hello, World!", TextFormat.Bold); + + // Create the primary footer. + var footer = section.Footers.Primary; + + // Add content to footer. + paragraph = footer.AddParagraph(); + paragraph.Add(new DateField { Format = "yyyy/MM/dd HH:mm:ss" }); + paragraph.Format.Alignment = ParagraphAlignment.Center; + + // Create a renderer for the MigraDoc document. + var pdfRenderer = new PdfDocumentRenderer() + { + // Associate the MigraDoc document with a renderer. + Document = document + }; + pdfDocumentRendererReference = new(pdfRenderer); + + // Layout and render document to PDF. + pdfRenderer.RenderDocument(); + pdfDocumentReference = new WeakReference(pdfRenderer.PdfDocument); + + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/MigraDocMemoryLeakTest"); + pdfRenderer.PdfDocument.Save(filename); + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(filename); + + // Check that all weak references are valid now. + documentReference.TryGetTarget(out _).Should().BeTrue(); + pdfDocumentReference.TryGetTarget(out _).Should().BeTrue(); + pdfDocumentRendererReference.TryGetTarget(out _).Should().BeTrue(); + } + } + } +} diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj index 1957a8a3..d9c11e09 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MigraDoc.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;net462 + $(PDFsharpTargetFrameworks_Tests_Exe) True ..\..\..\..\..\StrongnameKey.snk @@ -21,7 +21,8 @@ - + + diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MultipleFooters.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MultipleFooters.cs index 15c42bf0..f6777ccc 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MultipleFooters.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/MultipleFooters.cs @@ -53,10 +53,10 @@ public void Create_Multiple_Footers() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Multiple_Footers"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/footers/Multiple_Footers"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs index 891cf7b9..d30ea7d4 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/PageSizeTests.cs @@ -50,14 +50,14 @@ public void Test_Page_Formats() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Page_Formats"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/pages/Page_Formats"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } - private Document CreateDocument() + Document CreateDocument() { var text = "Lorem ipsum dolor sit amet, consetetur sadipscing elitr, sed diam nonumy eirmod tempor invidunt ut labore " + "et dolore magna aliquyam erat, sed diam voluptua. At vero eos et accusam et justo duo dolores et ea rebum. " + @@ -289,10 +289,10 @@ public void Test_Page_Formats_Inheritance() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("Page_Formats_Inheritance"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/pages/Page_Formats_Inheritance"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/RtfRendererTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/RtfRendererTests.cs index ae125bdd..2c00c08f 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/RtfRendererTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/RtfRendererTests.cs @@ -20,7 +20,7 @@ #if WPF using System.IO; #endif -#if !NET6_0_OR_GREATER +#if !NET8_0_OR_GREATER using PdfSharp.TestHelper; #endif @@ -68,8 +68,8 @@ public void Create_Hello_World_RtfRendererTests() // Create a renderer for the MigraDoc document. var rtfRenderer = new RtfDocumentRenderer(); - // Save the document... - var filename = IOUtility.GetTempFileName("HelloWorld", null); + // Save the document… + var filename = IOUtility.GetTempFullFileName("HelloWorld", null); #if DEBUG___ MigraDoc.DocumentObjectModel.IO.DdlWriter dw = new MigraDoc.DocumentObjectModel.IO.DdlWriter(filename + "_0.mdddl"); @@ -85,7 +85,7 @@ public void Create_Hello_World_RtfRendererTests() dw.WriteDocument(document); dw.Close(); #endif - //// ...and start a viewer. + //// … and start a viewer. //PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -150,9 +150,9 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) // Description: section.AddParagraph("This document contains several tabstop test cases. For each tabstop a tab is used to reach it.\n" + - $"By default the value \"{valueStr}\" is used for the content that should be aligned by the tabstops.\n" + + $"By default the value '{valueStr}' is used for the content that should be aligned by the tabstops.\n" + "For the special cases where an additional left aligned tabstop at position 0 is required for correct RTF generation, " + - $"the value \"{valueUnifyTabStopsStr}\" is used instead.\n" + + $"the value '{valueUnifyTabStopsStr}' is used instead.\n" + "This special case occurs, if a single decimal tabstop is used in a table rendered in RTF." + "In that case RTF requires no tab to reach the tabstop."); @@ -160,7 +160,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) var paragraph = section.AddParagraph("Alignment order"); paragraph.Style = StyleNames.Heading1; -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) section.AddParagraph(alignment.ToString()); #else @@ -173,7 +173,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) paragraph.Style = StyleNames.Heading1; TestHelper.DrawHorizontalPosition(section, position1); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) #else foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment))) @@ -193,7 +193,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) paragraph.Style = StyleNames.Heading1; TestHelper.DrawHorizontalPosition(section, position1, position2); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) #else foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment))) @@ -222,7 +222,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) var table = section.AddTable(); table.AddColumn(Unit.FromCentimeter(16)); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) #else foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment))) @@ -261,7 +261,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) table.AddColumn(Unit.FromCentimeter(2)); table.AddColumn(Unit.FromCentimeter(14)); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) #else foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment))) @@ -299,7 +299,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) table = section.AddTable(); table.AddColumn(Unit.FromCentimeter(16)); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) #else foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment))) @@ -331,7 +331,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) table = section.AddTable(); table.AddColumn(Unit.FromCentimeter(16)); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) #else foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment))) @@ -379,7 +379,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) table = section.AddTable(); table.AddColumn(Unit.FromCentimeter(16)); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) #else foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment))) @@ -417,7 +417,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) table = section.AddTable(); table.AddColumn(Unit.FromCentimeter(16)); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER foreach (var alignment in Enum.GetValues()) #else foreach (TabAlignment alignment in Enum.GetValues(typeof(TabAlignment))) @@ -448,10 +448,10 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) TestHelper.DrawHorizontalPosition(section, position1); // Render PDF and RTF files. - var filename = "Test_Tabs_"; + var filename = "unittests/migradoc/text/Test_Tabs_"; filename += Capabilities.BackwardCompatibility.DoNotUnifyTabStopHandling ? "DoNotUnifyTabStopHandling" : "UnifyTabStopHandling"; - var pdfFilename = PdfFileUtility.GetTempPdfFileName(filename); + var pdfFilename = PdfFileUtility.GetTempPdfFullFileName(filename); var rtfFilename = pdfFilename.Replace(".pdf", ".rtf"); var rtfRenderer = new RtfDocumentRenderer(); @@ -482,11 +482,11 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) { // Get a position of valueUnifyTabStopsStr. valuePos = rtf.IndexOf(valueUnifyTabStopsStr, searchPos, StringComparison.Ordinal); - valuePos.Should().BeGreaterThan(0, $"\"{valueUnifyTabStopsStr}\" should occur 5 times in the RTF file after the description text."); + valuePos.Should().BeGreaterThan(0, $"'{valueUnifyTabStopsStr}' should occur 5 times in the RTF file after the description text."); // Get the position of the cell containing the value. var cellPos = rtf.LastIndexOf(cellStr, valuePos, StringComparison.Ordinal); - cellPos.Should().BeGreaterThan(0, $"\"{valueUnifyTabStopsStr}\" should occur inside of a cell."); + cellPos.Should().BeGreaterThan(0, $"'{valueUnifyTabStopsStr}' should occur inside of a cell."); // Get the position of the decimal tabstop for this cell. var decTabStopPos = rtf.LastIndexOf(decimalTabStopStr, valuePos, StringComparison.Ordinal); @@ -532,7 +532,7 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) searchPos = valuePos + valueUnifyTabStopsStr.Length; } valuePos = rtf.IndexOf(valueUnifyTabStopsStr, searchPos, StringComparison.Ordinal); - valuePos.Should().BeLessThan(0, $"\"{valueUnifyTabStopsStr}\" should occur only 5 times in a cell in the RTF file after the description text."); + valuePos.Should().BeLessThan(0, $"'{valueUnifyTabStopsStr}' should occur only 5 times in a cell in the RTF file after the description text."); // Search for valueStr - the occurrences for that no additional tabstop should be added. searchPos = startSearchPos; @@ -540,13 +540,13 @@ public void Test_Tabs(bool doNotUnifyTabStopHandling) { // Get a position of valueStr. valuePos = rtf.IndexOf(valueStr, searchPos, StringComparison.Ordinal); - valuePos.Should().BeGreaterThan(0, $"\"{valueStr}\" should occur 35 times in the RTF file after the description text."); + valuePos.Should().BeGreaterThan(0, $"'{valueStr}' should occur 35 times in the RTF file after the description text."); // Get the position of the cell or paragraph containing the value. var cellPos = rtf.LastIndexOf(cellStr, valuePos, StringComparison.Ordinal); var parPos = rtf.LastIndexOf(paragraphStr, valuePos, StringComparison.Ordinal); var containerPos = Math.Max(cellPos, parPos); - containerPos.Should().BeGreaterThan(0, $"\"{valueStr}\" should occur inside of a cell or paragraph."); + containerPos.Should().BeGreaterThan(0, $"'{valueStr}' should occur inside of a cell or paragraph."); // Check all tabstop’s position elements for this cell or paragraph. var tabStopPositionSearchPos = containerPos; @@ -587,8 +587,8 @@ public void Create_Rtf_with_Image() // Create a renderer for the MigraDoc document. var rtfRenderer = new RtfDocumentRenderer(); - // Save the document... - var filename = IOUtility.GetTempFileName("HelloWorld", null); + // Save the document… + var filename = IOUtility.GetTempFullFileName("HelloWorld", null); #if DEBUG___ MigraDoc.DocumentObjectModel.IO.DdlWriter dw = new MigraDoc.DocumentObjectModel.IO.DdlWriter(filename + "_0.mdddl"); @@ -605,7 +605,7 @@ public void Create_Rtf_with_Image() dw.Close(); #endif - //// ...and start a viewer. + //// … and start a viewer. //PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -623,8 +623,8 @@ public void Create_Rtf_with_Embedded_Base64Image() // Create a renderer for the MigraDoc document. var rtfRenderer = new RtfDocumentRenderer(); - // Save the document... - var filename = IOUtility.GetTempFileName("HelloWorldEmbeddedBase64", null); + // Save the document… + var filename = IOUtility.GetTempFullFileName("HelloWorldEmbeddedBase64", null); #if DEBUG___ MigraDoc.DocumentObjectModel.IO.DdlWriter dw = new MigraDoc.DocumentObjectModel.IO.DdlWriter(filename + "_0.mdddl"); @@ -641,7 +641,7 @@ public void Create_Rtf_with_Embedded_Base64Image() dw.Close(); #endif - //// ...and start a viewer. + //// … and start a viewer. //PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -748,8 +748,8 @@ public void Create_Rtf_with_Base64Image(string assetName) // Create a renderer for the MigraDoc document. var rtfRenderer = new RtfDocumentRenderer(); - // Save the document... - var filename = IOUtility.GetTempFileName("HelloWorldBase64", null); + // Save the document… + var filename = IOUtility.GetTempFullFileName("HelloWorldBase64", null); #if DEBUG___ MigraDoc.DocumentObjectModel.IO.DdlWriter dw = new MigraDoc.DocumentObjectModel.IO.DdlWriter(filename + "_0.mdddl"); @@ -766,7 +766,7 @@ public void Create_Rtf_with_Base64Image(string assetName) dw.Close(); #endif - //// ...and start a viewer. + //// … and start a viewer. //PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -804,14 +804,14 @@ public void Test_Heading_Border() row.Height = Unit.FromCentimeter(10); } - var rtfFilename = IOUtility.GetTempFileName("Test_Heading_Border", "rtf"); + var rtfFilename = IOUtility.GetTempFullFileName("Test_Heading_Border", "rtf"); var rtfRenderer = new RtfDocumentRenderer(); rtfRenderer.Render(document, rtfFilename, Environment.CurrentDirectory); // Analyze rendered RTF. var rtf = File.ReadAllText(rtfFilename); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER // Split by row identifier and skip the first part, which is no row. var splitByRows = rtf.Split("\\trowd").Skip(1).ToArray(); splitByRows.Length.Should().Be(5, "as there are 5 rows"); diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs index ce761805..c90e80f6 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SecurityTests.cs @@ -3,7 +3,6 @@ using System.IO; using System.Security.Cryptography.X509Certificates; -using FluentAssertions; using Microsoft.Extensions.Logging; using MigraDoc.DocumentObjectModel; using MigraDoc.Rendering; @@ -20,6 +19,7 @@ using PdfSharp.TestHelper; using PdfSharp.TestHelper.Analysis.ContentStream; using Xunit; +using FluentAssertions; using static MigraDoc.Tests.Helper.SecurityTestHelper; using static PdfSharp.TestHelper.SecurityTestHelper; @@ -91,10 +91,18 @@ public void Dispose() // - Perm{X} = Permissions test // ReSharper disable once UnusedParameter.Local - static void OpenPdf(string filename) + static void OpenPdf(Stream stream) { #if true_ // Should be "true_" by default and in source control. Change to "true" to enable automatic PDF starting for testing purposes. - Process.Sta/rt(new ProcessStartInfo(filename) { UseShellExecute = true }); + if (stream is FileStream fileStream) + { + var filename = fileStream.Name; + Process.Start(new ProcessStartInfo(filename) { UseShellExecute = true }); + } + else if (stream is MemoryStream) + { + throw new InvalidOperationException("Cannot open PDF from MemoryStream."); + } #endif } @@ -103,9 +111,9 @@ public void Test_Write_NoPassword() { Skip.If(SkippableTests.SkipSlowTestsUnderDotNetFramework()); - var filename = AddPrefixToFilename("w.pdf"); - WriteStandardTestDocument(filename); - OpenPdf(filename); + var stream = AddPrefixToFilename("w.pdf"); + WriteStandardTestDocument(stream); + OpenPdf(stream); } [SkippableFact] @@ -118,9 +126,9 @@ public void Test_Read_NoPassword() var pdfDoc = PdfReader.Open(tempFile); var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filename = AddPrefixToFilename("r.pdf"); - pdfRenderer.Save(filename); - OpenPdf(filename); + var stream = AddPrefixToFilename("r.pdf"); + pdfRenderer.Save(stream, false); + OpenPdf(stream); } [SkippableTheory] @@ -203,13 +211,13 @@ public void Test_Read_UserPassword_Wrong_Fails(TestOptionsEnum optionsEnum) var options = TestOptions.ByEnum(optionsEnum); options.SetDefaultPasswords(true); - var tempFile = GetSecuredStandardTestDocument(options); + var tempDoc = GetSecuredStandardTestDocument(options); Exception? e = null; try { // ReSharper disable once UnusedVariable - var pdfDoc = PdfReader.Open(tempFile, PasswordWrong); + var pdfDoc = PdfReader.Open(tempDoc, PasswordWrong); } catch (Exception ex) { @@ -236,9 +244,9 @@ public void Test_Read_UserPassword(TestOptionsEnum optionsEnum) var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filename = AddPrefixToFilename("r U.pdf", options); - pdfRenderer.Save(filename); - OpenPdf(filename); + var stream = AddPrefixToFilename("r U.pdf", options); + pdfRenderer.Save(stream, false); + OpenPdf(stream); } [SkippableTheory] @@ -303,9 +311,9 @@ public void Test_Read_OwnerPassword_Without_Import(TestOptionsEnum optionsEnum) var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filename = AddPrefixToFilename("r O-X_Imp.pdf", options); - pdfRenderer.Save(filename); - OpenPdf(filename); + var stream = AddPrefixToFilename("r O-X_Imp.pdf", options); + pdfRenderer.Save(stream, false); + OpenPdf(stream); } [SkippableTheory] @@ -351,9 +359,9 @@ public void Test_Read_OwnerPassword(TestOptionsEnum optionsEnum) var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filename = AddPrefixToFilename("r O.pdf", options); - pdfRenderer.Save(filename); - OpenPdf(filename); + var stream = AddPrefixToFilename("r O.pdf", options); + pdfRenderer.Save(stream, false); + OpenPdf(stream); } [SkippableTheory] @@ -458,6 +466,7 @@ public void Test_Read_UserAndOwnerPassword_User_Fails(TestOptionsEnum optionsEnu [ClassData(typeof(TestData.AllWriteVersions))] [ClassData(typeof(TestData.AllWriteVersionsSkipped), Skip = SkippedTestOptionsMessage)] [InlineData(TestOptionsEnum.V5R5ReadOnly)] + [InlineData(TestOptionsEnum.V5WithoutMetadata)] public void Test_Read_UserAndOwnerPassword_User_Import(TestOptionsEnum optionsEnum) { Skip.If(SkippableTests.SkipSlowTestsUnderDotNetFramework()); @@ -474,9 +483,9 @@ public void Test_Read_UserAndOwnerPassword_User_Import(TestOptionsEnum optionsEn var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filename = AddPrefixToFilename("r UO-U_Imp.pdf", options); - pdfRenderer.Save(filename); - OpenPdf(filename); + var stream = AddPrefixToFilename("r UO-U_Imp.pdf", options); + pdfRenderer.Save(stream, false); + OpenPdf(stream); } [SkippableTheory] @@ -495,9 +504,9 @@ public void Test_Read_UserAndOwnerPassword_Owner(TestOptionsEnum optionsEnum) var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filename = AddPrefixToFilename("r UO-O.pdf", options); - pdfRenderer.Save(filename); - OpenPdf(filename); + var stream = AddPrefixToFilename("r UO-O.pdf", options); + pdfRenderer.Save(stream, false); + OpenPdf(stream); } [SkippableTheory] @@ -515,18 +524,18 @@ public void Test_Password_Long(TestOptionsEnum optionsEnum) options.SetPasswords(userPassword, ownerPassword); // Write encrypted file. - var filename = AddPrefixToFilename("w UO-long.pdf", options); - WriteSecuredStandardTestDocument(filename, options); - OpenPdf(filename); + var stream = AddPrefixToFilename("w UO-long.pdf", options); + WriteSecuredStandardTestDocument(stream, options); + OpenPdf(stream); // Read encrypted file and write it without encryption. - var pdfDoc = PdfReader.Open(filename, ownerPassword); + var pdfDoc = PdfReader.Open(stream, ownerPassword); var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filenameRead = AddPrefixToFilename("r UO-long.pdf", options); - pdfRenderer.Save(filenameRead); - OpenPdf(filenameRead); + var streamRead = AddPrefixToFilename("r UO-long.pdf", options); + pdfRenderer.Save(streamRead, false); + OpenPdf(streamRead); } [SkippableTheory] @@ -550,18 +559,18 @@ public void Test_Password_Unicode(TestOptionsEnum optionsEnum) options.SetPasswords(userPassword, ownerPassword); // Write encrypted file. - var filename = AddPrefixToFilename("w UO-unic.pdf", options); - WriteSecuredStandardTestDocument(filename, options); - OpenPdf(filename); + var stream = AddPrefixToFilename("w UO-unic.pdf", options); + WriteSecuredStandardTestDocument(stream, options); + OpenPdf(stream); // Read encrypted file and write it without encryption. - var pdfDoc = PdfReader.Open(filename, ownerPassword); + var pdfDoc = PdfReader.Open(stream, ownerPassword); var pdfRenderer = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filenameRead = AddPrefixToFilename("r UO-unic.pdf", options); - pdfRenderer.Save(filenameRead); - OpenPdf(filenameRead); + var streamRead = AddPrefixToFilename("r UO-unic.pdf", options); + pdfRenderer.Save(streamRead, false); + OpenPdf(streamRead); } /// @@ -577,7 +586,7 @@ public void Test_EmbeddedFile_WrapperEncrypted(TestOptionsEnum optionsEnum) options.SetDefaultPasswords(true, true); // Write file to embed. - var filenameEmbedded = "temp.pdf"; + var filenameEmbedded = PdfFileUtility.GetTempPdfFullFileName("temp_a_embedded"); var documentEmbedded = CreateEmptyTestDocument(); var sectionEmbedded = documentEmbedded.AddSection(); @@ -588,7 +597,7 @@ public void Test_EmbeddedFile_WrapperEncrypted(TestOptionsEnum optionsEnum) pdfRendererEmbedded.Save(filenameEmbedded); // Write file containing embedded file with only this embedded file stream encrypted. - var filename = AddPrefixToFilename("_EmbWrap w UO.pdf", options); + var stream = AddPrefixToFilename("_EmbWrap w UO.pdf", options); var referenceNameEmbedded = "referenceEmbedded"; var document = CreateEmptyTestDocument(); document.AddEmbeddedFile(referenceNameEmbedded, filenameEmbedded); @@ -599,16 +608,16 @@ public void Test_EmbeddedFile_WrapperEncrypted(TestOptionsEnum optionsEnum) var link = paragraph.AddHyperlinkToEmbeddedDocument(referenceNameEmbedded + '\\'); link.AddText("Link to embedded file"); - var pdfRenderer = RenderSecuredDocument(document, options); - pdfRenderer.Save(filename); + var streamRenderer = RenderSecuredDocument(document, options); + streamRenderer.Save(stream, false); // Read encrypted file and write it without encryption. - var pdfDoc = PdfReader.Open(filename, PasswordOwnerDefault); + var pdfDoc = PdfReader.Open(stream, PasswordOwnerDefault); var pdfRendererRead = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filenameRead = AddPrefixToFilename("_EmbWrap r UO.pdf", options); - pdfRendererRead.Save(filenameRead); - OpenPdf(filenameRead); + var streamRead = AddPrefixToFilename("_EmbWrap r UO.pdf", options); + pdfRendererRead.Save(streamRead, false); + OpenPdf(streamRead); } /// @@ -624,7 +633,7 @@ public void Test_EmbeddedFile_EmbEncrypted(TestOptionsEnum optionsEnum) options.SetDefaultPasswords(true, true); // Write file to embed. - var filenameEmbedded = "temp.pdf"; + var filenameEmbedded = PdfFileUtility.GetTempPdfFullFileName("temp_b_embedded"); var documentEmbedded = CreateEmptyTestDocument(); var sectionEmbedded = documentEmbedded.AddSection(); @@ -635,7 +644,7 @@ public void Test_EmbeddedFile_EmbEncrypted(TestOptionsEnum optionsEnum) pdfRendererEmbedded.Save(filenameEmbedded); // Write file containing embedded file with only this embedded file stream encrypted. - var filename = AddPrefixToFilename("_EmbEmb w UO.pdf", options); + var stream = AddPrefixToFilename("_EmbEmb w UO.pdf", options); var referenceNameEmbedded = "referenceEmbedded"; var document = CreateEmptyTestDocument(); document.AddEmbeddedFile(referenceNameEmbedded, filenameEmbedded); @@ -647,15 +656,15 @@ public void Test_EmbeddedFile_EmbEncrypted(TestOptionsEnum optionsEnum) link.AddText("Link to embedded file"); var pdfRenderer = RenderDocument(document); - pdfRenderer.Save(filename); + pdfRenderer.Save(stream, false); // Read encrypted file and write it without encryption. - var pdfDoc = PdfReader.Open(filename, PasswordOwnerDefault); + var pdfDoc = PdfReader.Open(stream, PasswordOwnerDefault); var pdfRendererRead = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filenameRead = AddPrefixToFilename("_EmbEmb r UO.pdf", options); - pdfRendererRead.Save(filenameRead); - OpenPdf(filenameRead); + var streamRead = AddPrefixToFilename("_EmbEmb r UO.pdf", options); + pdfRendererRead.Save(streamRead, false); + OpenPdf(streamRead); } /// @@ -671,7 +680,7 @@ public void Test_EmbeddedFile_BothEncrypted(TestOptionsEnum optionsEnum) options.SetDefaultPasswords(true, true); // Write file to embed. - var filenameEmbedded = "temp.pdf"; + var filenameEmbedded = PdfFileUtility.GetTempPdfFullFileName("temp_c_embedded"); var documentEmbedded = CreateEmptyTestDocument(); var sectionEmbedded = documentEmbedded.AddSection(); @@ -682,7 +691,7 @@ public void Test_EmbeddedFile_BothEncrypted(TestOptionsEnum optionsEnum) pdfRendererEmbedded.Save(filenameEmbedded); // Write file containing embedded file with only this embedded file stream encrypted. - var filename = AddPrefixToFilename("_EmbBoth w UO.pdf", options); + var stream = AddPrefixToFilename("_EmbBoth w UO.pdf", options); var referenceNameEmbedded = "referenceEmbedded"; var document = CreateEmptyTestDocument(); document.AddEmbeddedFile(referenceNameEmbedded, filenameEmbedded); @@ -694,18 +703,21 @@ public void Test_EmbeddedFile_BothEncrypted(TestOptionsEnum optionsEnum) link.AddText("Link to embedded file"); var pdfRenderer = RenderSecuredDocument(document, options); - pdfRenderer.Save(filename); + pdfRenderer.Save(stream, false); // Read encrypted file and write it without encryption. - var pdfDoc = PdfReader.Open(filename, PasswordOwnerDefault); + var pdfDoc = PdfReader.Open(stream, PasswordOwnerDefault); var pdfRendererRead = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filenameRead = AddPrefixToFilename("_EmbBoth r UO.pdf", options); - pdfRendererRead.Save(filenameRead); - OpenPdf(filenameRead); + var streamRead = AddPrefixToFilename("_EmbBoth r UO.pdf", options); + pdfRendererRead.Save(streamRead, false); + OpenPdf(streamRead); } #if true - const string SkippedTestEmbeddedFilesMessage = "The feature to encrypt embedded file streams only is not yet correctly implemented. The resulting files may not be readyble with PDF readers."; + const string SkippedTestEmbeddedFilesMessage = + "The feature to encrypt embedded file streams only is not yet correctly implemented. " + + "The resulting files may not be readable with PDF readers. " + + "This feature is not very important, as you can instead encrypt the file before importing it."; [SkippableTheory] [ClassData(typeof(TestData.V4), Skip = SkippedTestEmbeddedFilesMessage)] @@ -728,7 +740,7 @@ public void Test_EmbeddedFile_OnlyEmbeddedFileStreamsEncrypted(TestOptionsEnum o options.SetDefaultPasswords(true, true); // Write file to embed. - var filenameEmbedded = "temp.pdf"; + var filenameEmbedded = PdfFileUtility.GetTempPdfFullFileName("temp_d_embedded"); var documentEmbedded = CreateEmptyTestDocument(); var sectionEmbedded = documentEmbedded.AddSection(); @@ -739,7 +751,7 @@ public void Test_EmbeddedFile_OnlyEmbeddedFileStreamsEncrypted(TestOptionsEnum o pdfRendererEmbedded.Save(filenameEmbedded); // Write file containing embedded file with only this embedded file stream encrypted. - var filename = AddPrefixToFilename("_EmbOnly w UO.pdf", options); + var stream = AddPrefixToFilename("_EmbOnly w UO.pdf", options); var referenceNameEmbedded = "referenceEmbedded"; var document = CreateEmptyTestDocument(); document.AddEmbeddedFile(referenceNameEmbedded, filenameEmbedded); @@ -753,15 +765,15 @@ public void Test_EmbeddedFile_OnlyEmbeddedFileStreamsEncrypted(TestOptionsEnum o var pdfRenderer = RenderSecuredDocument(document, options); var pdfDocument = pdfRenderer.PdfDocument; pdfDocument.SecurityHandler.EncryptEmbeddedFileStreamsOnly(); - pdfRenderer.Save(filename); + pdfRenderer.Save(stream, false); // Read encrypted file and write it without encryption. - var pdfDoc = PdfReader.Open(filename, PasswordOwnerDefault); + var pdfDoc = PdfReader.Open(stream, PasswordOwnerDefault); var pdfRendererRead = new PdfDocumentRenderer { PdfDocument = pdfDoc }; - var filenameRead = AddPrefixToFilename("_EmbOnly r UO.pdf", options); - pdfRendererRead.Save(filenameRead); - OpenPdf(filenameRead); + var streamRead = AddPrefixToFilename("_EmbOnly r UO.pdf", options); + pdfRendererRead.Save(streamRead, false); + OpenPdf(streamRead); } [SkippableTheory] @@ -781,7 +793,7 @@ public void Test_Permissions(TestOptionsEnum optionsEnum) // Generates and checks test several test documents with each document containing one more permission property set to false (from none to all properties). for (var falseCount = 0; falseCount <= count; falseCount++) { - var filename = AddPrefixToFilename($"Perm{falseCount}.pdf", options); + var stream = AddPrefixToFilename($"Perm{falseCount}.pdf", options); var pdfRenderer = RenderSecuredStandardTestDocument(options); var pdfDoc = pdfRenderer.PdfDocument; @@ -793,9 +805,9 @@ public void Test_Permissions(TestOptionsEnum optionsEnum) permissionProperty.SetValue(pdfDoc.SecuritySettings, false); } - pdfRenderer.Save(filename); + pdfRenderer.Save(stream, false); - var pdfDocRead = PdfReader.Open(filename, PasswordUserDefault); + var pdfDocRead = PdfReader.Open(stream, PasswordUserDefault); // All properties from 0 to falseCount should be false. for (var i = 0; i < falseCount; i++) @@ -845,7 +857,7 @@ public void Test_Strings(TestOptionsEnum optionsEnum) LogHost.Factory = loggerFactory; // The randomizedTestStringCount should be big enough for a good chance to create encrypted strings that seem to begin with a Unicode BOM. - // These strings are an important test case to ensure reread as unicode is done after decryption. + // These strings are an important test case to ensure reread as Unicode is done after decryption. const int randomizedTestStringCount = 100000; // Define test strings. Avoid spaces, hyphens or other characters that may split the strings in two objects in the page content stream. @@ -870,8 +882,6 @@ public void Test_Strings(TestOptionsEnum optionsEnum) const int linesPerPage = 50; - var date = DateTime.Now; - var options = TestOptions.ByEnum(optionsEnum); options.SetDefaultPasswords(true); @@ -881,7 +891,7 @@ public void Test_Strings(TestOptionsEnum optionsEnum) var normalStyle = doc.Styles.Normal; normalStyle.ParagraphFormat.TabStops.AddTabStop(Unit.FromCentimeter(2)); - normalStyle.Font.Name = font.Name2; + normalStyle.Font.Name = font.Name; normalStyle.Font.Size = font.Size; var section = doc.AddSection(); @@ -905,6 +915,7 @@ public void Test_Strings(TestOptionsEnum optionsEnum) } // Render the document. + var date = DateTimeOffset.Now; var pdfRenderer = new PdfDocumentRenderer { Document = doc }; pdfRenderer.RenderDocument(); @@ -921,16 +932,16 @@ public void Test_Strings(TestOptionsEnum optionsEnum) // Secure and save the document. SecureDocument(pdfDocument, options); - var encryptedFile = AddPrefixToFilename("Test_Strings w U.pdf", options); - pdfRenderer.Save(encryptedFile); + var encryptedStream = AddPrefixToFilename("Test_Strings w U.pdf", options); + pdfRenderer.Save(encryptedStream, false); // Open the saved file - pdfDocument = PdfReader.Open(encryptedFile, PasswordUserDefault); + pdfDocument = PdfReader.Open(encryptedStream, PasswordUserDefault); // Ensure entries in DocumentInformation have not changed. var documentInfo = pdfDocument.Info; // We don’t know the saved CreationDate exactly, but it should be less than 5 seconds ago. - (documentInfo.CreationDate - date).TotalMilliseconds.Should().BeLessThan(5000, "PDF CreationDate should be less than 5 seconds ago."); + (documentInfo.CreationDate - date)?.TotalMilliseconds.Should().BeLessThan(5000, "PDF CreationDate should be less than 5 seconds ago."); documentInfo.Creator.Should().Be(MigraDocProductVersionInformation.Creator, "PDF Creator should match"); //documentInfo.Producer.Should().Be($"{PdfSharpProductVersionInformation.Creator} under {RuntimeInformation.OSDescription}", "PDF Producer should match"); documentInfo.Producer.Should().StartWith(PdfSharpProductVersionInformation.Creator, "PDF Producer should match"); @@ -953,7 +964,7 @@ public void Test_Strings(TestOptionsEnum optionsEnum) textInfo!.Text.Should().Be(i + "."); streamEnumerator.Text.MoveAndGetNext(true, out textInfo).Should().BeTrue(); - textInfo!.TextEquals(testString, font, out var encodedText).Should().BeTrue($"encoded test string {i} (\"{encodedText}\") should match in paragraphs"); + textInfo!.TextEquals(testString, font, out var encodedText).Should().BeTrue($"encoded test string {i} ('{encodedText}') should match in paragraphs"); if (i == 10) textInfo.Text.Should().Be(@"\(\)\\\(\\\)\\", "test string {i} should match in paragraphs"); @@ -996,7 +1007,6 @@ public void Test_Strings(TestOptionsEnum optionsEnum) } } - [Fact] public void Test_Hyperlink() { @@ -1046,12 +1056,18 @@ public void Test_SignedDocument(TestOptionsEnum optionsEnum) options.SetDefaultPasswords(true); var filename = AddPrefixToFilename("SigningWithEncryptionTest.pdf", options); - + var document = CreateDocument(); SecureDocument(document, options); // Save the document. document.Save(filename); + + // Read encrypted file and write it without encryption. + var pdfDocRead = PdfReader.Open(filename, PasswordUserDefault); + + var filenameRead = AddPrefixToFilename("Read SigningWithEncryptionTest.pdf", options); + pdfDocRead.Save(filenameRead); // Creates minimalistic document with hyperlink. @@ -1076,9 +1092,9 @@ static PdfDocument CreateDocument() layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144); var text = "Lorem ipsum..."; - textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), layoutRectangle, XStringFormats.TopLeft); + textFormatter.DrawString(text, font, XBrushes.Black, layoutRectangle, XStringFormats.TopLeft); - var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216)); + var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216)); var options = new DigitalSignatureOptions { // We do not set an appearance handler, so the default handler is used. @@ -1092,7 +1108,7 @@ static PdfDocument CreateDocument() Uri? timestampURI = String.IsNullOrEmpty(timestampURL) ? null : new Uri(timestampURL, UriKind.Absolute); - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options); + var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options); return document; } @@ -1120,4 +1136,4 @@ static X509Certificate2 GetCertificate(string certName) } } } -} \ No newline at end of file +} diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SpaceBeforeTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SpaceBeforeTests.cs index ce675634..c6a2b43a 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SpaceBeforeTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/SpaceBeforeTests.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using PdfSharp.TestHelper; @@ -54,10 +54,10 @@ public void Tests_for_SpaceBefore() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("SpaceBefore"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/SpaceBefore"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); #if DEBUG___ diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TableTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TableTests.cs index 87c2164a..f7eb7360 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TableTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TableTests.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using MigraDoc.DocumentObjectModel; @@ -60,10 +60,10 @@ public void Create_Table_Cell_with_Top_Border() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("TableTopBorder"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/TableTopBorder"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); #if DEBUG___ @@ -117,10 +117,10 @@ public void Create_a_cloned_table() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("ClonedTable"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/ClonedTable"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -170,10 +170,10 @@ public void Create_a_table_with_cloned_rows() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("ClonedTable"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/tables/ClonedTable"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } } diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Template.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Template.cs index 2854bed5..1c432096 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Template.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/Template.cs @@ -1,4 +1,4 @@ -// MigraDoc - Creating Documents on the Fly +// MigraDoc - Creating Documents on the Fly // See the LICENSE file in the solution root for more information. using MigraDoc.DocumentObjectModel; @@ -54,10 +54,10 @@ public void Create_Hello_World_TemplateMigraDocTests() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("HelloWorld"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/HelloWorld"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); #if DEBUG___ diff --git a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TextTests.cs b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TextTests.cs index 9d8c0066..4526c526 100644 --- a/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TextTests.cs +++ b/src/foundation/src/MigraDoc/tests/MigraDoc.Tests/TextTests.cs @@ -7,16 +7,13 @@ using MigraDoc.DocumentObjectModel.Fields; using MigraDoc.Rendering; using MigraDoc.RtfRendering; +using PdfSharp.Diagnostics; using PdfSharp.Fonts; using PdfSharp.Logging; using PdfSharp.Quality; -using PdfSharp.Snippets.Font; -#if CORE -#endif using PdfSharp.TestHelper; using Xunit; using FluentAssertions; -using PdfSharp.Diagnostics; namespace MigraDoc.Tests { @@ -59,10 +56,10 @@ public void Surrogate_Pairs_Test() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("HelloEmoji"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/text/HelloEmoji"); pdfRenderer.PdfDocument.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); #if DEBUG___ @@ -116,7 +113,7 @@ public void Document_with_No_Break_Hyphen() "No\u2011break\u2011hyphen-Test No\u2011break\u2011hyphen-Test No\u2011break\u2011hyphen-Test No\u2011break\u2011hyphen-Test 12345 "); paragraph.Format.Font.Bold = true; - var filename = IOUtility.GetTempFileName("DocumentWithNoBreakHyphen", null); + var filename = IOUtility.GetTempFullFileName("DocumentWithNoBreakHyphen", null); var pdfFilename = filename + ".pdf"; var pdfRenderer = new PdfDocumentRenderer { Document = document }; @@ -234,7 +231,7 @@ public void Document_with_No_Break_Hyphen_before_Tabs() paragraph.AddText("Heading level 2 with no-break hyphen replaced by hyphen character"); paragraph.Style = StyleNames.Heading2; - var filename = IOUtility.GetTempFileName("DocumentWithNoBreakHyphenBeforeTabs", null); + var filename = IOUtility.GetTempFullFileName("DocumentWithNoBreakHyphenBeforeTabs", null); var pdfFilename = filename + ".pdf"; var pdfRenderer = new PdfDocumentRenderer { Document = document }; @@ -316,7 +313,7 @@ public void DecimalTabulatorTest() }) }; - var filenamePattern = IOUtility.GetTempFileName("DecimalTabulator{0}", "{1}"); + var filenamePattern = IOUtility.GetTempFullFileName("DecimalTabulator{0}", "{1}"); foreach (var cultureInfo in cultureInfos) { @@ -337,6 +334,8 @@ public void DecimalTabulatorTest() foreach (var number in numbersByPage[pageIdx]) { + // "#,###.##" is intended here to check if numbers with and without a digit before the decimal separator are aligned correctly at the decimal tabulator. + // Only the numbers in numbersByPage are formatted here, so the result won’t be an empty string, as it would be if '0' would be formatted with it. var numberStr = number.ToString("#,###.##", cultureInfo); foreach (var unit in units) @@ -450,10 +449,10 @@ public void FooterLayoutTest() // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("FooterTest"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/migradoc/footer/FooterTest"); pdfRenderer.PdfDocument.Save(filename); - //// ...and start a viewer. + //// … and start a viewer. //Process.Sta/rt(new ProcessStartInfo(filename) { UseShellExecute = true }); } } diff --git a/src/foundation/src/PDFsharp/docs/AboutFonts.md b/src/foundation/src/PDFsharp/docs/AboutFonts.md index 8934b30e..ad998c31 100644 --- a/src/foundation/src/PDFsharp/docs/AboutFonts.md +++ b/src/foundation/src/PDFsharp/docs/AboutFonts.md @@ -158,7 +158,7 @@ glyph IDs of the Unicode code points of the original UTF-32 encoded text. The class **PdPdfTrueTypeFontfFont** is used by PDFsharp to represent TrueType fonts in a PDF document that use the WinAnsiEncoding string representation. -It uses lesser space for text in PDF file than the CID (character id) glyph encoded text representation, +It uses lesser space for text in PDF file than the CID (character ID) glyph encoded text representation, but the character set is limited to (roughly) ANSI characters (codepage 1252). ### PdfType0Font : PdfFont diff --git a/src/foundation/src/PDFsharp/docs/Notebook.md b/src/foundation/src/PDFsharp/docs/Notebook.md index 0d7d0b90..2f421a2c 100644 --- a/src/foundation/src/PDFsharp/docs/Notebook.md +++ b/src/foundation/src/PDFsharp/docs/Notebook.md @@ -12,11 +12,11 @@ ## New C# features -PDFsharp uses should be compatible with .NET Framework 4.7x and .NET Standard 2.0. -Therefore only C# features are used that are handled by the compiler, like primary constructors, newer pattern matching, +PDFsharp should be compatible with .NET Framework 4.6x and .NET Standard 2.0. +Therefore, only C# features are used that are handled by the compiler, like primary constructors, newer pattern matching, switch expressions, etc. However, there are some features that depend on the runtime. -PDFsharp assemblies need some additional code to work with frameworks other aht .NET 6 or higher. +PDFsharp assemblies need some additional code to work with frameworks other than .NET 8 or higher. PDFsharp currently uses the following: * **init-only setter** diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj index cd3012c2..1461e37d 100644 --- a/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj +++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features-gdi/PDFsharp.Features-gdi.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PDFsharp.Features true @@ -57,5 +57,4 @@ - diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj index 506da2b2..2b08916a 100644 --- a/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj +++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features-wpf/PDFsharp.Features-wpf.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PDFsharp.Features true @@ -57,5 +57,4 @@ - diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj index 0ca4840a..016b7cc1 100644 --- a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj +++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-gdi/PDFsharp.Features.Runner-gdi.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PDFsharp.Features true diff --git a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj index a22f8c63..7ba425db 100644 --- a/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj +++ b/src/foundation/src/PDFsharp/features/PDFsharp.Features.Runner-wpf/PDFsharp.Features.Runner-wpf.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PDFsharp.Features true diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj index 7791be9f..3db12542 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/PdfSharp.Features.Runner.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net462 + $(PDFsharpTargetFrameworks_Exe) CORE diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/Program.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/Program.cs index 4893e1c8..30b49810 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/Program.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features.Runner/Program.cs @@ -7,6 +7,7 @@ //using PdfSharp.Quality; //using PdfSharp.Snippets.Font; +using PdfSharp.Fonts; using PdfSharp.UniversalAccessibility; using Features = PdfSharp.Features.PdfSharpFeatures; @@ -16,6 +17,9 @@ class Program { static void Main(string[] args) { +#if CORE + GlobalFontSettings.UseWindowsFontsUnderWindows = true; +#endif Memory m = null; ReadOnlyMemory rom = null; Span s = null; @@ -41,14 +45,14 @@ static void Main(string[] args) // Drawing/text //features[PdfSharpFeatures.Names.Drawing_text_SurrogateChars__Surrogates].Run(); - //features[PdfSharpFeatures.Names.Drawing_text_SymbolFonts__Symbols].Run(); + features[PdfSharpFeatures.Names.Drawing_text_SymbolFonts__Symbols].Run(); - //features[PdfSharpFeatures.Names.Font_encoding_Encodings_AnsiEncoding].Run(); + features[PdfSharpFeatures.Names.Font_encoding_Encodings_AnsiEncoding].Run(); // Pdf/annotations // Pdf/pdfa - features[PdfSharpFeatures.Names.Pdf_pdfa_PdfA].Run(); + //features[PdfSharpFeatures.Names.Pdf_pdfa_PdfA].Run(); @@ -64,8 +68,8 @@ static void Main(string[] args) // Drawing.text //new Features.Drawing.AutoFontEncoding().Ensure_one_PdfFontDescriptor_per_FontFace(); - //new Features.Drawing.AutoFontEncoding().Test2(); - //new Features.Drawing.AutoFontEncoding().MeasureString_Test(); + new Features.Drawing.AutoFontEncoding().Test2(); + new Features.Drawing.AutoFontEncoding().MeasureString_Test(); //new Features.Drawing.Encodings().Ansi(); //new Features.Drawing.SurrogateChars().Test1(); diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/lines/Lines1.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/lines/Lines1.cs index aa1210e2..eb30adb8 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/lines/Lines1.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/lines/Lines1.cs @@ -3,7 +3,6 @@ using PdfSharp.Drawing; -//#pragma warning disable 1591 namespace PdfSharp.Features.Drawing { /// diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/paths/Paths.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/paths/Paths.cs index 749623f4..a6657830 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/paths/Paths.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/paths/Paths.cs @@ -3,7 +3,8 @@ using PdfSharp.Quality; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Drawing { public class Paths : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/shapes/RoundedRectangles.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/shapes/RoundedRectangles.cs index 65f69f24..bb1693e7 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/shapes/RoundedRectangles.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/shapes/RoundedRectangles.cs @@ -6,7 +6,8 @@ using PdfSharp.Quality; using PdfSharp.Snippets.Drawing; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Drawing { public class RoundedRectangles : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/AutoFontEncoding.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/AutoFontEncoding.cs index 77ca0165..3ffaeab2 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/AutoFontEncoding.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/AutoFontEncoding.cs @@ -11,7 +11,8 @@ using PdfSharp.Snippets.Drawing; using PdfSharp.Snippets.Font; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Drawing { public class AutoFontEncoding : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/NotoSans.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/NotoSans.cs index 07afd297..1c2d7594 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/NotoSans.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/NotoSans.cs @@ -13,7 +13,8 @@ using PdfSharp.Quality; using PdfSharp.Snippets.Font; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Drawing { public class NotoSans : Feature @@ -59,7 +60,7 @@ public void Load_all_Noto_Sans() gfx.DrawString($"{idx + 1:##}: (this is a symbol font)", fontInfo, XBrushes.Black, 20, y); else gfx.DrawString($"{idx + 1:##}: {pangram}", font, XBrushes.Black, 20, y); - gfx.DrawString($" {font.Name2} {Style(font, glyphTypeface)}", fontInfo, XBrushes.DarkBlue, 20, y + 12); + gfx.DrawString($" {font.Name} {Style(font, glyphTypeface)}", fontInfo, XBrushes.DarkBlue, 20, y + 12); y += 30; if ((idx + 1) % 30 == 0) diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SurrogateChars.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SurrogateChars.cs index 52208a01..880d7e7f 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SurrogateChars.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SurrogateChars.cs @@ -6,7 +6,8 @@ using PdfSharp.Quality; using PdfSharp.Snippets.Drawing; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Drawing { public class SurrogateChars : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SymbolFonts.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SymbolFonts.cs index bb4b7b2f..7d2582a3 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SymbolFonts.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Drawing/text/SymbolFonts.cs @@ -4,7 +4,8 @@ using PdfSharp.Quality; using PdfSharp.Snippets.Drawing; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Drawing { public class SymbolFontFeature : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontResolving.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontResolving.cs index f90bd702..7ae91835 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontResolving.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontResolving.cs @@ -6,7 +6,8 @@ using PdfSharp.Quality; using PdfSharp.Snippets.Font; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features { public class FontResolvers : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontSelection.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontSelection.cs index c3fa1a1f..feab4c31 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontSelection.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/FontSelection.cs @@ -3,7 +3,7 @@ using PdfSharp.Quality; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class namespace PdfSharp.Features { diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/Glyphs.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/Glyphs.cs index 8f7802ef..400eaf47 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/Glyphs.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/Glyphs.cs @@ -4,7 +4,7 @@ using PdfSharp.Quality; using PdfSharp.Snippets.Font; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class namespace PdfSharp.Features.Font { diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RenderInstalledFonts.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RenderInstalledFonts.cs index 2db9fb34..6c47cb65 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RenderInstalledFonts.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RenderInstalledFonts.cs @@ -8,7 +8,8 @@ using System.Drawing.Text; #endif -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Font { public class RenderInstalledFonts : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RotisWinAnsiTester.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RotisWinAnsiTester.cs index b5af41a3..d47b9feb 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RotisWinAnsiTester.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/RotisWinAnsiTester.cs @@ -15,7 +15,8 @@ using PdfSharp.Fonts; using PdfSharp.Quality; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Font { public class RotisWinAnsiTester : Feature @@ -169,7 +170,7 @@ static class FaceNames /// public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic) { - // Note: PDFsharp calls ResolveTypeface only once for each unique combination + // Note that PDFsharp calls ResolveTypeface only once for each unique combination // of familyName, isBold, and isItalic. // In this sample we use 6 fonts from the Segoe font family which come with the @@ -281,7 +282,7 @@ static class FaceNames /// public byte[] GetFont(string faceName) { - // Note: PDFsharp never calls GetFont twice with the same face name. + // Note that PDFsharp never calls GetFont twice with the same face name. // Return the bytes of a font. switch (faceName) @@ -394,7 +395,8 @@ static byte[] LoadFontData(string name) var count = (int)stream.Length; var data = new byte[count]; - stream.Read(data, 0, count); + var read = stream.Read(data, 0, count); + Debug.Assert(read == count); return data; } } diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/encoding/Encodings.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/encoding/Encodings.cs index 1e6a27d9..eb4f78d1 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/encoding/Encodings.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Font/encoding/Encodings.cs @@ -10,7 +10,8 @@ using PdfSharp.Snippets.Drawing; using PdfSharp.Snippets.Font; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Font { public class Encodings : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/Info.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/Info.cs index a688c7b0..749d7fdf 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/Info.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/Info.cs @@ -1,14 +1,10 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; -using System.Collections.Generic; -using System.Text; -using PdfSharp.Pdf; -using PdfSharp.Pdf.IO; using PdfSharp.Quality; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.IO { public class Info diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/LargePdfFiles.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/LargePdfFiles.cs index 3d39901f..901b5697 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/LargePdfFiles.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/LargePdfFiles.cs @@ -15,7 +15,8 @@ using PdfSharp.Snippets.Font; using PdfSharp.TestHelper; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.IO { public class LargePdfFiles : Feature diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/ObjectStreams.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/ObjectStreams.cs index b065d69c..11a7459c 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/ObjectStreams.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/IO/ObjectStreams.cs @@ -7,7 +7,8 @@ using PdfSharp.Pdf.IO; using PdfSharp.TestHelper; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.IO { /// diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/annotations/LinkAnnotations.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/annotations/LinkAnnotations.cs index d44c99ff..51b46454 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/annotations/LinkAnnotations.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/annotations/LinkAnnotations.cs @@ -4,7 +4,8 @@ using PdfSharp.Pdf.IO; using PdfSharp.Quality; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Pdf { public class LinkAnnotations diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/pdfa/PDF-A.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/pdfa/PDF-A.cs index 205f5a58..860d68b0 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/pdfa/PDF-A.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/Pdf/pdfa/PDF-A.cs @@ -8,7 +8,8 @@ using PdfSharp.Quality; using PdfSharp.UniversalAccessibility; -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features.Pdf { public class PdfA @@ -68,11 +69,11 @@ public void CreatePdfA() } sb.End(); - // Save the document... + // Save the document… var fullName = PdfFileUtility.GetTempPdfFullFileName("Features/Pdf-A/Test1" + Capabilities.Build.BuildTag); document.Save(fullName); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocument(fullName); } } diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj index 7a112f72..f42bf5a2 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharp.Features.csproj @@ -1,7 +1,7 @@  - net8.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) CORE @@ -31,5 +31,4 @@ - diff --git a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharpFeatures.cs b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharpFeatures.cs index 9aba016b..aba659f8 100644 --- a/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharpFeatures.cs +++ b/src/foundation/src/PDFsharp/features/PdfSharp.Features/PdfSharpFeatures.cs @@ -1,7 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -#pragma warning disable 1591 +#pragma warning disable CS1591 // Internal class + namespace PdfSharp.Features { public class PdfSharpFeatures diff --git a/src/foundation/src/PDFsharp/src/Directory.Build.props b/src/foundation/src/PDFsharp/src/Directory.Build.props index f6a2326b..c698c922 100644 --- a/src/foundation/src/PDFsharp/src/Directory.Build.props +++ b/src/foundation/src/PDFsharp/src/Directory.Build.props @@ -3,6 +3,7 @@ PDFsharp + $(CopyrightPdfSharp) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs index ae9d8e2d..1365ff62 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreview.cs @@ -9,12 +9,8 @@ #define DRAW_BMP_ #endif -using System; -using System.Diagnostics; using System.ComponentModel; -using System.Drawing; using System.Drawing.Drawing2D; -using System.Windows.Forms; using PdfSharp.Drawing; #if !GDI @@ -40,7 +36,7 @@ public class PagePreview : UserControl /// public delegate void RenderEvent(XGraphics gfx); - private Container components = null!; + Container components = null!; /// /// Initializes a new instance of the class. @@ -71,10 +67,6 @@ public PagePreview() //showNonPrintableArea = false; //virtualPrintableArea = new Rectangle(); - //_printableArea.GetType(); - //showNonPrintableArea.GetType(); - //virtualPrintableArea.GetType(); - // Prevent bogus compiler warnings _posOffset = new Point(); _virtualPage = new Rectangle(); @@ -171,7 +163,7 @@ public XGraphicsUnit PageUnit [DefaultValue((int)Zoom.FullPage), Description("Determines the zoom of the page."), Category("Preview Properties")] public Zoom Zoom { - get { return _zoom; } + get => _zoom; set { if ((int)value < (int)Zoom.Minimum || (int)value > (int)Zoom.Maximum) @@ -197,7 +189,7 @@ public Zoom Zoom [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public int ZoomPercent { - get { return _zoomPercent; } + get => _zoomPercent; set { if (value < (int)Zoom.Minimum || value > (int)Zoom.Maximum) @@ -223,7 +215,7 @@ public int ZoomPercent [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color PageColor { - get { return _pageColor; } + get => _pageColor; set { if (value != _pageColor) @@ -242,7 +234,7 @@ public Color PageColor [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Color DesktopColor { - get { return _desktopColor; } + get => _desktopColor; set { if (value != _desktopColor) @@ -252,7 +244,7 @@ public Color DesktopColor } } } - internal Color _desktopColor = SystemColors.ControlDark; + Color _desktopColor = SystemColors.ControlDark; /// /// Gets or sets a value indicating whether the scrollbars are visible. @@ -260,7 +252,7 @@ public Color DesktopColor [DefaultValue(true), Description("Determines whether the scrollbars are visible."), Category("Preview Properties")] public bool ShowScrollbars { - get { return _showScrollbars; } + get => _showScrollbars; set { if (value != _showScrollbars) @@ -280,7 +272,7 @@ public bool ShowScrollbars [DefaultValue(true), Description("Determines whether the page visible."), Category("Preview Properties")] public bool ShowPage { - get { return _showPage; } + get => _showPage; set { if (value != _showPage) @@ -315,7 +307,7 @@ public XSize PageSize [DesignerSerializationVisibility(DesignerSerializationVisibility.Visible)] public Size PageSizeF { - get { return new Size(Convert.ToInt32(_pageSize.Width), Convert.ToInt32(_pageSize.Height)); } + get => new(Convert.ToInt32(_pageSize.Width), Convert.ToInt32(_pageSize.Height)); set { _pageSize = value; @@ -349,7 +341,7 @@ public void SetRenderEvent(RenderEvent renderEvent) /// Required method for Designer support - do not modify /// the contents of this method with the code editor. /// - private void InitializeComponent() + void InitializeComponent() { Name = "PagePreview"; Size = new System.Drawing.Size(228, 252); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreviewCanvas.cs b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreviewCanvas.cs index 8b17ed04..471590d0 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreviewCanvas.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/PagePreviewCanvas.cs @@ -45,7 +45,7 @@ protected override void OnPaintBackground(PaintEventArgs e) { if (!_preview._showPage) { - e.Graphics.Clear(_preview._desktopColor); + e.Graphics.Clear(_preview.DesktopColor); return; } bool zoomChanged; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs index 04a353c6..752ab6f2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/Forms/enums/Zoom.cs @@ -76,7 +76,7 @@ public enum Zoom /// /// Sets the zoom factor so that the printable area of the document fits horizontally into the window. - /// Currently not yet implemented and the same as ZoomBestFit. + /// Not yet implemented and the same as ZoomBestFit. /// TextFit = -2, diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj index 9660d8cb..f35d5caa 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp-gdi/PdfSharp-gdi.csproj @@ -2,7 +2,7 @@ library - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp true @@ -10,9 +10,6 @@ $(AssemblyName) True ..\..\..\..\..\StrongnameKey.snk - - true true @@ -30,18 +27,14 @@ - - + + + + - - - - - - @@ -126,74 +119,63 @@ + + - - - - - - - - - - - - - - - - - - - + + - + - - + + - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -206,10 +188,11 @@ + + - @@ -222,14 +205,14 @@ - - + + @@ -246,16 +229,41 @@ - - + + + - + - + + + + + + + + + + + + + + + + + + + + - + + + + + + @@ -277,12 +285,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -296,12 +330,23 @@ - + + + + + + + + + + + + + - @@ -309,17 +354,12 @@ - - - - - + - - + @@ -330,8 +370,10 @@ + + @@ -345,25 +387,34 @@ + + + + + + + + - - + + + @@ -371,7 +422,7 @@ - + @@ -379,6 +430,11 @@ + + + + + @@ -390,10 +446,10 @@ + - @@ -407,23 +463,35 @@ - + + + + - - - + + + + + + + + + + + + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj index fe0ae4ed..be90e4ec 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp-wpf/PdfSharp-wpf.csproj @@ -2,16 +2,13 @@ library - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp true WPF True ..\..\..\..\..\StrongnameKey.snk - - @@ -27,18 +24,12 @@ - - + + - - - - - - @@ -123,74 +114,63 @@ + + - - - - - - - - - - - - - - - - - - + - - + + - + - - + + - + - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + @@ -203,10 +183,11 @@ + + - @@ -219,14 +200,14 @@ - - + + @@ -243,16 +224,41 @@ - - + + + - + - + + + + + + + + + + + + + + + + + + + + - + + + + + + @@ -274,12 +280,38 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + @@ -293,12 +325,23 @@ - + + + + + + + + + + + + + - @@ -306,17 +349,12 @@ - - - - - + - - + @@ -327,8 +365,10 @@ + + @@ -342,25 +382,34 @@ + + + + + + + + - - + + + @@ -376,6 +425,11 @@ + + + + + @@ -387,10 +441,10 @@ + - @@ -408,17 +462,24 @@ + + - - + + + + + + + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj index 7b4065d3..11dfb9bf 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-gdi/PdfSharp.BarCodes-gdi.csproj @@ -2,7 +2,7 @@ library - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp true @@ -17,7 +17,8 @@ - + + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj index acdcb75a..3374afe7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes-wpf/PdfSharp.BarCodes-wpf.csproj @@ -2,7 +2,7 @@ library - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp true @@ -16,7 +16,8 @@ - + + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeOmr.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeOmr.cs index 0a086838..2c592395 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeOmr.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/CodeOmr.cs @@ -97,7 +97,7 @@ public double MakerThickness ///// Renders the mark at the given position. ///// ///// The mark position to render. - //private void RenderMark(int position) + //void RenderMark(int position) //{ // double yPos = TopLeft.Y + UpperDistance + position * ToUnit(markDistance).Centimeter; // //Center mark @@ -114,7 +114,7 @@ public double MakerThickness // get { return markDistance; } // set { markDistance = value; } //} - //private MarkDistance markDistance = MarkDistance.Inch2_6; + //MarkDistance markDistance = MarkDistance.Inch2_6; ///// ///// Converts a mark distance to an XUnit object. @@ -161,7 +161,7 @@ public double MakerThickness // get { return upperDistance; } // set { upperDistance = value; } //} - //private double upperDistance = XUnit.FromInch(8.0 / 6.0).Centimeter; + //double upperDistance = XUnit.FromInch(8.0 / 6.0).Centimeter; ///// ///// The lower distance from the last possible mark to the end of the reading zone. @@ -172,7 +172,7 @@ public double MakerThickness // get { return lowerDistance; } // set { lowerDistance = value; } //} - //private double lowerDistance = XUnit.FromInch(2.0 / 6.0).Centimeter; + //double lowerDistance = XUnit.FromInch(2.0 / 6.0).Centimeter; ///// ///// Gets or sets the width of the reading zone. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/DataMatrixImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/DataMatrixImage.cs index 60ef2b81..a42147fb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/DataMatrixImage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/DataMatrixImage.cs @@ -103,7 +103,7 @@ internal char[] DataMatrix() grid = Iec16022Ecc200(matrixColumns, matrixRows, _encoding, _text.Length, _text, len, maxlen, ecclen); if (grid == null || matrixColumns == 0) - throw new ArgumentException(BcgSR.DataMatrixNull); //DaSt: ever happen? + throw new ArgumentException(BcgSR.DataMatrixNull); return grid; } @@ -619,7 +619,7 @@ public static void InitGalois(int poly) alog[v] = p; log[p] = v; p <<= 1; - if ((p & b) != 0) //DaSt: check! + if ((p & b) != 0) p ^= poly; } } @@ -644,7 +644,7 @@ public static void InitReedSolomon(int nsym, int index) rspoly[i] = 1; for (k = i - 1; k > 0; k--) { - if (rspoly[k] != 0) //DaSt: check! + if (rspoly[k] != 0) rspoly[k] = alog[(log[rspoly[k]] + index) % logmod]; rspoly[k] ^= rspoly[k - 1]; } @@ -668,12 +668,12 @@ public void EncodeReedSolomon(int length, int[] data, ref int[] result) m = result[rlen - 1] ^ data[i]; for (k = rlen - 1; k > 0; k--) { - if ((m != 0) && (rspoly[k] != 0)) //DaSt: check! + if (m != 0 && rspoly[k] != 0) result[k] = result[k - 1] ^ alog[(log[m] + log[rspoly[k]]) % logmod]; else result[k] = result[k - 1]; } - if ((m != 0) && (rspoly[0] != 0)) //DaSt: check! + if (m != 0 && rspoly[0] != 0) result[0] = alog[(log[m] + log[rspoly[0]]) % logmod]; else result[0] = 0; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/OmrData.cs b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/OmrData.cs index 2549092a..d189113e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/OmrData.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/Drawing.BarCodes/OmrData.cs @@ -9,7 +9,7 @@ namespace PdfSharp.Drawing.BarCodes /// class OmrData { - private OmrData() + OmrData() { } public static OmrData ForTesting @@ -48,7 +48,7 @@ public static OmrData ForTesting /// Adds a mark description by name. /// /// The name to for setting or unsetting the mark. - private void AddMarkDescription(string name) + void AddMarkDescription(string name) { if (_marksInitialized) throw new InvalidOperationException(BcgSR.OmrAlreadyInitialized); @@ -57,7 +57,7 @@ private void AddMarkDescription(string name) ++AddedDescriptions; } - private void InitMarks() + void InitMarks() { if (AddedDescriptions == 0) throw new InvalidOperationException(); @@ -67,7 +67,7 @@ private void InitMarks() _marksInitialized = true; } - private int FindIndex(string name) + int FindIndex(string name) { if (!_marksInitialized) InitMarks(); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj index e1ebb1cd..5f0b37c5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.BarCodes/PdfSharp.BarCodes.csproj @@ -2,7 +2,7 @@ library - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp CORE True @@ -14,7 +14,8 @@ - + + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj index 7ab21f61..c77e86b0 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj @@ -2,7 +2,7 @@ library - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp.Charting true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save deleted file mode 100644 index f6de3245..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-gdi/PdfSharp.Charting-gdi.csproj - save +++ /dev/null @@ -1,300 +0,0 @@ - - - - library - net8.0-windows;net9.0-windows;net10.0-windows;net462 - true - PdfSharp.Charting - true - True - ..\..\..\..\..\StrongnameKey.snk - - - - true - - - - - Charting.Renderers\AreaChartRenderer.cs - - - Charting.Renderers\AreaPlotAreaRenderer.cs - - - Charting.Renderers\AxisRenderer.cs - - - Charting.Renderers\AxisTitleRenderer.cs - - - Charting.Renderers\BarChartRenderer.cs - - - Charting.Renderers\BarClusteredLegendRenderer.cs - - - Charting.Renderers\BarClusteredPlotAreaRenderer.cs - - - Charting.Renderers\BarDataLabelRenderer.cs - - - Charting.Renderers\BarGridlinesRenderer.cs - - - Charting.Renderers\BarPlotAreaRenderer.cs - - - Charting.Renderers\BarStackedPlotAreaRenderer.cs - - - Charting.Renderers\ChartRenderer.cs - - - Charting.Renderers\Colors.cs - - - Charting.Renderers\ColumnChartRenderer.cs - - - Charting.Renderers\ColumnClusteredPlotAreaRenderer.cs - - - Charting.Renderers\ColumnDataLabelRenderer.cs - - - Charting.Renderers\ColumnLikeChartRenderer.cs - - - Charting.Renderers\ColumnLikeGridlinesRenderer.cs - - - Charting.Renderers\ColumnLikeLegendRenderer.cs - - - Charting.Renderers\ColumnLikePlotAreaRenderer.cs - - - Charting.Renderers\ColumnPlotAreaRenderer.cs - - - Charting.Renderers\ColumnStackedPlotAreaRenderer.cs - - - Charting.Renderers\CombinationChartRenderer.cs - - - Charting.Renderers\Converter.cs - - - Charting.Renderers\DataLabelRenderer.cs - - - Charting.Renderers\GridlinesRenderer.cs - - - Charting.Renderers\HorizontalStackedYAxisRenderer.cs - - - Charting.Renderers\HorizontalXAxisRenderer.cs - - - Charting.Renderers\HorizontalYAxisRenderer.cs - - - Charting.Renderers\LegendEntryRenderer.cs - - - Charting.Renderers\LegendRenderer.cs - - - Charting.Renderers\LineChartRenderer.cs - - - Charting.Renderers\LineFormatRenderer.cs - - - Charting.Renderers\LinePlotAreaRenderer.cs - - - Charting.Renderers\MarkerRenderer.cs - - - Charting.Renderers\PieChartRenderer.cs - - - Charting.Renderers\PieClosedPlotAreaRenderer.cs - - - Charting.Renderers\PieDataLabelRenderer.cs - - - Charting.Renderers\PieExplodedPlotAreaRenderer.cs - - - Charting.Renderers\PieLegendRenderer.cs - - - Charting.Renderers\PiePlotAreaRenderer.cs - - - Charting.Renderers\PlotAreaBorderRenderer.cs - - - Charting.Renderers\PlotAreaRenderer.cs - - - Charting.Renderers\Renderer.cs - - - Charting.Renderers\RendererInfo.cs - - - Charting.Renderers\RendererParameters.cs - - - Charting.Renderers\VerticalStackedYAxisRenderer.cs - - - Charting.Renderers\VerticalXAxisRenderer.cs - - - Charting.Renderers\VerticalYAxisRenderer.cs - - - Charting.Renderers\WallRenderer.cs - - - Charting.Renderers\XAxisRenderer.cs - - - Charting.Renderers\YAxisRenderer.cs - - - Charting\Axis.cs - - - Charting\AxisTitle.cs - - - Charting\Chart.cs - - - Charting\ChartFrame.cs - - - Charting\ChartObject.cs - - - Charting\DataLabel.cs - - - Charting\DocumentObject.cs - - - Charting\DocumentObjectCollection.cs - - - Charting\enums\BlankType.cs - - - Charting\enums\ChartType.cs - - - Charting\enums\DataLabelPosition.cs - - - Charting\enums\DataLabelType.cs - - - Charting\enums\DockingType.cs - - - Charting\enums\FontProperties.cs - - - Charting\enums\HorizontalAlignment.cs - - - Charting\enums\LineStyle.cs - - - Charting\enums\MarkerStyle.cs - - - Charting\enums\TickMarkType.cs - - - Charting\enums\Underline.cs - - - Charting\enums\VerticalAlignment.cs - - - Charting\FillFormat.cs - - - Charting\Font.cs - - - Charting\Gridlines.cs - - - Charting\Legend.cs - - - Charting\LineFormat.cs - - - Charting\PlotArea.cs - - - Charting\Point.cs - - - Charting\PSCSR.cs - - - Charting\Series.cs - - - Charting\SeriesCollection.cs - - - Charting\SeriesElements.cs - - - Charting\TickLabels.cs - - - Charting\XSeries.cs - - - Charting\XSeriesElements.cs - - - Charting\XValue.cs - - - Charting\XValues.cs - - - Properties\GlobalDeclarations.cs - - - - - - - - - - - - diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj index 9ce77dc1..54015d53 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting-wpf/PdfSharp.Charting-wpf.csproj @@ -2,7 +2,7 @@ library - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp.Charting true diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaChartRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaChartRenderer.cs index 2d7de575..d2c88f9e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaChartRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaChartRenderer.cs @@ -144,7 +144,7 @@ internal void InitSeries() var elements = sri.Series._seriesElements; if (elements != null) { - sri.PointRendererInfos = new PointRendererInfo[elements.Count ]; + sri.PointRendererInfos = new PointRendererInfo[elements.Count]; for (int pointIdx = 0; pointIdx < sri.PointRendererInfos.Length; ++pointIdx) { var pri = new PointRendererInfo(); @@ -155,7 +155,7 @@ internal void InitSeries() pri.LineFormat = sri.LineFormat; pri.FillFormat = sri.FillFormat; if (point._lineFormat != null && !point._lineFormat.Color.IsEmpty) - pri.LineFormat = new XPen(point._lineFormat.Color, point._lineFormat.Width.Point); + pri.LineFormat = new XPen(point._lineFormat.Color, (float_)point._lineFormat.Width.Point); if (point._fillFormat != null && !point._fillFormat.Color.IsEmpty) pri.FillFormat = new XSolidBrush(point._fillFormat.Color); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaPlotAreaRenderer.cs index 9cd85e35..d0857f23 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AreaPlotAreaRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -39,16 +42,16 @@ internal override void Draw() { int count = sri.Series.Elements.Count; var points = new XPoint[count + 2]; - points[0] = new XPoint(xMajorTick / 2, 0); + points[0] = new XPoint((float_)(xMajorTick / 2), 0); for (int idx = 0; idx < count; idx++) { double pointValue = sri.Series.Elements[idx].Value; if (Double.IsNaN(pointValue)) pointValue = 0; - points[idx + 1] = new XPoint(idx + xMajorTick / 2, pointValue); + points[idx + 1] = new XPoint((float_)(idx + xMajorTick / 2), (float_)pointValue); } - points[count + 1] = new XPoint(count - 1 + xMajorTick / 2, 0); + points[count + 1] = new XPoint((float_)(count - 1 + xMajorTick / 2), 0); matrix.TransformPoints(points); gfx.DrawPolygon(sri.LineFormat, sri.FillFormat, points, XFillMode.Winding); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisRenderer.cs index d1fa14f5..7e8f9c33 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisRenderer.cs @@ -62,7 +62,7 @@ protected void InitTickLabels(AxisRendererInfo rendererInfo, XFont defaultFont) else { rendererInfo.TickLabelsFont = defaultFont; - rendererInfo.TickLabelsBrush = new XSolidBrush(XColors.Black); + rendererInfo.TickLabelsBrush = XBrushes.Black; rendererInfo.TickLabelsFormat = GetDefaultTickLabelsFormat(); } } @@ -107,7 +107,7 @@ protected void InitGridlines(AxisRendererInfo rendererInfo) else if (rendererInfo.Axis?.HasMinorGridlines ?? false) { // No minor gridlines object are given, but user asked for. - rendererInfo.MinorGridlinesLineFormat = new XPen(XColors.Black, DefaultGridLineWidth); + rendererInfo.MinorGridlinesLineFormat = new XPen(XColors.Black, (float_)DefaultGridLineWidth); } if (rendererInfo.Axis?._majorGridlines != null) @@ -118,7 +118,7 @@ protected void InitGridlines(AxisRendererInfo rendererInfo) else if (rendererInfo.Axis?.HasMajorGridlines ?? false) { // No major gridlines object are given, but user asked for. - rendererInfo.MajorGridlinesLineFormat = new XPen(XColors.Black, DefaultGridLineWidth); + rendererInfo.MajorGridlinesLineFormat = new XPen(XColors.Black, (float_)DefaultGridLineWidth); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisTitleRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisTitleRenderer.cs index 4b660513..e50013e1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisTitleRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/AxisTitleRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -37,7 +40,7 @@ internal override void Format() points[1].Y = size.Height; XMatrix matrix = new XMatrix(); - matrix.RotatePrepend(-atri.AxisTitleOrientation); + matrix.RotatePrepend((float_)(-atri.AxisTitleOrientation)); matrix.TransformPoints(points); size.Width = Math.Abs(points[1].X - points[0].X); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredLegendRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredLegendRenderer.cs index 705d9c29..ba7f8409 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredLegendRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredLegendRenderer.cs @@ -40,31 +40,31 @@ internal override void Draw() if (lri.BorderPen != null!) paddingFactor = 2; XRect legendRect = lri.Rect; - legendRect.X += LeftPadding * paddingFactor; + legendRect.X += (float_)(LeftPadding * paddingFactor); if (verticalLegend) - legendRect.Y = legendRect.Bottom - BottomPadding * paddingFactor; + legendRect.Y = (float_)(legendRect.Bottom - BottomPadding * paddingFactor); else - legendRect.Y += TopPadding * paddingFactor; + legendRect.Y += (float_)(TopPadding * paddingFactor); if (cri.LegendRendererInfo != null) { foreach (var leri in cri.LegendRendererInfo.Entries) { if (verticalLegend) - legendRect.Y -= leri.Height; + legendRect.Y -= (float_)leri.Height; XRect entryRect = legendRect; - entryRect.Width = leri.Width; - entryRect.Height = leri.Height; + entryRect.Width = (float_)leri.Width; + entryRect.Height = (float_)leri.Height; leri.Rect = entryRect; parms.RendererInfo = leri; ler.Draw(); if (verticalLegend) - legendRect.Y -= EntrySpacing; + legendRect.Y -= (float_)EntrySpacing; else - legendRect.X += entryRect.Width + EntrySpacing; + legendRect.X += (float_)(entryRect.Width + EntrySpacing); } } @@ -72,10 +72,10 @@ internal override void Draw() if (lri.BorderPen != null) { XRect borderRect = lri.Rect; - borderRect.X += LeftPadding; - borderRect.Y += TopPadding; - borderRect.Width -= LeftPadding + RightPadding; - borderRect.Height -= TopPadding + BottomPadding; + borderRect.X += (float_)LeftPadding; + borderRect.Y += (float_)TopPadding; + borderRect.Width -= (float_)(LeftPadding + RightPadding); + borderRect.Height -= (float_)(TopPadding + BottomPadding); gfx.DrawRectangle(lri.BorderPen, borderRect); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredPlotAreaRenderer.cs index 34e4026b..708e7e6f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarClusteredPlotAreaRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -72,10 +75,10 @@ protected override void CalcBars() (y0, y1) = (y1, y0); } - points[0].X = y0; // upper left - points[0].Y = x0; - points[1].X = y1; // lower right - points[1].Y = x1; + points[0].X =(float_)y0; // upper left + points[0].Y =(float_)x0; + points[1].X =(float_)y1; // lower right + points[1].Y =(float_)x1; cri.PlotAreaRendererInfo.Matrix.TransformPoints(points); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarGridlinesRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarGridlinesRenderer.cs index 152a06aa..009bc5f9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarGridlinesRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarGridlinesRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -56,10 +59,10 @@ internal override void Draw() lineFormatRenderer = new LineFormatRenderer(gfx, xari.MinorGridlinesLineFormat); for (double x = xMin + xMinorTick; x < xMax; x += xMinorTick) { - points[0].Y = x; - points[0].X = yMin; - points[1].Y = x; - points[1].X = yMax; + points[0].Y = (float_)x; + points[0].X = (float_)yMin; + points[1].Y = (float_)x; + points[1].X = (float_)yMax; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -70,10 +73,10 @@ internal override void Draw() lineFormatRenderer = new LineFormatRenderer(gfx, xari.MajorGridlinesLineFormat); for (double x = xMin; x <= xMax; x += xMajorTick) { - points[0].Y = x; - points[0].X = yMin; - points[1].Y = x; - points[1].X = yMax; + points[0].Y = (float_)x; + points[0].X = (float_)yMin; + points[1].Y = (float_)x; + points[1].X = (float_)yMax; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -84,10 +87,10 @@ internal override void Draw() lineFormatRenderer = new LineFormatRenderer(gfx, yari.MinorGridlinesLineFormat); for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick) { - points[0].Y = xMin; - points[0].X = y; - points[1].Y = xMax; - points[1].X = y; + points[0].Y = (float_)xMin; + points[0].X = (float_)y; + points[1].Y = (float_)xMax; + points[1].X = (float_)y; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -98,10 +101,10 @@ internal override void Draw() lineFormatRenderer = new LineFormatRenderer(gfx, yari.MajorGridlinesLineFormat); for (double y = yMin; y <= yMax; y += yMajorTick) { - points[0].Y = xMin; - points[0].X = y; - points[1].Y = xMax; - points[1].X = y; + points[0].Y = (float_)xMin; + points[0].X = (float_)y; + points[1].Y = (float_)xMax; + points[1].X = (float_)y; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarPlotAreaRenderer.cs index 4cfcc2a9..1fbfe56f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarPlotAreaRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -33,9 +36,11 @@ internal override void Format() XRect plotAreaBox = cri.PlotAreaRendererInfo.Rect; cri.PlotAreaRendererInfo.Matrix = new XMatrix(); - cri.PlotAreaRendererInfo.Matrix.TranslatePrepend(-yMin, xMin); - cri.PlotAreaRendererInfo.Matrix.Scale(plotAreaBox.Width / (yMax - yMin), plotAreaBox.Height / (xMax - xMin), XMatrixOrder.Append); - cri.PlotAreaRendererInfo.Matrix.Translate(plotAreaBox.X, plotAreaBox.Y, XMatrixOrder.Append); + cri.PlotAreaRendererInfo.Matrix.TranslatePrepend((float_)(-yMin), (float_)xMin); + //cri.PlotAreaRendererInfo.Matrix.Scale((float_)(plotAreaBox.Width / (yMax - yMin)), (float_)(plotAreaBox.Height / (xMax - xMin)), XMatrixOrder.Append); + cri.PlotAreaRendererInfo.Matrix.ScaleAppend((float_)(plotAreaBox.Width / (yMax - yMin)), (float_)(plotAreaBox.Height / (xMax - xMin))); + //cri.PlotAreaRendererInfo.Matrix.Translate(plotAreaBox.X, plotAreaBox.Y, XMatrixOrder.Append); + cri.PlotAreaRendererInfo.Matrix.TranslateAppend(plotAreaBox.X, plotAreaBox.Y); CalcBars(); } @@ -71,9 +76,9 @@ internal override void Draw() { XPoint[] points = new XPoint[2]; points[0].X = 0; - points[0].Y = xMin; + points[0].Y = (float_)xMin; points[1].X = 0; - points[1].Y = xMax; + points[1].Y = (float_)xMax; cri.PlotAreaRendererInfo.Matrix.TransformPoints(points); if (cri.YAxisRendererInfo.MinorGridlinesLineFormat != null) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarStackedPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarStackedPlotAreaRenderer.cs index 733925f4..520af014 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarStackedPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/BarStackedPlotAreaRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -66,10 +69,10 @@ protected override void CalcBars() yMax += y; } - points[0].Y = x0; // top left - points[0].X = y0; - points[1].Y = x1; // bottom right - points[1].X = y1; + points[0].Y = (float_)x0; // top left + points[0].X = (float_)y0; + points[1].Y = (float_)x1; // bottom right + points[1].X = (float_)y1; cri.PlotAreaRendererInfo.Matrix.TransformPoints(points); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ChartRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ChartRenderer.cs index 52e4c9f0..05e1c5b4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ChartRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ChartRenderer.cs @@ -32,28 +32,28 @@ protected XRect LayoutLegend() cri.LegendRendererInfo.X = remainingRect.Left; cri.LegendRendererInfo.Y = remainingRect.Height / 2 - cri.LegendRendererInfo.Height / 2; double width = cri.LegendRendererInfo.Width + LegendSpacing; - remainingRect.X += width; - remainingRect.Width -= width; + remainingRect.X += (float_)width; + remainingRect.Width -= (float_)width; break; case DockingType.Right: cri.LegendRendererInfo.X = remainingRect.Right - cri.LegendRendererInfo.Width; cri.LegendRendererInfo.Y = remainingRect.Height / 2 - cri.LegendRendererInfo.Height / 2; - remainingRect.Width -= cri.LegendRendererInfo.Width + LegendSpacing; + remainingRect.Width -= (float_)(cri.LegendRendererInfo.Width + LegendSpacing); break; case DockingType.Top: cri.LegendRendererInfo.X = remainingRect.Width / 2 - cri.LegendRendererInfo.Width / 2; cri.LegendRendererInfo.Y = remainingRect.Top; double height = cri.LegendRendererInfo.Height + LegendSpacing; - remainingRect.Y += height; - remainingRect.Height -= height; + remainingRect.Y += (float_)height; + remainingRect.Height -= (float_)height; break; case DockingType.Bottom: cri.LegendRendererInfo.X = remainingRect.Width / 2 - cri.LegendRendererInfo.Width / 2; cri.LegendRendererInfo.Y = remainingRect.Bottom - cri.LegendRendererInfo.Height; - remainingRect.Height -= cri.LegendRendererInfo.Height + LegendSpacing; + remainingRect.Height -= (float_)(cri.LegendRendererInfo.Height + LegendSpacing); break; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Colors.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Colors.cs index 3acb583e..937b9955 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Colors.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Colors.cs @@ -14,7 +14,7 @@ sealed class ColumnColors /// Gets the color for column/bar charts from the specified index. /// public static XColor Item(int index) - => XColor.FromArgb((int)SeriesColorValues[index]); + => XColor.FromArgb((uint)SeriesColorValues[index]); /// /// Colors for column/bar charts taken from Excel. @@ -43,7 +43,7 @@ static class LineColors /// Gets the color for line charts from the specified index. /// public static XColor Item(int index) - => XColor.FromArgb((int)LineColorValues[index]); + => XColor.FromArgb((uint)LineColorValues[index]); /// /// Colors for line charts taken from Excel. @@ -72,7 +72,7 @@ static class PieColors /// Gets the color for pie charts from the specified index. /// public static XColor Item(int index) - => XColor.FromArgb((int)SectorColorValues[index]); + => XColor.FromArgb((uint)SectorColorValues[index]); /// /// Colors for pie charts taken from Excel. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnClusteredPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnClusteredPlotAreaRenderer.cs index 22a00301..8b7a6048 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnClusteredPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnClusteredPlotAreaRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -72,10 +75,10 @@ protected override void CalcColumns() (y0, y1) = (y1, y0); } - points[0].X = x0; // upper left - points[0].Y = y1; - points[1].X = x1; // lower right - points[1].Y = y0; + points[0].X = (float_)x0; // upper left + points[0].Y = (float_)y1; + points[1].X = (float_)x1; // lower right + points[1].Y = (float_)y0; cri.PlotAreaRendererInfo.Matrix.TransformPoints(points); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeGridlinesRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeGridlinesRenderer.cs index bcdcde8f..7422eb3c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeGridlinesRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeGridlinesRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -55,10 +58,10 @@ internal override void Draw() lineFormatRenderer = new LineFormatRenderer(gfx, xari.MinorGridlinesLineFormat); for (double x = xMin + xMinorTick; x < xMax; x += xMinorTick) { - points[0].X = x; - points[0].Y = yMin; - points[1].X = x; - points[1].Y = yMax; + points[0].X = (float_)x; + points[0].Y = (float_)yMin; + points[1].X = (float_)x; + points[1].Y = (float_)yMax; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -69,10 +72,10 @@ internal override void Draw() lineFormatRenderer = new LineFormatRenderer(gfx, xari.MajorGridlinesLineFormat); for (double x = xMin; x <= xMax; x += xMajorTick) { - points[0].X = x; - points[0].Y = yMin; - points[1].X = x; - points[1].Y = yMax; + points[0].X = (float_)x; + points[0].Y = (float_)yMin; + points[1].X = (float_)x; + points[1].Y = (float_)yMax; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -83,10 +86,10 @@ internal override void Draw() lineFormatRenderer = new LineFormatRenderer(gfx, yari.MinorGridlinesLineFormat); for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick) { - points[0].X = xMin; - points[0].Y = y; - points[1].X = xMax; - points[1].Y = y; + points[0].X = (float_)xMin; + points[0].Y = (float_)y; + points[1].X = (float_)xMax; + points[1].Y = (float_)y; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -97,10 +100,10 @@ internal override void Draw() lineFormatRenderer = new LineFormatRenderer(gfx, yari.MajorGridlinesLineFormat); for (double y = yMin; y <= yMax; y += yMajorTick) { - points[0].X = xMin; - points[0].Y = y; - points[1].X = xMax; - points[1].Y = y; + points[0].X = (float_)xMin; + points[0].Y = (float_)y; + points[1].X = (float_)xMax; + points[1].Y = (float_)y; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeLegendRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeLegendRenderer.cs index 3d85779b..78013535 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeLegendRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikeLegendRenderer.cs @@ -32,10 +32,10 @@ internal override RendererInfo Init() lri.Legend = cri.Chart._legend; lri.Font = Converter.ToXFont(lri.Legend._font, cri.DefaultFont); - lri.FontColor = new XSolidBrush(XColors.Black); + lri.FontColor = XBrushes.Black; if (lri.Legend._lineFormat != null) - lri.BorderPen = Converter.ToXPen(lri.Legend._lineFormat, XColors.Black, DefaultLineWidth, XDashStyle.Solid); + lri.BorderPen = Converter.ToXPen(lri.Legend._lineFormat, XColors.Black, DefaultLineWidth/*, XDashStyle.Solid*/); lri.Entries = new LegendEntryRendererInfo[cri.SeriesRendererInfos.Length]; int index = 0; @@ -49,7 +49,7 @@ internal override RendererInfo Init() }; if (sri.MarkerRendererInfo != null!) { - leri.MarkerSize.Width = leri.MarkerSize.Height = sri.MarkerRendererInfo.MarkerSize.Point; + leri.MarkerSize.Width = leri.MarkerSize.Height =(float_) sri.MarkerRendererInfo.MarkerSize.Point; leri.MarkerPen = new XPen(sri.MarkerRendererInfo.MarkerForegroundColor); leri.MarkerBrush = new XSolidBrush(sri.MarkerRendererInfo.MarkerBackgroundColor); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikePlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikePlotAreaRenderer.cs index 2f2491be..27faeaf7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikePlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnLikePlotAreaRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -32,10 +35,12 @@ internal override void Format() XRect plotAreaBox = cri.PlotAreaRendererInfo.Rect; cri.PlotAreaRendererInfo.Matrix = new XMatrix(); - cri.PlotAreaRendererInfo.Matrix.TranslatePrepend(-xMin, yMax); - cri.PlotAreaRendererInfo.Matrix.Scale(plotAreaBox.Width / xMax, plotAreaBox.Height / (yMax - yMin), XMatrixOrder.Append); + cri.PlotAreaRendererInfo.Matrix.TranslatePrepend((float_)(-xMin), (float_)yMax); + //cri.PlotAreaRendererInfo.Matrix.Scale(plotAreaBox.Width / xMax, plotAreaBox.Height / (yMax - yMin), XMatrixOrder.Append); + cri.PlotAreaRendererInfo.Matrix.ScaleAppend((float_)(plotAreaBox.Width / xMax), (float_)(plotAreaBox.Height / (yMax - yMin))); cri.PlotAreaRendererInfo.Matrix.ScalePrepend(1, -1); - cri.PlotAreaRendererInfo.Matrix.Translate(plotAreaBox.X, plotAreaBox.Y, XMatrixOrder.Append); + //cri.PlotAreaRendererInfo.Matrix.Translate(plotAreaBox.X, plotAreaBox.Y, XMatrixOrder.Append); + cri.PlotAreaRendererInfo.Matrix.TranslateAppend(plotAreaBox.X, plotAreaBox.Y); } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnPlotAreaRenderer.cs index 27d6d7df..cd080bce 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnPlotAreaRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -55,9 +58,9 @@ internal override void Draw() if (yMin < 0 && yMax > 0) { var points = new XPoint[2]; - points[0].X = xMin; + points[0].X = (float_)xMin; points[0].Y = 0; - points[1].X = xMax; + points[1].X = (float_)xMax; points[1].Y = 0; cri.PlotAreaRendererInfo.Matrix.TransformPoints(points); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnStackedPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnStackedPlotAreaRenderer.cs index 5e9652e1..e3e781d4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnStackedPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/ColumnStackedPlotAreaRenderer.cs @@ -2,7 +2,11 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Diagnostics; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#else using PdfSharp.Drawing; +#endif namespace PdfSharp.Charting.Renderers { @@ -69,10 +73,10 @@ protected override void CalcColumns() yMax += y; } - points[0].X = x0; // upper left - points[0].Y = y1; - points[1].X = x1; // lower right - points[1].Y = y0; + points[0].X = (float_)x0; // upper left + points[0].Y = (float_)y1; + points[1].X = (float_)x1; // lower right + points[1].Y = (float_)y0; cri.PlotAreaRendererInfo.Matrix.TransformPoints(points); @@ -89,7 +93,7 @@ protected override void CalcColumns() /// /// Stacked columns are always inside. /// - protected override bool IsDataInside(double yMin, double yMax, double yValue) + protected override bool IsDataInside(double yMin, double yMax, double yValue) => true; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Converter.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Converter.cs index de79ad77..e5faf7c0 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Converter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/Converter.cs @@ -17,6 +17,9 @@ static class Converter /// internal static XFont ToXFont(Font? font, XFont defaultFont) { +#if PSGFX + return null!; +#else var xFont = defaultFont; if (font != null) { @@ -37,6 +40,7 @@ internal static XFont ToXFont(Font? font, XFont defaultFont) xFont = new XFont(fontFamily, size, fontStyle); } return xFont; +#endif } /// @@ -46,13 +50,30 @@ internal static XFont ToXFont(Font? font, XFont defaultFont) internal static XPen ToXPen(LineFormat? lineFormat, XPen defaultPen) => ToXPen(lineFormat, defaultPen.Color, defaultPen.Width, defaultPen.DashStyle); + internal static XPen ToXPen(LineFormat? lineFormat, XColor defaultColor, double defaultWidth) + { + return ToXPen(lineFormat, defaultColor, defaultWidth, +#if PSGFX + XDashStyles.Solid); +#else + XDashStyle.Solid); +#endif + } + /// /// Creates a XPen based on the specified line format. If not specified color, width and dash style /// will be taken from the defaultColor, defaultWidth and defaultDashStyle parameters. /// internal static XPen ToXPen(LineFormat? lineFormat, XColor defaultColor, double defaultWidth, - XDashStyle defaultDashStyle = XDashStyle.Solid) +#if PSGFX + XDashStyle? defaultDashStyle) +#else + XDashStyle defaultDashStyle ) +#endif { +#if PSGFX + return XPens.Black; +#else XPen pen; if (lineFormat == null) { @@ -75,11 +96,16 @@ internal static XPen ToXPen(LineFormat? lineFormat, XColor defaultColor, double pen = new XPen(color, width) { +#if PSGFX + DashStyle = lineFormat.DashStyle, +#else DashStyle = lineFormat.DashStyle, DashOffset = 10 * width +#endif }; } return pen; +#endif } /// @@ -88,9 +114,13 @@ internal static XPen ToXPen(LineFormat? lineFormat, XColor defaultColor, double /// internal static XBrush ToXBrush(FillFormat? fillFormat, XColor defaultColor) { +#if PSGFX + return XBrushes.Black; +#else if (fillFormat == null || fillFormat.Color.IsEmpty) return new XSolidBrush(defaultColor); return new XSolidBrush(fillFormat.Color); +#endif } /// @@ -99,9 +129,13 @@ internal static XBrush ToXBrush(FillFormat? fillFormat, XColor defaultColor) /// internal static XBrush ToXBrush(Font? font, XColor defaultColor) { +#if PSGFX + return XBrushes.Black; +#else if (font == null || font.Color.IsEmpty) return new XSolidBrush(defaultColor); return new XSolidBrush(font.Color); +#endif } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/DataLabelRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/DataLabelRenderer.cs index 83db0c3f..d6f3d058 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/DataLabelRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/DataLabelRenderer.cs @@ -38,7 +38,7 @@ internal override RendererInfo Init() { dlri.Format = "0"; dlri.Font = cri.DefaultDataLabelFont; - dlri.FontColor = new XSolidBrush(XColors.Black); + dlri.FontColor = XBrushes.Black; dlri.Position = DataLabelPosition.InsideEnd; if (cri.Chart._type == ChartType.Pie2D || cri.Chart._type == ChartType.PieExploded2D) dlri.Type = DataLabelType.Percent; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalXAxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalXAxisRenderer.cs index 9a046b46..59c7508b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalXAxisRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalXAxisRenderer.cs @@ -110,9 +110,9 @@ internal override void Draw() tickLabelStep = xari.Width / countTickLabels; //XPoint startPos = new XPoint(xari.X + tickLabelStep / 2, xari.Y + /*xari.TickLabelsHeight +*/ xari.MajorTickMarkWidth); - XPoint startPos = new XPoint(xari.X + tickLabelStep / 2, xari.Y + xari.TickLabelsHeight); + XPoint startPos = new XPoint((float_)(xari.X + tickLabelStep / 2), (float_)(xari.Y + xari.TickLabelsHeight)); if (xari.MajorTickMark != TickMarkType.None) - startPos.Y += xari.MajorTickMarkWidth; + startPos.Y += (float_)xari.MajorTickMarkWidth; foreach (var xs in (xari.XValues ?? throw new InvalidOperationException()).Cast()) // BUG_OLD??? { for (int idx = 0; idx < countTickLabels && idx < xs.Count; idx++) @@ -124,7 +124,7 @@ internal override void Draw() XSize size = gfx.MeasureString(tickLabel, xari.TickLabelsFont); gfx.DrawString(tickLabel, xari.TickLabelsFont, xari.TickLabelsBrush, startPos.X - size.Width / 2, startPos.Y); } - startPos.X += tickLabelStep; + startPos.X += (float_)tickLabelStep; } } @@ -142,13 +142,13 @@ internal override void Draw() { int countMinorTickMarks = (int)(xMax / xMinorTick); double minorTickMarkStep = xari.Width / countMinorTickMarks; - startPos.X = xari.X; + startPos.X = (float_)xari.X; for (int x = 0; x <= countMinorTickMarks; x++) { - points[0].X = startPos.X + minorTickMarkStep * x; - points[0].Y = minorTickMarkStart; - points[1].X = points[0].X; - points[1].Y = minorTickMarkEnd; + points[0].X = (float_)(startPos.X + minorTickMarkStep * x); + points[0].Y = (float_)minorTickMarkStart; + points[1].X = (float_)points[0].X; + points[1].Y = (float_)minorTickMarkEnd; lineFormatRenderer.DrawLine(points[0], points[1]); } } @@ -160,13 +160,13 @@ internal override void Draw() double majorTickMarkStep = xari.Width; if (countMajorTickMarks != 0) majorTickMarkStep = xari.Width / countMajorTickMarks; - startPos.X = xari.X; + startPos.X = (float_)xari.X; for (int x = 0; x <= countMajorTickMarks; x++) { - points[0].X = startPos.X + majorTickMarkStep * x; - points[0].Y = majorTickMarkStart; - points[1].X = points[0].X; - points[1].Y = majorTickMarkEnd; + points[0].X = (float_)(startPos.X + majorTickMarkStep * x); + points[0].Y = (float_)majorTickMarkStart; + points[1].X = (float_)points[0].X; + points[1].Y = (float_)majorTickMarkEnd; lineFormatRenderer.DrawLine(points[0], points[1]); } } @@ -174,10 +174,10 @@ internal override void Draw() // Axis. if (xari.LineFormat != null) { - points[0].X = xari.X; - points[0].Y = xari.Y; - points[1].X = xari.X + xari.Width; - points[1].Y = xari.Y; + points[0].X = (float_)xari.X; + points[0].Y = (float_)xari.Y; + points[1].X = (float_)(xari.X + xari.Width); + points[1].Y = (float_)xari.Y; if (xari.MajorTickMark != TickMarkType.None) { points[0].X -= xari.LineFormat.Width / 2; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalYAxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalYAxisRenderer.cs index 1db1392f..14da5cdb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalYAxisRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/HorizontalYAxisRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -69,7 +72,7 @@ internal override void Format() } // Add space for tick marks. - size.Height += yari.MajorTickMarkWidth * 1.5; + size.Height += (float_)(yari.MajorTickMarkWidth * 1.5); // Measure axis title. XSize titleSize = new XSize(0, 0); @@ -80,8 +83,8 @@ internal override void Format() parms.RendererInfo = yari; var atr = new AxisTitleRenderer(parms); atr.Format(); - titleSize.Height = yari.AxisTitleRendererInfo.Height; - titleSize.Width = yari.AxisTitleRendererInfo.Width; + titleSize.Height = (float_)yari.AxisTitleRendererInfo.Height; + titleSize.Width = (float_)yari.AxisTitleRendererInfo.Width; } yari.Height = size.Height + titleSize.Height; @@ -107,9 +110,11 @@ internal override void Draw() double yMinorTick = yari.MinorTick; XMatrix matrix = new XMatrix(); - matrix.TranslatePrepend(-yMin, -yari.Y); - matrix.Scale(yari.InnerRect.Width / (yMax - yMin), 1, XMatrixOrder.Append); - matrix.Translate(yari.X, yari.Y, XMatrixOrder.Append); + matrix.TranslatePrepend((float_)(-yMin), (float_)(-yari.Y)); + //matrix.Scale(yari.InnerRect.Width / (yMax - yMin), 1, XMatrixOrder.Append); + matrix.ScaleAppend((float_)(yari.InnerRect.Width / (yMax - yMin)), 1); + //matrix.Translate(yari.X, yari.Y, XMatrixOrder.Append); + matrix.TranslateAppend((float_)yari.X, (float_)yari.Y); // Draw axis. // First draw tick marks, second draw axis. @@ -124,10 +129,10 @@ internal override void Draw() { for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick) { - points[0].X = y; - points[0].Y = minorTickMarkStart; - points[1].X = y; - points[1].Y = minorTickMarkEnd; + points[0].X = (float_)y; + points[0].Y = (float_)minorTickMarkStart; + points[1].X = (float_)y; + points[1].Y = (float_)minorTickMarkEnd; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -144,18 +149,18 @@ internal override void Draw() XSize labelSize = gfx.MeasureString(str, yari.TickLabelsFont); if (yari.MajorTickMark != TickMarkType.None) { - labelSize.Height += 1.5f * yari.MajorTickMarkWidth; - points[0].X = y; - points[0].Y = majorTickMarkStart; - points[1].X = y; - points[1].Y = majorTickMarkEnd; + labelSize.Height += 1.5f * (float_)yari.MajorTickMarkWidth; + points[0].X = (float_)y; + points[0].Y = (float_)majorTickMarkStart; + points[1].X = (float_)y; + points[1].Y = (float_)majorTickMarkEnd; matrix.TransformPoints(points); lineFormatRenderer.DrawLine(points[0], points[1]); } XPoint[] layoutText = new XPoint[1]; - layoutText[0].X = y; - layoutText[0].Y = yari.Y + 1.5 * yari.MajorTickMarkWidth; + layoutText[0].X = (float_)y; + layoutText[0].Y = (float_)(yari.Y + 1.5 * yari.MajorTickMarkWidth); matrix.TransformPoints(layoutText); layoutText[0].X -= labelSize.Width / 2; // Center text vertically. gfx.DrawString(str, yari.TickLabelsFont, yari.TickLabelsBrush, layoutText[0], xsf); @@ -163,10 +168,10 @@ internal override void Draw() if (yari.LineFormat != null) { - points[0].X = yMin; - points[0].Y = yari.Y; - points[1].X = yMax; - points[1].Y = yari.Y; + points[0].X = (float_)yMin; + points[0].Y = (float_)yari.Y; + points[1].X = (float_)yMax; + points[1].Y = (float_)yari.Y; matrix.TransformPoints(points); if (yari.MajorTickMark != TickMarkType.None) { @@ -184,7 +189,7 @@ internal override void Draw() parms.Graphics = gfx; parms.RendererInfo = yari; XRect rcTitle = yari.Rect; - rcTitle.Height = yari.AxisTitleRendererInfo.Height; + rcTitle.Height = (float_)yari.AxisTitleRendererInfo.Height; rcTitle.Y += yari.Rect.Height - rcTitle.Height; yari.AxisTitleRendererInfo.Rect = rcTitle; AxisTitleRenderer atr = new AxisTitleRenderer(parms); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendEntryRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendEntryRenderer.cs index be7cd128..b933016c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendEntryRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendEntryRenderer.cs @@ -26,8 +26,8 @@ internal override void Format() var leri = (LegendEntryRendererInfo)_rendererParms.RendererInfo; // Initialize - leri.MarkerArea.Width = MaxLegendMarkerWidth; - leri.MarkerArea.Height = MaxLegendMarkerHeight; + leri.MarkerArea.Width = (float_)MaxLegendMarkerWidth; + leri.MarkerArea.Height = (float_)MaxLegendMarkerHeight; leri.MarkerSize = new XSize(); leri.MarkerSize.Width = leri.MarkerArea.Width; leri.MarkerSize.Height = leri.MarkerArea.Height; @@ -41,7 +41,7 @@ internal override void Format() leri.TextSize = gfx.MeasureString(leri.EntryText, leri.LegendRendererInfo.Font); if (leri.SeriesRendererInfo.Series._chartType == ChartType.Line) { - leri.MarkerSize.Width = leri.SeriesRendererInfo.MarkerRendererInfo.MarkerSize.Value; + leri.MarkerSize.Width = (float_)leri.SeriesRendererInfo.MarkerRendererInfo.MarkerSize.Value; leri.MarkerArea.Width = Math.Max(3 * leri.MarkerSize.Width, leri.MarkerArea.Width); } @@ -64,20 +64,20 @@ internal override void Draw() if (leri.SeriesRendererInfo.Series._chartType == ChartType.Line) { // Draw line. - XPoint posLineStart = new XPoint(leri.X, leri.Y + leri.Height / 2); - XPoint posLineEnd = new XPoint(leri.X + leri.MarkerArea.Width, leri.Y + leri.Height / 2); + XPoint posLineStart = new XPoint((float_)leri.X, (float_)(leri.Y + leri.Height / 2)); + XPoint posLineEnd = new XPoint((float_)(leri.X + leri.MarkerArea.Width), (float_)(leri.Y + leri.Height / 2)); gfx.DrawLine(new XPen(((XSolidBrush)leri.MarkerBrush).Color), posLineStart, posLineEnd); // Draw marker. double x = leri.X + leri.MarkerArea.Width / 2; - XPoint posMarker = new XPoint(x, leri.Y + leri.Height / 2); + XPoint posMarker = new XPoint((float_)x, (float_)(leri.Y + leri.Height / 2)); MarkerRenderer.Draw(gfx, posMarker, leri.SeriesRendererInfo.MarkerRendererInfo); } else { // Draw series rectangle for column, bar or pie charts. - rect = new XRect(leri.X, leri.Y, leri.MarkerArea.Width, leri.MarkerArea.Height); - rect.Y += (leri.Height - leri.MarkerArea.Height) / 2; + rect = new XRect((float_)leri.X, (float_)leri.Y, leri.MarkerArea.Width, leri.MarkerArea.Height); + rect.Y += (float_)((leri.Height - leri.MarkerArea.Height) / 2); gfx.DrawRectangle(leri.MarkerPen, leri.MarkerBrush, rect); } @@ -85,7 +85,7 @@ internal override void Draw() if (leri.EntryText.Length > 0) { rect = leri.Rect; - rect.X += leri.MarkerArea.Width + LegendEntryRenderer.SpacingBetweenMarkerAndText; + rect.X += (float_)(leri.MarkerArea.Width + LegendEntryRenderer.SpacingBetweenMarkerAndText); XStringFormat format = new XStringFormat(); format.LineAlignment = XLineAlignment.Near; gfx.DrawString(leri.EntryText, leri.LegendRendererInfo.Font, diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendRenderer.cs index e6276d9a..f33559dc 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LegendRenderer.cs @@ -87,27 +87,27 @@ internal override void Draw() bool verticalLegend = (lri.Legend._docking == DockingType.Left || lri.Legend._docking == DockingType.Right); int paddingFactor = 1; - if (lri.BorderPen != null) + if (lri.BorderPen != null!) paddingFactor = 2; XRect legendRect = lri.Rect; - legendRect.X += LegendRenderer.LeftPadding * paddingFactor; - legendRect.Y += LegendRenderer.TopPadding * paddingFactor; + legendRect.X += (float_)(LegendRenderer.LeftPadding * paddingFactor); + legendRect.Y += (float_)(LegendRenderer.TopPadding * paddingFactor); if (cri.LegendRendererInfo != null) { foreach (var leri in cri.LegendRendererInfo.Entries) { XRect entryRect = legendRect; - entryRect.Width = leri.Width; - entryRect.Height = leri.Height; + entryRect.Width = (float_)leri.Width; + entryRect.Height = (float_)leri.Height; leri.Rect = entryRect; parms.RendererInfo = leri; ler.Draw(); if (verticalLegend) - legendRect.Y += entryRect.Height + LegendRenderer.EntrySpacing; + legendRect.Y += (float_)(entryRect.Height + LegendRenderer.EntrySpacing); else - legendRect.X += entryRect.Width + LegendRenderer.EntrySpacing; + legendRect.X += (float_)(entryRect.Width + LegendRenderer.EntrySpacing); } } @@ -115,10 +115,10 @@ internal override void Draw() if (lri.BorderPen != null) { XRect borderRect = lri.Rect; - borderRect.X += LegendRenderer.LeftPadding; - borderRect.Y += LegendRenderer.TopPadding; - borderRect.Width -= LegendRenderer.LeftPadding + LegendRenderer.RightPadding; - borderRect.Height -= LegendRenderer.TopPadding + LegendRenderer.BottomPadding; + borderRect.X += (float_)LegendRenderer.LeftPadding; + borderRect.Y += (float_)LegendRenderer.TopPadding; + borderRect.Width -= (float_)(LegendRenderer.LeftPadding + LegendRenderer.RightPadding); + borderRect.Height -= (float_)(LegendRenderer.TopPadding + LegendRenderer.BottomPadding); gfx.DrawRectangle(lri.BorderPen, borderRect); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineChartRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineChartRenderer.cs index 60ab3441..56f9be91 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineChartRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineChartRenderer.cs @@ -1,7 +1,11 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +#if PSGFX +using PdfSharp.Graphics.Media; +#else using PdfSharp.Drawing; +#endif namespace PdfSharp.Charting.Renderers { @@ -139,8 +143,11 @@ internal void InitSeries() sri.LineFormat = Converter.ToXPen(sri.Series._lineFormat, LineColors.Item(seriesIndex), ChartRenderer.DefaultSeriesLineWidth); else sri.LineFormat = Converter.ToXPen(sri.Series._lineFormat, sri.Series.MarkerBackgroundColor, ChartRenderer.DefaultSeriesLineWidth); +#if PSGFX + sri.LineFormat.LineJoin = PenLineJoin.Bevel; +#else sri.LineFormat.LineJoin = XLineJoin.Bevel; - +#endif MarkerRendererInfo mri = new MarkerRendererInfo(); sri.MarkerRendererInfo = mri; @@ -154,7 +161,7 @@ internal void InitSeries() mri.MarkerSize = sri.Series.MarkerSize; if (mri.MarkerSize.Point == 0) - mri.MarkerSize = XUnit.FromPoint(7); + mri.MarkerSize = XUnit.FromPoint(7); // TODO Why not XUnitPt??? if (!sri.Series._markerStyleInitialized) //mri.MarkerStyle = (MarkerStyle)(seriesIndex % (Enum.GetNames(typeof(MarkerStyle)).Length - 1) + 1); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineFormatRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineFormatRenderer.cs index 51578bfa..c6681b32 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineFormatRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LineFormatRenderer.cs @@ -33,7 +33,7 @@ public LineFormatRenderer(XGraphics gfx, LineFormat? lineFormat, double defaultW //if (visible) if (visible && lineFormat != null) { - _pen = new XPen(lineFormat.Color, width) + _pen = new XPen(lineFormat.Color,(float_) width) { DashStyle = lineFormat.DashStyle }; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LinePlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LinePlotAreaRenderer.cs index e61c0f5e..67a28c14 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LinePlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/LinePlotAreaRenderer.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -50,14 +53,14 @@ internal override void Draw() double v = sri.Series.Elements[idx].Value; if (Double.IsNaN(v) /*&& cri.Chart.DisplayBlanksAs == BlankType.Zero*/) // TODO_OLD DisplayBlanksAs v = 0; - points[idx] = new XPoint(idx + xMajorTick / 2, v); + points[idx] = new XPoint((float_)(idx + xMajorTick / 2), (float_)v); } matrix.TransformPoints(points); gfx.DrawLines(sri.LineFormat, points); DrawMarker(gfx, points, sri); } - + //gfx.ResetClip(); } gfx.Restore(state); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/MarkerRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/MarkerRenderer.cs index 167a0028..95f7f08a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/MarkerRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/MarkerRenderer.cs @@ -24,7 +24,7 @@ internal static void Draw(XGraphics graphics, XPoint pos, MarkerRendererInfo ren double x0, y0, x1, y1; double g; - var foreground = new XPen(rendererInfo.MarkerForegroundColor, 0.5); + var foreground = new XPen(rendererInfo.MarkerForegroundColor, 0.5f); var background = new XSolidBrush(rendererInfo.MarkerBackgroundColor); var gp = new XGraphicsPath(); @@ -117,8 +117,8 @@ internal static void Draw(XGraphics graphics, XPoint pos, MarkerRendererInfo ren double rad = -(Math.PI / 2); // 90° for (int idx = 0; idx < 10; idx += 2) { - points[idx].X = pos.X + outerCircle * Math.Cos(rad); - points[idx].Y = pos.Y + outerCircle * Math.Sin(rad); + points[idx].X = (float_)(pos.X + outerCircle * Math.Cos(rad)); + points[idx].Y = (float_)(pos.Y + outerCircle * Math.Sin(rad)); rad += radStep; } @@ -126,27 +126,33 @@ internal static void Draw(XGraphics graphics, XPoint pos, MarkerRendererInfo ren rad = -(Math.PI / 4); // 45° double x = innerCircle * Math.Cos(rad); double y = innerCircle * Math.Sin(rad); - points[1].X = pos.X + x; - points[1].Y = pos.Y + y; - points[9].X = pos.X - x; - points[9].Y = pos.Y + y; + points[1].X = (float_)(pos.X + x); + points[1].Y = (float_)(pos.Y + y); + points[9].X = (float_)(pos.X - x); + points[9].Y = (float_)(pos.Y + y); rad += radStep; x = innerCircle * Math.Cos(rad); y = innerCircle * Math.Sin(rad); - points[3].X = pos.X + x; - points[3].Y = pos.Y + y; - points[7].X = pos.X - x; - points[7].Y = pos.Y + y; + points[3].X = (float_)(pos.X + x); + points[3].Y = (float_)(pos.Y + y); + points[7].X = (float_)(pos.X - x); + points[7].Y = (float_)(pos.Y + y); rad += radStep; y = innerCircle * Math.Sin(rad); points[5].X = pos.X; - points[5].Y = pos.Y + y; + points[5].Y = (float_)(pos.Y + y); +#if PSGFX + throw new NotImplementedException("AddLines"); + //gp.AddLines(points); + } +#else gp.AddLines(points); } break; +#endif } - gp.CloseFigure(); + if (rendererInfo.MarkerStyle != MarkerStyle.Dot) { graphics.DrawPath(background, gp); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieClosedPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieClosedPlotAreaRenderer.cs index 717ff666..0cde816a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieClosedPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieClosedPlotAreaRenderer.cs @@ -45,10 +45,10 @@ protected override void CalcSectors() XRect pieRect = cri.PlotAreaRendererInfo.Rect; if (textMeasure != 0) { - pieRect.X += textMeasure; - pieRect.Y += textMeasure; - pieRect.Width -= 2 * textMeasure; - pieRect.Height -= 2 * textMeasure; + pieRect.X += (float_)textMeasure; + pieRect.Y += (float_)textMeasure; + pieRect.Width -= (float_)(2 * textMeasure); + pieRect.Height -= (float_)(2 * textMeasure); } double startAngle = 270; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieExplodedPlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieExplodedPlotAreaRenderer.cs index 87862276..3363cd90 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieExplodedPlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieExplodedPlotAreaRenderer.cs @@ -45,10 +45,10 @@ protected override void CalcSectors() XRect pieRect = cri.PlotAreaRendererInfo.Rect; if (textMeasure != 0) { - pieRect.X += textMeasure; - pieRect.Y += textMeasure; - pieRect.Width -= 2 * textMeasure; - pieRect.Height -= 2 * textMeasure; + pieRect.X += (float_)textMeasure; + pieRect.Y += (float_)textMeasure; + pieRect.Width -= (float_)(2 * textMeasure); + pieRect.Height -= (float_)(2 * textMeasure); } XPoint origin = new XPoint(pieRect.X + pieRect.Width / 2, pieRect.Y + pieRect.Height / 2); @@ -74,11 +74,11 @@ protected override void CalcSectors() sectorStartAngle = Math.Max(0, startAngle + deltaAngle); sectorSweepAngle = Math.Max(sweepAngle, sweepAngle - deltaAngle); - p1.X = origin.X + rInnerCircle * Math.Cos(midAngle / 180 * Math.PI); - p1.Y = origin.Y + rInnerCircle * Math.Sin(midAngle / 180 * Math.PI); - innerRect.X = p1.X - rOuterCircle + rInnerCircle; - innerRect.Y = p1.Y - rOuterCircle + rInnerCircle; - innerRect.Width = (rOuterCircle - rInnerCircle) * 2; + p1.X = (float_)(origin.X + rInnerCircle * Math.Cos(midAngle / 180 * Math.PI)); + p1.Y = (float_)(origin.Y + rInnerCircle * Math.Sin(midAngle / 180 * Math.PI)); + innerRect.X = (float_)(p1.X - rOuterCircle + rInnerCircle); + innerRect.Y = (float_)(p1.Y - rOuterCircle + rInnerCircle); + innerRect.Width = (float_)((rOuterCircle - rInnerCircle) * 2); innerRect.Height = innerRect.Width; sector.Rect = innerRect; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieLegendRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieLegendRenderer.cs index f62893fc..982481d9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieLegendRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PieLegendRenderer.cs @@ -35,10 +35,15 @@ internal PieLegendRenderer(RendererParameters parms) : base(parms) }; lri.Font = Converter.ToXFont(lri.Legend._font, cri.DefaultFont); - lri.FontColor = new XSolidBrush(XColors.Black); + lri.FontColor = XBrushes.Black; if (lri.Legend._lineFormat != null) - lri.BorderPen = Converter.ToXPen(lri.Legend._lineFormat, XColors.Black, DefaultLineWidth, XDashStyle.Solid); + lri.BorderPen = Converter.ToXPen(lri.Legend._lineFormat, XColors.Black, DefaultLineWidth, +#if PSGFX + XDashStyles.Solid); +#else + XDashStyle.Solid); +#endif XSeries? xseries = null; if (cri.Chart._xValues != null) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PiePlotAreaRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PiePlotAreaRenderer.cs index 9e4a2de9..0ccafc90 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PiePlotAreaRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/PiePlotAreaRenderer.cs @@ -20,7 +20,7 @@ internal PiePlotAreaRenderer(RendererParameters parms) : base(parms) /// /// Layouts and calculates the space used by the pie plot area. /// - internal override void Format() + internal override void Format() => CalcSectors(); /// @@ -44,14 +44,14 @@ internal override void Draw() foreach (SectorRendererInfo sector in sri.PointRendererInfos) { if (!Double.IsNaN(sector.StartAngle) && !Double.IsNaN(sector.SweepAngle)) - gfx.DrawPie(sector.FillFormat, sector.Rect, sector.StartAngle, sector.SweepAngle); + gfx.DrawPie(sector.FillFormat, sector.Rect, (float_)sector.StartAngle, (float_)sector.SweepAngle); } // Draw border of the sectors. foreach (SectorRendererInfo sector in sri.PointRendererInfos) { if (!Double.IsNaN(sector.StartAngle) && !Double.IsNaN(sector.SweepAngle)) - gfx.DrawPie(sector.LineFormat, sector.Rect, sector.StartAngle, sector.SweepAngle); + gfx.DrawPie(sector.LineFormat, sector.Rect, (float_)sector.StartAngle, (float_)sector.SweepAngle); } gfx.Restore(state); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererInfo.cs index ab22415a..4611b079 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererInfo.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererInfo.cs @@ -7,11 +7,16 @@ namespace PdfSharp.Charting.Renderers { /// /// Represents the base class of all renderer infos. - /// Renderer infos are used to hold all necessary information and time consuming calculations + /// Renderer infos are used to hold all necessary information and time-consuming calculations /// between rendering cycles. /// abstract class RendererInfo - { } + { + void Foo() + { + var x = new XFont("xxx", 10); + } + } /// /// Base class for all renderer infos which defines an area. @@ -24,7 +29,7 @@ abstract class AreaRendererInfo : RendererInfo public virtual double X { get => _rect.X; - set => _rect.X = value; + set => _rect.X = (float_)value; } /// @@ -33,7 +38,7 @@ public virtual double X public virtual double Y { get => _rect.Y; - set => _rect.Y = value; + set => _rect.Y = (float_)value; } /// @@ -42,7 +47,7 @@ public virtual double Y public virtual double Width { get => _rect.Width; - set => _rect.Width = value; + set => _rect.Width = (float_)value; } /// @@ -51,7 +56,7 @@ public virtual double Width public virtual double Height { get => _rect.Height; - set => _rect.Height = value; + set => _rect.Height = (float_)value; } /// @@ -298,7 +303,7 @@ public override double X set { base.X = value; - InnerRect.X = value; + InnerRect.X = (float_)value; } } @@ -310,7 +315,7 @@ public override double Y set { base.Y = value; - InnerRect.Y = value + LabelSize.Height / 2; + InnerRect.Y = (float_)(value + LabelSize.Height / 2); } } @@ -322,7 +327,7 @@ public override double Height set { base.Height = value; - InnerRect.Height = value - (InnerRect.Y - Y); + InnerRect.Height = (float_)(value - (InnerRect.Y - Y)); } } @@ -334,7 +339,7 @@ public override double Width set { base.Width = value; - InnerRect.Width = value - LabelSize.Width / 2; + InnerRect.Width = (float_)(value - LabelSize.Width / 2); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererParameters.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererParameters.cs index 095e98e2..eda7e7bf 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererParameters.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/RendererParameters.cs @@ -23,7 +23,7 @@ public RendererParameters() public RendererParameters(XGraphics gfx, double x, double y, double width, double height) { Graphics = gfx; - Box = new XRect(x, y, width, height); + Box = new XRect((float_)x, (float_)y, (float_)width, (float_)height); } /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalXAxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalXAxisRenderer.cs index 89fd7c4e..270dfc84 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalXAxisRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalXAxisRenderer.cs @@ -102,7 +102,7 @@ internal override void Draw() // Draw tick labels. Each tick label will be aligned centered. int countTickLabels = (int)xMax; double tickLabelStep = xari.Height / countTickLabels; - XPoint startPos = new XPoint(xari.X + xari.Width - xari.MajorTickMarkWidth, xari.Y + tickLabelStep / 2); + XPoint startPos = new XPoint((float_)(xari.X + xari.Width - xari.MajorTickMarkWidth), (float_)(xari.Y + tickLabelStep / 2)); if (xari.XValues != null) { foreach (XSeries xs in xari.XValues) @@ -113,7 +113,7 @@ internal override void Draw() string tickLabel = xv.ValueField; var size = gfx.MeasureString(tickLabel, xari.TickLabelsFont); gfx.DrawString(tickLabel, xari.TickLabelsFont, xari.TickLabelsBrush, startPos.X - size.Width, startPos.Y + size.Height / 2); - startPos.Y += tickLabelStep; + startPos.Y += (float_)tickLabelStep; } } } @@ -132,12 +132,12 @@ internal override void Draw() { int countMinorTickMarks = (int)(xMax / xMinorTick); double minorTickMarkStep = xari.Height / countMinorTickMarks; - startPos.Y = xari.Y; + startPos.Y = (float_)xari.Y; for (int x = 0; x <= countMinorTickMarks; x++) { - points[0].X = minorTickMarkStart; - points[0].Y = startPos.Y + minorTickMarkStep * x; - points[1].X = minorTickMarkEnd; + points[0].X = (float_)minorTickMarkStart; + points[0].Y = (float_)(startPos.Y + minorTickMarkStep * x); + points[1].X = (float_)minorTickMarkEnd; points[1].Y = points[0].Y; lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -148,12 +148,12 @@ internal override void Draw() { int countMajorTickMarks = (int)(xMax / xMajorTick); double majorTickMarkStep = xari.Height / countMajorTickMarks; - startPos.Y = xari.Y; + startPos.Y = (float_)xari.Y; for (int x = 0; x <= countMajorTickMarks; x++) { - points[0].X = majorTickMarkStart; - points[0].Y = startPos.Y + majorTickMarkStep * x; - points[1].X = majorTickMarkEnd; + points[0].X = (float_)(majorTickMarkStart); + points[0].Y = (float_)(startPos.Y + majorTickMarkStep * x); + points[1].X = (float_)(majorTickMarkEnd); points[1].Y = points[0].Y; lineFormatRenderer.DrawLine(points[0], points[1]); } @@ -162,10 +162,10 @@ internal override void Draw() // Axis. if (xari.LineFormat != null) { - points[0].X = xari.X + xari.Width; - points[0].Y = xari.Y; - points[1].X = xari.X + xari.Width; - points[1].Y = xari.Y + xari.Height; + points[0].X = (float_)(xari.X + xari.Width); + points[0].Y = (float_)(xari.Y); + points[1].X = (float_)(xari.X + xari.Width); + points[1].Y = (float_)(xari.Y + xari.Height); if (xari.MajorTickMark != TickMarkType.None) { points[0].Y -= xari.LineFormat.Width / 2; @@ -178,7 +178,7 @@ internal override void Draw() var atri = xari.AxisTitleRendererInfo; if (atri != null && atri.AxisTitleText != null && atri.AxisTitleText.Length > 0) { - XRect rect = new XRect(xari.X, xari.Y + xari.Height / 2, atri.AxisTitleSize.Width, 0); + var rect = new XRect((float_)xari.X, (float_)(xari.Y + xari.Height / 2), atri.AxisTitleSize.Width, 0); gfx.DrawString(atri.AxisTitleText, atri.AxisTitleFont, atri.AxisTitleBrush, rect); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalYAxisRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalYAxisRenderer.cs index 89b3f0ba..a8450e54 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalYAxisRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting.Renderers/VerticalYAxisRenderer.cs @@ -3,6 +3,9 @@ using PdfSharp.Diagnostics; using PdfSharp.Drawing; +#if PSGFX +using PdfSharp.Graphics.Media.MatrixExtensions; +#endif namespace PdfSharp.Charting.Renderers { @@ -19,7 +22,7 @@ internal VerticalYAxisRenderer(RendererParameters parms) : base(parms) { } /// - /// Returns a initialized rendererInfo based on the Y axis. + /// Returns an initialized rendererInfo based on the Y axis. /// internal override RendererInfo Init() { @@ -70,26 +73,26 @@ internal override void Format() } // add space for tickmarks - size.Width += yari.MajorTickMarkWidth * 1.5; + size.Width += (float_)(yari.MajorTickMarkWidth * 1.5); // Measure axis title XSize titleSize = new XSize(0, 0); - if (yari.AxisTitleRendererInfo != null) + if (yari.AxisTitleRendererInfo != null!) { RendererParameters parms = new RendererParameters(); parms.Graphics = gfx; parms.RendererInfo = yari; AxisTitleRenderer atr = new AxisTitleRenderer(parms); atr.Format(); - titleSize.Height = yari.AxisTitleRendererInfo.Height; - titleSize.Width = yari.AxisTitleRendererInfo.Width; + titleSize.Height = (float_)yari.AxisTitleRendererInfo.Height; + titleSize.Width = (float_)yari.AxisTitleRendererInfo.Width; } yari.Height = Math.Max(size.Height, titleSize.Height); yari.Width = size.Width + titleSize.Width; yari.InnerRect = yari.Rect; - yari.InnerRect.Y += yari.TickLabelsFont.Height / 2; + yari.InnerRect.Y += (float_)(yari.TickLabelsFont.Height / 2); yari.LabelSize = labelSize; } } @@ -100,7 +103,7 @@ internal override void Format() internal override void Draw() { var yari = ((ChartRendererInfo)_rendererParms.RendererInfo).YAxisRendererInfo; - if (yari == null) + if (yari == null) return; double yMin = yari.MinimumScale; @@ -109,10 +112,12 @@ internal override void Draw() double yMinorTick = yari.MinorTick; XMatrix matrix = new XMatrix(); - matrix.TranslatePrepend(-yari.InnerRect.X, yMax); - matrix.Scale(1, yari.InnerRect.Height / (yMax - yMin), XMatrixOrder.Append); + matrix.TranslatePrepend((float_)(-yari.InnerRect.X), (float_)yMax); + //matrix.Scale(1, yari.InnerRect.Height / (yMax - yMin), XMatrixOrder.Append); + matrix.ScaleAppend(1, (float_)(yari.InnerRect.Height / (yMax - yMin))); matrix.ScalePrepend(1, -1); // mirror horizontal - matrix.Translate(yari.InnerRect.X, yari.InnerRect.Y, XMatrixOrder.Append); + //matrix.Translate(yari.InnerRect.X, yari.InnerRect.Y, XMatrixOrder.Append); + matrix.TranslateAppend(yari.InnerRect.X, yari.InnerRect.Y); // Draw axis. // First draw tick marks, second draw axis. @@ -131,21 +136,25 @@ internal override void Draw() { for (double y = yMin + yMinorTick; y < yMax; y += yMinorTick) { - points[0].X = minorTickMarkStart; - points[0].Y = y; - points[1].X = minorTickMarkEnd; - points[1].Y = y; + points[0].X = (float_)minorTickMarkStart; + points[0].Y = (float_)y; + points[1].X = (float_)minorTickMarkEnd; + points[1].Y = (float_)y; matrix.TransformPoints(points); minorTickMarkLineFormat.DrawLine(points[0], points[1]); } } - double lineSpace = yari.TickLabelsFont.GetHeight(); // old: yari.TickLabelsFont.GetHeight(gfx); + double lineSpace = (float_)yari.TickLabelsFont.GetHeight(); // old: yari.TickLabelsFont.GetHeight(gfx); +#if PSGFX + int cellSpace = yari.TickLabelsFont.FontFamily.GetLineSpacing((Drawing.XFontStyleEx)yari.TickLabelsFont.Style); +#else int cellSpace = yari.TickLabelsFont.FontFamily.GetLineSpacing(yari.TickLabelsFont.Style); +#endif double xHeight = yari.TickLabelsFont.Metrics.XHeight; XSize labelSize = new XSize(0, 0); - labelSize.Height = lineSpace * xHeight / cellSpace; + labelSize.Height = (float_)(lineSpace * xHeight / cellSpace); int countTickLabels = (int)((yMax - yMin) / yMajorTick) + 1; for (int idx = 0; idx < countTickLabels; idx++) @@ -158,21 +167,21 @@ internal override void Draw() // Draw major tick marks. if (yari.MajorTickMark != TickMarkType.None) { - labelSize.Width += yari.MajorTickMarkWidth * 1.5; - points[0].X = majorTickMarkStart; - points[0].Y = y; - points[1].X = majorTickMarkEnd; - points[1].Y = y; + labelSize.Width += (float_)(yari.MajorTickMarkWidth * 1.5); + points[0].X = (float_)majorTickMarkStart; + points[0].Y = (float_)y; + points[1].X = (float_)majorTickMarkEnd; + points[1].Y = (float_)y; matrix.TransformPoints(points); majorTickMarkLineFormat.DrawLine(points[0], points[1]); } else - labelSize.Width += SpaceBetweenLabelAndTickmark; + labelSize.Width += (float_)SpaceBetweenLabelAndTickmark; // Draw label text. var layoutText = new XPoint[1]; layoutText[0].X = yari.InnerRect.X + yari.InnerRect.Width - labelSize.Width; - layoutText[0].Y = y; + layoutText[0].Y = (float_)y; matrix.TransformPoints(layoutText); layoutText[0].Y += labelSize.Height / 2; // Center text vertically. gfx.DrawString(str, yari.TickLabelsFont, yari.TickLabelsBrush, layoutText[0]); @@ -182,9 +191,9 @@ internal override void Draw() if (yari.LineFormat != null && yari.LineFormat.Width > 0) { points[0].X = yari.InnerRect.X + yari.InnerRect.Width; - points[0].Y = yMin; + points[0].Y = (float_)yMin; points[1].X = yari.InnerRect.X + yari.InnerRect.Width; - points[1].Y = yMax; + points[1].Y = (float_)yMax; matrix.TransformPoints(points); if (yari.MajorTickMark != TickMarkType.None) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/ChartFrame.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/ChartFrame.cs index 36e67888..d2d1260c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/ChartFrame.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/ChartFrame.cs @@ -58,9 +58,13 @@ public void Draw(XGraphics gfx) Size.Width, Size.Height, 20, 20); XRect chartRect = new XRect(Location.X, Location.Y, Size.Width, Size.Height); +#if PSGFX + var brush = new XSolidBrush(XColor.FromArgb(0xFFD0DEEF)); +#else var brush = new XLinearGradientBrush(chartRect, XColor.FromArgb(0xFFD0DEEF), XColors.White, XLinearGradientMode.Vertical); - var penBorder = new XPen(XColors.SteelBlue, 2.5); +#endif + var penBorder = new XPen(XColors.SteelBlue, 2.5f); gfx.DrawRoundedRectangle(penBorder, brush, Location.X, Location.Y, Size.Width, Size.Height, 15, 15); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/LineFormat.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/LineFormat.cs index 38144763..7d71b7be 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/LineFormat.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Charting/LineFormat.cs @@ -49,6 +49,9 @@ internal LineFormat(DocumentObject parent) : base(parent) { } /// Gets or sets the dash style of the line. /// public XDashStyle DashStyle { get; set; } +#if PSGFX + = XDashStyles.Solid; +#endif /// /// Gets or sets the style of the line. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj index 2e5d3abf..b9a40f4e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/PdfSharp.Charting.csproj @@ -2,7 +2,7 @@ library - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Properties/GlobalDeclarations.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Properties/GlobalDeclarations.cs index 36271702..50a15c97 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Properties/GlobalDeclarations.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Charting/Properties/GlobalDeclarations.cs @@ -2,7 +2,12 @@ // See the LICENSE file in the solution root for more information. global using static System.FormattableString; - +#if PSGFX +global using PdfSharp.Graphics.XGfx; +#else +global using float_ = double; +global using FLOAT_ = double; +#endif using System.Runtime.InteropServices; [assembly: ComVisible(false)] diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs index b9642363..01a98b76 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PdfSharpDefaultSigner.cs @@ -1,7 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER using System.Net.Http.Headers; #endif using System.Security.Cryptography; @@ -16,7 +16,7 @@ namespace PdfSharp.Pdf.Signatures public class PdfSharpDefaultSigner : IDigitalSigner { static readonly Oid SignatureTimeStampOid = new("1.2.840.113549.1.9.16.2.14"); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER const string TimestampQueryContentType = "application/timestamp-query"; const string TimestampReplyContentType = "application/timestamp-reply"; #endif @@ -31,7 +31,7 @@ public PdfSharpDefaultSigner(X509Certificate2 certificate, PdfMessageDigestType { Certificate = certificate; DigestType = digestType; -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER TimeStampAuthorityUri = timeStampAuthorityUri; #else // We don’t know how to get a time stamp with .NET Standard. @@ -100,7 +100,7 @@ public async Task GetSignatureAsync(Stream stream) PdfMessageDigestType.SHA384 => Oid.FromFriendlyName("sha384", OidGroup.HashAlgorithm), PdfMessageDigestType.SHA512 => Oid.FromFriendlyName("sha512", OidGroup.HashAlgorithm), // PdfMessageDigestType.RIPEMD160 => Oid.FromFriendlyName("???"), // ??? - _ => throw new NotImplementedException($"Digest type {DigestType} not supported by this signer.") + _ => throw new NotSupportedException($"Digest type {DigestType} not supported by this signer.") } } /* { IncludeOption = X509IncludeOption.WholeChain } */; signer.UnsignedAttributes.Add(new Pkcs9SigningTime()); @@ -109,7 +109,7 @@ public async Task GetSignatureAsync(Stream stream) if (TimeStampAuthorityUri is not null) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER await AddTimestampFromTSAAsync(signedCms).ConfigureAwait(false); #else // Already checked in constructor. @@ -129,7 +129,7 @@ public async Task GetSignatureAsync(Stream stream) bool MustAddTimeStamp { get; init; } -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER async Task AddTimestampFromTSAAsync(SignedCms signedCms) { // Generate our nonce to identify the pair request-response. @@ -151,7 +151,7 @@ async Task AddTimestampFromTSAAsync(SignedCms signedCms) PdfMessageDigestType.SHA384 => HashAlgorithmName.SHA384, PdfMessageDigestType.SHA512 => HashAlgorithmName.SHA512, // PdfMessageDigestType.RIPEMD160 => HashAlgorithmName.SHA512, // ??? - _ => throw new NotImplementedException($"Digest type {DigestType} not supported by this signer.") + _ => throw new NotSupportedException($"Digest type {DigestType} not supported by this signer.") }; // Now we generate the request to send to the RFC3161 signing authority. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PsCryptoMsg.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PsCryptoMsg.cs index b72438d9..11b363cd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PsCryptoMsg.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/PsCryptoMsg.cs @@ -1,21 +1,44 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using Microsoft.Extensions.Logging; +using PdfSharp.Internal; namespace PdfSharp.Pdf.Signatures { /// /// PDFsharp cryptography message. /// - readonly struct PsCryptoMsg(PsCryptoMsgId id, string message) + readonly struct PsCryptoMsg(PsCryptoMsgId id, string message) : IErrorMessageInfo { + + int IErrorMessageInfo.Id => (int)Id; + public PsCryptoMsgId Id { get; init; } = id; + public string Name { get; init; } = "Crypto" + id; + public string Message { get; init; } = message; public EventId EventId => new((int)Id, EventName); public string EventName => Id.ToString(); } + + readonly struct PsCryptoMsg2(T id, string message) : IErrorMessageInfo where T : Enum + { + // I’m frustrated. After 25 year of programming C# I was not able to + // cast Id into integer and need to ask ChatGPT to tell me the correct way. + int IErrorMessageInfo.Id => (int)(object)Id; + + public T Id { get; init; } = id; + + public string Name { get; init; } = "Crypto" + id; + + public string Message { get; init; } = message; + + public EventId EventId => new((int)(object)Id, EventName); + + public string EventName => Id.ToString(); + } } \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/enum/PsCryptoMsgId.cs b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/enum/PsCryptoMsgId.cs index 97bebb8d..dfb171f1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/enum/PsCryptoMsgId.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/Pdf.Signatures/enum/PsCryptoMsgId.cs @@ -10,7 +10,7 @@ namespace PdfSharp.Pdf.Signatures /// // GPT 4 recommends to use Crypto instead of Cry as abbreviation, // because Cry is too ambiguous. - enum PsCryptoMsgId + internal enum PsCryptoMsgId { None = 0, diff --git a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj index 93a9cd84..a971d3e4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp.Cryptography/PdfSharp.Cryptography.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp True ..\..\..\..\..\StrongnameKey.snk @@ -12,7 +12,8 @@ - + + @@ -26,7 +27,8 @@ - + + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Configuration.cs b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Configuration.cs index 9c060891..4c88ae9e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Configuration.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Configuration.cs @@ -1,6 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +//#define WRITE_LEADING_ZEROS + global using System.Diagnostics; global using PdfSharp.Diagnostics; global using System.Globalization; @@ -13,16 +15,50 @@ namespace PdfSharp /// static class Config { +#if WRITE_LEADING_ZEROS #if DEBUG public const string SignificantDecimalPlaces0 = "0"; // for testing only public const string SignificantDecimalPlaces1 = "0.#"; // for testing only #endif - public const string SignificantDecimalPlaces2 = "0.##"; - public const string SignificantDecimalPlaces3 = "0.###"; - public const string SignificantDecimalPlaces4 = "0.####"; - public const string SignificantDecimalPlaces7 = "0.#######"; - public const string SignificantDecimalPlaces10 = "0.##########"; - public const string SignificantDecimalPlaces1Plus9 = "0.0#########"; + public const string SignificantDecimalPlaces2 = "0.##;-0.##;0"; + public const string SignificantDecimalPlaces3 = "0.###;-0.###;0"; + public const string SignificantDecimalPlaces4 = "0.####;-0.####;0"; + public const string SignificantDecimalPlaces7 = "0.#######;-0.#######;0"; + public const string SignificantDecimalPlaces10 = "0.##########;-0.##########;0"; // Used by DebuggerDisplay only. + public const string SignificantDecimalPlaces1Plus9 = "0.0#########;-0.0#########;0.0"; // Used by CReal only. + + // #US263 CReal writes 5 as "5.0" while PdfReal writes 5 as "5". + + public const string NumberFormat2SignificantDecimalPlacesWithLeadingZero = "0.##;-0.##;0"; + /// + /// The number format2 significant decimal places with leading zero + /// + public const string NumberFormat_0_2 = "0.##;-0.##;0"; + public const string NumberFormat_x_2 = ".##;-.##;0"; +#else +#if DEBUG + public const string SignificantDecimalPlaces0 = "0"; // for testing only + public const string SignificantDecimalPlaces1 = ".#;-.#;0"; // for testing only +#endif + public const string SignificantDecimalPlaces2 = ".##;-.##;0"; + public const string SignificantDecimalPlaces3 = ".###;-.###;0"; + public const string SignificantDecimalPlaces4 = ".####;-.####;0"; + public const string SignificantDecimalPlaces7 = ".#######;-.#######;0"; + public const string SignificantDecimalPlaces10 = ".##########;-.##########;0"; // Used by DebuggerDisplay only. + public const string SignificantDecimalPlaces1Plus9 = ".0#########;-.0#########;0.0"; // Used by CReal only. + + public const string NumberFormat2SignificantDecimalPlacesWithLeadingZero = "0.##;-0.##;0"; + /// + /// The number format2 significant decimal places with leading zero + /// + public const string NumberFormat_0_2 = "0.##;-0.##;0"; + public const string NumberFormat_x_2 = ".##;-.##;0"; +#endif + + public const string ColorFormat = NumberFormat_x_2; + public const string ColorFormatLz = NumberFormat_0_2; + public const string CoordFormat = NumberFormat_x_2; + public const string CoordFormatLz = NumberFormat_0_2; } static class Const diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs index 5ad69353..5933a2a2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/!internal/Directives.cs @@ -12,14 +12,36 @@ #warning *********************************************************** #endif +#if !DEBUG && PDFSHARP_DEBUG +#warning **************************************************************** +#warning ***** ‘PDFSHARP_DEBUG’ MUST BE UNDEFINED FOR FINAL RELEASE ***** +#warning **************************************************************** +#endif + +#if !DEBUG && FEWER_FRAMEWORKS +#warning ****************************************************************** +#warning ***** ‘FEWER_FRAMEWORKS’ MUST BE UNDEFINED FOR FINAL RELEASE ***** +#warning ****************************************************************** +#endif + #if TEST_CODE_ // ‘’ // Ensure not to accidentally rename ‘TEST_CODE’ to ‘TEST_CODE_’. // This would compile code previously disabled with ‘#if TEST_CODE_’. -// Rename ‘TEST_CODE’ always to ‘TEST_CODE_xxx’ in ‘Directory.Build.targets’. -#warning ***************************************************** -#warning ***** ‘TEST_CODE_’ MUST NEVER BE DEFINED ***** -#warning ***** THIS ACCIDENTALLY ACTIVATES EXCLUDED CODE ***** -#warning ***************************************************** +// Rename ‘TEST_CODE’ always to ‘TEST_CODExxx’ in ‘Directory.Build.targets’. +#warning ********************************************************* +#warning ***** ‘TEST_CODE_’ MUST NEVER BE DEFINED ***** +#warning ***** THIS MAY ACCIDENTALLY ACTIVATES EXCLUDED CODE ***** +#warning ********************************************************* +#endif + +#if PDFSHARP_DEBUG_ // ‘’ +// Ensure not to accidentally rename ‘PDFSHARP_DEBUG’ to ‘PDFSHARP_DEBUG_’. +// This would compile code previously disabled with ‘#if PDFSHARP_DEBUG_’. +// Rename ‘PDFSHARP_DEBUG’ always to ‘PDFSHARP_DEBUGxxx’ in ‘Directory.Build.targets’. +#warning ********************************************************* +#warning ***** ‘PDFSHARP_DEBUG_’ MUST NEVER BE DEFINED ***** +#warning ***** THIS MAY ACCIDENTALLY ACTIVATES EXCLUDED CODE ***** +#warning ********************************************************* #endif #if GDI && WPF diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/DebugBreakHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/DebugBreakHelper.cs new file mode 100644 index 00000000..1735e782 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/DebugBreakHelper.cs @@ -0,0 +1,143 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#if PDFSHARP_DEBUG +#if !DEBUG +#warning "PDFSHARP_DEBUG set in a RELEASE build." +#endif + +using PdfSharp.Pdf; + +// Missing XML comment for publicly visible type or member because +// this is empira internal stuff not contained in a release build. +#pragma warning disable CS1591 // Internal class + +namespace PdfSharp.Diagnostics +{ + /// + /// Some flags that can be set or test anywhere in the code + /// to communicate a particular condition from e.g. test code + /// into PDFsharp. Some kind of code-based conditional break-points. + /// MUST NOT COME INTO NUGET PACKAGES! + /// + public static class DebugBreakHelper + { + /// + /// Gets or sets conditional break point 1. + /// + public static bool ShouldBreak1 { get; set; } + + /// + /// Gets or sets conditional break point 2. + /// + public static bool ShouldBreak2 { get; set; } + + /// + /// Gets or sets conditional break point 3. + /// + public static bool ShouldBreak3 { get; set; } + + /// + /// Gets or sets conditional break point 4. + /// + public static bool ShouldBreak4 { get; set; } + + /// + /// Gets or sets conditional break point 5. + /// + public static bool ShouldBreak5 { get; set; } + + //public static void BreakOn1() + //{ + // if (ShouldBreak1) + // Debugger.Break(); + //} + } + + /// + /// An empira internal class for debugging and testing PDFsharp. + /// + public class PdfSharpDebug + { + public void AddObjectId(PdfObjectID id) + => ObjectIdSet.Add(MakeId(id.ObjectNumber, id.GenerationNumber), null); + + public void AddObjectId(int objectNumber, int generationNumber) + => ObjectIdSet.Add(MakeId(objectNumber, generationNumber), null); + + public void AddItemNumber(PdfItem item) + => ItemNumberSet.Add(item.ItemNumber, null); + + public void AddItemNumber(int itemNumber) + => ItemNumberSet.Add(itemNumber, null); + + public void AddDeadContainer(PdfContainer cont) + { + DeadContainers.Add(cont, null); + } + + public bool IsInObjectIdSet(PdfObjectID id) => ObjectIdSet.ContainsKey(MakeId(id)); + + public bool IsInObjectIdSet(int objectNumber, int generationNumber = 0) => ObjectIdSet.ContainsKey(MakeId(objectNumber, generationNumber)); + + public bool IsInItemNumberSet(PdfItem item) => ItemNumberSet.ContainsKey(item.ItemNumber); + + public bool IsInItemNumberSet(int itemNumber) => ItemNumberSet.ContainsKey(itemNumber); + + public readonly Dictionary Stuff = []; + + // === + + public bool BreakInCrossReferenceTable { get; set; } + + public bool SaveDocumentWithNoPages { get; set; } + + public bool AllowOpenWithUserPasswordOnly { get; set; } + + public bool SaveImportedDocument { get; set; } + + // === + + /// + /// Gets or sets conditional break point 1. + /// + public bool ShouldBreak1 { get; set; } + + /// + /// Gets or sets conditional break point 2. + /// + public bool ShouldBreak2 { get; set; } + + /// + /// Gets or sets conditional break point 3. + /// + public bool ShouldBreak3 { get; set; } + + /// + /// Gets or sets conditional break point 4. + /// + public bool ShouldBreak4 { get; set; } + + /// + /// Gets or sets conditional break point 5. + /// + public bool ShouldBreak5 { get; set; } + + // === + + public static readonly PdfSharpDebug Instance = new(); + + // === + + long MakeId(int objectNumber, int generationNumber) => objectNumber << 16 + generationNumber; + + long MakeId(PdfObjectID id) => MakeId(id.ObjectNumber, id.GenerationNumber); + + static readonly Dictionary ObjectIdSet = []; + + static readonly Dictionary ItemNumberSet = []; + + static readonly Dictionary DeadContainers = []; + } +} +#endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/PdfSharpCore.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/PdfSharpCore.cs index 98fde181..51c6749d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/PdfSharpCore.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Diagnostics/PdfSharpCore.cs @@ -1,8 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using PdfSharp.Fonts; using PdfSharp.Internal; +using PdfSharp.Fonts; +using PdfSharp.Internal.OpenType; using PdfSharp.Logging; namespace PdfSharp.Diagnostics @@ -15,26 +16,29 @@ public static class PdfSharpCore /// /// Resets PDFsharp to a state equivalent to the state after /// the assemblies are loaded. + /// Intended for unit testing only. /// public static void ResetAll() { Capabilities.ResetAll(); GlobalFontSettings.ResetAll(); PdfSharpLogHost.ResetLogging(); - Globals.Global.RecreateGlobals(); + PsGlobals.RecreatePsGlobals(); + OtGlobals.RecreateOtGlobals(); - if (FontFactory.HasFontSources) - throw new InvalidOperationException("Internal error."); + //if (FontFactory.HasFontSources) + // throw new InvalidOperationException("Internal error."); } /// /// Resets the font management equivalent to the state after /// the assemblies are loaded. + /// Intended for unit testing only. /// public static void ResetFontManagement() { GlobalFontSettings.ResetAll(true); - Globals.Global.IncrementVersion(); + PsGlobals.Global.IncrementVersion(); } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs deleted file mode 100644 index 9e1e7f9f..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/IImageImporter.cs +++ /dev/null @@ -1,369 +0,0 @@ -// 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; - -namespace PdfSharp.Drawing -{ - /// - /// This interface will be implemented by specialized classes, one for JPEG, one for BMP, one for PNG, one for GIF. Maybe more. - /// - interface IImageImporter - { - /// - /// Imports the image. Returns null if the image importer does not support the format. - /// - ImportedImage? ImportImage(StreamReaderHelper stream); - - /// - /// Prepares the image data needed for the PDF file. - /// - ImageData PrepareImage(ImagePrivateData data); - } - - /// - /// Helper for dealing with Stream data. - /// - class StreamReaderHelper : IDisposable - { - internal StreamReaderHelper(byte[] data) - { - OriginalStream = null!; - Data = data; - Length = data.Length; - } - - internal StreamReaderHelper(Stream stream, int streamLength) - { - OriginalStream = stream; - - if (stream is MemoryStream ms) - { - // If the given stream is a MemoryStream, work with it. - if (ms.TryGetBuffer(out var buffer)) - { - // Buffer is accessible - use it. - Data = buffer.Array ?? throw new ArgumentNullException(nameof(stream), "Stream has no content byte array."); - Length = (int)ms.Length; - // If buffer is larger than needed, create a new buffer with required size. - if (Data.Length > Length) - { - var tmp = new Byte[Length]; - Buffer.BlockCopy(Data, 0, tmp, 0, Length); - Data = tmp; - } - } - else - { - // Buffer of given stream is not accessible, so read stream into new buffer. - OwnedMemoryStream = new(streamLength); - stream.CopyTo(OwnedMemoryStream); - Data = OwnedMemoryStream.GetBuffer(); - Length = (int)OwnedMemoryStream.Length; - PdfSharpLogHost.Logger.LogWarning("LoadImage: MemoryStream with buffer that is not publicly visible was used. " + - "For better performance, set 'publiclyVisible' to true when creating the MemoryStream."); - } - } - else - { - // If the given stream is not a MemoryStream, copy the stream to a new MemoryStream. - if (streamLength > -1) - { - // Simple case: length of stream is known, create a MemoryStream with correct buffer size. - OwnedMemoryStream = new(streamLength); - stream.CopyTo(OwnedMemoryStream); - Data = OwnedMemoryStream.GetBuffer(); - Length = (int)OwnedMemoryStream.Length; - } - else - { - // Complex case: length of stream is not known. - // This only occurs with streams that do not support the Length property. - OwnedMemoryStream = new(); - stream.CopyTo(OwnedMemoryStream); - Data = OwnedMemoryStream.GetBuffer(); - Length = (int)OwnedMemoryStream.Length; - // If buffer is larger than needed, create a new buffer with required size. - if (Data.Length > Length) - { - var tmp = new Byte[Length]; - Buffer.BlockCopy(Data, 0, tmp, 0, Length); - Data = tmp; - } - } - } - } - - internal byte GetByte(int offset) - { - if (CurrentOffset + offset >= Length) - throw new InvalidOperationException("Index out of range."); - - return Data[CurrentOffset + offset]; - } - - internal ushort GetWord(int offset, bool bigEndian) - { - if (CurrentOffset + offset + 1 >= Length) - throw new InvalidOperationException("Index out of range."); - - return (ushort)(bigEndian - ? (Data[CurrentOffset + offset++] << 8) + Data[CurrentOffset + offset] - : Data[CurrentOffset + offset++] + (Data[CurrentOffset + offset] << 8)); - } - - internal uint GetDWord(int offset, bool bigEndian) - { - if (CurrentOffset + offset + 3 >= Length) - throw new InvalidOperationException("Index out of range."); - - // Are you a good developer? - // What’s wrong with this code? - //return (bigEndian - // ? ((uint)Data[CurrentOffset + offset++] << 24) + ((uint)Data[CurrentOffset + offset++] << 16) - // + ((uint)Data[CurrentOffset + offset++] << 8) + Data[CurrentOffset + offset] - // : Data[CurrentOffset + offset++] + ((uint)Data[CurrentOffset + offset++] << 8)) - // + ((uint)Data[CurrentOffset + offset++] << 16) + ((uint)Data[CurrentOffset + offset] << 24); - return (uint)(bigEndian - ? (Data[CurrentOffset + offset++] << 24) + - (Data[CurrentOffset + offset++] << 16) + - (Data[CurrentOffset + offset++] << 8) + - Data[CurrentOffset + offset] - : Data[CurrentOffset + offset++] + - (Data[CurrentOffset + offset++] << 8) + - (Data[CurrentOffset + offset++] << 16) + - (Data[CurrentOffset + offset] << 24)); - } - - /// - /// Resets this instance. - /// - public void Reset() => CurrentOffset = 0; - - /// - /// Gets the original stream. - /// - public Stream OriginalStream { get; } - - internal int CurrentOffset { get; set; } - - /// - /// Gets the data as byte[]. - /// - public byte[] Data { get; } - - /// - /// Gets the length of Data. - /// - public int Length { get; } - -#if CORE || GDI || WPF - /// - /// Gets the owned memory stream. Can be null if no MemoryStream was created. - /// - public MemoryStream? OwnedMemoryStream { get; private set; } -#endif - - public void Dispose() - { -#if CORE || GDI || WPF - OwnedMemoryStream?.Dispose(); - OwnedMemoryStream = null; -#endif - } - } - - /// - /// The imported image. - /// - abstract class ImportedImage - { - /// - /// Initializes a new instance of the class. - /// - protected ImportedImage(ImagePrivateData? data) - { - Data = data; - if (data != null) - data.Image = this; - } - - /// - /// Initializes a new instance of the class. - /// - protected ImportedImage() - : this(null) - { } - - /// - /// Gets information about the image. - /// - public ImageInformation Information { get; private set; } = new ImageInformation(); - -#if true - /// - /// Gets the image data needed for the PDF file. - /// - // Data is created on demand without internal caching. - public ImageData ImageData(PdfDocumentOptions options) - { - return PrepareImageData(options); - } -#else - /// - /// Gets the image data needed for the PDF file. - /// - public ImageData ImageData - { - get { if(!HasImageData) _imageData = PrepareImageData(); return _imageData; } - private set => _imageData = value; - } - ImageData _imageData; - - /// - /// Gets a value indicating whether image data for the PDF file was already prepared. - /// - public bool HasImageData => _imageData != null; -#endif - - internal virtual ImageData PrepareImageData(PdfDocumentOptions options) - { - throw new NotImplementedException(); - } - - //IImageImporter _importer; - internal ImagePrivateData? Data; - } - - /// - /// Public information about the image, filled immediately. - /// Note: The stream will be read and decoded on the first call to PrepareImageData(). - /// ImageInformation can be filled for corrupted images that will throw an exception on PrepareImageData(). - /// - class ImageInformation - { - internal enum ImageFormats - { - // ReSharper disable InconsistentNaming - /// - /// Value not set. - /// - Undefined = -1, - - /// - /// Standard JPEG format (RGB). - /// - JPEG, - - /// - /// Gray-scale JPEG format. - /// - JPEGGRAY, - - /// - /// JPEG file with inverted CMYK, thus RGBW. - /// - JPEGRGBW, - - /// - /// JPEG file with CMYK. - /// - JPEGCMYK, - - Palette1, - Palette4, - Palette8, - Grayscale8, - RGB24, - ARGB32 - - // ReSharper restore InconsistentNaming - } - - internal ImageFormats ImageFormat = ImageFormats.Undefined; - - /// - /// The width of the image in pixel. - /// - internal uint Width; - - /// - /// The height of the image in pixel. - /// - internal uint Height; - - /// - /// The horizontal DPI (dots per inch). Can be 0 if not supported by the image format. - /// Note: JFIF (JPEG) files may contain either DPI or DPM or just the aspect ratio. Windows BMP files will contain DPM. Other formats may support any combination, including none at all. - /// - internal double HorizontalDPI; - - /// - /// The vertical DPI (dots per inch). Can be 0 if not supported by the image format. - /// - internal double VerticalDPI; - - /// - /// The horizontal DPM (dots per meter). Can be 0 if not supported by the image format. - /// - internal double HorizontalDPM; - - /// - /// The vertical DPM (dots per meter). Can be 0 if not supported by the image format. - /// - internal double VerticalDPM; - - /// - /// The horizontal component of the aspect ratio. Can be 0 if not supported by the image format. - /// Note: Aspect ratio will be set if either DPI or DPM was set but may also be available in the absence of both DPI and DPM. - /// - internal double HorizontalAspectRatio; - - /// - /// The vertical component of the aspect ratio. Can be 0 if not supported by the image format. - /// - internal double VerticalAspectRatio; - - /// - /// The bit count per pixel. Only valid for certain images, will be 0 otherwise. - /// - internal uint BitCount; - - /// - /// The colors used. Only valid for images with palettes, will be 0 otherwise. - /// - internal uint ColorsUsed; - - /// - /// The default DPI (dots per inch) for images that do not have DPI information. - /// - internal double DefaultDPI; - } - - /// - /// Contains internal data. This includes a reference to the Stream if data for PDF was not yet prepared. - /// - abstract class ImagePrivateData - { - internal ImagePrivateData() - { } - - /// - /// Gets the image. - /// - public ImportedImage Image - { - get => _image ?? NRT.ThrowOnNull(); - internal set => _image = value; - } - ImportedImage? _image; - } - - /// - /// Contains data needed for PDF. Will be prepared when needed. - /// - abstract class ImageData - { } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporter.cs deleted file mode 100644 index 208169fb..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporter.cs +++ /dev/null @@ -1,85 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using System.IO; - -namespace PdfSharp.Drawing.Internal -{ - /// - /// The class that imports images of various formats. - /// - class ImageImporter - { - // TODO_OLD Make a singleton! - /// - /// Gets the image importer. - /// - public static ImageImporter GetImageImporter() // StL: To what kind of design pattern this function identifies itself? - { - return new ImageImporter(); - } - - ImageImporter() - { - _importers.Add(new ImageImporterJpeg()); - _importers.Add(new ImageImporterBmp()); - _importers.Add(new ImageImporterPng()); - // TODO_OLD: Special importer for PDF? Or dealt with at a higher level? - } - - /// - /// Imports the image. - /// - public ImportedImage? ImportImage(Stream stream) - { - long length = -1; - try - { - length = stream.Length; - } - catch (NotSupportedException) - { - // We eat this exception. - // We can handle streams that do not return their length. - } - catch (Exception ex) - { - // Unexpected exception. - throw new InvalidOperationException("Cannot determine the length of the stream. Use a stream that supports the Length property. Consider copying the image to a MemoryStream.", ex); - } - - if (length < -1 || length > Int32.MaxValue) - throw new InvalidOperationException($"Image files with a size of {length} bytes are not supported. Use image files smaller than 2 GiB."); - - using var helper = new StreamReaderHelper(stream, (int)length); - return TryImageImport(helper); - } - -#if GDI || WPF || CORE - /// - /// Imports the image. - /// - public ImportedImage? ImportImage(string filename) - { - var data = File.ReadAllBytes(filename); - - using var helper = new StreamReaderHelper(data); - return TryImageImport(helper); - } - - ImportedImage? TryImageImport(StreamReaderHelper helper) - { - // Try all registered importers to see if any of them can handle the image. - foreach (var importer in _importers) - { - helper.Reset(); - var image = importer.ImportImage(helper); - if (image != null) - return image; - } - return null; - } -#endif - readonly List _importers = []; - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterBmp.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterBmp.cs deleted file mode 100644 index a7e6c25b..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterBmp.cs +++ /dev/null @@ -1,636 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using PdfSharp.Pdf; -using PdfSharp.Pdf.Advanced; -using static PdfSharp.Drawing.ImageInformation; - -namespace PdfSharp.Drawing.Internal -{ - class ImageImporterBmp : ImageImporterRoot, IImageImporter - { - public ImportedImage? ImportImage(StreamReaderHelper stream) - { - try - { - stream.CurrentOffset = 0; - if (TestBitmapFileHeader(stream, out var offsetImageData)) - { - // Note: TestBitmapFileHeader updates stream.CurrentOffset on success. - - ImagePrivateDataBitmap ipd = new ImagePrivateDataBitmap(stream.Data, stream.Length); - ImportedImage ii = new ImportedImageBitmap(ipd); - ii.Information.DefaultDPI = 96; // Assume 96 DPI if information not provided in the file. - - if (TestBitmapInfoHeader(stream, ii, offsetImageData)) - { - return ii; - } - } - } - // ReSharper disable once EmptyGeneralCatchClause - catch (Exception) - { - // Eat exceptions to have this image importer skipped. - // We try to find an image importer that can handle the image. - } - return null; - } - - bool TestBitmapFileHeader(StreamReaderHelper stream, out int offset) - { - offset = 0; - // File must start with "BM". - if (stream.GetWord(0, true) == 0x424d) - { - int filesize = (int)stream.GetDWord(2, false); - // Integrity check: filesize set in BM header should match size of the stream. - // We test "<" instead of "!=" to allow extra bytes at the end of the stream. - if (filesize < stream.Length) - return false; - - offset = (int)stream.GetDWord(10, false); - stream.CurrentOffset += 14; - return true; - } - return false; - } - - bool TestBitmapInfoHeader(StreamReaderHelper stream, ImportedImage ii, int offset) - { - int size = (int)stream.GetDWord(0, false); - if (size is 40 or 108 or 124) // sizeof BITMAPINFOHEADER == 40, sizeof BITMAPV4HEADER == 108, sizeof BITMAPV5HEADER == 124 - { - uint width = stream.GetDWord(4, false); - int height = (int)stream.GetDWord(8, false); - int planes = stream.GetWord(12, false); - uint bitCount = stream.GetWord(14, false); - int compression = (int)stream.GetDWord(16, false); - int sizeImage = (int)stream.GetDWord(20, false); - int xPelsPerMeter = (int)stream.GetDWord(24, false); - int yPelsPerMeter = (int)stream.GetDWord(28, false); - uint colorsUsed = stream.GetDWord(32, false); - uint colorsImportant = stream.GetDWord(36, false); - // TODO_OLD Integrity and plausibility checks. - if (sizeImage != 0 && sizeImage + offset > stream.Length) - return false; - - var privateData = (ImagePrivateDataBitmap?)ii.Data; - - // Return true only for supported formats. - if (compression is 0 or 3) // BI_RGB == 0, BI_BITFIELDS == 3 - { - var data = (ImagePrivateDataBitmap?)ii.Data ?? NRT.ThrowOnNull(); - //((ImagePrivateDataBitmap)ii.Data).Offset = offset; - //((ImagePrivateDataBitmap)ii.Data).ColorPaletteOffset = stream.CurrentOffset + size; - data.Offset = offset; - data.ColorPaletteOffset = stream.CurrentOffset + size; - ii.Information.BitCount = bitCount; - ii.Information.Width = width; - ii.Information.Height = (uint)Math.Abs(height); - ii.Information.HorizontalDPM = xPelsPerMeter; - ii.Information.VerticalDPM = yPelsPerMeter; - if (privateData == null) - NRT.ThrowOnNull(); - privateData.FlippedImage = height < 0; - if (planes == 1 && bitCount == 24) - { - // RGB24 - ii.Information.ImageFormat = ImageInformation.ImageFormats.RGB24; - - // TODO_OLD: Verify Mask if size >= 108 && compression == 3. - return true; - } - if (planes == 1 && bitCount == 32) - { - // ARGB32 - //ii.Information.ImageFormat = ImageInformation.ImageFormats.ARGB32; - ii.Information.ImageFormat = compression == 0 ? - ImageInformation.ImageFormats.RGB24 : - ImageInformation.ImageFormats.ARGB32; - - // TODO_OLD: tell RGB from ARGB. Idea: assume RGB if alpha is always 0. - - // Verify Mask if size >= 108 && compression == 3. - if (size >= 108 && compression == 3) - { - uint maskRed = stream.GetDWord(40, false); - uint maskGreen = stream.GetDWord(44, false); - uint maskBlue = stream.GetDWord(48, false); - uint maskAlpha = stream.GetDWord(52, false); - // Reject images that do not have the expected byte order. - if (maskRed != 0xff000000 || - maskGreen != 0x00ff0000 || - maskBlue != 0x0000ff00 || - maskAlpha != 0x000000ff) - { - // Not yet supported. - return false; - } - } - return true; - } - if (planes == 1 && bitCount == 8) - { - // Palette8 - ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette8; - ii.Information.ColorsUsed = colorsUsed; - - return true; - } - if (planes == 1 && bitCount == 4) - { - // Palette4 - ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette4; - ii.Information.ColorsUsed = colorsUsed; - - return true; - } - if (planes == 1 && bitCount == 1) - { - // Palette1 - ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette1; - ii.Information.ColorsUsed = colorsUsed; - - return true; - } - // TODO_OLD Implement more formats! - } - } - return false; - } - - public ImageData PrepareImage(ImagePrivateData data) - { - throw new NotImplementedException(); - } - } - - /// - /// Bitmap refers to the format used in PDF. Will be used for BMP, PNG, TIFF, GIF, and others. - /// - class ImportedImageBitmap : ImportedImage - { - /// - /// Initializes a new instance of the class. - /// - public ImportedImageBitmap(ImagePrivateDataBitmap data) - : base(data) - { } - - internal override ImageData PrepareImageData(PdfDocumentOptions options) - { - ImagePrivateDataBitmap data = (ImagePrivateDataBitmap?)Data ?? NRT.ThrowOnNull(); - ImageDataBitmap imageData = new ImageDataBitmap(options); - //imageData.Data = data.Data; - //imageData.Length = data.Length; - - data.CopyBitmap(imageData, options); - - return imageData; - } - } - - /// - /// Contains data needed for PDF. Will be prepared when needed. - /// Bitmap refers to the format used in PDF. Will be used for BMP, PNG, TIFF, GIF, and others. - /// - class ImageDataBitmap : ImageData - { - internal ImageDataBitmap(PdfDocumentOptions options) - { - Options = options; - } - - internal ImageDataBitmap(byte[] data, byte[] mask) - { - Data = data; - Length = Data.Length; - AlphaMask = mask; - AlphaMaskLength = AlphaMask?.Length ?? 0; - // TODO_OLD Bitmap mask? - } - - /// - /// Gets the data. - /// - public byte[] Data { get; internal set; } = null!; - - /// - /// Gets the length. - /// - public int Length { get; internal set; } - - /// - /// Gets the data for the CCITT format. - /// - public byte[]? DataFax { get; internal set; } = null; - - /// - /// Gets the length. - /// - public int LengthFax { get; internal set; } - - public byte[] AlphaMask { get; internal set; } = null!; - - public int AlphaMaskLength { get; internal set; } - - public byte[] BitmapMask { get; internal set; } = null!; - - public int BitmapMaskLength { get; internal set; } - - public byte[] PaletteData { get; set; } = null!; - - public int PaletteDataLength { get; set; } - - public bool SegmentedColorMask; - - public int IsBitonal; - - public int K; - - public bool IsGray; - - internal readonly PdfDocumentOptions? Options; - } - - /// - /// Image data needed for Windows bitmap images. - /// - class ImagePrivateDataBitmap : ImagePrivateData - { - /// - /// Initializes a new instance of the class. - /// - public ImagePrivateDataBitmap(byte[] data, int length) - { - _data = data; - _length = length; - } - - /// - /// Gets the data. - /// - public byte[] Data => _data; - - readonly byte[] _data; - - /// - /// Gets the length. - /// - public int Length => _length; - - readonly int _length; - - /// - /// True if first line is the top line, false if first line is the bottom line of the image. When needed, lines will be reversed while converting data into PDF format. - /// - internal bool FlippedImage; - - /// - /// The offset of the image data in Data. - /// - internal int Offset; - - /// - /// The offset of the color palette in Data. - /// - internal int ColorPaletteOffset; - - internal void CopyBitmap(ImageDataBitmap dest, PdfDocumentOptions options) - { - switch (Image?.Information.ImageFormat ?? NRT.ThrowOnNull()) - { - case ImageInformation.ImageFormats.ARGB32: - CopyTrueColorMemoryBitmap(4, 8, true, dest); - break; - - case ImageInformation.ImageFormats.RGB24: - if (this.Image.Information.BitCount == 32) - CopyTrueColorMemoryBitmap(4, 8, false, dest); - else - CopyTrueColorMemoryBitmap(3, 8, false, dest); - break; - - case ImageInformation.ImageFormats.Palette8: - CopyIndexedMemoryBitmap(8, false, dest, options); - break; - - case ImageInformation.ImageFormats.Palette4: - CopyIndexedMemoryBitmap(4, false, dest, options); - break; - - case ImageInformation.ImageFormats.Palette1: - CopyIndexedMemoryBitmap(1, false, dest, options); - break; - - default: - throw new NotImplementedException(); - } - } - - /// - /// Copies images without color palette. - /// - /// 4 (32bpp RGB), 3 (24bpp RGB, 32bpp ARGB) - /// 8 - /// true (ARGB), false (RGB) - /// Destination - void CopyTrueColorMemoryBitmap(int components, int bits, bool hasAlpha, ImageDataBitmap dest) - { - int width = (int)Image.Information.Width; - int height = (int)Image.Information.Height; - - int logicalComponents = components; - if (components == 4) - logicalComponents = 3; - - byte[] imageData = new byte[logicalComponents * width * height]; - - bool hasMask = false; - bool hasAlphaMask = false; - byte[]? alphaMask = hasAlpha ? new byte[width * height] : null; - MonochromeMask? mask = hasAlpha ? - new MonochromeMask(width, height) : null; - - int nFileOffset = Offset; - int nOffsetRead = 0; - if (logicalComponents == 3) - { - if (hasAlpha) - { - // Improvement: Get byte order for V5 bitmaps from bitmap header. - // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header - // "If the bV5Compression member of the BITMAPV5HEADER is BI_BITFIELDS, the bmiColors member contains three DWORD color masks that specify the red, green, and blue components of each pixel. Each DWORD in the bitmap array represents a single pixel." - - // Experimental: Expected BGRA, but found ARGB. - ++nFileOffset; - } - - for (int y = 0; y < height; ++y) - { - // TODO_OLD Handle Flipped. - int nOffsetWrite = 3 * (height - 1 - y) * width; - int nOffsetWriteAlpha = 0; - if (hasAlpha) - { - mask!.StartLine(y); // NRT - nOffsetWriteAlpha = (height - 1 - y) * width; - } - - for (int x = 0; x < width; ++x) - { - imageData[nOffsetWrite] = Data[nFileOffset + nOffsetRead + 2]; - imageData[nOffsetWrite + 1] = Data[nFileOffset + nOffsetRead + 1]; - imageData[nOffsetWrite + 2] = Data[nFileOffset + nOffsetRead]; - if (hasAlpha) - { - //byte maskValue = Data[nFileOffset + nOffsetRead + 3]; // Expected, but does not always work. - byte maskValue = Data[nFileOffset + nOffsetRead - 1]; // Experimental. See above. - mask!.AddPel(maskValue); - alphaMask![nOffsetWriteAlpha] = maskValue; - if (!hasMask || !hasAlphaMask) - { - if (maskValue != 255) - { - hasMask = true; - if (maskValue != 0) - hasAlphaMask = true; - } - } - ++nOffsetWriteAlpha; - } - nOffsetRead += hasAlpha ? 4 : components; - nOffsetWrite += 3; - } - nOffsetRead = 4 * ((nOffsetRead + 3) / 4); // Align to 32 bit boundary - } - } - else if (components == 1) - { - // Grayscale - throw new NotImplementedException("Image format not supported (grayscales)."); - } - - dest.Data = imageData; - dest.Length = imageData.Length; - - if (alphaMask != null && hasAlphaMask) - { - dest.AlphaMask = alphaMask; - dest.AlphaMaskLength = alphaMask.Length; - } - - if (mask != null && hasMask) - { - dest.BitmapMask = mask.MaskData; - dest.BitmapMaskLength = mask.MaskData.Length; - } - } - - void CopyIndexedMemoryBitmap(int bits, bool checkTransparency, ImageDataBitmap dest, PdfDocumentOptions options) - { - int firstMaskColor = -1, lastMaskColor = -1; - bool segmentedColorMask = false; - - int bytesColorPaletteOffset = ((ImagePrivateDataBitmap)Image.Data!).ColorPaletteOffset; // GDI+ always returns Windows bitmaps: sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER - - int bytesFileOffset = ((ImagePrivateDataBitmap)Image.Data).Offset; - uint paletteColors = Image.Information.ColorsUsed; - int width = (int)Image.Information.Width; - int height = (int)Image.Information.Height; - - MonochromeMask? mask = checkTransparency ? new MonochromeMask(width, height) : null; - - bool isGray = bits == 8 && (paletteColors == 256 || paletteColors == 0); - int isBitonal = 0; // 0: false; >0: true; <0: true (inverted) - byte[] paletteData = new byte[3 * paletteColors]; - for (int color = 0; color < paletteColors; ++color) - { - paletteData[3 * color] = Data[bytesColorPaletteOffset + 4 * color + 2]; - paletteData[3 * color + 1] = Data[bytesColorPaletteOffset + 4 * color + 1]; - paletteData[3 * color + 2] = Data[bytesColorPaletteOffset + 4 * color + 0]; - if (isGray) - isGray = paletteData[3 * color] == paletteData[3 * color + 1] && - paletteData[3 * color] == paletteData[3 * color + 2]; - - if (checkTransparency && Data[bytesColorPaletteOffset + 4 * color + 3] < 128) - { - // We treat this as transparency: - if (firstMaskColor == -1) - firstMaskColor = color; - if (lastMaskColor == -1 || lastMaskColor == color - 1) - lastMaskColor = color; - if (lastMaskColor != color) - segmentedColorMask = true; - } - //else - //{ - // // We treat this as opacity: - //} - } - - if (bits == 1) - { - if (paletteColors == 0) - isBitonal = 1; - if (paletteColors == 2) - { - if (paletteData[0] == 0 && - paletteData[1] == 0 && - paletteData[2] == 0 && - paletteData[3] == 255 && - paletteData[4] == 255 && - paletteData[5] == 255) - isBitonal = 1; // Black on white - if (paletteData[5] == 0 && - paletteData[4] == 0 && - paletteData[3] == 0 && - paletteData[2] == 255 && - paletteData[1] == 255 && - paletteData[0] == 255) - isBitonal = -1; // White on black - } - } - - // NYI: (no sample found where this was required) - // if (segmentedColorMask = true) - // { ... } - - bool isFaxEncoding = false; - byte[] imageData = new byte[((width * bits + 7) / 8) * height]; - byte[] imageDataFax = null!; - int k = 0; - - if (bits == 1 && options.EnableCcittCompressionForBilevelImages) - { - // TODO_OLD: flag/option? - // We try Group 3 1D and Group 4 (2D) encoding here and keep the smaller byte array. - //byte[] temp = new byte[imageData.Length]; - //int ccittSize = DoFaxEncoding(ref temp, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height); - - // It seems that Group 3 2D encoding never beats both other encodings, therefore we don’t call it here. - //byte[] temp2D = new byte[imageData.Length]; - //uint dpiY = (uint)image.VerticalResolution; - //uint kTmp = 0; - //int ccittSize2D = DoFaxEncoding2D((uint)bytesFileOffset, ref temp2D, imageBits, (uint)width, (uint)height, dpiY, out kTmp); - //k = (int) kTmp; - - byte[] tempG4 = new byte[imageData.Length]; - int ccittSizeG4 = PdfImage.DoFaxEncodingGroup4(ref tempG4, Data, (uint)bytesFileOffset, (uint)width, (uint)height); - - isFaxEncoding = /*ccittSize > 0 ||*/ ccittSizeG4 > 0; - if (isFaxEncoding) - { - //if (ccittSize == 0) - // ccittSize = 0x7fffffff; - if (ccittSizeG4 == 0) - ccittSizeG4 = 0x7fffffff; - //if (ccittSize <= ccittSizeG4) - //{ - // Array.Resize(ref temp, ccittSize); - // imageDataFax = temp; - // k = 0; - //} - //else - { - Array.Resize(ref tempG4, ccittSizeG4); - imageDataFax = tempG4; - k = -1; - } - } - } - - //if (!isFaxEncoding) - { - int bytesOffsetRead = 0; - if (bits == 8 || bits == 4 || bits == 1) - { - int bytesPerLine = (width * bits + 7) / 8; - for (int y = 0; y < height; ++y) - { - mask?.StartLine(y); - int bytesOffsetWrite = (height - 1 - y) * ((width * bits + 7) / 8); - for (int x = 0; x < bytesPerLine; ++x) - { - if (isGray) - { - // Lookup the gray value from the palette: - imageData[bytesOffsetWrite] = paletteData[3 * Data[bytesFileOffset + bytesOffsetRead]]; - } - else - { - // Store the palette index. - imageData[bytesOffsetWrite] = Data[bytesFileOffset + bytesOffsetRead]; - } - if (firstMaskColor != -1) - { - int n = Data[bytesFileOffset + bytesOffsetRead]; - if (bits == 8) - { - // TODO_OLD???: segmentedColorMask == true => bad mask NYI - mask?.AddPel((n >= firstMaskColor) && (n <= lastMaskColor)); - } - else if (bits == 4) - { - // TODO_OLD???: segmentedColorMask == true => bad mask NYI - int n1 = (n & 0xf0) / 16; - int n2 = (n & 0x0f); - mask?.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor)); - mask?.AddPel((n2 >= firstMaskColor) && (n2 <= lastMaskColor)); - } - else if (bits == 1) - { - // TODO_OLD???: segmentedColorMask == true => bad mask NYI - for (int bit = 1; bit <= 8; ++bit) - { - int n1 = (n & 0x80) / 128; - mask?.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor)); - n *= 2; - } - } - } - bytesOffsetRead += 1; - bytesOffsetWrite += 1; - } - bytesOffsetRead = 4 * ((bytesOffsetRead + 3) / 4); // Align to 32 bit boundary - } - } - else - { - throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3"); - } - } - - dest.Data = imageData; - dest.Length = imageData.Length; - - if (imageDataFax != null) - { - dest.DataFax = imageDataFax; - dest.LengthFax = imageDataFax.Length; - } - - dest.IsGray = isGray; - dest.K = k; - dest.IsBitonal = isBitonal; - - dest.PaletteData = paletteData; - dest.PaletteDataLength = paletteData.Length; - dest.SegmentedColorMask = segmentedColorMask; - - //if (alphaMask != null) - //{ - // dest.AlphaMask = alphaMask; - // dest.AlphaMaskLength = alphaMask.Length; - //} - - if (mask != null && mask.MaskUsed && firstMaskColor != -1) - { - dest.BitmapMask = mask.MaskData; - dest.BitmapMaskLength = mask.MaskData.Length; - } - - } - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs deleted file mode 100644 index c7096aed..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterJpeg.cs +++ /dev/null @@ -1,465 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using System; -using System.Text; -using PdfSharp.Pdf; - -namespace PdfSharp.Drawing.Internal -{ - // ReSharper disable once InconsistentNaming - class ImageImporterJpeg : ImageImporterRoot, IImageImporter - { - // TODO_OLD Find information about JPEG2000. - - // Notes: JFIF is big-endian. - - public ImportedImage? ImportImage(StreamReaderHelper stream) - { - try - { - stream.CurrentOffset = 0; - // Test 2 magic bytes. - if (TestFileHeader(stream)) - { - // Skip over 2 magic bytes. - stream.CurrentOffset += 2; - - var ipd = new ImagePrivateDataDct(stream.Data, stream.Length); - var ii = new ImportedImageJpeg(ipd); - ii.Information.DefaultDPI = 72; // Assume 72 DPI if information not provided in the file. - if (TestJfifHeader(stream, ii)) - { - bool colorHeader = false, infoHeader = false; - - while (MoveToNextHeader(stream)) - { - if (TestColorFormatHeader(stream, ii)) - { - colorHeader = true; - } - else if (TestInfoHeader(stream, ii)) - { - infoHeader = true; - } - } - if (colorHeader && infoHeader) - return ii; - } - } - } - // ReSharper disable once EmptyGeneralCatchClause - catch (Exception) - { - // Eat exceptions to have this image importer skipped. - // We try to find an image importer that can handle the image. - } - return null; - } - - bool TestFileHeader(StreamReaderHelper stream) - { - // File must start with 0xffd8. - return stream.GetWord(0, true) == 0xffd8; - } - - bool TestJfifHeader(StreamReaderHelper stream, ImportedImage ii) - { - // Just in case the header is not the first block in the file, search through all blocks now. - //var currentOffset = stream.CurrentOffset; - - bool header = TestJfifHeaderWorker(stream, ii) || - TestExifHeaderWorker(stream/*, ii*/) || - TestApp2HeaderWorker(stream) || - TestApp13HeaderWorker(stream); - - return header; - } - - bool TestJfifHeaderWorker(StreamReaderHelper stream, ImportedImage ii) - { - // The App0 header should be the first header in every JFIF file. - if (stream.GetWord(0, true) == 0xffe0) - { - // TODO_OLD Skip EXIF header if it precedes the JFIF header. - - // Now check for text "JFIF". - if (stream.GetDWord(4, true) == 0x4a464946) - { - int blockLength = stream.GetWord(2, true); - if (blockLength >= 16) - { - // ReSharper disable once UnusedVariable - int version = stream.GetWord(9, true); - int units = stream.GetByte(11); - int densityX = stream.GetWord(12, true); - int densityY = stream.GetWord(14, true); - - switch (units) - { - case 0: // Aspect ratio only. - ii.Information.HorizontalAspectRatio = densityX; - ii.Information.VerticalAspectRatio = densityY; - break; - case 1: // DPI. - ii.Information.HorizontalDPI = densityX; - ii.Information.VerticalDPI = densityY; - break; - case 2: // DPCM. - ii.Information.HorizontalDPM = densityX * 100; - ii.Information.VerticalDPM = densityY * 100; - break; - } - - // More information here? More tests? - return true; - } - } - } - return false; - } - - bool TestExifHeaderWorker(StreamReaderHelper stream/*, ImportedImage ii*/) - { - // The App0 header should be the first header in every JFIF file. - if (stream.GetWord(0, true) == 0xffe1) - { - // Now check for text "Exif". - if (stream.GetDWord(4, true) == 0x45786966) - { - // We expect 00 00 after Exif. - int eos = stream.GetWord(8, true); - // We expect MM or II. - int ft = stream.GetWord(10, true); - if (eos == 0 && - (ft == 0x4d4d || ft == 0x4949)) - { - // More information here? More tests? - // TODO_OLD Find JFIF header in the EXIF header. - // EXIF headers are similar to TIFF. - return true; - } - } - } - return false; - } - - bool TestApp2HeaderWorker(StreamReaderHelper stream) - { - // Check for APP2 header. - if (stream.GetWord(0, true) == 0xffe2) - { - int length = stream.GetWord(2, true); - - StringBuilder identifier = new(); - int idx = 4; - do - { - byte c = stream.GetByte(idx); - if (c == 0x00) - { - break; - } - identifier.Append((char)c); - idx++; - } while (idx < length); - - var id = identifier.ToString(); - if (!id.Equals("ICC_PROFILE")) - { - return false; - } - - return true; - } - return false; - } - - bool TestApp13HeaderWorker(StreamReaderHelper stream) - { - // Check for APP13 header. - if (stream.GetWord(0, true) == 0xffed) - { - int length = stream.GetWord(2, true); - - StringBuilder identifier = new(); - int idx = 4; - do - { - byte c = stream.GetByte(idx); - if (c == 0x00) - { - break; - } - identifier.Append((char)c); - idx++; - } while (idx < length); - - var id = identifier.ToString(); - if (!id.StartsWith("Photoshop ") && !id.StartsWith("Adobe_Photoshop")) // "Photoshop 3.0", "Adobe_Photoshop2.5:", etc. - { - return false; - } - - idx++; - if (idx + 3 < length && stream.GetDWord(idx, true) == 0x3842494d) // 8BIM - { - return true; - } - } - return false; - } - - bool TestColorFormatHeader(StreamReaderHelper stream, ImportedImage ii) - { - // The SOS header (start of scan). - if (stream.GetWord(0, true) == 0xffda) - { - int components = stream.GetByte(4); - if (components < 1 || components > 4 || components == 2) - return false; - // 1 for grayscale, 3 for RGB, 4 for CMYK. - - int blockLength = stream.GetWord(2, true); - // Integrity check: correct size? - if (blockLength != 6 + 2 * components) - return false; - - // Eventually do more tests here. - // Info: we assume that all JPEG files with 4 components are RGBW (inverted CMYK) and not CMYK. - // We add a test to tell CMYK from RGBW when we encounter a test file in CMYK format. - var format = components == 3 - ? ImageInformation.ImageFormats.JPEG - : (components == 1 - ? ImageInformation.ImageFormats.JPEGGRAY - : ImageInformation.ImageFormats.JPEGRGBW); - if (ii.Information.ImageFormat == ImageInformation.ImageFormats.Undefined) - ii.Information.ImageFormat = format; -#if DEBUG - else - { - Debug.Assert(format == ii.Information.ImageFormat); - } -#endif - - return true; - } - return false; - } - - bool TestInfoHeader(StreamReaderHelper stream, ImportedImage ii) - { - // The SOF header (start of frame). - int header = stream.GetWord(0, true); - if (header >= 0xffc0 && header <= 0xffc3 || - header >= 0xffc9 && header <= 0xffcb) - { - // Lines in image. - int sizeY = stream.GetWord(5, true); - // Samples per line. - int sizeX = stream.GetWord(7, true); - - int components = stream.GetByte(9); - if (components is 1 or 3 or 4) - { - // Info: we assume that all JPEG files with 4 components are RGBW (inverted CMYK) and not CMYK. - // We add a test to tell CMYK from RGBW when we encounter a test file in CMYK format. - var format = components == 3 - ? ImageInformation.ImageFormats.JPEG - : (components == 1 - ? ImageInformation.ImageFormats.JPEGGRAY - : ImageInformation.ImageFormats.JPEGRGBW); - - // We have more faith in the information from the SOF header than the SOS header. -#if DEBUG - // Assert information in Debug mode. - if (ii.Information.ImageFormat != ImageInformation.ImageFormats.Undefined) - { - Debug.Assert(format == ii.Information.ImageFormat); - } -#endif - // Always use information from SOF header. Just overwrite previously set format, maybe from SOS header. - ii.Information.ImageFormat = format; - } - - ii.Information.Width = (uint)sizeX; - ii.Information.Height = (uint)sizeY; - - return true; - } - return false; - } - - bool MoveToNextHeader(StreamReaderHelper stream) - { - int blockLength = stream.GetWord(2, true); - - int headerMagic = stream.GetByte(0); - int headerType = stream.GetByte(1); - - if (headerMagic == 0xff) - { - // EOI: last header. - if (headerType == 0xd9) - return false; - - // 0xff followed by 0x00 is not a valid JPEG tag. Skip this entry. - // If the value 0xFF is ever needed in a JPEG file, it must be escaped by immediately - // following it with 0x00. This is called "byte stuffing". - // Source: https://www.ccoderun.ca/programming/2017-01-31_jpeg/ - // Check for standalone markers 0x01 and 0xd0 through 0xd7. - if (headerType is 0x00 or 0x01 or >= 0xd0 and <= 0xd7) - { - stream.CurrentOffset += 2; - return true; - } - - // Now assume header with block size. - stream.CurrentOffset += 2 + blockLength; - return true; - } - return false; - } - - public ImageData PrepareImage(ImagePrivateData data) - { - throw new NotImplementedException(); - } - - //int GetJpgSizeTestCode(byte[] pData, uint FileSizeLow, out int pWidth, out int pHeight) - //{ - // pWidth = -1; - // pHeight = -1; - - // int i = 0; - - // if ((pData[i] == 0xFF) && (pData[i + 1] == 0xD8) && (pData[i + 2] == 0xFF) && (pData[i + 3] == 0xE0)) - // { - // i += 4; - - // // Check for valid JPEG header (null terminated JFIF) - // if ((pData[i + 2] == 'J') && (pData[i + 3] == 'F') && (pData[i + 4] == 'I') && (pData[i + 5] == 'F') - // && (pData[i + 6] == 0x00)) - // { - - // //Retrieve the block length of the first block since the first block will not contain the size of file - // int block_length = pData[i] * 256 + pData[i + 1]; - - // while (i < FileSizeLow) - // { - // //Increase the file index to get to the next block - // i += block_length; - - // if (i >= FileSizeLow) - // { - // //Check to protect against segmentation faults - // return -1; - // } - - // if (pData[i] != 0xFF) - // { - // return -2; - // } - - // if (pData[i + 1] == 0xC0) - // { - // //0xFFC0 is the "Start of frame" marker which contains the file size - // //The structure of the 0xFFC0 block is quite simple [0xFFC0][ushort length][uchar precision][ushort x][ushort y] - // pHeight = pData[i + 5] * 256 + pData[i + 6]; - // pWidth = pData[i + 7] * 256 + pData[i + 8]; - - // return 0; - // } - // else - // { - // i += 2; //Skip the block marker - - // //Go to the next block - // block_length = pData[i] * 256 + pData[i + 1]; - // } - // } - - // //If this point is reached then no size was found - // return -3; - // } - // else - // { - // return -4; - // } //Not a valid JFIF string - // } - // else - // { - // return -5; - // } //Not a valid SOI header - - // //return -6; - //} // GetJpgSize - } - - /// - /// Imported JPEG image. - /// - class ImportedImageJpeg : ImportedImage - { - /// - /// Initializes a new instance of the class. - /// - public ImportedImageJpeg(ImagePrivateDataDct data) - : base(data) - { } - - internal override ImageData PrepareImageData(PdfDocumentOptions options) - { - var data = (ImagePrivateDataDct?)Data ?? NRT.ThrowOnNull(); - var imageData = new ImageDataDct - { - Data = data.Data, - Length = data.Length - }; - - return imageData; - } - } - - /// - /// Contains data needed for PDF. Will be prepared when needed. - /// - class ImageDataDct : ImageData - { - /// - /// Gets the data. - /// - public byte[] Data { get; internal set; } = null!; - - /// - /// Gets the length. - /// - public int Length { get; internal set; } - } - - /*internal*/ - /// - /// Private data for JPEG images. - /// - class ImagePrivateDataDct : ImagePrivateData - { - /// - /// Initializes a new instance of the class. - /// - public ImagePrivateDataDct(byte[] data, int length) - { - Data = data; - Length = length; - } - - /// - /// Gets the data. - /// - public byte[] Data { get; } - - /// - /// Gets the length. - /// - public int Length { get; } - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterRoot.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterRoot.cs deleted file mode 100644 index a3413db0..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterRoot.cs +++ /dev/null @@ -1,9 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -namespace PdfSharp.Drawing.Internal -{ - abstract class ImageImporterRoot - { - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Layout/XTextFormatter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Layout/XTextFormatter.cs index bdb8b5f5..f0d6fe5a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Layout/XTextFormatter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Layout/XTextFormatter.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; -using System.Collections.Generic; using PdfSharp.Pdf.IO; namespace PdfSharp.Drawing.Layout @@ -167,7 +165,7 @@ void CreateBlocks() blockLength = 0; _blocks.Add(new Block(BlockType.LineBreak)); } - // The non-breaking space is whitespace, so we treat it like non-whitespace. + // The non-breaking space is whitespace, so we treat it like non-white-space. else if (ch != Chars.NonBreakableSpace && Char.IsWhiteSpace(ch)) { if (inNonWhiteSpace) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs index 69fcc3f7..fa894a01 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs @@ -27,6 +27,11 @@ namespace PdfSharp.Drawing.Pdf /// sealed class PdfGraphicsState(XGraphicsPdfRenderer renderer) : ICloneable { + const string DefaultNumberFormat2 = Config.SignificantDecimalPlaces2; + const string DefaultNumberFormat3 = Config.SignificantDecimalPlaces3; + const string DefaultNumberFormat4 = Config.SignificantDecimalPlaces4; + const string DefaultNumberFormat7 = Config.SignificantDecimalPlaces7; + public PdfGraphicsState Clone() { var state = (PdfGraphicsState)MemberwiseClone(); @@ -37,7 +42,7 @@ public PdfGraphicsState Clone() internal int Level; - internal InternalGraphicsState InternalState = default!; + internal InternalGraphicsState InternalState = null!; public void PushState() { @@ -58,14 +63,14 @@ public void PopState() int _realizedLineJoin = -1; double _realizedMiterLimit = -1; XDashStyle _realizedDashStyle = (XDashStyle)(-1); - string _realizedDashPattern = default!; - XColor _realizedStrokeColor = XColor.Empty; + string _realizedDashPattern = null!; + XColor? _realizedStrokeColor; // Null means no stroke color has been realized yet. bool _realizedStrokeOverPrint; public void RealizePen(XPen pen, PdfColorMode colorMode) { - const string format = Config.SignificantDecimalPlaces3; - const string format2 = Config.SignificantDecimalPlaces2; + const string format = DefaultNumberFormat3; + const string format2 = DefaultNumberFormat2; XColor color = pen.Color; bool overPrint = pen.Overprint; @@ -165,7 +170,7 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) if (colorMode != PdfColorMode.Cmyk) { - if (_realizedStrokeColor.Rgb != color.Rgb) + if (_realizedStrokeColor == null || _realizedStrokeColor.Value.Rgb != color.Rgb) { renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb)); renderer.Append(" RG\n"); @@ -173,21 +178,25 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) } else { - if (!ColorSpaceHelper.IsEqualCmyk(_realizedStrokeColor, color)) + if (_realizedStrokeColor == null || !ColorSpaceHelper.IsEqualCmyk(_realizedStrokeColor.Value, color)) { renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk)); renderer.Append(" K\n"); } } - if (renderer.Owner.Version >= 14 && (_realizedStrokeColor.A != color.A || _realizedStrokeOverPrint != overPrint)) + if (renderer.Owner.Version >= 14 // CA and ca keys of PdfExtGState are supported since PDF version 1.4. + && (// Add PdfExtGState, if no stroke color has been realized yet... + _realizedStrokeColor == null + // or if alpha or overPrint value has changed. + || _realizedStrokeColor.Value.A != color.A || _realizedStrokeOverPrint != overPrint)) { PdfExtGState extGState = renderer.Owner.ExtGStateTable.GetExtGStateStroke(color.A, overPrint); string gs = renderer.Resources.AddExtGState(extGState); renderer.AppendFormatString("{0} gs\n", gs); // Must create transparency group. - if (renderer._page != null! && color.A < 1) + if (renderer._page != null! && color.A < 1.0) renderer._page.TransparencyUsed = true; } _realizedStrokeColor = color; @@ -198,7 +207,7 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) #region Fill - XColor _realizedFillColor = XColor.Empty; + XColor? _realizedFillColor; // Null means no fill color has been realized yet. bool _realizedNonStrokeOverPrint; public void RealizeBrush(XBrush brush, PdfColorMode colorMode, int renderingMode, double fontEmSize) @@ -253,7 +262,7 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode) if (colorMode != PdfColorMode.Cmyk) { - if (_realizedFillColor.IsEmpty || _realizedFillColor.Rgb != color.Rgb) + if (_realizedFillColor == null || _realizedFillColor.Value.Rgb != color.Rgb) { renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb)); renderer.Append(" rg\n"); @@ -263,16 +272,19 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode) { Debug.Assert(colorMode == PdfColorMode.Cmyk); - if (_realizedFillColor.IsEmpty || !ColorSpaceHelper.IsEqualCmyk(_realizedFillColor, color)) + if (_realizedFillColor == null || !ColorSpaceHelper.IsEqualCmyk(_realizedFillColor.Value, color)) { renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk)); renderer.Append(" k\n"); } } - if (renderer.Owner.Version >= 14 && (_realizedFillColor.A != color.A || _realizedNonStrokeOverPrint != overPrint)) + if (renderer.Owner.Version >= 14 // CA and ca keys of PdfExtGState are supported since PDF version 1.4. + && (// Add PdfExtGState, if no fill color has been realized yet... + _realizedFillColor == null + // or if alpha or overPrint value has changed. + || _realizedFillColor.Value.A != color.A || _realizedNonStrokeOverPrint != overPrint)) { - PdfExtGState extGState = renderer.Owner.ExtGStateTable.GetExtGStateNonStroke(color.A, overPrint); string gs = renderer.Resources.AddExtGState(extGState); renderer.AppendFormatString("{0} gs\n", gs); @@ -288,7 +300,7 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode) internal void RealizeNonStrokeTransparency(double transparency, PdfColorMode colorMode) { - XColor color = _realizedFillColor; + XColor color = _realizedFillColor ?? XColor.Empty; // Use empty color as origin if no fill color has been realized yet. color.A = transparency; RealizeFillColor(color, _realizedNonStrokeOverPrint, colorMode); } @@ -305,9 +317,11 @@ internal void RealizeNonStrokeTransparency(double transparency, PdfColorMode col int _realizedRenderingMode; // Reference: TABLE 5.2 Text state operators / Page 398 double _realizedCharSpace; // Reference: TABLE 5.2 Text state operators / Page 398 + internal string RealizedFontName => _realizedFontName; + public void RealizeFont(XGlyphTypeface glyphTypeface, double emSize, XBrush brush, int renderingMode, FontType fontType) { - const string format = Config.SignificantDecimalPlaces3; + const string format = DefaultNumberFormat3; // So far rendering mode 0 (fill text) and 2 (fill, then stroke text) only. RealizeBrush(brush, renderer._colorMode, renderingMode, emSize); // _renderer.page.document.Options.ColorMode); @@ -342,26 +356,23 @@ public void RealizeFont(XGlyphTypeface glyphTypeface, double emSize, XBrush brus string fontName = renderer.GetFontName(glyphTypeface, fontType, out _realizedFont); if (fontName != _realizedFontName || _realizedFontSize != emSize) { - s_formatTf ??= "{0} {1:" + format + "} Tf\n"; + const string formatTf = "{0} {1:" + format + "} Tf\n"; if (renderer.Gfx.PageDirection == XPageDirection.Downwards) { // earlier: // renderer.AppendFormatFont("{0} {1:" + format + "} Tf\n", fontName, emSize); - renderer.AppendFormatFont(s_formatTf, fontName, emSize); + renderer.AppendFormatFont(formatTf, fontName, emSize); } else { // earlier: // renderer.AppendFormatFont("{0} {1:" + format + "} Tf\n", fontName, emSize); - renderer.AppendFormatFont(s_formatTf, fontName, emSize); + renderer.AppendFormatFont(formatTf, fontName, emSize); } _realizedFontName = fontName; _realizedFontSize = emSize; } } - // ReSharper disable InconsistentNaming - static string? s_formatTf; - // ReSharper restore InconsistentNaming public XPoint RealizedTextPosition; @@ -432,8 +443,15 @@ public void AddTransform(XMatrix value, XMatrixOrder matrixOrder) { // Take chirality into account and // invert the direction of rotation. +#if true + // Prevent getting negative zeros in cm operator. + transform.M12 = value.M12 == 0.0 ? 0 : -value.M12; + transform.M21 = value.M21 == 0.0 ? 0 : -value.M21; +#else + // Can result in e.g. "1 -0 -0 1 200 150 cm". transform.M12 = -value.M12; transform.M21 = -value.M21; +#endif } UnrealizedCtm.Prepend(transform); @@ -450,16 +468,16 @@ public void RealizeCtm() { Debug.Assert(!UnrealizedCtm.IsIdentity, "mrCtm is unnecessarily set."); - const string format = Config.SignificantDecimalPlaces7; - s_formatCtm ??= "{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" - + format + "} {5:" + format + "} cm\n"; + const string format = DefaultNumberFormat7; + const string formatCtm = + "{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm\n"; double[] matrix = UnrealizedCtm.GetElements(); // Use up to six decimal digits to prevent round up problems. // earlier: // renderer.AppendFormatArgs("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm\n", // matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); - renderer.AppendFormatArgs(s_formatCtm, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); + renderer.AppendFormatArgs(formatCtm, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); RealizedCtm.Prepend(UnrealizedCtm); UnrealizedCtm = new XMatrix(); @@ -469,7 +487,7 @@ public void RealizeCtm() } } // ReSharper disable InconsistentNaming - static string? s_formatCtm; + //static string? s_formatCtm; // ReSharper restore InconsistentNaming #endregion diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs index 32a856c6..90e19214 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs @@ -4,9 +4,14 @@ #define ITALIC_SIMULATION using System.Text; -using Microsoft.Extensions.Logging; +using PdfSharp.Internal; +using PdfSharp.Internal.OpenType; using PdfSharp.Events; using PdfSharp.Fonts.Internal; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Internal; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; #if GDI using System.Drawing.Drawing2D; #endif @@ -20,29 +25,37 @@ using SysPoint = Windows.Foundation.Point; using SysSize = Windows.Foundation.Size; #endif -using PdfSharp.Fonts.OpenType; -using PdfSharp.Internal; -using PdfSharp.Logging; -using PdfSharp.Fonts; -using PdfSharp.Pdf; -using PdfSharp.Pdf.Internal; -using PdfSharp.Pdf.Advanced; -using PdfSharp.Pdf.IO; // ReSharper disable RedundantNameQualifier // ReSharper disable CompareOfFloatsByEqualityOperator +namespace PdfSharp.Pdf // #FILE +{ + /// + /// Either XGraphics or DrawingContext object. + /// + internal interface IPageContentRenderer + { + // Just a marker interface. + } +} + namespace PdfSharp.Drawing.Pdf { /// /// Represents a drawing surface for PdfPages. /// - class XGraphicsPdfRenderer : IXGraphicsRenderer + class XGraphicsPdfRenderer : IXGraphicsRenderer, IPageContentRenderer { + const string DefaultNumberFormat2 = Config.SignificantDecimalPlaces2; + const string DefaultNumberFormat3 = Config.SignificantDecimalPlaces3; + const string DefaultNumberFormat4 = Config.SignificantDecimalPlaces4; + const string DefaultNumberFormat7 = Config.SignificantDecimalPlaces7; + public XGraphicsPdfRenderer(PdfPage page, XGraphics gfx, XGraphicsPdfPageOptions options) { _page = page; - _colorMode = page._document.Options.ColorMode; + _colorMode = page.Document.Options.ColorMode; _options = options; _gfx = gfx; _content = new StringBuilder(); @@ -75,12 +88,30 @@ public void Close() { if (_page != null!) { - var content2 = _page.RenderContent!; // NRT - content2.CreateStream(PdfEncoders.RawEncoding.GetBytes(GetContent())); + var renderer = _page.RenderContent?.Renderer; + if (!ReferenceEquals(renderer, this)) + { + throw new InvalidOperationException( + "This XGraphicsPdfRenderer is not the owner of currently rendered content stream. " + + "Maybe a PDFsharp Graphics DrawingContext is still open."); + } + var content = _page.RenderContent; + Debug.Assert(content != null); + var contentString = GetContent(); + // Create stream of content even if contentString is empty, but remove content below + // except it is the only content stream. + content.CreateStream(PdfEncoders.RawEncoding.GetBytes(contentString)); _gfx = null!; - _page.RenderContent!.SetRenderer(null); - _page.RenderContent = null!; + content.SetRenderer(null); + _page.RenderContent = null; + // Remove empty content stream, but only if page has at least another content stream. + if (contentString.Length == 0 && _page.Contents.Elements.Count > 1) + { + _page.Contents.Elements.Remove(content); + Debug.Assert(_page.Contents.Elements.Count != 0); + } + content = null!; _page = null!; } else if (_form != null!) @@ -116,7 +147,7 @@ public void DrawLine(XPen pen, double x1, double y1, double x2, double y2) public void DrawLines(XPen pen, XPoint[] points) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); if (pen == null) @@ -130,7 +161,7 @@ public void DrawLines(XPen pen, XPoint[] points) Realize(pen); - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); for (int idx = 1; idx < count; idx++) AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y); @@ -149,7 +180,7 @@ public void DrawBezier(XPen pen, double x1, double y1, double x2, double y2, dou public void DrawBeziers(XPen pen, XPoint[] points) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); if (pen == null) @@ -166,7 +197,7 @@ public void DrawBeziers(XPen pen, XPoint[] points) Realize(pen); - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); for (int idx = 1; idx < count; idx += 3) AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", @@ -182,7 +213,7 @@ public void DrawBeziers(XPen pen, XPoint[] points) public void DrawCurve(XPen pen, XPoint[] points, double tension) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); if (pen == null) @@ -201,7 +232,7 @@ public void DrawCurve(XPen pen, XPoint[] points, double tension) Realize(pen); - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); if (count == 2) { @@ -223,7 +254,7 @@ public void DrawCurve(XPen pen, XPoint[] points, double tension) public void DrawArc(XPen pen, double x, double y, double width, double height, double startAngle, double sweepAngle) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); if (pen == null) @@ -240,7 +271,7 @@ public void DrawArc(XPen pen, double x, double y, double width, double height, d public void DrawRectangle(XPen? pen, XBrush? brush, double x, double y, double width, double height) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); if (pen == null && brush == null) @@ -248,7 +279,7 @@ public void DrawRectangle(XPen? pen, XBrush? brush, double x, double y, double w throw new ArgumentNullException(nameof(pen) + " and " + nameof(brush)); } - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; Realize(pen, brush); //AppendFormat123("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} re\n", x, y, width, -height); @@ -279,7 +310,7 @@ public void DrawRectangles(XPen? pen, XBrush? brush, XRect[] rects) public void DrawRoundedRectangle(XPen? pen, XBrush? brush, double x, double y, double width, double height, double ellipseWidth, double ellipseHeight) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); XGraphicsPath path = new XGraphicsPath(); @@ -292,7 +323,7 @@ public void DrawRoundedRectangle(XPen? pen, XBrush? brush, double x, double y, d public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double width, double height) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); Realize(pen, brush); @@ -309,7 +340,7 @@ public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double wid double y0 = rect.Y + δy; // Approximate an ellipse by drawing four cubic splines. - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", x0 + δx, y0); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", x0 + δx, y0 + fy, x0 + fx, y0 + δy, x0, y0 + δy); @@ -327,7 +358,7 @@ public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double wid public void DrawPolygon(XPen? pen, XBrush? brush, XPoint[] points, XFillMode fillmode) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); Realize(pen, brush); @@ -336,7 +367,7 @@ public void DrawPolygon(XPen? pen, XBrush? brush, XPoint[] points, XFillMode fil if (points.Length < 2) throw new ArgumentException(PsMsgs.PointArrayAtLeast(2), nameof(points)); - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); for (int idx = 1; idx < count; idx++) AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y); @@ -350,12 +381,12 @@ public void DrawPie(XPen? pen, XBrush? brush, double x, double y, double width, double startAngle, double sweepAngle) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); Realize(pen, brush); - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", x + width / 2, y + height / 2); AppendPartialArc(x, y, width, height, startAngle, sweepAngle, PathStart.LineTo1st, new XMatrix()); AppendStrokeFill(pen, brush, XFillMode.Alternate, true); @@ -366,7 +397,7 @@ public void DrawPie(XPen? pen, XBrush? brush, double x, double y, double width, public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, double tension, XFillMode fillmode) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); int count = points.Length; @@ -380,7 +411,7 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, double te Realize(pen, brush); - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); if (count == 2) { @@ -403,7 +434,7 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, double te public void DrawPath(XPen? pen, XBrush? brush, XGraphicsPath path) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); if (pen == null && brush == null) @@ -444,7 +475,7 @@ public void DrawPath(XPen? pen, XBrush? brush, XGraphicsPath path) public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFormat format) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.DrawString }); font.CheckVersion(); @@ -652,7 +683,7 @@ public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFo Debug.Assert(textParts.Sum(p => p.Count) == glyphCount, "Character count mismatch."); - const string format2 = Config.SignificantDecimalPlaces4; + const string format2 = DefaultNumberFormat4; var layerBytes = new byte[2]; foreach (var textPart in textParts) { @@ -670,7 +701,7 @@ public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFo if (layer.paletteIndex != 0xffff) { var color = otDescriptor.FontFace.cpal!.colorRecords[layer.paletteIndex]; - _gfxState.RealizeBrush(new XSolidBrush(color), _colorMode, 0, 0); + _gfxState.RealizeBrush(new XSolidBrush(XColor.FromArgb(color.Argb)), _colorMode, 0, 0); } else { @@ -744,7 +775,7 @@ void RenderText(string text, XFont font, XBrush brush, double x, double y, doubl } // Select the number of decimal places used for the relative text positioning. - const string formatTj = Config.SignificantDecimalPlaces4; + const string formatTj = DefaultNumberFormat4; #if ITALIC_SIMULATION if (italicSimulation) { @@ -805,8 +836,8 @@ void RenderText(string text, XFont font, XBrush brush, double x, double y, doubl } } #else - AdjustTextMatrix(ref pos); - AppendFormat2("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text); + AdjustTextMatrix(ref pos); + AppendFormat2("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text); #endif if (underline) { @@ -880,10 +911,10 @@ public void DrawString(string s, XGlyphTypeface typeface, XBrush brush, XRect re public void DrawImage(XImage image, double x, double y, double width, double height) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; string name = Realize(image); if (image is not XForm form) @@ -945,10 +976,10 @@ public void DrawImage(XImage image, double x, double y, double width, double hei public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit srcUnit) { // #PDF-UA - if (Owner._uaManager != null) + if (Owner.UAManager != null) Owner.Events.OnPageGraphicsAction(Owner, new PageGraphicsEventArgs(Owner) { Page = _page, Graphics = Gfx, ActionType = PageGraphicsActionType.Draw }); - const string format = Config.SignificantDecimalPlaces4; + const string numFormat = DefaultNumberFormat4; double x = destRect.X; double y = destRect.Y; @@ -960,13 +991,23 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit { if (_gfx.PageDirection == XPageDirection.Downwards) { - AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do\nQ\n", +#if true + const string format = "q {2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n"; + AppendFormatImage(format, x, y + height, width, height, name); +#else + AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", x, y + height, width, height, name); +#endif } else { +#if true + const string format = "q {2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n"; + AppendFormatImage(format, x, y, width, height, name); +#else AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", x, y, width, height, name); +#endif } } else @@ -984,7 +1025,13 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit { var xForm = form as XPdfForm; // Reset colors in this graphics state. Usually PDF images should set them, but in rare cases they don’t and this may result in changed colors inside the image. +#if true + bool resetColor = xForm != null; + const string resetColorCode = "\n0 g\n0 G\n"; +#else + bool resetColorXX = xForm != null; var resetColor = xForm != null ? "\n0 g\n0 G\n" : " "; +#endif if (_gfx.PageDirection == XPageDirection.Downwards) { @@ -996,14 +1043,27 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit xDraw -= xForm.Page!.MediaBox.X1; yDraw += xForm.Page.MediaBox.Y1; } +#if true + const string formatReset = "q" + resetColorCode + "{2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n"; + const string format = "q {2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n"; + + AppendFormatImage(resetColor ? formatReset : format, xDraw, yDraw + height, cx, cy, name); +#else AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", xDraw, yDraw + height, cx, cy, name); +#endif } else { // TODO_OLD Translation for MediaBox. +#if true + const string formatReset = "q" + resetColorCode + "{2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n"; + const string format = "q {2:" + numFormat + "} 0 0 {3:" + numFormat + "} {0:" + numFormat + "} {1:" + numFormat + "} cm {4} Do Q\n"; + AppendFormatImage(resetColor ? formatReset : format, x, y, cx, cy, name); +#else AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", x, y, cx, cy, name); +#endif } } } @@ -1187,7 +1247,7 @@ void AppendPartialArc(double x, double y, double width, double height, double st α = α + (1 + Math.Floor((Math.Abs(α) / 360))) * 360; else if (α > 360) α = α - Math.Floor(α / 360) * 360; - Debug.Assert(α >= 0 && α <= 360); + Debug.Assert(α is >= 0 and <= 360); double β = sweepAngle; if (β < -360) @@ -1335,7 +1395,7 @@ void AppendPartialArcQuadrant(double x, double y, double width, double height, d sinβ = Math.Sin(β); double cosβ = Math.Cos(β); - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; XPoint pt1, pt2, pt3; if (!reflect) { @@ -1391,7 +1451,7 @@ void AppendPartialArcQuadrant(double x, double y, double width, double height, d void AppendPartialArc(SysPoint point1, SysPoint point2, double rotationAngle, SysSize size, bool isLargeArc, SweepDirection sweepDirection, PathStart pathStart) { - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; Debug.Assert(pathStart == PathStart.Ignore1st); @@ -1417,7 +1477,7 @@ void AppendPartialArc(SysPoint point1, SysPoint point2, double rotationAngle, /// void AppendCurveSegment(XPoint pt0, XPoint pt1, XPoint pt2, XPoint pt3, double tension3) { - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", pt1.X + tension3 * (pt2.X - pt0.X), pt1.Y + tension3 * (pt2.Y - pt0.Y), pt2.X - tension3 * (pt3.X - pt1.X), pt2.Y - tension3 * (pt3.Y - pt1.Y), @@ -1595,7 +1655,7 @@ internal void AppendPath(GraphicsPath path) #if CORE || GDI void AppendPath(XPoint[] points, byte[] types) { - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; int count = points.Length; if (count == 0) return; @@ -1649,7 +1709,7 @@ void AppendPath(XPoint[] points, byte[] types) /// internal void AppendPath(PathGeometry geometry) { - const string format = Config.SignificantDecimalPlaces4; + const string format = DefaultNumberFormat4; foreach (PathFigure figure in geometry.Figures) { @@ -1825,11 +1885,6 @@ internal void Append(string value) internal void AppendFormatArgs(string format, params object[] args) { _content.AppendFormat(CultureInfo.InvariantCulture, format, args); -#if DEBUG_ - string dummy = _content.ToString(); - dummy = dummy.Substring(Math.Max(0, dummy.Length - 100)); - dummy.GetType(); -#endif } internal void AppendFormatString(string format, string s) @@ -1946,6 +2001,8 @@ internal void BeginPage() DefaultViewMatrix = new XMatrix(); if (_gfx.PageDirection == XPageDirection.Downwards) { + // Case Downwards, the default and only implemented direction. + // Take TrimBox into account. PageHeightPt = Size.Height; XPoint trimOffset = new XPoint(); @@ -1993,20 +2050,16 @@ internal void BeginPage() if (!DefaultViewMatrix.IsIdentity) { Debug.Assert(_gfxState.RealizedCtm.IsIdentity); - //_gfxState.RealizedCtm = DefaultViewMatrix; - const string format = Config.SignificantDecimalPlaces7; + const string format = DefaultNumberFormat7; double[] cm = DefaultViewMatrix.GetElements(); AppendFormatArgs("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ", cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]); } - - // Set page transformation - //double[] cm = DefaultViewMatrix.GetElements(); - //AppendFormat("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ", - // cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]); } else { + // Case Upwards - not implemented. + // Scale with page units. switch (_gfx.PageUnit) { @@ -2035,7 +2088,7 @@ internal void BeginPage() // Save initial graphic state. SaveState(); // Set page transformation. - const string format = Config.SignificantDecimalPlaces7; + const string format = DefaultNumberFormat7; double[] cm = DefaultViewMatrix.GetElements(); AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} cm ", cm[0], cm[1], cm[2], cm[3], cm[4], cm[5]); @@ -2075,13 +2128,8 @@ internal void BeginGraphicMode() if (_streamMode == StreamMode.Graphic) return; - Debug.Assert(_streamMode == StreamMode.Text, "Undefined stream mode. Check what happened."); - - // Why the check? - if (_streamMode == StreamMode.Text) - _content.Append("ET\n"); - _streamMode = StreamMode.Graphic; + _content.Append("ET\n"); } /// @@ -2092,10 +2140,9 @@ internal void BeginTextMode() if (_streamMode == StreamMode.Text) return; - Debug.Assert(_streamMode == StreamMode.Graphic, "Undefined stream mode. Check what happened."); - _streamMode = StreamMode.Text; _content.Append("BT\n"); + // Text matrix is empty after BT. _gfxState.RealizedTextPosition = new XPoint(); _gfxState.ItalicSimulationOn = false; @@ -2409,6 +2456,7 @@ PdfGraphicsState RestoreState(InternalGraphicsState state) return top; } + internal PdfGraphicsState PdfGraphicsState => _gfxState; /// /// The current graphical state. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/GeometryHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/GeometryHelper.cs index 61233900..b31b416b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/GeometryHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/GeometryHelper.cs @@ -410,7 +410,7 @@ public static List BezierCurveFromArc(XPoint point1, XPoint point2, XSiz // Get vector from chord to center. XVector vectRotated; - // (comparing two Booleans here!) + // (comparing two booleans here!) if (isLargeArc == isCounterclockwise) vectRotated = new XVector(-vect.Y, vect.X); else @@ -430,7 +430,7 @@ public static List BezierCurveFromArc(XPoint point1, XPoint point2, XSiz double α = Math.Atan2(pt1.Y - center.Y, pt1.X - center.X); double β = Math.Atan2(pt2.Y - center.Y, pt2.X - center.X); - // (another comparison of two Booleans!) + // (another comparison of two booleans!) if (isLargeArc == (Math.Abs(β - α) < Math.PI)) { if (α < β) @@ -605,9 +605,9 @@ public static double dist = (4/3)*(R - Rcos(a/2)) / Rsin(a/2) and use some trig: - __________ + __________ cos(a/2) = \/1 + cos(a) / 2 - ________________ __________ + ________________ __________ R*cos(a/2) = \/R^2 + R^2 cos(a) / 2 = \/R^2 + rDot / 2 */ double cos = (radSquared + dot) / 2; // =(R*cos(a))^2 diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs index 92572bcb..38eec1b1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapEncoder.cs @@ -3,6 +3,7 @@ using PdfSharp.Internal; #if GDI +using PdfSharp.Internal.Threading; using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs index 817e5a08..1335e54f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapImage.cs @@ -1,15 +1,15 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -#if GDI + using PdfSharp.Internal; +using PdfSharp.Internal.Threading; +#if GDI #endif #if WPF -using PdfSharp.Internal; #endif #if WUI using Windows.UI.Xaml.Media.Imaging; -using PdfSharp.Internal; #endif namespace PdfSharp.Drawing diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs index b4c7b117..724cf93b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBitmapSource.cs @@ -1,8 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -#if CORE -#endif +using PdfSharp.Internal.Threading; #if GDI using PdfSharp.Internal; #endif @@ -29,8 +28,9 @@ public override int PixelWidth get { #if CORE - if (_importedImage != null) - return (int)_importedImage.Information.Width; + if (_importedImage!= null) + return _importedImage.PixelWidth; + return 100; #endif #if GDI && !WPF @@ -65,7 +65,8 @@ public override int PixelHeight { #if CORE if (_importedImage != null) - return (int)_importedImage.Information.Height; + return _importedImage.PixelHeight; + return 100; #endif #if GDI && !WPF diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBrush.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBrush.cs index 2304ed2e..754eea27 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBrush.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XBrush.cs @@ -37,11 +37,11 @@ public static implicit operator XBrush(Brush brush) else if (brush is LinearGradientBrush lgBrush) { // xbrush = new LinearGradientBrush(lgBrush.Rectangle, lgBrush.co(solidBrush.Color); - throw new NotImplementedException("XBrush type not yet supported by PDFsharp."); + throw new NotSupportedException("XBrush type not yet supported by PDFsharp."); } else { - throw new NotImplementedException("XBrush type not supported by PDFsharp."); + throw new NotSupportedException("XBrush type not supported by PDFsharp."); } return xbrush; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs index 7f162c37..8ea51b0c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XColor.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the solution root for more information. using System.ComponentModel; +using PdfSharp.Internal; #if GDI using System.Drawing; #endif @@ -125,18 +126,15 @@ internal XColor(XKnownColor knownColor) /// /// Creates an XColor structure from a 32-bit ARGB value. /// - public static XColor FromArgb(int argb) - { - return new XColor((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb)); - } + [Obsolete("Use FromArgb(uint argb).")] + public static XColor FromArgb(int argb) + => new((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb)); /// /// Creates an XColor structure from a 32-bit ARGB value. /// - public static XColor FromArgb(uint argb) - { - return new XColor((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb)); - } + public static XColor FromArgb(uint argb) + => new((byte)(argb >> 24), (byte)(argb >> 16), (byte)(argb >> 8), (byte)(argb)); // from System.Drawing.Color //public static XColor FromArgb(int alpha, Color baseColor); @@ -732,27 +730,6 @@ public double GS /// public static readonly XColor Empty = new(); - /// - /// Special property for XmlSerializer only. - /// - public string RgbCmykG - { - get => Invariant($"{_r};{_g};{_b};{_c:0.###};{_m:0.###};{_y:0.###};{_k:0.###};{_gs:0.###};{_a:0.###}"); - set - { - string[] values = value.Split(';'); - _r = byte.Parse(values[0], CultureInfo.InvariantCulture); - _g = byte.Parse(values[1], CultureInfo.InvariantCulture); - _b = byte.Parse(values[2], CultureInfo.InvariantCulture); - _c = float.Parse(values[3], CultureInfo.InvariantCulture); - _m = float.Parse(values[4], CultureInfo.InvariantCulture); - _y = float.Parse(values[5], CultureInfo.InvariantCulture); - _k = float.Parse(values[6], CultureInfo.InvariantCulture); - _gs = float.Parse(values[7], CultureInfo.InvariantCulture); - _a = float.Parse(values[8], CultureInfo.InvariantCulture); - } - } - static void CheckByte(int val, string name) { if (val is < 0 or > 0xFF) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs index ab342412..438b1821 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFont.cs @@ -2,6 +2,13 @@ // See the LICENSE file in the solution root for more information. using System.ComponentModel; +using PdfSharp.Internal.OpenType; +using PdfSharp.Internal.Threading; +using PdfSharp.Fonts; +using PdfSharp.Fonts.Internal; +using PdfSharp.Internal; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; #if GDI using GdiFontFamily = System.Drawing.FontFamily; using GdiFont = System.Drawing.Font; @@ -16,14 +23,10 @@ #if WUI using UwpFontFamily = Windows.UI.Xaml.Media.FontFamily; #endif -using PdfSharp.Fonts; -using PdfSharp.Fonts.Internal; -using PdfSharp.Fonts.OpenType; -using PdfSharp.Internal; -using PdfSharp.Pdf; -using PdfSharp.Pdf.Advanced; -// ReSharper disable ConvertToAutoProperty +// v7.0 review + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Drawing { @@ -345,7 +348,7 @@ void Initialize() // In principle an XFont is an XGlyphTypeface plus an em-size. GlyphTypeface = XGlyphTypeface.GetOrCreateFrom(_familyName, fontResolvingOptions); - GlyphTypeface.FontFace.SetFontEmbedding(_pdfOptions.FontEmbedding); + GlyphTypeface.OTFontFace.SetFontEmbedding((OpenTypeFontEmbedding)_pdfOptions.FontEmbedding); } #if GDI // Create font by using font family. @@ -359,7 +362,7 @@ void Initialize() WpfFontFamily = GlyphTypeface.FontFamily.WpfFamily; WpfTypeface = GlyphTypeface.WpfTypeface; - WpfFontFamily ??= new WpfFontFamily(Name2); + WpfFontFamily ??= new WpfFontFamily(Name); WpfTypeface ??= FontHelper.CreateTypeface(WpfFontFamily ?? NRT.ThrowOnNull(), _style); #endif @@ -374,7 +377,7 @@ void InitializeFromGdi() { try { - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); if (GdiFontFamily != null!) { // Create font based on its family. @@ -383,13 +386,8 @@ void InitializeFromGdi() if (GdiFont != null!) { -#if DEBUG_ - string name1 = _gdiFont.Name; - string name2 = _gdiFont.OriginalFontName; - string name3 = _gdiFont.SystemFontName; -#endif _familyName = GdiFont.FontFamily.Name; - // TODO_OLD: _glyphTypeface = XGlyphTypeface.GetOrCreateFrom(_gdiFont); + // TODO_OLD: _glyphTypeface = XGlyphTypeface.GetOrCreateFromBytes(_gdiFont); } else { @@ -401,7 +399,7 @@ void InitializeFromGdi() CreateDescriptorAndInitializeFontMetrics(); } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } #endif @@ -438,10 +436,18 @@ void InitializeFromWpf() void CreateDescriptorAndInitializeFontMetrics() // TODO_OLD: refactor { Debug.Assert(_fontMetrics == null, "InitializeFontMetrics() was already called."); - OpenTypeDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(this); //_familyName, _style, _glyphTypeface.FontFace); - _fontMetrics = new XFontMetrics(OpenTypeDescriptor.FontName3, OpenTypeDescriptor.UnitsPerEm, OpenTypeDescriptor.Ascender, OpenTypeDescriptor.Descender, - OpenTypeDescriptor.Leading, OpenTypeDescriptor.LineSpacing, OpenTypeDescriptor.CapHeight, OpenTypeDescriptor.XHeight, OpenTypeDescriptor.StemV, 0, 0, 0, - OpenTypeDescriptor.UnderlinePosition, OpenTypeDescriptor.UnderlineThickness, OpenTypeDescriptor.StrikeoutPosition, OpenTypeDescriptor.StrikeoutSize); + + //var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache; + //OpenTypeDescriptor = (OpenTypeDescriptor)fontDescriptorCache.GetOrCreateDescriptorFor(this); //_familyName, _style, _glyphTypeface.FontFace); + //Debug.Assert(ReferenceEquals(OpenTypeDescriptor, GlyphTypeface.OTFontFace.OTDescriptor)); + OpenTypeDescriptor = GlyphTypeface.OTFontFace.OTDescriptor; + + _fontMetrics = new XFontMetrics(OpenTypeDescriptor.Key, OpenTypeDescriptor.UnitsPerEm, + OpenTypeDescriptor.Ascender, OpenTypeDescriptor.Descender, OpenTypeDescriptor.Leading, + OpenTypeDescriptor.LineSpacing, OpenTypeDescriptor.CapHeight, OpenTypeDescriptor.XHeight, + OpenTypeDescriptor.StemV, 0, 0, 0, + OpenTypeDescriptor.UnderlinePosition, OpenTypeDescriptor.UnderlineThickness, + OpenTypeDescriptor.StrikeoutPosition, OpenTypeDescriptor.StrikeoutSize); XFontMetrics fm = Metrics; @@ -479,13 +485,15 @@ void CreateDescriptorAndInitializeFontMetrics() // TODO_OLD: refactor [Browsable(false)] public XFontFamily FontFamily => GlyphTypeface.FontFamily; + [Obsolete("Use Name to get the fonts family name. Name2 was accidentally released.")] + public string Name2 => GlyphTypeface.FontFamily.Name; + /// /// Gets the font family name. /// - // [Obsolete("This function returns the font family name, not the face name. Use xxx.FontFamily.Name or xxx.FaceName")] - public string Name2 => GlyphTypeface.FontFamily.Name; + public string Name => GlyphTypeface.FamilyName; - internal string FaceName => GlyphTypeface.FaceName; + //internal string FaceName => GlyphTypeface.FontName; /// /// Gets the em-size of this font measured in the unit of this font object. @@ -567,34 +575,19 @@ public int CellSpace int _cellSpace; /// - /// Gets the cell ascent, the area above the base line that is used by the font. + /// Gets the cell ascent, the area above the baseline that is used by the font. /// public int CellAscent { get; internal set; } /// - /// Gets the cell descent, the area below the base line that is used by the font. + /// Gets the cell descent, the area below the baseline that is used by the font. /// public int CellDescent { get; internal set; } /// /// Gets the font metrics. /// - /// The metrics. - public XFontMetrics Metrics - { - get - { - // Code moved to InitializeFontMetrics(). - //if (_fontMetrics == null) - //{ - // FontDescriptor descriptor = FontDescriptorStock.Global.CreateDescriptor(this); - // _fontMetrics = new XFontMetrics(descriptor.FontName, descriptor.UnitsPerEm, descriptor.Ascender, descriptor.Descender, - // descriptor.Leading, descriptor.LineSpacing, descriptor.CapHeight, descriptor.XHeight, descriptor.StemV, 0, 0, 0); - //} - Debug.Assert(_fontMetrics != null, "InitializeFontMetrics() not yet called."); - return _fontMetrics; - } - } + public XFontMetrics Metrics => _fontMetrics; XFontMetrics _fontMetrics = default!; @@ -690,11 +683,11 @@ public double GetHeight(XGraphics graphics) /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - internal XGlyphTypeface GlyphTypeface { get; private set; } = default!; + internal XGlyphTypeface GlyphTypeface { get; private set; } = null!; - internal OpenTypeDescriptor OpenTypeDescriptor { get; private set; } = default!; + internal OpenTypeFontDescriptor OpenTypeDescriptor { get; private set; } = null!; - internal string FamilyName => _familyName; + string FamilyName => _familyName; string _familyName = ""; @@ -748,11 +741,11 @@ internal static XFontStyleEx FontStyleFrom(GdiFont font) /////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////////// - /// - /// Cache PdfFontTable.FontSelector to speed up finding the right PdfFont - /// if this font is used more than once. - /// - internal string? PdfFontSelector { get; set; } + ///// + ///// Cache PdfFontTable.FontSelector to speed up finding the right PdfFont + ///// if this font is used more than once. + ///// + //internal string? PdfFontSelector { get; set; } internal void CheckVersion() => GlyphTypeface.CheckVersion(); @@ -763,7 +756,7 @@ internal static XFontStyleEx FontStyleFrom(GdiFont font) string DebuggerDisplay // ReSharper restore UnusedMember.Local { - get => Invariant($"font=('{Name2}' {Size:0.##}{(Bold ? " bold" : "")}{(Italic ? " italic" : "")} {GlyphTypeface.StyleSimulations})"); + get => Invariant($"font=('{Name}' {Size:0.##}{(Bold ? " bold" : "")}{(Italic ? " italic" : "")} {GlyphTypeface.StyleSimulations})"); } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontFamily.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontFamily.cs index 5ac663b8..a55ba173 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontFamily.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontFamily.cs @@ -1,18 +1,19 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal.OpenType; +using PdfSharp.Fonts; +using PdfSharp.Internal; #if GDI using GdiFont = System.Drawing.Font; using GdiFontFamily = System.Drawing.FontFamily; using GdiFontStyle = System.Drawing.FontStyle; #endif #if WPF +using PdfSharp.Fonts.Internal; using WpfFontFamily = System.Windows.Media.FontFamily; using WpfFontStyle = System.Windows.FontStyle; #endif -using PdfSharp.Fonts; -using PdfSharp.Fonts.Internal; -using PdfSharp.Fonts.OpenType; namespace PdfSharp.Drawing { @@ -62,20 +63,20 @@ internal XFontFamily(string familyName, bool createPlatformObjects) //} #endif - internal static XFontFamily CreateFromName_not_used(string name, bool createPlatformFamily) - { - XFontFamily fontFamily = new XFontFamily(name); - if (createPlatformFamily) - { -#if GDI - //fontFamily._gdiFamily = new System.Drawing.FontFamily(name); -#endif -#if WPF - //fontFamily._wpfFamily = new System.Windows.Media.FontFamily(name); -#endif - } - return fontFamily; - } +//// internal static XFontFamily CreateFromName_not_used(string name, bool createPlatformFamily) +//// { +//// XFontFamily fontFamily = new XFontFamily(name); +//// if (createPlatformFamily) +//// { +////#if GDI +//// //fontFamily._gdiFamily = new System.Drawing.FontFamily(name); +////#endif +////#if WPF +//// //fontFamily._wpfFamily = new System.Windows.Media.FontFamily(name); +////#endif +//// } +//// return fontFamily; +//// } /// /// An XGlyphTypeface for a font source that comes from a custom font resolver @@ -84,15 +85,16 @@ internal static XFontFamily CreateFromName_not_used(string name, bool createPlat internal static XFontFamily GetOrCreateFontFamily(string name) { // Custom font resolver face names must not clash with platform family names. - var fontFamilyInternal = FontFamilyCache.GetFamilyByName(name); + var fontFamilyCache = PsGlobals.Global.Fonts.FontFamilyCache; + var fontFamilyInternal = fontFamilyCache.GetFamilyByName(name); if (fontFamilyInternal == null) { fontFamilyInternal = FontFamilyInternal.GetOrCreateFromName(name, false); - fontFamilyInternal = FontFamilyCache.CacheOrGetFontFamily(fontFamilyInternal); + fontFamilyInternal = fontFamilyCache.CacheOrGetFontFamily(fontFamilyInternal); } // Create font family and save it in cache. Do not try to create platform objects. - return new XFontFamily(fontFamilyInternal); + return new(fontFamilyInternal); } #if GDI @@ -107,7 +109,7 @@ internal static XFontFamily GetOrCreateFromGdi(GdiFont font) internal static XFontFamily GetOrCreateFromWpf(WpfFontFamily wpfFontFamily) { FontFamilyInternal fontFamilyInternal = FontFamilyInternal.GetOrCreateFromWpf(wpfFontFamily); - return new XFontFamily(fontFamilyInternal); + return new(fontFamilyInternal); } #endif @@ -132,7 +134,9 @@ public double LineSpacing /// public int GetCellAscent(XFontStyleEx style) { - OpenTypeDescriptor descriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptor(Name, style); + var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache; + OpenTypeFontDescriptor descriptor = (OpenTypeFontDescriptor)fontDescriptorCache.GetOrCreateDescriptor(Name, style); + int result = descriptor.Ascender; #if DEBUG_ && GDI int gdiValue = _gdiFamily.GetCellAscent((FontStyle)style); @@ -146,7 +150,8 @@ public int GetCellAscent(XFontStyleEx style) /// public int GetCellDescent(XFontStyleEx style) { - OpenTypeDescriptor descriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptor(Name, style); + var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache; + OpenTypeFontDescriptor descriptor = (OpenTypeFontDescriptor)fontDescriptorCache.GetOrCreateDescriptor(Name, style); int result = descriptor.Descender; #if DEBUG_ && GDI int gdiValue = _gdiFamily.GetCellDescent((FontStyle)style); @@ -160,7 +165,8 @@ public int GetCellDescent(XFontStyleEx style) /// public int GetEmHeight(XFontStyleEx style) { - OpenTypeDescriptor descriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptor(Name, style); + var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache; + OpenTypeFontDescriptor descriptor = (OpenTypeFontDescriptor)fontDescriptorCache.GetOrCreateDescriptor(Name, style); int result = descriptor.UnitsPerEm; #if DEBUG_ && GDI int gdiValue = _gdiFamily.GetEmHeight((FontStyle)style); @@ -179,7 +185,8 @@ public int GetEmHeight(XFontStyleEx style) /// public int GetLineSpacing(XFontStyleEx style) { - OpenTypeDescriptor descriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptor(Name, style); + var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache; + OpenTypeFontDescriptor descriptor = (OpenTypeFontDescriptor)fontDescriptorCache.GetOrCreateDescriptor(Name, style); int result = descriptor.LineSpacing; #if DEBUG_ && GDI int gdiValue = _gdiFamily.GetLineSpacing((FontStyle)style); @@ -204,12 +211,12 @@ public bool IsStyleAvailable(XFontStyleEx style) throw new InvalidOperationException("In Core build it is the responsibility of the developer to provide all required font faces."); #endif #if GDI && !WPF - if (GdiFamily != null) + if (GdiFamily != null!) return GdiFamily.IsStyleAvailable((GdiFontStyle)xStyle); return false; #endif #if WPF && !GDI - if (WpfFamily != null) + if (WpfFamily != null!) return FontHelper.IsStyleAvailable(this, xStyle); return false; #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontSource.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontSource.cs index 47aaa679..01cb0d18 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontSource.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XFontSource.cs @@ -2,10 +2,11 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Fonts; -using PdfSharp.Fonts.Internal; +using PdfSharp.Internal; +using PdfSharp.Internal.OpenType; + #if GDI using System.Runtime.InteropServices; -using PdfSharp.Internal; using GdiFont = System.Drawing.Font; using GdiFontStyle = System.Drawing.FontStyle; #endif @@ -14,7 +15,8 @@ using WpfTypeface = System.Windows.Media.Typeface; using WpfGlyphTypeface = System.Windows.Media.GlyphTypeface; #endif -using PdfSharp.Fonts.OpenType; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Drawing { @@ -24,21 +26,23 @@ namespace PdfSharp.Drawing [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] public class XFontSource { - // Implementation Notes - // - // * XFontSource represents a single font (file) in memory. - // * An XFontSource holds a reference to its OpenTypeFontFace. - // * To prevent large heap fragmentation this class must exist only once. - // * ttcf postponed to PDFsharp.Fonts. + // Based on OpenTypeFontSource. + + // Implementation moved to OpenTypeFontSource. + // XFontSource is just a thin wrapper of this class. // Signature of a true type collection font. const uint ttcf = 0x66637474; - XFontSource(byte[] bytes, ulong key) + + XFontSource(byte[] bytes, ulong checksumKey = 0) { - //_fontName = null!; // B_UG? - Bytes = bytes; - _key = key; + _otFontSource = OpenTypeFontSource.GetOrCreateFrom(bytes, checksumKey); + } + + XFontSource(OpenTypeFontSource otFontSource) + { + _otFontSource = otFontSource; } /// @@ -47,13 +51,24 @@ public class XFontSource /// public static XFontSource GetOrCreateFrom(byte[] bytes) { - ulong key = FontHelper.CalcChecksum(bytes); - if (!FontFactory.TryGetFontSourceByKey(key, out var fontSource)) - { - fontSource = new XFontSource(bytes, key); - // Theoretically the font source could be created by a different thread in the meantime. - fontSource = FontFactory.CacheFontSource(fontSource); - } + ulong checksumKey = ChecksumHelper.CalcChecksum(bytes); + var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache; + if (fontSourceCache.TryGetFontSourceByKey(checksumKey, out var fontSource)) + return fontSource; + + fontSource = new(bytes, checksumKey); + fontSource = fontSourceCache.CacheFontSource(fontSource); + return fontSource; + } + + public static XFontSource GetOrCreateFrom(OpenTypeFontSource otFontSource) + { + var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache; + if (fontSourceCache.TryGetFontSourceByKey(otFontSource.ChecksumKey, out var fontSource)) + return fontSource; + + fontSource = new(otFontSource); + fontSource = fontSourceCache.CacheFontSource(fontSource); return fontSource; } @@ -70,17 +85,16 @@ public static XFontSource CreateFromFile(string path) return GetOrCreateFrom(bytes); } -#if CORE - internal static XFontSource GetOrCreateFromGlyphTypeface(string typefaceKey, XGlyphTypeface? glyphTypeface) + /// + /// Creates an XFontSource from a uri to a font file. + /// + public static XFontSource CreateFromFile(Uri fontSourceUri) { - // #CORE NYI - throw new NotImplementedException(nameof(GetOrCreateFromGlyphTypeface)); + if (!fontSourceUri.IsFile) + throw new ArgumentException(GfxMsgs.Font_NoFileUri(fontSourceUri).Message); - //byte[] bytes = null; //FontDataHelper.SegoeWP; - //XFontSource fontSource = GetOrCreateFrom(typefaceKey, bytes); - //return fontSource; + return CreateFromFile(fontSourceUri.LocalPath); } -#endif #if GDI internal static XFontSource? GetOrCreateFromGdi(string typefaceKey, GdiFont gdiFont) @@ -128,7 +142,7 @@ static byte[] ReadFontBytesFromGdi(GdiFont gdiFont) isTtcf = true; } error = Marshal.GetLastWin32Error(); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER Debug.Assert(error == 0); #else // We ignore error 127 here. @@ -178,8 +192,9 @@ internal static byte[] ReadFontBytesFromWpf(WpfGlyphTypeface wpfGlyphTypeface) static XFontSource GetOrCreateFrom(string typefaceKey, byte[] fontBytes) { - ulong key = FontHelper.CalcChecksum(fontBytes); - if (FontFactory.TryGetFontSourceByKey(key, out var fontSource)) + ulong checksumKey = ChecksumHelper.CalcChecksum(fontBytes); + var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache; + if (fontSourceCache.TryGetFontSourceByKey(checksumKey, out var fontSource)) { // The font source already exists, but is not yet cached under the specified typeface key. FontFactory.CacheExistingFontSourceWithNewTypefaceKey(typefaceKey, fontSource); @@ -187,8 +202,8 @@ static XFontSource GetOrCreateFrom(string typefaceKey, byte[] fontBytes) else { // No font source exists. Create new one and cache it. - fontSource = new XFontSource(fontBytes, key); - FontFactory.CacheNewFontSource(typefaceKey, fontSource); + fontSource = new(fontBytes, checksumKey); + fontSourceCache.CacheNewFontSource(typefaceKey, fontSource); } return fontSource; } @@ -205,48 +220,37 @@ public static XFontSource CreateCompiledFont(byte[] bytes) /// /// Gets or sets the font face. /// - internal OpenTypeFontFace FontFace + internal OpenTypeFontFace OTFontFace { - get => _fontFace; - set - { - _fontFace = value; - _fontName = value.name.FullFontName; - } + get => _otFontSource.OTFontFace; + set => _otFontSource.OTFontFace = value; } - OpenTypeFontFace _fontFace = default!; // NRT /// /// Gets the key that uniquely identifies this font source. /// - internal ulong Key + internal ulong ChecksumKey { - get - { - if (_key == 0) - _key = FontHelper.CalcChecksum(Bytes); - return _key; - } + get => _otFontSource.ChecksumKey; } - ulong _key; /// - /// Gets the name of the font’s name table. + /// //??? What??? Gets the name of the font’s name table. /// - public string FontName => _fontName; + public string FontFaceKey => _otFontSource.OTFontFace.OTDescriptor.Key ?? null!; - string _fontName = default!; + //string _fontFaceKey = null!; /// /// Gets the bytes of the font. /// - public byte[] Bytes { get; } + public byte[] Bytes => _otFontSource.Bytes; /// /// Returns a hash code for this instance. /// public override int GetHashCode() - => (int)((Key >> 32) ^ Key); + => _otFontSource.GetHashCode(); /// /// Determines whether the specified object is equal to the current object. @@ -259,14 +263,18 @@ public override bool Equals(object? obj) { if (obj is not XFontSource fontSource) return false; - return Key == fontSource.Key; + return ChecksumKey == fontSource.ChecksumKey; } + public OpenTypeFontSource OTFontSource => _otFontSource; + + readonly OpenTypeFontSource _otFontSource; + /// /// Gets the DebuggerDisplayAttribute text. /// // ReSharper disable UnusedMember.Local internal string DebuggerDisplay => - String.Format(CultureInfo.InvariantCulture, "XFontSource: '{0}', keyhash={1}", FontName, Key % 99991 /* largest prime number less than 100000 */); + String.Format(CultureInfo.InvariantCulture, "XFontSource: '{0}', keyhash={1}", FontFaceKey, ChecksumKey % 99991 /* largest prime number less than 100000 */); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XForm.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XForm.cs index 1fb078bd..79c8a22f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XForm.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XForm.cs @@ -9,6 +9,8 @@ using PdfSharp.Drawing.Pdf; using PdfSharp.Pdf; using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; +using static PdfSharp.Pdf.PdfDictionary; namespace PdfSharp.Drawing { @@ -59,7 +61,7 @@ public XForm(XGraphics gfx, XSize size) if (gfx == null) throw new ArgumentNullException(nameof(gfx)); if (size.Width < 1 || size.Height < 1) - throw new ArgumentNullException(nameof(size), "The size of the XPdfForm is to small."); + throw new ArgumentNullException(nameof(size), "The size of the XPdfForm is too small."); _formState = FormState.Created; //templateSize = size; @@ -98,7 +100,7 @@ public XForm(XGraphics gfx, XUnit width, XUnit height) public XForm(PdfDocument document, XRect viewBox) { if (viewBox.Width < 1 || viewBox.Height < 1) - throw new ArgumentNullException(nameof(viewBox), "The size of the XPdfForm is to small."); + throw new ArgumentNullException(nameof(viewBox), "The size of the XPdfForm is too small."); // I must tie the XPdfForm to a document immediately, because otherwise I would have no place where // to store the resources. if (document == null!) @@ -113,6 +115,33 @@ public XForm(PdfDocument document, XRect viewBox) _pdfForm.Elements.SetRectangle(PdfFormXObject.Keys.BBox, rect); } + /// + /// Initializes a new instance of the class that represents an existing PdfFormXObject. + /// + /// The PdfFormXObject. + /// The bounding box of the PdfFormXObject. + public XForm(PdfFormXObject formXObject, PdfRectangle boundingBox) + { + var viewBox = boundingBox.ToXRect(); + var document = formXObject.Document; + + if (viewBox.Width < 1 || viewBox.Height < 1) + throw new ArgumentNullException(nameof(viewBox), "The size of the XPdfForm is too small."); + // I must tie the XPdfForm to a document immediately, because otherwise I would have no place where + // to store the resources. + if (document == null!) + throw new ArgumentNullException(nameof(document), "An XPdfForm template must be associated with a document at creation time."); + + _formState = FormState.Created; + _document = document; + _pdfForm = formXObject; + formXObject.SetForm(this); + //_templateSize = size; + _viewBox = viewBox; + + _pdfForm.Elements.SetRectangle(PdfFormXObject.Keys.BBox, boundingBox); + } + /// /// Initializes a new instance of the class that represents a page of a PDF document. /// @@ -122,7 +151,7 @@ public XForm(PdfDocument document, XSize size) : this(document, new XRect(0, 0, size.Width, size.Height)) { ////if (size.width < 1 || size.height < 1) - //// throw new ArgumentNullException("size", "The size of the XPdfForm is to small."); + //// throw new ArgumentNullException("size", "The size of the XPdfForm is too small."); ////// I must tie the XPdfForm to a document immediately, because otherwise I would have no place where ////// to store the resources. ////if (document == null) @@ -170,7 +199,7 @@ public void DrawingFinished() internal void AssociateGraphics(XGraphics gfx) { if (_formState == FormState.NotATemplate) - throw new NotImplementedException("The current version of PDFsharp cannot draw on an imported page."); + throw new NotSupportedException("The current version of PDFsharp cannot draw on an imported page."); if (_formState == FormState.UnderConstruction) throw new InvalidOperationException("An XGraphics object already exists for this form."); @@ -182,7 +211,7 @@ internal void AssociateGraphics(XGraphics gfx) _formState = FormState.UnderConstruction; Gfx = gfx; } - internal XGraphics Gfx = default!; + internal XGraphics? Gfx; #if true_ /// @@ -199,17 +228,43 @@ protected override void Dispose(bool disposing) /// internal virtual void Finish() { +#if CORE + if (_formState is FormState.NotATemplate or FormState.Finished) + return; + + + Debug.Assert(_formState is FormState.Created or FormState.UnderConstruction); + _formState = FormState.Finished; + Gfx?.Dispose(); + Gfx = null; + + if (PdfRenderer != null!) + { + //pdfForm.CreateStream(PdfEncoders.RawEncoding.GetBytes(PdfRenderer.GetContent())); + PdfRenderer.Close(); + Debug.Assert(PdfRenderer == null!); +#if true_ + if (_document.Options.CompressContentStreams) + { + _pdfForm!.Stream!.Value = Filtering.FlateDecode.Encode(_pdfForm.Stream.Value, _document.Options.FlateEncodeMode); + _pdfForm.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + } +#endif + int length = _pdfForm!.Stream!.Length; + _pdfForm.Elements.SetInteger("/Length", length); + } +#endif #if GDI if (_formState is FormState.NotATemplate or FormState.Finished) return; - if (Gfx.Metafile != null) + if (Gfx?.Metafile != null) _gdiImage = Gfx.Metafile; Debug.Assert(_formState is FormState.Created or FormState.UnderConstruction); _formState = FormState.Finished; - Gfx.Dispose(); - Gfx = null!; + Gfx?.Dispose(); + Gfx = null; if (PdfRenderer != null!) { @@ -219,11 +274,11 @@ internal virtual void Finish() if (_document.Options.CompressContentStreams) { - _pdfForm!.Stream.Value = Filtering.FlateDecode.Encode(_pdfForm.Stream.Value, _document.Options.FlateEncodeMode); - _pdfForm.Elements["/Filter"] = new PdfName("/FlateDecode"); + _pdfForm!.Stream!.Value = Filtering.FlateDecode.Encode(_pdfForm.Stream.Value, _document.Options.FlateEncodeMode); + _pdfForm.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); } - int length = _pdfForm!.Stream.Length; - _pdfForm.Elements.SetInteger("/Length", length); + int length = _pdfForm!.Stream!.Length; + _pdfForm.Elements.SetInteger(PdfStream.Keys.Length, length); } #endif #if WPF @@ -336,7 +391,7 @@ public virtual XMatrix Transform _transform = value; } } - internal XMatrix _transform; + XMatrix _transform; internal PdfResources Resources { @@ -368,7 +423,7 @@ internal string GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out return name; } - string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) + string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) => GetFontName(glyphTypeface, fontType, out pdfFont); /// @@ -399,7 +454,7 @@ internal string GetFontName(string idName, byte[] fontData, out PdfFont pdfFont) return name; } - string IContentStream.GetFontName(string idName, byte[] fontData, out PdfFont pdfFont) + string IContentStream.GetFontName(string idName, byte[] fontData, out PdfFont pdfFont) => GetFontName(idName, fontData, out pdfFont); /// @@ -417,7 +472,7 @@ internal string GetImageName(XImage image) /// /// Implements the interface because the primary function is internal. /// - string IContentStream.GetImageName(XImage image) + string IContentStream.GetImageName(XImage image) => GetImageName(image); internal PdfFormXObject PdfForm @@ -446,7 +501,7 @@ internal string GetFormName(XForm form) /// /// Implements the interface because the primary function is internal. /// - string IContentStream.GetFormName(XForm form) + string IContentStream.GetFormName(XForm form) => GetFormName(form); /// @@ -455,7 +510,7 @@ string IContentStream.GetFormName(XForm form) /// internal PdfFormXObject? _pdfForm; // TODO_OLD: make private - internal XGraphicsPdfRenderer PdfRenderer = default!; + internal XGraphicsPdfRenderer PdfRenderer = null!; #if WPF /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs index d7b6c902..85a06eaa 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGlyphTypeface.cs @@ -1,6 +1,13 @@ // 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.Internal; +using PdfSharp.Internal.OpenType; +using PdfSharp.Internal.Threading; +using PdfSharp.Fonts.Internal; +using PdfSharp.Fonts; #if GDI using System.Drawing; using System.Drawing.Drawing2D; @@ -9,9 +16,6 @@ using GdiFontStyle = System.Drawing.FontStyle; #endif #if WPF -//using System.Windows; -//using System.Windows.Documents; -//using System.Windows.Media; using WpfFontFamily = System.Windows.Media.FontFamily; using WpfTypeface = System.Windows.Media.Typeface; using WpfGlyphTypeface = System.Windows.Media.GlyphTypeface; @@ -20,58 +24,44 @@ #if WUI using Windows.UI.Xaml.Media; #endif -using Microsoft.Extensions.Logging; -using PdfSharp.Fonts; -using PdfSharp.Fonts.Internal; -using PdfSharp.Fonts.OpenType; -using PdfSharp.Internal; -using PdfSharp.Logging; namespace PdfSharp.Drawing { + // TODO-7.0: Use OpenTypeGlyphTypeface as implementation of XGlyphTypeface. + /// /// Specifies a physical font face that corresponds to a font file on the disk or in memory. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] public sealed class XGlyphTypeface { - // Implementation Notes - // - // * Each XGlyphTypeface can belong to one or more XFont objects. - // * An XGlyphTypeface hold an XFontFamily. - // * XGlyphTypeface hold a reference to an OpenTypeFontFace. - const string KeySuffix = ":TFK"; // Typeface Key - #if CORE - XGlyphTypeface(string key, XFontFamily fontFamily, XFontSource fontSource, XStyleSimulations styleSimulations) + XGlyphTypeface(string key, XFontFamily fontFamily, XFontSource fontSource, + XStyleSimulations styleSimulations) { Key = key; FontFamily = fontFamily; FontSource = fontSource; - FontFace = OpenTypeFontFace.CetOrCreateFrom(fontSource); + Debug.Assert(fontSource.OTFontFace != null); + //OTFontFace = OpenTypeFontFace.GetOrCreateFrom(fontSource.OTFontSource); + OTFontFace = fontSource.OTFontFace; - // Check why it fails. - //Debug.Assert(ReferenceEquals(FontSource.FontFace, FontFace)); - StyleSimulations = styleSimulations; Initialize(); } #endif #if GDI - XGlyphTypeface(string key, XFontFamily fontFamily, XFontSource fontSource, XStyleSimulations styleSimulations, GdiFont gdiFont) + XGlyphTypeface(string key, XFontFamily fontFamily, XFontSource fontSource, + XStyleSimulations styleSimulations, GdiFont gdiFont) { Key = key; FontFamily = fontFamily; FontSource = fontSource; - - FontFace = OpenTypeFontFace.CetOrCreateFrom(fontSource); - Debug.Assert(ReferenceEquals(FontSource.FontFace, FontFace)); - + OTFontFace = FontSource.OTFontFace; _gdiFont = gdiFont; - StyleSimulations = styleSimulations; Initialize(); } @@ -83,16 +73,13 @@ public sealed class XGlyphTypeface /// public XGlyphTypeface(XFontSource fontSource) { - string familyName = fontSource.FontFace.name.Name; + string familyName = fontSource.OTFontFace.name.OTFamilyName; FontFamily = new XFontFamily(familyName, false); - FontFace = fontSource.FontFace; - IsBold = FontFace.os2.IsBold; - IsItalic = FontFace.os2.IsItalic; - + OTFontFace = fontSource.OTFontFace; + IsBold = OTFontFace.os2.IsBold; + IsItalic = OTFontFace.os2.IsItalic; Key = ComputeGtfKey(familyName, IsBold, IsItalic); - //_fontFamily =xfont FontFamilyCache.GetFamilyByName(familyName); FontSource = fontSource; - Initialize(); } #endif @@ -104,13 +91,9 @@ public XGlyphTypeface(XFontSource fontSource) FontFamily = fontFamily; FontSource = fontSource; StyleSimulations = styleSimulations; - - FontFace = OpenTypeFontFace.CetOrCreateFrom(fontSource); - Debug.Assert(ReferenceEquals(FontSource.FontFace, FontFace)); - + OTFontFace = FontSource.OTFontFace; WpfTypeface = wpfTypeface; WpfGlyphTypeface = wpfGlyphTypeface; - Initialize(); } #endif @@ -123,7 +106,7 @@ public XGlyphTypeface(XFontSource fontSource) _fontSource = fontSource; _styleSimulations = styleSimulations; - _fontFace = OpenTypeFontFace.CetOrCreateFrom(fontSource); + _fontFace = OpenTypeFontFace.GetOrCreateFromBytes(fontSource); Debug.Assert(ReferenceEquals(_fontSource.FontFace, _fontFace)); //_wpfTypeface = wpfTypeface; @@ -141,16 +124,15 @@ internal static XGlyphTypeface GetOrCreateFrom(string familyName, FontResolvingO try { // Lock around TryGetGlyphTypeface and AddGlyphTypeface. - Locks.EnterFontFactory(); - if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) + Locks.EnterFontManagement(); + var glyphTypefaceCache = PsGlobals.Global.Fonts.GlyphTypefaceCache; + if (glyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out 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. { @@ -176,7 +158,7 @@ internal static XGlyphTypeface GetOrCreateFrom(string familyName, FontResolvingO } void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) - => PdfSharpLogHost.Logger.LogError("A font resolver cannot resolve font family '{}' and throws an exception, " + + => PdfSharpLogHost.Logger.LogError("A font resolver cannot resolve font family '{familyName}' and throws an exception, " + "but it must return null if the font cannot be resolved. Exception text: " + ex.Message, name); if (fontResolverInfo == null) @@ -194,7 +176,7 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) throw new InvalidOperationException($"No appropriate font found for family name '{familyName}'."); } #if GDI - GdiFont gdiFont = default!; + GdiFont gdiFont = null!; #endif #if WPF // ReSharper disable once TooWideLocalVariableScope @@ -215,9 +197,6 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) #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); #endif #if GDI // Reuse GDI+ font from platform font resolver. @@ -244,7 +223,8 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) } // 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); + var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache; + XFontSource fontSource = fontSourceCache.GetFontSourceByFontName(fontResolverInfo.FaceName); Debug.Assert(fontSource != null); // Each font source already contains its OpenTypeFontFace. @@ -260,9 +240,9 @@ void LogErrorBecauseFontResolverThrowsException(string name, Exception ex) #if WUI glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, fontResolverInfo.StyleSimulations); #endif - GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); + glyphTypefaceCache.AddGlyphTypeface(glyphTypeface); } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } return glyphTypeface; } @@ -277,9 +257,10 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) try { // Lock around TryGetGlyphTypeface and AddGlyphTypeface. - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); string typefaceKey = ComputeGtfKey(gdiFont); - if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) + var glyphTypefaceCache = PsGlobals.Global.Fonts.GlyphTypefaceCache; + if (glyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out glyphTypeface)) { // We have the glyph typeface already in cache. return glyphTypeface; @@ -292,15 +273,16 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) // Check if styles must be simulated. XStyleSimulations styleSimulations = XStyleSimulations.None; - if (gdiFont.Bold && !fontSource.FontFace.os2.IsBold) + if (gdiFont.Bold && !fontSource.OTFontFace.os2.IsBold) styleSimulations |= XStyleSimulations.BoldSimulation; - if (gdiFont.Italic && !fontSource.FontFace.os2.IsItalic) + if (gdiFont.Italic && !fontSource.OTFontFace.os2.IsItalic) styleSimulations |= XStyleSimulations.ItalicSimulation; glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, styleSimulations, gdiFont); - GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); + //var glyphTypefaceCache = PsGlobals.Global.Fonts.GlyphTypefaceCache; + glyphTypefaceCache.AddGlyphTypeface(glyphTypeface); } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } return glyphTypeface; } @@ -324,7 +306,7 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) // Lock around TryGetGlyphTypeface and AddGlyphTypeface. try { - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); // Create WPF glyph typeface. if (!wpfTypeface.TryGetGlyphTypeface(out var wpfGlyphTypeface)) @@ -343,7 +325,8 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) // ReSharper restore UnusedVariable #endif - if (GlyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out var glyphTypeface)) + var glyphTypefaceCache = PsGlobals.Global.Fonts.GlyphTypefaceCache; + if (glyphTypefaceCache.TryGetGlyphTypeface(typefaceKey, out var glyphTypeface)) { // We have the glyph typeface already in cache. return glyphTypeface; @@ -355,11 +338,11 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) glyphTypeface = new XGlyphTypeface(typefaceKey, fontFamily, fontSource, (XStyleSimulations)wpfGlyphTypeface.StyleSimulations, wpfTypeface, wpfGlyphTypeface); - GlyphTypefaceCache.AddGlyphTypeface(glyphTypeface); + glyphTypefaceCache.AddGlyphTypeface(glyphTypeface); return glyphTypeface; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } #endif @@ -368,7 +351,7 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) /// public XFontFamily FontFamily { get; } - internal OpenTypeFontFace FontFace { get; } + internal OpenTypeFontFace OTFontFace { get; } /// /// Gets the font source of this glyph typeface. @@ -377,51 +360,49 @@ public static XGlyphTypeface GetOrCreateFromGdi(GdiFont gdiFont) void Initialize() { - FamilyName = FontFace.name.Name; - //if (String.IsNullOrEmpty(FaceName) || FaceName.StartsWith("?", StringComparison.Ordinal)) -#if TEST_CODE_ - if (!String.IsNullOrEmpty(FaceName)) - _ = typeof(int); -#endif - FaceName = FontFace.name.FullFontName; - StyleName = FontFace.name.Style; - DisplayName = FontFace.name.FullFontName; + FamilyName = OTFontFace.name.OTFamilyName; + FaceName = OTFontFace.name.OTSubfamilyName; + FontName = DisplayName = OTFontFace.name.OTFullFontName; if (String.IsNullOrEmpty(DisplayName)) { - DisplayName = FamilyName; - if (!String.IsNullOrEmpty(StyleName)) - DisplayName += " (" + StyleName + ")"; + FontName = DisplayName = FamilyName; + if (!String.IsNullOrEmpty(FaceName)) + DisplayName += " (" + FaceName + ")"; } // Bold, as defined in OS/2 table. - IsBold = FontFace.os2.IsBold; - // Debug.Assert(_isBold == (_fontFace.os2.usWeightClass > 400), "Check font weight."); + IsBold = OTFontFace.os2.IsBold; // Italic, as defined in OS/2 table. - IsItalic = FontFace.os2.IsItalic; + IsItalic = OTFontFace.os2.IsItalic; } /// - /// Gets the name of the font face. This can be a file name, an URI, or a GUID. + /// Gets the name of the font face. This can be a file name, a URI, or a GUID. /// - internal string FaceName { get; private set; } = default!; + internal string FontName{ get; private set; } = null!; // Former FaceName /// /// Gets the English family name of the font, for example "Arial". /// - public string FamilyName { get; private set; } = default!; + public string FamilyName { get; private set; } = null!; /// /// Gets the English subfamily name of the font, /// for example "Bold". /// - public string StyleName { get; private set; } = default!; + /// + /// Prior to PDFsharp 7 this function incorrectly returns the family. + /// Starting with PDFsharp 7 it returns the correct value. + /// This is a breaking change. + /// + public string FaceName { get; private set; } = null!; /// /// Gets the English display name of the font, /// for example "Arial italic". /// - public string DisplayName { get; private set; } = default!; + public string DisplayName { get; private set; } = null!; /// /// Gets a value indicating whether the font weight is bold. @@ -492,9 +473,9 @@ internal static string ComputeGtfKey(string familyName, FontResolvingOptions fon var italic = fontResolvingOptions.IsItalic; var key = bold switch { - false when !italic => name + "/N/400/500" + simulationSuffix + KeySuffix, - true when !italic => name + "/N/700/500" + simulationSuffix + KeySuffix, - false when italic => name + "/I/400/500" + simulationSuffix + KeySuffix, + false when !italic => name + "/N/400/5" + simulationSuffix + KeySuffix, + true when !italic => name + "/N/700/5" + simulationSuffix + KeySuffix, + false when italic => name + "/I/400/5" + simulationSuffix + KeySuffix, _ => name + "/I/700/500" + simulationSuffix + KeySuffix, }; return key; @@ -589,8 +570,8 @@ internal static string ComputeGtfKey(WpfGlyphTypeface wpfGlyphTypeface) internal WpfTypeface? WpfTypeface { get; } internal WpfGlyphTypeface? WpfGlyphTypeface { get; } #endif - internal void CheckVersion() => Globals.Global.Fonts.CheckVersion(_globalFontStorageVersion); - readonly int _globalFontStorageVersion = Globals.Global.Fonts.Version; + internal void CheckVersion() => PsGlobals.Global.Fonts.CheckVersion(_globalFontStorageVersion); + readonly int _globalFontStorageVersion = PsGlobals.Global.Fonts.Version; /// /// Gets the DebuggerDisplayAttribute text. @@ -598,7 +579,7 @@ internal static string ComputeGtfKey(WpfGlyphTypeface wpfGlyphTypeface) // ReSharper disable UnusedMember.Local internal string DebuggerDisplay // ReSharper restore UnusedMember.Local - => Invariant($"{FamilyName} - {StyleName} ({FaceName})"); + => Invariant($"{FamilyName} - {FaceName} ({FontName})"); } } @@ -721,5 +702,4 @@ Gets the designed weight of the font represented by the GlyphTypeface object. XHeight Gets the Western x-height relative to em size for the font represented by the GlyphTypeface object. - */ diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs index a867471d..60a8a67b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphics.cs @@ -3,6 +3,16 @@ using System.Text; using Microsoft.Extensions.Logging; +using PdfSharp.Internal.OpenType; +using PdfSharp.Internal.Threading; +using PdfSharp.Pdf.Internal; +using PdfSharp.Pdf; +using PdfSharp.Drawing.Pdf; +using PdfSharp.Events; +using PdfSharp.Fonts.Internal; +using PdfSharp.Internal; +using PdfSharp.Logging; +using PdfSharp.Pdf.Advanced; #if GDI using System.Drawing.Drawing2D; @@ -38,17 +48,9 @@ using SysSize = Windows.Foundation.Size; using SysRect = Windows.Foundation.Rect; #endif -using PdfSharp.Pdf; -using PdfSharp.Drawing.Pdf; -using PdfSharp.Events; -using PdfSharp.Fonts.Internal; -using PdfSharp.Internal; -using PdfSharp.Logging; -//using PdfSharp.Internal; -using PdfSharp.Pdf.Advanced; -using PdfSharp.Pdf.Internal; -#pragma warning disable 1587 +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member +#pragma warning disable 1587 // TODO_DOC: XML comment is not placed on a valid language element // ReSharper disable UseNullPropagation // ReSharper disable RedundantNameQualifier // ReSharper disable UseNameofExpression @@ -127,7 +129,7 @@ public sealed class XGraphics : IDisposable break; default: - throw new NotImplementedException("unit"); + throw new NotSupportedException($"Unit {pageUnit}"); } _pageDirection = pageDirection; @@ -168,7 +170,7 @@ public sealed class XGraphics : IDisposable XGraphicsUnit.Millimeter => new XSize(XUnitPt.FromMillimeter(size.Width), XUnitPt.FromMillimeter(size.Height)), XGraphicsUnit.Centimeter => new XSize(XUnitPt.FromCentimeter(size.Width), XUnitPt.FromCentimeter(size.Height)), XGraphicsUnit.Presentation => new XSize(XUnitPt.FromPresentation(size.Width), XUnitPt.FromPresentation(size.Height)), - _ => throw new NotImplementedException("unit") + _ => throw new NotSupportedException($"Unit {pageUnit}") }; _pageDirection = pageDirection; @@ -239,7 +241,7 @@ public sealed class XGraphics : IDisposable break; default: - throw new NotImplementedException("unit"); + throw new NotSupportedException($"Unit {pageUnit}"); } _pageDirection = pageDirection; @@ -291,7 +293,7 @@ public sealed class XGraphics : IDisposable break; default: - throw new NotImplementedException("unit"); + throw new NotSupportedException($"Unit {pageUnit}"); } _pageDirection = pageDirection; @@ -304,6 +306,10 @@ public sealed class XGraphics : IDisposable /// XGraphics(PdfPage page, XGraphicsPdfPageOptions options, XGraphicsUnit pageUnit, XPageDirection pageDirection) { +#if DEBUG + if (page.IsDead) + _ = typeof(int); +#endif page.Owner.EnsureNotYetSaved(); if (page == null) @@ -313,38 +319,38 @@ public sealed class XGraphics : IDisposable throw new ArgumentException("You cannot draw on a page that is not owned by a PdfDocument object.", nameof(page)); if (page.RenderContent != null) - throw new InvalidOperationException("An XGraphics object already exists for this page and must be disposed before a new one can be created."); + throw new InvalidOperationException("Another page content renderer object already exists for this page and must be disposed before a new one can be created."); if (page.Owner.IsReadOnly) throw new InvalidOperationException("Cannot create XGraphics for a page of a document that cannot be modified. Use PdfDocumentOpenMode.Modify."); - _gsStack = new GraphicsStateStack(this); + _gsStack = new(this); + PdfContent? content; switch (options) { - case XGraphicsPdfPageOptions.Replace: - page.Contents.Elements.Clear(); - goto case XGraphicsPdfPageOptions.Append; + case XGraphicsPdfPageOptions.Append: + content = page.Contents.AppendContent(); + break; case XGraphicsPdfPageOptions.Prepend: content = page.Contents.PrependContent(); break; - case XGraphicsPdfPageOptions.Append: - content = page.Contents.AppendContent(); - break; + case XGraphicsPdfPageOptions.Replace: + page.Contents.Elements.Clear(); + goto case XGraphicsPdfPageOptions.Append; default: throw new InvalidOperationException(); } page.RenderContent = content; - #if CORE TargetContext = XGraphicTargetContext.CORE; #endif #if GDI // _gfx is not needed anymore for drawing on a page. - _gfx = default!; + _gfx = null!; TargetContext = XGraphicTargetContext.GDI; #endif #if WPF @@ -381,7 +387,7 @@ public sealed class XGraphics : IDisposable break; default: - throw new NotImplementedException("unit"); + throw new NotSupportedException($"Unit {pageUnit}"); } _pageUnit = pageUnit; _pageDirection = pageDirection; @@ -592,7 +598,6 @@ public static XGraphics FromGraphics(Graphics graphics, XSize size, XGraphicsUni PdfSharpLogHost.Logger.XGraphicsCreated("GDI+ Graphics object"); return gfx; - } ///// @@ -660,7 +665,7 @@ public static XGraphics FromPdfPage(PdfPage page) { var gfx = new XGraphics(page, XGraphicsPdfPageOptions.Append, XGraphicsUnit.Point, XPageDirection.Downwards); // #PDF-UA - if (page.Owner._uaManager != null) + if (page.Owner.UAManager != null) page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated }); PdfSharpLogHost.Logger.XGraphicsCreated("PDF page"); @@ -675,7 +680,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsUnit unit) { var gfx = new XGraphics(page, XGraphicsPdfPageOptions.Append, unit, XPageDirection.Downwards); // #PDF-UA - if (page.Owner._uaManager != null) + if (page.Owner.UAManager != null) page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated }); PdfSharpLogHost.Logger.XGraphicsCreated("PDF page"); @@ -690,7 +695,7 @@ public static XGraphics FromPdfPage(PdfPage page, XPageDirection pageDirection) { var gfx = new XGraphics(page, XGraphicsPdfPageOptions.Append, XGraphicsUnit.Point, pageDirection); // #PDF-UA - if (page.Owner._uaManager != null) + if (page.Owner.UAManager != null) page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated }); PdfSharpLogHost.Logger.XGraphicsCreated("PDF page"); @@ -705,7 +710,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option { var gfx = new XGraphics(page, options, XGraphicsUnit.Point, XPageDirection.Downwards); // #PDF-UA - if (page.Owner._uaManager != null) + if (page.Owner.UAManager != null) page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated }); PdfSharpLogHost.Logger.XGraphicsCreated("PDF page"); @@ -720,7 +725,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option { var gfx = new XGraphics(page, options, XGraphicsUnit.Point, pageDirection); // #PDF-UA - if (page.Owner._uaManager != null) + if (page.Owner.UAManager != null) page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated }); PdfSharpLogHost.Logger.XGraphicsCreated("PDF page"); @@ -735,7 +740,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option { XGraphics gfx = new XGraphics(page, options, unit, XPageDirection.Downwards); // #PDF-UA - if (page.Owner._uaManager != null) + if (page.Owner.UAManager != null) page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated }); PdfSharpLogHost.Logger.XGraphicsCreated("PDF page"); @@ -750,7 +755,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option { var gfx = new XGraphics(page, options, unit, pageDirection); // #PDF-UA - if (page.Owner._uaManager != null) + if (page.Owner.UAManager != null) page.Owner.Events.OnPageGraphicsCreated(page.Owner, new PageGraphicsEventArgs(page.Owner) { Page = page, Graphics = gfx, ActionType = PageGraphicsActionType.GraphicsCreated }); PdfSharpLogHost.Logger.XGraphicsCreated("PDF page"); @@ -763,7 +768,7 @@ public static XGraphics FromPdfPage(PdfPage page, XGraphicsPdfPageOptions option /// public static XGraphics FromPdfForm(XPdfForm form) { - if (form.Gfx != null!) + if (form.Gfx != null) return form.Gfx; var gfx = new XGraphics(form, form.Owner.RenderEvents); @@ -778,7 +783,7 @@ public static XGraphics FromPdfForm(XPdfForm form) /// public static XGraphics FromForm(XForm form) { - if (form.Gfx != null!) + if (form.Gfx != null) return form.Gfx; var gfx = new XGraphics(form, form.Owner.RenderEvents); @@ -833,7 +838,7 @@ public static XGraphics FromForm(XForm form) /// void Initialize() { - _pageOrigin = new XPoint(); + _pageOrigin = new(); double pageHeight = _pageSize.Height; var targetPage = PdfPage; @@ -854,7 +859,7 @@ void Initialize() { try { - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); if (_gfx != null) matrix = _gfx.Transform; @@ -882,7 +887,7 @@ void Initialize() _gfx.Transform = (GdiMatrix)matrix; } } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } #endif #if WPF @@ -923,11 +928,14 @@ void Initialize() matrix.TranslatePrepend(trimOffset.X, -trimOffset.Y); DefaultViewMatrix = matrix; - _transform = new XMatrix(); + _transformationMatrix = new XMatrix(); } /// /// Releases all resources used by this object. + /// PDFsharp disposes XGraphics objects automatically. + /// You must call Dispose if you want to create another XGraphics object or a + /// PDFsharp Graphics DrawingContext for this page. /// public void Dispose() => Dispose(true); @@ -1005,7 +1013,7 @@ public XPageDirection PageDirection { // Is there really anybody who needs the concept of XPageDirection.Upwards? if (value != XPageDirection.Downwards) - throw new NotImplementedException("PageDirection must be XPageDirection.Downwards in current implementation."); + throw new NotSupportedException("PageDirection must be XPageDirection.Downwards in current implementation."); } } readonly XPageDirection _pageDirection; @@ -1020,7 +1028,7 @@ public XPoint PageOrigin { // Is there really anybody who needs to set the page origin? if (value != new XPoint()) - throw new NotImplementedException("PageOrigin cannot be modified in current implementation."); + throw new NotSupportedException("PageOrigin cannot be modified in current implementation."); } } XPoint _pageOrigin; @@ -1031,10 +1039,6 @@ public XPoint PageOrigin public XSize PageSize { get => _pageSize; - //set - //{ - // throw new NotImplementedException("PageSize cannot be modified in current implementation."); - //} } XSize _pageSize; XSize _pageSizePoints; @@ -3644,7 +3648,6 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan { PdfSharpLogHost.Logger.LogInformation($"DrawString XLineAlignment.Center: y={y} test={test}"); } - #endif break; @@ -3704,7 +3707,6 @@ public void DrawString(string text, XFont font, XBrush brush, XRect layoutRectan } #endif } - _renderer?.DrawString(text, font, brush, layoutRectangle, format); } @@ -4344,8 +4346,10 @@ public XGraphicsState Save() if (TargetContext == XGraphicTargetContext.CORE || TargetContext == XGraphicTargetContext.NONE) { xState = new XGraphicsState(); - InternalGraphicsState iState = new InternalGraphicsState(this, xState); - iState.Transform = _transform; + InternalGraphicsState iState = new InternalGraphicsState(this, xState) + { + Transform = _transformationMatrix + }; _gsStack.Push(iState); } else @@ -4361,7 +4365,7 @@ public XGraphicsState Save() Locks.EnterGdiPlus(); xState = new XGraphicsState(_gfx != null! ? _gfx.Save() : null); InternalGraphicsState iState = new InternalGraphicsState(this, xState); - iState.Transform = _transform; + iState.Transform = _transformationMatrix; _gsStack.Push(iState); } finally { Locks.ExitGdiPlus(); } @@ -4372,7 +4376,7 @@ public XGraphicsState Save() { xState = new XGraphicsState(); InternalGraphicsState iState = new InternalGraphicsState(this, xState); - iState.Transform = _transform; + iState.Transform = _transformationMatrix; _gsStack.Push(iState); } #endif @@ -4394,7 +4398,7 @@ public void Restore(XGraphicsState state) if (TargetContext == XGraphicTargetContext.CORE) { _gsStack.Restore(state.InternalState); - _transform = state.InternalState.Transform; + _transformationMatrix = state.InternalState.Transform; } #endif #if GDI @@ -4406,7 +4410,7 @@ public void Restore(XGraphicsState state) _gsStack.Restore(state.InternalState); if (_gfx != null!) _gfx.Restore(state.GdiState!); // BUG_OLD NRT - _transform = state.InternalState.Transform; + _transformationMatrix = state.InternalState.Transform; } finally { Locks.ExitGdiPlus(); } } @@ -4415,7 +4419,7 @@ public void Restore(XGraphicsState state) if (TargetContext == XGraphicTargetContext.WPF) { _gsStack.Restore(state.InternalState); - _transform = state.InternalState.Transform; + _transformationMatrix = state.InternalState.Transform; } #endif _renderer?.Restore(state); @@ -4512,7 +4516,7 @@ public XGraphicsContainer BeginContainer(XRect dstRect, XRect srcRect, XGraphics throw new InvalidOperationException(nameof(xContainer)); InternalGraphicsState iState = new InternalGraphicsState(this, xContainer); - iState.Transform = _transform; + iState.Transform = _transformationMatrix; _gsStack.Push(iState); @@ -4559,7 +4563,7 @@ public void EndContainer(XGraphicsContainer container) #if WPF // Nothing to do. #endif - _transform = container.InternalState.Transform; + _transformationMatrix = container.InternalState.Transform; _renderer?.EndContainer(container); } @@ -4842,21 +4846,18 @@ public void MultiplyTransform(XMatrix matrix, XMatrixOrder order) /// The transformation matrix cannot be set. Instead use Save/Restore or BeginContainer/EndContainer to /// save the state before Transform is called and later restore to the previous transform. /// - public XMatrix Transform - { - get { return _transform; } - } + public XMatrix Transform => _transformationMatrix; /// /// Applies a new transformation to the current transformation matrix. /// void AddTransform(XMatrix transform, XMatrixOrder order) { - XMatrix matrix = _transform; + XMatrix matrix = _transformationMatrix; matrix.Multiply(transform, order); - _transform = matrix; + _transformationMatrix = matrix; matrix = DefaultViewMatrix; - matrix.Multiply(_transform, XMatrixOrder.Prepend); + matrix.Multiply(_transformationMatrix, XMatrixOrder.Prepend); #if CORE_ // No concept of target context in Core build. if (TargetContext == XGraphicTargetContext.CORE) { @@ -4976,10 +4977,10 @@ public void IntersectClip(XGraphicsPath path) { try { - Locks.EnterGdiPlus(); + Lock.EnterGdiPlus(); _gfx.SetClip(path._gdipPath, CombineMode.Intersect); } - finally { Locks.ExitGdiPlus(); } + finally { Lock.ExitGdiPlus(); } } else { @@ -5034,11 +5035,16 @@ public XGraphicsInternals Internals /// /// (Under construction. May change in future versions.) /// +#if true_ // Change to 'true_' if code does not compile. public SpaceTransformer Transformer - { - get { return _transformer ??= new SpaceTransformer(this); } - } - SpaceTransformer _transformer = default!; + => field ??= new SpaceTransformer(this); + +#else + public SpaceTransformer Transformer + => _transformer ??= new SpaceTransformer(this); + + SpaceTransformer? _transformer; +#endif #endregion @@ -5281,7 +5287,7 @@ internal void AppendToContentStream(string str) /// /// The transformation matrix from XGraphics world space to page unit space. /// - XMatrix _transform; + XMatrix _transformationMatrix; /// /// The graphics state stack. @@ -5331,6 +5337,15 @@ public Graphics Graphics get { return _gfx._gfx; } } #endif + public string? RealizedFontName + { + get + { + if (_gfx._renderer is XGraphicsPdfRenderer renderer) + return renderer.PdfGraphicsState.RealizedFontName; + return null; + } + } /// /// Gets the content string builder of XGraphicsPdfRenderer, if it exists. @@ -5384,7 +5399,7 @@ public XRect WorldToDefaultPage(XRect rect) double ymin = Math.Min(Math.Min(points[0].Y, points[1].Y), Math.Min(points[2].Y, points[3].Y)); double ymax = Math.Max(Math.Max(points[0].Y, points[1].Y), Math.Max(points[2].Y, points[3].Y)); - return new XRect(xmin, ymin, xmax - xmin, ymax - ymin); + return new(xmin, ymin, xmax - xmin, ymax - ymin); } /// @@ -5393,7 +5408,7 @@ public XRect WorldToDefaultPage(XRect rect) public XPoint WorldToDefaultPage(XPoint point) { XMatrix matrix = _gfx.Transform; - matrix.Transform(point); + matrix.Transform(point); // StL/25-11-22: This does nothing! double height = _gfx.PageSize.Height; point.Y = height - point.Y; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs index 6f566d09..b4288c80 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XGraphicsPath.cs @@ -1,6 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; +using PdfSharp.Internal.Threading; +using PdfSharp.Fonts.Internal; #if GDI using System.Drawing.Drawing2D; #endif @@ -18,8 +21,6 @@ using SysSize = Windows.Foundation.Size; using SysRect = Windows.Foundation.Rect; #endif -using PdfSharp.Internal; -using PdfSharp.Fonts.Internal; namespace PdfSharp.Drawing { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs index 48146f31..7873481e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XImage.cs @@ -1,6 +1,13 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using Microsoft.Extensions.Logging; +using PdfSharp.Internal; +using PdfSharp.Internal.Threading; +using PdfSharp.Internal.Imaging; +using PdfSharp.Logging; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.Advanced; #if CORE // Nothing to import. #endif @@ -8,7 +15,6 @@ using System.Drawing; using System.Drawing.Drawing2D; using System.Drawing.Imaging; -using PdfSharp.Internal; #endif #if WPF using System.Text; @@ -17,9 +23,6 @@ #if WUI using Windows.UI.Xaml.Media.Imaging; #endif -using PdfSharp.Drawing.Internal; -using PdfSharp.Pdf.IO; -using PdfSharp.Pdf.Advanced; namespace PdfSharp.Drawing { @@ -39,10 +42,10 @@ public class XImage : IDisposable { // The hierarchy is adapted to WPF // - // XImage <-- ImageSource + // XImage <-- ImageSource // XForm // PdfForm - // XBitmapSource <-- BitmapSource + // XBitmapSource <-- BitmapSource // XBitmapImage <-- BitmapImage // ??? @@ -77,6 +80,7 @@ protected XImage() /// XImage(Image image) { + // Procedure -> Create ImportedImage _gdiImage = image; #if WPF // Is defined in hybrid build. _wpfImage = ImageHelper.CreateBitmapSource(image); @@ -155,11 +159,14 @@ public static BitmapImage BitmapFromUri(Uri uri) #endif _path = path; - //FileStream file = new FileStream(filename, FileMode.Open); - //BitsLength = (int)file.Length; - //Bits = new byte[BitsLength]; - //file.Read(Bits, 0, BitsLength); - //file.Close(); + // Use ImageImporter for supported formats. + if (TryImportImage(path, out var image)) + { + _importedImage = image; + Initialize(); + return; + } + #if GDI try { @@ -172,21 +179,20 @@ public static BitmapImage BitmapFromUri(Uri uri) _wpfImage = BitmapFromUri(new Uri(path)); #endif -#if true_ - float vres = image.VerticalResolution; - float hres = image.HorizontalResolution; - SizeF size = image.PhysicalDimension; - int flags = image.Flags; - Size sz = image.Size; - GraphicsUnit units = GraphicsUnit.Millimeter; - RectangleF rect = image.GetBounds(ref units); - int width = image.Width; -#endif Initialize(); } XImage(Stream stream) { + // Use ImageImporter for supported formats. + if (TryImportImage(stream, out var image)) + { + _importedImage = image; + // We do not store stream here because ImageImporter gets all information we need. + Initialize(); + return; + } + //// Create a dummy unique path. //_path = "*" + Guid.NewGuid().ToString("B"); @@ -318,6 +324,7 @@ public static XImage FromBitmapImageStreamThatCannotSeek(Stream stream) #endif #if CORE + /// /// Creates an image from the specified file. /// @@ -327,10 +334,16 @@ public static XImage FromFile(string path) if (PdfReader.TestPdfFile(path) > 0) return new XPdfForm(path); - var ii = ImageImporter.GetImageImporter(); - var i = ii.ImportImage(path) ?? throw new InvalidOperationException("Unsupported image format."); + PdfSharp.Internal.Imaging.ImportedImage ii; + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + // We can use FromStream, now that we have a stream. + var success = ToBeNamed.TryImportImage(stream, out ii!); + if (!success) + throw new InvalidOperationException("Unsupported image format."); + } - var image = new XImage(i); + var image = new XImage(ii); image._path = path; return image; } @@ -351,10 +364,11 @@ public static XImage FromStream(Stream stream) if (PdfReader.TestPdfFile(stream) > 0) return new XPdfForm(stream); - var ii = ImageImporter.GetImageImporter(); - var i = ii.ImportImage(stream) ?? throw new InvalidOperationException("Unsupported image format."); + var success = ToBeNamed.TryImportImage(stream, out var ii); + if (!success) + throw new InvalidOperationException("Unsupported image format."); - XImage image = new XImage(i); + XImage image = new XImage(ii!); image._stream = stream; return image; } @@ -371,10 +385,12 @@ public static XImage FromBitmapImageStreamThatCannotSeek(Stream stream) if (stream.CanSeek) throw new InvalidOperationException("Use this function only for streams that do not support Seek."); - var ii = ImageImporter.GetImageImporter(); - var i = ii.ImportImage(stream) ?? throw new InvalidOperationException("Unsupported image format."); + var worker = new StreamReaderWorker(stream); + var success = ToBeNamed.TryImportImage(worker.Data, out var i); + if (!success) + throw new InvalidOperationException("Unsupported image format."); - XImage image = new XImage(i); + XImage image = new XImage(i!); image._stream = stream; return image; } @@ -385,7 +401,7 @@ public static XImage FromBitmapImageStreamThatCannotSeek(Stream stream) /// Creates an image from the specified file. /// /// The path to a BMP, PNG, GIF, JPEG, TIFF, or PDF file. - /// Uses an platform-independent implementation if set to true. + /// Uses a platform-independent implementation if set to true. /// The platform-dependent implementation, if available, will support more image formats. public static XImage FromFile(string path, bool platformIndependent) { @@ -394,10 +410,12 @@ public static XImage FromFile(string path, bool platformIndependent) // TODO_OLD: Check PDF file. - var ii = ImageImporter.GetImageImporter(); - var i = ii.ImportImage(path) ?? throw new InvalidOperationException("Unsupported image format."); ; + var reader = new StreamReaderWorker(path); + var success = ToBeNamed.TryImportImage(reader.Data, out var img); + if (!success) + throw new InvalidOperationException("Unsupported image format."); ; - var image = new XImage(i); + var image = new XImage(img!); image._path = path; return image; } @@ -406,7 +424,7 @@ public static XImage FromFile(string path, bool platformIndependent) /// Creates an image from the specified stream.
///
/// The stream containing a BMP, PNG, GIF, JPEG, TIFF, or PDF file. - /// Uses an platform-independent implementation if set to true. + /// Uses a platform-independent implementation if set to true. /// The platform-dependent implementation, if available, will support more image formats. public static XImage FromStream(Stream stream, bool platformIndependent) { @@ -418,10 +436,11 @@ public static XImage FromStream(Stream stream, bool platformIndependent) if (stream == null) throw new ArgumentNullException(nameof(stream)); - var ii = ImageImporter.GetImageImporter(); - var i = ii.ImportImage(stream) ?? throw new InvalidOperationException("Unsupported image format."); + var success = ToBeNamed.TryImportImage(stream, out var img); + if (!success) + throw new InvalidOperationException("Unsupported image format."); - XImage image = new XImage(i); + XImage image = new XImage(img!); image._stream = stream; return image; } @@ -471,7 +490,7 @@ internal void Initialize() if (_importedImage != null) { // In PDF there are two formats: JPEG and PDF bitmap. - if (_importedImage is ImportedImageJpeg) + if (_importedImage is ImportedJpegImage) _format = XImageFormat.Jpeg; else _format = XImageFormat.Png; @@ -769,7 +788,7 @@ internal static bool ReadJpegFile(string filename, int maxRead, ref byte[]? imag imageBits[9] == 0x46 && imageBits[10] == 0x0*/) { - // HACK_OLD: store the file in PDF if extension matches ... + // HACK_OLD: store the file in PDF if extension matches... return null; } return false; @@ -828,7 +847,7 @@ public virtual double Width { #if CORE || GDI || WPF if (_importedImage != null) - return _importedImage.Information.Width; + return _importedImage.PixelWidth; #endif #if CORE return 100; @@ -869,8 +888,7 @@ public virtual double Height { #if CORE || GDI || WPF if (_importedImage != null) - return _importedImage.Information.Height; - + return _importedImage.PixelHeight; #endif #if CORE return 100; @@ -925,12 +943,7 @@ public virtual double PointWidth #if CORE || GDI || WPF if (_importedImage != null) { - if (_importedImage.Information.HorizontalDPM > 0) - return _importedImage.Information.Width * FactorDPM72 / _importedImage.Information.HorizontalDPM; - if (_importedImage.Information.HorizontalDPI > 0) - return _importedImage.Information.Width * 72 / _importedImage.Information.HorizontalDPI; - // Assume 72 DPI if information not available. - return _importedImage.Information.Width; + return _importedImage.Width; } #endif #if CORE @@ -951,9 +964,6 @@ public virtual double PointWidth Debug.Assert(DoubleUtil.AreRoughlyEqual(gdiWidth, wpfWidth, 5)); return wpfWidth; #endif - //#if GDI && !WPF - // return _gdiImage.Width * 72 / _gdiImage.HorizontalResolution; - //#endif #if WPF && !GDI Debug.Assert(Math.Abs(_wpfImage.PixelWidth * 72 / _wpfImage.DpiX - _wpfImage.Width * (72.0 / 96.0)) < 0.001); return _wpfImage.Width * (72.0 / 96.0); @@ -976,12 +986,7 @@ public virtual double PointHeight #if CORE || GDI || WPF if (_importedImage != null) { - if (_importedImage.Information.VerticalDPM > 0) - return _importedImage.Information.Height * FactorDPM72 / _importedImage.Information.VerticalDPM; - if (_importedImage.Information.VerticalDPI > 0) - return _importedImage.Information.Height * 72 / _importedImage.Information.VerticalDPI; - // Assume 72 DPI if information not available. - return _importedImage.Information.Height; + return _importedImage.Height; } #endif #if CORE @@ -1001,9 +1006,6 @@ public virtual double PointHeight Debug.Assert(DoubleUtil.AreRoughlyEqual(gdiHeight, wpfHeight, 5)); return wpfHeight; #endif - //#if GDI && !WPF - // return _gdiImage.Height * 72 / _gdiImage.HorizontalResolution; - //#endif #if WPF && !GDI Debug.Assert(Math.Abs(_wpfImage.PixelHeight * 72 / _wpfImage.DpiY - _wpfImage.Height * (72.0 / 96.0)) < 0.001); return _wpfImage.Height * (72.0 / 96.0); @@ -1023,7 +1025,7 @@ public virtual int PixelWidth { #if CORE || GDI || WPF if (_importedImage != null) - return (int)_importedImage.Information.Width; + return (int)_importedImage.PixelWidth; #endif #if CORE return 100; @@ -1042,9 +1044,6 @@ public virtual int PixelWidth Debug.Assert(gdiWidth == wpfWidth); return wpfWidth; #endif - //#if GDI && !WPF - // return _gdiImage.Width; - //#endif #if WPF && !GDI return _wpfImage.PixelWidth; #endif @@ -1063,7 +1062,7 @@ public virtual int PixelHeight { #if CORE || GDI || WPF if (_importedImage != null) - return (int)_importedImage.Information.Height; + return (int)_importedImage.PixelHeight; #endif #if CORE return 100; @@ -1109,13 +1108,7 @@ public virtual double HorizontalResolution #if CORE || GDI || WPF if (_importedImage != null) { - if (_importedImage.Information.HorizontalDPI > 0) - return _importedImage.Information.HorizontalDPI; - if (_importedImage.Information.HorizontalDPM > 0) - return _importedImage.Information.HorizontalDPM / FactorDPM; - if (_importedImage.Information.DefaultDPI > 0) - return _importedImage.Information.DefaultDPI; - return 96; + return _importedImage.DpiX; } #endif #if CORE @@ -1157,13 +1150,7 @@ public virtual double VerticalResolution #if CORE || GDI || WPF if (_importedImage != null) { - if (_importedImage.Information.VerticalDPI > 0) - return (double)_importedImage.Information.VerticalDPI; - if (_importedImage.Information.VerticalDPM > 0) - return (double)(_importedImage.Information.VerticalDPM / FactorDPM); - if (_importedImage.Information.DefaultDPI > 0) - return (double)_importedImage.Information.DefaultDPI; - return 96; + return _importedImage.DpiY; } #endif #if CORE @@ -1209,6 +1196,40 @@ public virtual bool Interpolate XImageFormat? _format; + static bool TryImportImage(string path, + [MaybeNullWhen(false)] out ImportedImage image) + { + image = null!; + try + { + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var success = ToBeNamed.TryImportImage(stream, out image!); + if (!success) + return false; + } + + return true; + } + catch (Exception ex) + { + PdfSharpLogHost.Logger.LogWarning( + $"Unhandled exception in TryImportImage, maybe caused by an unsupported image format. {ex.ToString()}"); + } + return false; + } + + static bool TryImportImage(Stream stream, + [MaybeNullWhen(false)] out ImportedImage image) + { + image = null!; + var success = ToBeNamed.TryImportImage(stream, out image!); + if (!success) + return false; + + return true; + } + #if WPF /// /// Gets a value indicating whether this image is JPEG. @@ -1399,7 +1420,6 @@ internal XGraphics? AssociatedGraphics XGraphics? _associatedGraphics; #if CORE || GDI || WPF - // ReSharper disable once InconsistentNaming internal ImportedImage? _importedImage; #endif #if GDI diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XKnownColorTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XKnownColorTable.cs index 08448b28..f8eb7df3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XKnownColorTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XKnownColorTable.cs @@ -186,6 +186,6 @@ static XKnownColorTable() ColorTable[140] = 0xFF9ACD32; // YellowGreen } - internal static uint[] ColorTable = new uint[141]; + internal static readonly uint[] ColorTable = new uint[141]; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs index 2adbb397..ce88b043 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XLinearGradientBrush.cs @@ -1,9 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; using System.ComponentModel; -using PdfSharp.Internal; +using PdfSharp.Internal.Threading; #if GDI using System.Drawing; using System.Drawing.Drawing2D; @@ -120,17 +119,17 @@ public XLinearGradientBrush(XRect rect, XColor color1, XColor color2, XLinearGra //public XLinearGradientBrush(RectangleF rect, XColor color1, XColor color2, double angle, bool isAngleScaleable); //public XLinearGradientBrush(RectangleF rect, XColor color1, XColor color2, double angle, bool isAngleScaleable); - //private Blend _GetBlend(); - //private ColorBlend _GetInterpolationColors(); - //private XColor[] _GetLinearColors(); - //private RectangleF _GetRectangle(); - //private Matrix _GetTransform(); - //private WrapMode _GetWrapMode(); - //private void _SetBlend(Blend blend); - //private void _SetInterpolationColors(ColorBlend blend); - //private void _SetLinearColors(XColor color1, XColor color2); - //private void _SetTransform(Matrix matrix); - //private void _SetWrapMode(WrapMode wrapMode); + //Blend _GetBlend(); + //ColorBlend _GetInterpolationColors(); + //XColor[] _GetLinearColors(); + //RectangleF _GetRectangle(); + //Matrix _GetTransform(); + //WrapMode _GetWrapMode(); + //void _SetBlend(Blend blend); + //void _SetInterpolationColors(ColorBlend blend); + //void _SetLinearColors(XColor color1, XColor color2); + //void _SetTransform(Matrix matrix); + //void _SetWrapMode(WrapMode wrapMode); //public override object Clone(); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XMatrix.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XMatrix.cs index f03a5f43..bf437487 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XMatrix.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XMatrix.cs @@ -15,10 +15,8 @@ namespace PdfSharp.Drawing /// Represents a 3-by-3 matrix that represents an affine 2D transformation. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - [Serializable, - StructLayout(LayoutKind - .Sequential)] //, TypeConverter(typeof(MatrixConverter)), ValueSerializer(typeof(MatrixValueSerializer))] - public struct XMatrix : IFormattable + [Serializable, StructLayout(LayoutKind.Sequential)] //, TypeConverter(typeof(MatrixConverter)), ValueSerializer(typeof(MatrixValueSerializer))] + public struct XMatrix : IFormattable, IEquatable { [Flags] internal enum XMatrixTypes @@ -315,7 +313,7 @@ public void Scale(double scaleX, double scaleY, XMatrixOrder order) [Obsolete("Use ScaleAppend or ScalePrepend explicitly, because in GDI+ and WPF the defaults are contrary.", true)] // ReSharper disable InconsistentNaming public void Scale(double scaleXY) - // ReSharper restore InconsistentNaming + // ReSharper restore InconsistentNaming { throw new InvalidOperationException("Temporarily out of order."); //Scale(scaleXY, scaleXY, XMatrixOrder.Prepend); @@ -326,7 +324,7 @@ public void Scale(double scaleXY) ///
// ReSharper disable InconsistentNaming public void ScaleAppend(double scaleXY) - // ReSharper restore InconsistentNaming + // ReSharper restore InconsistentNaming { Scale(scaleXY, scaleXY, XMatrixOrder.Append); } @@ -336,7 +334,7 @@ public void ScaleAppend(double scaleXY) ///
// ReSharper disable InconsistentNaming public void ScalePrepend(double scaleXY) - // ReSharper restore InconsistentNaming + // ReSharper restore InconsistentNaming { Scale(scaleXY, scaleXY, XMatrixOrder.Prepend); } @@ -346,7 +344,7 @@ public void ScalePrepend(double scaleXY) ///
// ReSharper disable InconsistentNaming public void Scale(double scaleXY, XMatrixOrder order) - // ReSharper restore InconsistentNaming + // ReSharper restore InconsistentNaming { Scale(scaleXY, scaleXY, order); } @@ -492,7 +490,7 @@ public void RotateAt(double angle, XPoint point) ///
public void RotateAtAppend(double angle, XPoint point) { - RotateAt(angle, point, XMatrixOrder.Append); + RotateAt(angle * Const.Deg2Rad, point, XMatrixOrder.Append); } /// @@ -508,9 +506,9 @@ public void RotateAtPrepend(double angle, XPoint point) /// public void RotateAt(double angle, XPoint point, XMatrixOrder order) { + angle %= 360.0; if (order == XMatrixOrder.Append) { - angle %= 360.0; this *= CreateRotationRadians(angle * Const.Deg2Rad, point.X, point.Y); //Translate(point.X, point.Y, order); @@ -519,7 +517,6 @@ public void RotateAt(double angle, XPoint point, XMatrixOrder order) } else { - angle %= 360.0; this = CreateRotationRadians(angle * Const.Deg2Rad, point.X, point.Y) * this; } @@ -803,9 +800,10 @@ public void Invert() { double determinant = Determinant; if (DoubleUtil.IsZero(determinant)) - throw - new InvalidOperationException( - "NotInvertible"); //SR.Get(SRID.Transform_NotInvertible, new object[0])); + { + throw new InvalidOperationException( + "NotInvertible"); //SR.Get(SRID.Transform_NotInvertible, new object[0])); + } switch (_type) { @@ -830,13 +828,13 @@ public void Invert() return; default: - { - double detInvers = 1.0 / determinant; - SetMatrix(_m22 * detInvers, -_m12 * detInvers, -_m21 * detInvers, _m11 * detInvers, - (_m21 * _offsetY - _offsetX * _m22) * detInvers, - (_offsetX * _m12 - _m11 * _offsetY) * detInvers, XMatrixTypes.Unknown); - break; - } + { + double detInvers = 1.0 / determinant; + SetMatrix(_m22 * detInvers, -_m12 * detInvers, -_m21 * detInvers, _m11 * detInvers, + (_m21 * _offsetY - _offsetX * _m22) * detInvers, + (_offsetX * _m12 - _m11 * _offsetY) * detInvers, XMatrixTypes.Unknown); + break; + } } } @@ -1225,7 +1223,7 @@ internal void MultiplyPoint(ref double x, ref double y) y *= _m22; return; - case (XMatrixTypes.Scaling | XMatrixTypes.Translation): + case XMatrixTypes.Scaling | XMatrixTypes.Translation: x *= _m11; x += _offsetX; y *= _m22; @@ -1305,8 +1303,7 @@ static XMatrix CreateIdentity() /// /// Sets the matrix. /// - void SetMatrix(double m11, double m12, double m21, double m22, double offsetX, double offsetY, - XMatrixTypes type) + void SetMatrix(double m11, double m12, double m21, double m22, double offsetX, double offsetY, XMatrixTypes type) { _m11 = m11; _m12 = m12; @@ -1360,8 +1357,8 @@ internal static class MatrixHelper // Fast multiplication taking matrix type into account. Reflectored from WPF. internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2) { - XMatrixTypes type1 = matrix1._type; - XMatrixTypes type2 = matrix2._type; + var type1 = matrix1._type; + var type2 = matrix2._type; if (type2 != XMatrixTypes.Identity) { if (type1 == XMatrixTypes.Identity) @@ -1389,13 +1386,11 @@ internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2) { switch (((int)type1 << 4) | (int)type2) { - //case 0x22: case ((int)XMatrixTypes.Scaling << 4) | (int)XMatrixTypes.Scaling: matrix1._m11 *= matrix2._m11; matrix1._m22 *= matrix2._m22; return; - //case 0x23: case ((int)XMatrixTypes.Scaling << 4) | (int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling: @@ -1406,7 +1401,6 @@ internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2) matrix1._type = XMatrixTypes.Scaling | XMatrixTypes.Translation; return; - //case 0x32: case (((int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling) << 4) | (int)XMatrixTypes.Scaling: matrix1._m11 *= matrix2._m11; @@ -1415,7 +1409,6 @@ internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2) matrix1._offsetY *= matrix2._m22; return; - //case 0x33: case (((int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling) << 4) | (int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling: matrix1._m11 *= matrix2._m11; @@ -1423,28 +1416,26 @@ internal static void MultiplyMatrix(ref XMatrix matrix1, ref XMatrix matrix2) matrix1._offsetX = matrix2._m11 * matrix1._offsetX + matrix2._offsetX; matrix1._offsetY = matrix2._m22 * matrix1._offsetY + matrix2._offsetY; return; + - //case 0x24: case ((int)XMatrixTypes.Scaling << 4) | (int)XMatrixTypes.Unknown: - //case 0x34: + case (((int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling) << 4) | (int)XMatrixTypes.Unknown: - //case 0x42: + case ((int)XMatrixTypes.Unknown << 4) | (int)XMatrixTypes.Scaling: - //case 0x43: + case ((int)XMatrixTypes.Unknown << 4) | (int)XMatrixTypes.Translation | (int)XMatrixTypes.Scaling: - //case 0x44: + case ((int)XMatrixTypes.Unknown << 4) | (int)XMatrixTypes.Unknown: matrix1 = new XMatrix( matrix1._m11 * matrix2._m11 + matrix1._m12 * matrix2._m21, matrix1._m11 * matrix2._m12 + matrix1._m12 * matrix2._m22, matrix1._m21 * matrix2._m11 + matrix1._m22 * matrix2._m21, matrix1._m21 * matrix2._m12 + matrix1._m22 * matrix2._m22, - matrix1._offsetX * matrix2._m11 + matrix1._offsetY * matrix2._m21 + - matrix2._offsetX, - matrix1._offsetX * matrix2._m12 + matrix1._offsetY * matrix2._m22 + - matrix2._offsetY); + matrix1._offsetX * matrix2._m11 + matrix1._offsetY * matrix2._m21 + matrix2._offsetX, + matrix1._offsetX * matrix2._m12 + matrix1._offsetY * matrix2._m22 + matrix2._offsetY); return; } } @@ -1460,8 +1451,8 @@ internal static void PrependOffset(ref XMatrix matrix, double offsetX, double of } else { - matrix._offsetX += (matrix._m11 * offsetX) + (matrix._m21 * offsetY); - matrix._offsetY += (matrix._m12 * offsetX) + (matrix._m22 * offsetY); + matrix._offsetX += matrix._m11 * offsetX + matrix._m21 * offsetY; + matrix._offsetY += matrix._m12 * offsetX + matrix._m22 * offsetY; if (matrix._type != XMatrixTypes.Unknown) matrix._type |= XMatrixTypes.Translation; } @@ -1521,7 +1512,7 @@ internal static void TransformRect(ref XRect rect, ref XMatrix matrix) /// The debugger display. // ReSharper disable UnusedMember.Local string DebuggerDisplay - // ReSharper restore UnusedMember.Local + // ReSharper restore UnusedMember.Local { get { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs index a3f3335b..d55d0fc8 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPdfForm.cs @@ -19,7 +19,7 @@ namespace PdfSharp.Drawing /// PDF document. XPdfForm objects are used like images to draw an existing PDF page of an external /// document in the current document. XPdfForm objects can only be placed in PDF documents. If you try /// to draw them using a XGraphics based on an GDI+ context no action is taken if no placeholder image - /// is specified. Otherwise, the place holder is drawn. + /// is specified. Otherwise, the placeholder is drawn. ///
public class XPdfForm : XForm { @@ -90,7 +90,7 @@ void Initialize() ///
internal override void Finish() { - if (_formState == FormState.NotATemplate || _formState == FormState.Finished) + if (_formState is FormState.NotATemplate or FormState.Finished) return; base.Finish(); @@ -110,10 +110,10 @@ internal override void Finish() // if (_document.Options.CompressContentStreams) // { // _pdfForm.Stream.Value = Filtering.FlateDecode.Encode(pdfForm.Stream.Value); - // _pdfForm.Elements["/Filter"] = new PdfName("/FlateDecode"); + // _pdfForm.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); // } // int length = _pdfForm.Stream.Length; - // _pdfForm.Elements.SetInteger("/Length", length); + // _pdfForm.Elements.SetInteger(PdfStream.Keys.Length, length); //} } @@ -267,14 +267,24 @@ public override XSize Size ///
public override XMatrix Transform { - get => _transform; + //get => _transform; + //set + //{ + // if (_transform != value) + // { + // // Discard PdfFromXObject when Transform changed. + // _pdfForm = null; + // _transform = value; + // } + //} + get => base.Transform; set { - if (_transform != value) + if (Transform != value) { // Discard PdfFromXObject when Transform changed. _pdfForm = null; - _transform = value; + Transform = value; } } } @@ -339,7 +349,7 @@ internal PdfDocument ExternalDocument ///
public static string ExtractPageNumber(string path, out int pageNumber) { - // Note: duplicated in class ImageHelper. + // This code is duplicated in class ImageHelper. if (path == null) throw new ArgumentNullException(nameof(path)); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs index db29ce1e..c1c9f4bd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPen.cs @@ -1,8 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -#if GDI using PdfSharp.Internal; +using PdfSharp.Internal.Threading; +#if GDI using System.Drawing.Drawing2D; using GdiPen = System.Drawing.Pen; #endif @@ -250,7 +251,7 @@ public static implicit operator XPen(Pen pen) DashStyle = (XDashStyle)pen.DashStyle, _miterLimit = pen.MiterLimit }, - _ => throw new NotImplementedException("Pen type not supported by PDFsharp.") + _ => throw new NotSupportedException("Pen type not supported by PDFsharp.") }; // Custom dash style, fix by drice2@ageone.de. if (pen.DashStyle == System.Drawing.Drawing2D.DashStyle.Custom) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPoint.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPoint.cs index b80d06a6..aba5b4fc 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPoint.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XPoint.cs @@ -26,7 +26,7 @@ namespace PdfSharp.Drawing [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] [Serializable] [StructLayout(LayoutKind.Sequential)] // TypeConverter(typeof(PointConverter)), ValueSerializer(typeof(PointValueSerializer))] - public struct XPoint : IFormattable + public struct XPoint : IFormattable, IEquatable { /// /// Initializes a new instance of the XPoint class with the specified coordinates. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRadialGradientBrush.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRadialGradientBrush.cs index a7ff5c0a..dcbde7ea 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRadialGradientBrush.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRadialGradientBrush.cs @@ -198,7 +198,7 @@ internal override System.Drawing.Brush RealizeGdiBrush() GdiLinearGradientBrush brush; try { - Locks.EnterGdiPlus(); + Lock.EnterGdiPlus(); if (_useRect) { brush = new GdiLinearGradientBrush(_rect.ToRectangleF(), @@ -214,7 +214,7 @@ internal override System.Drawing.Brush RealizeGdiBrush() brush.Transform = _matrix.ToGdiMatrix(); //brush.WrapMode = WrapMode.Clamp; } - finally { Locks.ExitGdiPlus(); } + finally { Lock.ExitGdiPlus(); } return brush; #else return null!; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRect.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRect.cs index 10f1641a..bd748006 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRect.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XRect.cs @@ -26,7 +26,7 @@ namespace PdfSharp.Drawing /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] [Serializable, StructLayout(LayoutKind.Sequential)] // , ValueSerializer(typeof(RectValueSerializer)), TypeConverter(typeof(RectConverter))] - public struct XRect : IFormattable + public struct XRect : IFormattable, IEquatable { /// /// Initializes a new instance of the XRect class. @@ -141,7 +141,7 @@ public static XRect FromLTRB(double left, double top, double right, double botto /// /// Determines whether the two rectangles are equal. /// - // ReSharper disable CompareOfFloatsByEqualityOperator + // ReSharper disable CompareOfFloatsByEqualityOperator public static bool operator ==(XRect rect1, XRect rect2) => rect1.X == rect2.X && rect1.Y == rect2.Y && rect1.Width == rect2.Width && rect1.Height == rect2.Height; // ReSharper restore CompareOfFloatsByEqualityOperator @@ -363,7 +363,7 @@ public double Right get { if (IsEmpty) - return double.NegativeInfinity; + return Double.NegativeInfinity; return _x + _width; } } @@ -376,7 +376,7 @@ public double Bottom get { if (IsEmpty) - return double.NegativeInfinity; + return Double.NegativeInfinity; return _y + _height; } } @@ -660,7 +660,7 @@ public void Scale(double scaleX, double scaleY) /// /// Converts this instance to a System.Drawing.RectangleF. /// - public RectangleF ToRectangleF() + public RectangleF ToRectangleF() => new((float)_x, (float)_y, (float)_width, (float)_height); #endif @@ -668,13 +668,13 @@ public RectangleF ToRectangleF() /// /// Performs an implicit conversion from a System.Drawing.Rectangle to an XRect. /// - public static implicit operator XRect(Rectangle rect) + public static implicit operator XRect(Rectangle rect) => new(rect.X, rect.Y, rect.Width, rect.Height); /// /// Performs an implicit conversion from a System.Drawing.RectangleF to an XRect. /// - public static implicit operator XRect(RectangleF rect) + public static implicit operator XRect(RectangleF rect) => new(rect.X, rect.Y, rect.Width, rect.Height); #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XSize.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XSize.cs index 86d851eb..f970a2b9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XSize.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XSize.cs @@ -267,7 +267,6 @@ public double Height /// /// Gets the DebuggerDisplayAttribute text. /// - /// The debugger display. // ReSharper disable UnusedMember.Local string DebuggerDisplay // ReSharper restore UnusedMember.Local diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XTypeface.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XTypeface.cs index d1f799aa..6b4c8fa1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XTypeface.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XTypeface.cs @@ -17,7 +17,7 @@ namespace PdfSharp.Drawing /// Represents a combination of XFontFamily, XFontWeight, XFontStyleEx, and XFontStretch. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public class XTypeface // Note: In English, it’s spelled 'typeface', but 'font face'. + public class XTypeface // Note that in English it’s spelled 'typeface', but 'font face'. { /// /// Initializes a new instance of the class. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XVector.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XVector.cs index c3f94ebb..457ae065 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XVector.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/XVector.cs @@ -19,7 +19,7 @@ namespace PdfSharp.Drawing [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] [Serializable] [StructLayout(LayoutKind.Sequential)] - public struct XVector : IFormattable + public struct XVector : IFormattable, IEquatable { /// /// Initializes a new instance of the struct. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XFontStyleEx.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XFontStyleEx.cs index dd18c517..8de2e228 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XFontStyleEx.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XFontStyleEx.cs @@ -10,8 +10,6 @@ namespace PdfSharp.Drawing [Flags] public enum XFontStyleEx // Same values as System.Drawing.FontStyle. { - // Will be renamed to XGdiFontStyle or XWinFontStyle. - /// /// Normal text. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XGraphicsUnit.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XGraphicsUnit.cs index 5c6a9a5a..4f2891b6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XGraphicsUnit.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XGraphicsUnit.cs @@ -8,6 +8,8 @@ namespace PdfSharp.Drawing /// public enum XGraphicsUnit // NOT the same values as System.Drawing.GraphicsUnit { + // Values are identical to PdfSharp.Graphics.GraphicsUnit. + /// /// Specifies a printer’s point (1/72 inch) as the unit of measure. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XSmoothingMode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XSmoothingMode.cs index beac608f..cddaf507 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XSmoothingMode.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing/enums/XSmoothingMode.cs @@ -10,7 +10,7 @@ namespace PdfSharp.Drawing /// and the edges of filled areas. /// [Flags] - public enum XSmoothingMode // same values as System.Drawing.Drawing2D.SmoothingMode + public enum XSmoothingMode // Same values as System.Drawing.Drawing2D.SmoothingMode { // Not used in PDF rendering process. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/DocumentEvents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/DocumentEvents.cs index 6ba09fbe..0951d676 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Events/DocumentEvents.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/DocumentEvents.cs @@ -3,117 +3,64 @@ using PdfSharp.Drawing; using PdfSharp.Pdf; +using PdfSharp.Pdf.Metadata; + +// TODO REMOVE +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Events { - /// - /// The event type of PageEvent. - /// - public enum PageEventType - { - /// - /// A new page was created. - /// - Created, - - /// - /// A page was moved. - /// - Moved, - - /// - /// A page was imported from another document. - /// - Imported, - - /// - /// A page was removed. - /// - Removed - } - - /// + /// // TODO /// EventArgs for changes in the PdfPages of a document. /// - public class PageEventArgs(PdfObject source) : PdfSharpEventArgs(source) + public class DocumentMetadataEventArgs(PdfDocument source) : PdfSharpEventArgs(source) { - /// - /// Gets or sets the affected page. - /// - public PdfPage Page { get; set; } = default!; - - /// - /// Gets or sets the page index of the affected page. - /// - public int PageIndex { get; set; } + public required PdfMetadata Metadata { get; init; } - /// - /// The event type of PageEvent. - /// - public PageEventType EventType { get; internal set; } + public required DocumentMetadataInfo Info { get; init; } } - /// - /// EventHandler for OnPageAdded and OnPageRemoved. + /// // TODO + /// /// /// The sender of the event. - /// The PageEventArgs of the event. - public delegate void PageAddedOrRemovedEventHandler(object sender, PageEventArgs e); + /// The XmlMetadataEventArgs of the event. + public delegate void DocumentMetadataEventHandler(object sender, DocumentMetadataEventArgs e); +} +namespace PdfSharp.Events // MaOs4StLa Review. +{ /// - /// The action type of PageGraphicsEvent. + /// EventArgs for a document. /// - public enum PageGraphicsActionType - { - /// - /// The XGraphics object for the page was created. - /// - GraphicsCreated = 1, - - /// - /// DrawString() was called on the page’s XGraphics object. - /// - DrawString, - - /// - /// Another method drawing content was called on the page’s XGraphics object. - /// - Draw - } + public class DocumentEventArgs(PdfDocument source) : PdfSharpEventArgs(source) + { } + public delegate void DocumentEventHandler(object sender, DocumentEventArgs e); +} + +namespace PdfSharp.Events +{ /// - /// EventArgs for actions on a page’s XGraphics object. + /// A class encapsulating all events of a PdfDocument. /// - public class PageGraphicsEventArgs(PdfObject source) : PdfSharpEventArgs(source) + public class DocumentEvents { /// - /// Gets the page that causes the event. + /// An event raised if a document gets disposed. /// - public PdfPage Page { get; internal set; } = default!; + /// The sender of the event. + /// The DocumentEventArgs of the event. + public void OnDisposed(object sender, DocumentEventArgs args) // MaOs4StLa Review. + { + Disposed?.Invoke(sender, args); + } /// - /// Gets the created XGraphics object. + /// EventHandler for OnDisposed. /// - public XGraphics Graphics { get; internal set; } = default!; + public event DocumentEventHandler? Disposed; // MaOs4StLa Review. - /// - /// The action type of PageGraphicsEvent. - /// - public PageGraphicsActionType ActionType { get; internal set; } - } - - /// - /// EventHandler for OnPageGraphicsAction. - /// - /// The sender of the event. - /// The PageGraphicsEventArgs of the event. - public delegate void PageGraphicsEventHandler(object sender, PageGraphicsEventArgs e); - - /// - /// A class encapsulating all events of a PdfDocument. - /// - public class DocumentEvents - { /// /// An event raised if a page was added. /// @@ -173,5 +120,23 @@ public void OnPageGraphicsAction(object sender, PageGraphicsEventArgs args) /// EventHandler for OnPageGraphicsAction. /// public event PageGraphicsEventHandler? PageGraphicsAction; + /// + /// An event raised if something is drawn on a page’s XGraphics object. TODO + /// + /// The sender of the event. + /// The PageGraphicsEventArgs of the event. + public void OnCreateDocumentMetadata(object sender, DocumentMetadataEventArgs args) + { + if (CreateDocumentMetadata == null) + throw new InvalidOperationException("The document event CreateDocumentMetadata is not set and cannot be invoked. "+ + "You must provide a delegate that handle the CreateDocumentMetadata event."); + + CreateDocumentMetadata.Invoke(sender, args); + } + + /// + /// EventHandler for OnCreateDocumentMetadata. + /// + public event DocumentMetadataEventHandler? CreateDocumentMetadata; } } \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageEvents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageEvents.cs new file mode 100644 index 00000000..ecac0d98 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageEvents.cs @@ -0,0 +1,62 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; + +namespace PdfSharp.Events // #FILE PageEvents.cs +{ + /// + /// The event type of PageEvent. + /// + public enum PageEventType + { + /// + /// A new page was created. + /// + Created, + + /// + /// A page was moved. + /// + Moved, + + /// + /// A page was imported from another document. + /// + Imported, + + /// + /// A page was removed. + /// + Removed + } + + /// + /// EventArgs for changes in the PdfPages of a document. + /// + //public class PageEventArgs(PdfObject source) : PdfSharpEventArgs(source) + public class PageEventArgs(PdfDocument source) : PdfSharpEventArgs(source) + { + /// + /// Gets or sets the affected page. + /// + public PdfPage Page { get; set; } = null!; + + /// + /// Gets or sets the page index of the affected page. + /// + public int PageIndex { get; set; } + + /// + /// The event type of PageEvent. + /// + public PageEventType EventType { get; internal set; } + } + + /// + /// EventHandler for OnPageAdded and OnPageRemoved. + /// + /// The sender of the event. + /// The PageEventArgs of the event. + public delegate void PageAddedOrRemovedEventHandler(object sender, PageEventArgs e); +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageGraphicsEvents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageGraphicsEvents.cs new file mode 100644 index 00000000..7818566e --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PageGraphicsEvents.cs @@ -0,0 +1,57 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; +using PdfSharp.Pdf; + +namespace PdfSharp.Events +{ + /// + /// The action type of PageGraphicsEvent. + /// + public enum PageGraphicsActionType + { + /// + /// The XGraphics object for the page was created. + /// + GraphicsCreated = 1, + + /// + /// DrawString() was called on the page’s XGraphics object. + /// + DrawString, + + /// + /// Another method drawing content was called on the page’s XGraphics object. + /// + Draw + } + + /// + /// EventArgs for actions on a page’s XGraphics object. + /// + public class PageGraphicsEventArgs(PdfDocument source) : PdfSharpEventArgs(source) + { + /// + /// Gets the page that causes the event. + /// + public PdfPage Page { get; internal init; } = null!; + + /// + /// Gets the created XGraphics object. + /// + public XGraphics Graphics { get; internal init; } = null!; + + /// + /// The action type of PageGraphicsEvent. + /// + public PageGraphicsActionType ActionType { get; internal init; } + } + + /// + /// EventHandler for OnPageGraphicsAction. + /// + /// The sender of the event. + /// The PageGraphicsEventArgs of the event. + public delegate void PageGraphicsEventHandler(object sender, PageGraphicsEventArgs e); +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/PdfSharpEventArgs.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PdfSharpEventArgs.cs index c51203b0..e1406a22 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Events/PdfSharpEventArgs.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/PdfSharpEventArgs.cs @@ -3,16 +3,18 @@ using PdfSharp.Pdf; +// v7.0.0 Ready + namespace PdfSharp.Events { /// /// Base class for EventArgs in PDFsharp. /// - public abstract class PdfSharpEventArgs(PdfObject source) : EventArgs + public abstract class PdfSharpEventArgs(PdfDocument source) : EventArgs { /// - /// The source of the event. + /// The source PDF document of the event. /// - public PdfObject Source { get; set; } = source; + public PdfDocument Source { get; set; } = source; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Events/RenderEvents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Events/RenderEvents.cs index 82a4ffbd..12286d31 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Events/RenderEvents.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Events/RenderEvents.cs @@ -1,8 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal.OpenType; using PdfSharp.Drawing; -using PdfSharp.Fonts; using PdfSharp.Pdf; namespace PdfSharp.Events @@ -10,7 +10,8 @@ namespace PdfSharp.Events /// /// EventArgs for PrepareTextEvent. /// - public class PrepareTextEventArgs(PdfObject source, XFont font, string text) : PdfSharpEventArgs(source) + public class PrepareTextEventArgs(PdfDocument source, XFont font, string text) + : PdfSharpEventArgs(source) { /// /// Gets the font used to draw the text. @@ -35,7 +36,8 @@ public class PrepareTextEventArgs(PdfObject source, XFont font, string text) : P /// /// EventArgs for RenderTextEvent. /// - public class RenderTextEventArgs(PdfObject source, XFont font, CodePointGlyphIndexPair[] codePointGlyphIndexPair) : PdfSharpEventArgs(source) + public class RenderTextEventArgs(PdfDocument source, XFont font, CodePointGlyphIndexPair[] codePointGlyphIndexPair) + : PdfSharpEventArgs(source) { /// /// Gets or sets a value indicating whether the determination of the glyph identifiers must be reevaluated. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontHelper.cs index 1114e3ba..0432136e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontHelper.cs @@ -1,6 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal.OpenType; +using PdfSharp.Drawing; #if GDI using PdfSharp.Logging; using GdiFontFamily = System.Drawing.FontFamily; @@ -22,15 +24,6 @@ using Windows.UI.Text; using Windows.UI.Xaml.Media; #endif -//using System.Drawing; -//using Microsoft.Extensions.Logging; -//using PdfSharp.Events; -//using PdfSharp.Fonts; -using Microsoft.Extensions.Logging; -using PdfSharp.Drawing; -using PdfSharp.Fonts; -//using PdfSharp.Fonts.OpenType; -//using PdfSharp.Logging; namespace PdfSharp.Fonts.Internal { @@ -39,109 +32,6 @@ namespace PdfSharp.Fonts.Internal /// static class FontHelper { -#if true_ // #DELETE 25-12-31 - /// - /// Measure string directly from font data. - /// - public static XSize MeasureString(string text, XFont font) - { - Debug.Assert(false, "Use the new version with code run parameter."); - - if (String.IsNullOrEmpty(text)) - return new(); // not XSize.Empty ! - - var cp = UnicodeHelper.Utf32FromString2(text); - var codePoints = font.OpenTypeDescriptor.GlyphIndicessFromCodepoints(cp); - //var codeRun = new CharacterCodeRun(codePoints); - - //// Invoke RenderEvent. - //var args = new RenderCodeRunEventArgs(Owner) - //{ - // Font = font, - // CodeRun = codeRun - //}; - //Owner.RenderEvents.OnRenderCodeRun(this, args); - //codeRun = args.CodeRun; - //codePoints = codeRun.Items; - - return MeasureString(codePoints, font); -#if true_ // Keep until 2025-12-31 for reference - var size = new XSize(); - var descriptor = FontDescriptorCache.GetOrCreateDescriptorFor(font) as OpenTypeDescriptor; - if (descriptor != null) - { - // Height is the sum of ascender and descender. - size.Height = (descriptor.Ascender + descriptor.Descender) * font.Size / font.UnitsPerEm; - Debug.Assert(descriptor.Ascender > 0, "Ascender must be greater than 0."); - - bool isSymbolFont = descriptor.IsSymbolFont; - int length = text.Length; - int width = 0; - for (int idx = 0; idx < length; idx++) - { - char ch = text[idx]; - // H/A/C/K: Unclear what to do here. - if (ch < 32) - continue; - - if (Char.IsLowSurrogate(ch)) - { - // We only come here when the text contains a low surrogate not preceded by a high surrogate. - // This is an error in the UTF-32 text and therefore ignored. - PdfSharpLogHost.FontManagementLogger.LogWarning("Unexpected low surrogate found: 0x{Char:X2}", ch); - continue; - } - - int glyphIndex; - if (isSymbolFont) - { - ch = descriptor.RemapSymbolChar(ch); - glyphIndex = descriptor.BmpCodepointToGlyphIndex(ch); - } - else if (Char.IsHighSurrogate(ch)) - { - // UTF16 surrogate pair expected. - if (++idx < length) - { - var ch2 = text[idx]; - if (Char.IsLowSurrogate(ch2) is false) - { - PdfSharpLogHost.FontManagementLogger.LogWarning("High surrogate 0x{Char:X2} not followed by low surrogate.", ch); - continue; - } - glyphIndex = descriptor.SurrogatePairToGlyphIndex(ch, ch2); - } - else - { - PdfSharpLogHost.FontManagementLogger.LogWarning("High surrogate 0x{Char:X2} found at end of string.", ch); - continue; - } - } - else - { - // BMP character. - glyphIndex = descriptor.BmpCodepointToGlyphIndex(ch); - } - width += descriptor.GlyphIDToWidth(glyphIndex); - } - size.Width = width * font.Size / descriptor.UnitsPerEm; - - // Adjust bold simulation. - if ((font.GlyphTypeface.StyleSimulations & XStyleSimulations.BoldSimulation) == XStyleSimulations.BoldSimulation) - { - // Add 2% of the em-size for each character. - // Unsure how to deal with white space. Currently count as regular character. - size.Width += length * font.Size * Const.BoldEmphasis; - } - } - // BUG_OLD: Is it correct to return an empty size if we have no descriptor? - Debug.Assert(descriptor != null, "No OpenTypeDescriptor."); - - return size; -#endif - } -#endif - /// /// Measure string directly from font data. /// This function expects that the code run is ready to be measured. @@ -174,7 +64,7 @@ public static XSize MeasureString(CodePointGlyphIndexPair[] codeRun, XFont font) if ((font.GlyphTypeface.StyleSimulations & XStyleSimulations.BoldSimulation) == XStyleSimulations.BoldSimulation) { // Add 2% of the em-size for each character. - // Unsure how to deal with white space. Currently count as regular character. + // Unsure how to deal with white-space. Currently count as regular character. size.Width += length * font.Size * Const.BoldEmphasis; } return size; @@ -338,42 +228,6 @@ public static bool IsStyleAvailable(XFontFamily family, XFontStyleEx style) return false; } #endif - - /// - /// Calculates an Adler32 checksum combined with the buffer length - /// in a 64-bit unsigned integer. - /// - public static ulong CalcChecksum(byte[] buffer) - { - if (buffer == null) - throw new ArgumentNullException(nameof(buffer)); - - const uint prime = 65521; // largest prime smaller than 65536 - uint s1 = 0; - uint s2 = 0; - int length = buffer.Length; - int offset = 0; - while (length > 0) - { - int n = 3800; - if (n > length) - n = length; - length -= n; - while (--n >= 0) - { - s1 += buffer[offset++]; - s2 += s1; - } - s1 %= prime; - s2 %= prime; - } - //return ((ulong)((ulong)(((ulong)s2 << 16) | (ulong)s1)) << 32) | (ulong)buffer.Length; - ulong ul1 = ((ulong)s2 << 16) | s1; - //ul1 |= s1; - uint ui2 = (uint)buffer.Length; - return (ul1 << 32) | ui2; - } - public static XFontStyleEx CreateStyle(bool isBold, bool isItalic) => (isBold ? XFontStyleEx.Bold : 0) | (isItalic ? XFontStyleEx.Italic : 0); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontResolvingOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontResolvingOptions.cs index f516f6bf..abde8ae8 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontResolvingOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/FontResolvingOptions.cs @@ -1,13 +1,6 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -#if GDI -using System.Drawing; -using System.Drawing.Text; -#endif -#if WPF -using System.Windows.Media; -#endif using PdfSharp.Drawing; namespace PdfSharp.Fonts.Internal diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs-DELETE b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs-DELETE new file mode 100644 index 00000000..583889b5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs-DELETE @@ -0,0 +1,140 @@ +// DELETE + +////// PDFsharp - A .NET library for processing PDF +////// See the LICENSE file in the solution root for more information. + +////using Microsoft.Extensions.Logging; +////using PdfSharp.Internal.OpenType; +////using PdfSharp.Logging; +////using PdfSharp.Internal; + +////namespace PdfSharp.Fonts.Internal +////{ +////#warning remove from here +//// /// +//// /// A bunch of internal functions to handle Unicode. +//// /// +//// static class UnicodeHelper +//// { +//// internal const char HighSurrogateStart = '\uD800'; +//// internal const char HighSurrogateEnd = '\uDBFF'; +//// internal const char LowSurrogateStart = '\uDC00'; +//// internal const char LowSurrogateEnd = '\uDFFF'; +//// internal const int HighSurrogateRange = 0x3FF; +//// internal const int UnicodePlane01Start = 0x1_0000; + +//// /// +//// /// Converts a UTF-16 string into an array of Unicode code points. +//// /// +//// /// The string to be converted. +//// /// if set to true [coerce ANSI]. +//// /// The non ANSI. +//// public static int[] Utf32FromString(string s, bool coerceAnsi = false, byte nonAnsi = (byte)'?') // #NFM docu, design, ... +//// { +//// if (String.IsNullOrEmpty(s)) +//// return []; + +//// int length = s.Length; +//// var result = new int[length]; +//// int iRes = 0; + +//// for (int idx = 0; idx < length; idx++) +//// { +//// ref var current = ref result[iRes++]; + +//// var ch = s[idx]; +//// current = ch; +//// int high10Bits = ch - HighSurrogateStart; +//// if ((uint)high10Bits <= HighSurrogateRange) +//// { +//// // Case: ch is a high surrogate. +//// idx++; +//// if ((uint)idx < (uint)s.Length) +//// { +//// var ch2 = s[idx]; +//// int low10Bits = ch2 - LowSurrogateStart; +//// if ((uint)low10Bits <= HighSurrogateRange) +//// { +//// // Case: ch2 is a low surrogate. + +//// // Combine high and low surrogate code points into UTF-32 Unicode code point. +//// current = (high10Bits << 10) + low10Bits + UnicodePlane01Start; +//// } +//// else +//// { +//// // Case: Unpaired high surrogate. +//// LogHost.Logger./*FontManagementLogger.*/LogDebug("High surrogate 0x{Char:X2} not followed by a low surrogate.", ch); +//// idx--; +//// continue; +//// } +//// } +//// else +//// { +//// // Case: High surrogate at string end. +//// LogHost.Logger./*FontManagementLogger.*/LogDebug("High surrogate 0x{Char:X2} found at end of string.", ch); +//// break; +//// } +//// } +//// else +//// { +//// // Case: ch is in BMP range. + +//// //if (ch - LOW_SURROGATE_START <= HIGH_SURROGATE_RANGE) +//// if ((uint)ch - LowSurrogateStart <= HighSurrogateRange) +//// { +//// // Case: unpaired low surrogate. +//// // We only come here when the text contains a low surrogate not preceded by a high surrogate. +//// // This is an error in the UTF-32 text. +//// LogHost.Logger./*FontManagementLogger.*/LogDebug("Unexpected low surrogate found: 0x{Char:X2}", ch); +//// continue; +//// } + +//// if (coerceAnsi && !AnsiEncoding.IsAnsi(ch)) +//// current = nonAnsi; +//// } +//// } +//// if (iRes < length) +//// Array.Resize(ref result, iRes); +//// return result; +//// } + +//// /// +//// /// Converts a UTF-16 string into an array of code points of a symbol font. +//// /// +//// public static int[] SymbolCodePointsFromString(string s, OpenTypeDescriptor openTypeDescriptor) +//// { +//// if (String.IsNullOrEmpty(s)) +//// return []; + +//// int length = s.Length; +//// var result = new int[length]; + +//// for (int idx = 0; idx < length; idx++) +//// result[idx] = openTypeDescriptor.RemapSymbolChar(s[idx]); + +//// return result; +//// } + +//// /// +//// /// Convert a surrogate pair to UTF-32 code point. +//// /// Similar to Char.ConvertToUtf32 but never throws an error. +//// /// Instead, returns 0 if one of the surrogates are invalid. +//// /// +//// /// The high surrogate. +//// /// The low surrogate. +//// public static int ConvertToUtf32(char highSurrogate, char lowSurrogate) +//// { +//// uint highSurrogateOffset = (uint)highSurrogate - UnicodeHelper.HighSurrogateStart; +//// uint lowSurrogateOffset = (uint)lowSurrogate - UnicodeHelper.LowSurrogateStart; + +//// // If surrogates not in range return 0. +//// // The cool code using underflow effect to check two ranges with one comparison comes from the .NET source code. +//// if ((highSurrogateOffset | lowSurrogateOffset) > UnicodeHelper.HighSurrogateRange) +//// return 0; +//// // Convert to code point. +//// return ((int)highSurrogateOffset << 10) + (lowSurrogate - UnicodeHelper.LowSurrogateStart) + UnicodePlane01Start; +//// } + +//// internal static bool IsInRange(char c, char min, char max) => (uint)(c - min) <= (uint)(max - min); +//// } +////} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs deleted file mode 100644 index c481dc50..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/FontDescriptor.cs +++ /dev/null @@ -1,203 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -#if GDI -using System.Drawing; -using System.Drawing.Drawing2D; -#endif -#if WPF -using System.Windows; -using System.Windows.Media; -#endif -using PdfSharp.Pdf.Internal; -using PdfSharp.Fonts; -using PdfSharp.Drawing; -using PdfSharp.Internal; - -#pragma warning disable 0649 - -namespace PdfSharp.Fonts.OpenType -{ - /// - /// Base class for all font descriptors. - /// Currently only OpenTypeDescriptor is derived from this base class. - /// - class FontDescriptor - { - protected FontDescriptor(string key) - { - Key = key; - } - - public string Key { get; } - - /// - /// - /// - public string FontName3 { get; init; } = default!; // #NFM check format of this name. It is the name of the XFont or the name of XGlyphTypeface. - - /// - /// - /// - public string Weight - { - get; - private set; - // BUG_OLD: never set - } = default!; - - /// - /// Gets a value indicating whether this instance belongs to a bold font. - /// - public virtual bool IsBoldFace => false; - - /// - /// - /// - public float ItalicAngle { get; protected set; } - - /// - /// Gets a value indicating whether this instance belongs to an italic font. - /// - public virtual bool IsItalicFace => false; - - /// - /// - /// - public int XMin { get; protected set; } - - /// - /// - /// - public int YMin { get; protected set; } - - /// - /// - /// - public int XMax { get; protected set; } - - /// - /// - /// - public int YMax { get; protected set; } - - /// - /// - /// - public bool IsFixedPitch - { - get; - init; // BUG_OLD: never set - } - - /// - /// - /// - public int UnderlinePosition { get; protected set; } - - /// - /// - /// - public int UnderlineThickness { get; protected set; } - - /// - /// - /// - public int StrikeoutPosition { get; protected set; } - - /// - /// - /// - public int StrikeoutSize { get; protected set; } - - /// - /// - /// - public string Version - { - get; - private set; - // BUG_OLD: never set - } = default!; - - /// - /// - /// - public string EncodingScheme - { - get; - private set; - // BUG_OLD: never set - } = default!; - - /// - /// - /// - public int UnitsPerEm { get; protected set; } - - /// - /// - /// - public int CapHeight { get; protected set; } - - /// - /// - /// - public int XHeight { get; protected set; } - - /// - /// - /// - public int Ascender { get; protected set; } - - /// - /// - /// - public int Descender { get; protected set; } - - /// - /// - /// - public int Leading { get; protected set; } - - /// - /// - /// - public int Flags - { - get; - private set; - // BUG_OLD: never set - } - - /// - /// - /// - public int StemV { get; protected set; } - - /// - /// - /// - public int LineSpacing { get; protected set; } - - internal static string ComputeFdKey(string name, XFontStyleEx style) - => ComputeFdKey(name, - (style & XFontStyleEx.Bold) != 0, - (style & XFontStyleEx.Italic) != 0); - - internal static string ComputeFdKey(string name, bool isBold, bool isItalic) - { - name = name.ToLowerInvariant(); - var key = isBold switch - { - false when !isItalic => name + '/', - true when !isItalic => name + "/b", - false when isItalic => name + "/i", - _ => name + "/bi" - }; - return key; - } - - internal readonly int GlobalVersion = Globals.Global.Version; - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs deleted file mode 100644 index 48d99ecc..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphDataTable.cs +++ /dev/null @@ -1,174 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -#define VERBOSE_ - -using PdfSharp.Internal; -using System; -using System.Collections.Generic; - -//using Fixed = System.Int32; -//using FWord = System.Int16; -//using UFWord = System.UInt16; - -namespace PdfSharp.Fonts.OpenType -{ - /// - /// This table contains information that describes the glyphs in the font in the TrueType outline format. - /// Information regarding the rasterizer (scaler) refers to the TrueType rasterizer. - /// http://www.microsoft.com/typography/otspec/glyf.htm - /// - class GlyphDataTable : OpenTypeFontTable - { - public const string Tag = TableTagNames.Glyf; - - internal byte[] GlyphTable = default!; - - public GlyphDataTable() - : base(null, Tag) - { - DirectoryEntry.Tag = TableTagNames.Glyf; - } - - public GlyphDataTable(OpenTypeFontFace fontData) - : base(fontData, Tag) - { - DirectoryEntry.Tag = TableTagNames.Glyf; - Read(); - } - - /// - /// Converts the bytes in a handy representation. - /// - public void Read() - { - try - { - // Not yet needed... - } - // ReSharper disable once RedundantCatchClause - catch (Exception) - { - throw; - } - } - - /// - /// Gets the data of the specified glyph. - /// - public byte[] GetGlyphData(int glyph) - { - //var loca = _fontData!.loca; - int start = GetOffset(glyph); - int next = GetOffset(glyph + 1); - int count = next - start; - byte[] bytes = new byte[count]; - Buffer.BlockCopy(_fontData!.FontSource!.Bytes, start, bytes, 0, count); - return bytes; - } - - /// - /// Gets the size of the byte array that defines the glyph. - /// - public int GetGlyphSize(int glyph) - { - //var loca = _fontData.loca; - return GetOffset(glyph + 1) - GetOffset(glyph); - } - - /// - /// Gets the offset of the specified glyph relative to the first byte of the font image. - /// - public int GetOffset(int glyph) - { - return DirectoryEntry.Offset + _fontData!.loca.LocaTable[glyph]; - } - - /// - /// Adds for all composite glyphs, the glyphs the composite one is made of. - /// - public void CompleteGlyphClosure(Dictionary glyphs) - { - int count = glyphs.Count; - ushort[] glyphArray = new ushort[glyphs.Count]; - glyphs.Keys.CopyTo(glyphArray, 0); - // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because of .NET Framework - if (!glyphs.ContainsKey(0)) - glyphs.Add(0, null); - // #NFM - // Ensure no other threads can alter the Position property of this OpenTypeFontFace instance, - // see https://forum.pdfsharp.net/viewtopic.php?f=2&t=2248#p10378 - try - { - Locks.EnterFontFactory(); - for (int idx = 0; idx < count; idx++) - AddCompositeGlyphs(glyphs, glyphArray[idx]); - } - finally { Locks.ExitFontFactory(); } - } - - /// - /// If the specified glyph is a composite glyph add the glyphs it is made of to the glyph table. - /// - void AddCompositeGlyphs(Dictionary glyphs, int glyph) - { - //int start = fontData.loca.GetOffset(glyph); - int start = GetOffset(glyph); - // Has no contour? - if (start == GetOffset(glyph + 1)) - return; - _fontData!.Position = start; - int numContours = _fontData.ReadShort(); - // Isn’t a composite glyph? - if (numContours >= 0) - return; - _fontData.SeekOffset(8); - for (; ; ) - { - int flags = _fontData.ReadUFWord(); - ushort cGlyph = _fontData.ReadUFWord(); - if (!glyphs.ContainsKey(cGlyph)) - glyphs.Add(cGlyph, null); - if ((flags & MORE_COMPONENTS) == 0) - return; - int offset = (flags & ARG_1_AND_2_ARE_WORDS) == 0 ? 2 : 4; - if ((flags & WE_HAVE_A_SCALE) != 0) - offset += 2; - else if ((flags & WE_HAVE_AN_X_AND_Y_SCALE) != 0) - offset += 4; - if ((flags & WE_HAVE_A_TWO_BY_TWO) != 0) - offset += 8; - _fontData.SeekOffset(offset); - } - } - - /// - /// Prepares the font table to be compiled into its binary representation. - /// - public override void PrepareForCompilation() - { - base.PrepareForCompilation(); - - if (DirectoryEntry.Length == 0) - DirectoryEntry.Length = GlyphTable.Length; - DirectoryEntry.CheckSum = CalcChecksum(GlyphTable); - } - - /// - /// Converts the font into its binary representation. - /// - public override void Write(OpenTypeFontWriter writer) - { - writer.Write(GlyphTable, 0, DirectoryEntry.PaddedLength); - } - - // ReSharper disable InconsistentNaming - // Constants from OpenType spec. - const int ARG_1_AND_2_ARE_WORDS = 1; - const int WE_HAVE_A_SCALE = 8; - const int MORE_COMPONENTS = 32; - const int WE_HAVE_AN_X_AND_Y_SCALE = 64; - const int WE_HAVE_A_TWO_BY_TWO = 128; - // ReSharper restore InconsistentNaming - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs deleted file mode 100644 index 13e04a2c..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GlyphTypefaceCache.cs +++ /dev/null @@ -1,73 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using System.Diagnostics.CodeAnalysis; -using System.Text; -using PdfSharp.Drawing; -using PdfSharp.Internal; - -namespace PdfSharp.Fonts.OpenType -{ - /// - /// Global table of all glyph typefaces. - /// - 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(); } - } - - 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(); } - } - - internal static void Reset() - { - Globals.Global.Fonts.GlyphTypefacesByKey.Clear(); - } - - 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; - int count = familyKeys.Count; - string[] keys = new string[count]; - familyKeys.CopyTo(keys, 0); - Array.Sort(keys, StringComparer.OrdinalIgnoreCase); - foreach (string key in keys) - state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.GlyphTypefacesByKey[key].DebuggerDisplay); - state.Append("\n"); - return state.ToString(); - } - } -} - -namespace PdfSharp.Internal -{ - partial class Globals - { - partial class FontStorage - { - /// - /// Maps typeface key to glyph typeface. - /// - public readonly Dictionary GlyphTypefacesByKey = new(StringComparer.Ordinal); - } - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontWriter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontWriter.cs deleted file mode 100644 index fef0bed6..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontWriter.cs +++ /dev/null @@ -1,30 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -namespace PdfSharp.Fonts.OpenType -{ - /// - /// Represents a writer for True Type font files. - /// - class OpenTypeFontWriter : FontWriter - { - /// - /// Initializes a new instance of the class. - /// - public OpenTypeFontWriter(Stream stream) - : base(stream) - { } - - /// - /// Writes a table name. - /// - public void WriteTag(string tag) - { - Debug.Assert(tag.Length == 4); - WriteByte((byte)(tag[0])); - WriteByte((byte)(tag[1])); - WriteByte((byte)(tag[2])); - WriteByte((byte)(tag[3])); - } - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs deleted file mode 100644 index 99ad3332..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontfaceCache.cs +++ /dev/null @@ -1,116 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using System.Text; -using PdfSharp.Internal; -using System.Diagnostics.CodeAnalysis; -using PdfSharp.Fonts.OpenType; - -namespace PdfSharp.Fonts.OpenType -{ - /// - /// Global table of all OpenType font faces cached by their face name and check sum. - /// - [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - static class OpenTypeFontFaceCache - { - /// - /// Tries to get font face by its key. - /// - 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(); } - } - - /// - /// Tries to get font face by its check sum. - /// - 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(); } - } - - public static OpenTypeFontFace AddFontFace(OpenTypeFontFace fontFace) - { - try - { - 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; - } - finally { Locks.ExitFontFactory(); } - } - - internal static void Reset() - { - Globals.Global.Fonts.FontFaceCache.Clear(); - Globals.Global.Fonts.FontFacesByCheckSum.Clear(); - } - - 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; - int count = familyKeys.Count; - string[] keys = new string[count]; - familyKeys.CopyTo(keys, 0); - Array.Sort(keys, StringComparer.OrdinalIgnoreCase); - foreach (string key in keys) - state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontFaceCache[key].DebuggerDisplay); - state.Append("\n"); - return state.ToString(); - } - - /// - /// Gets the DebuggerDisplayAttribute text. - /// - // ReSharper disable UnusedMember.Local - static string DebuggerDisplay - // ReSharper restore UnusedMember.Local - => String.Format(CultureInfo.InvariantCulture, "Font faces: {0}", Globals.Global.Fonts.FontFaceCache.Count); - } -} - -namespace PdfSharp.Internal -{ - partial class Globals - { - partial class FontStorage - { - /// - /// Maps face name to OpenType font face. - /// - public readonly Dictionary FontFaceCache = []; - - /// - /// Maps font source key to OpenType font face. - /// - public readonly Dictionary FontFacesByCheckSum = []; - } - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs index 83071d6b..3c2444d8 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CMapInfo.cs @@ -1,25 +1,24 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using Microsoft.Extensions.Logging; -using PdfSharp.Fonts.Internal; -using PdfSharp.Fonts.OpenType; -using PdfSharp.Logging; -using PdfSharp.Pdf.Internal; +using System.Collections; +using PdfSharp.Internal.OpenType; namespace PdfSharp.Fonts { /// - /// Helper class that determines the characters used in a particular font. + /// Helper class that collects the characters and glyphs used in a particular font. + /// The glyphs are used in /FontDescriptor for calculating the font subset. + /// The characters are used in /TrueType fonts with /WinAnsiEncoding for the /Widths array. + /// The characters are used in /Type0 fonts with /Identity-H encoding for /ToUnicode map and + /// in the descendant /CIDFontType2 for the /W array. /// class CMapInfo { - public CMapInfo(OpenTypeDescriptor descriptor) + public CMapInfo(int glyphCount) { - Debug.Assert(descriptor != null); - _descriptor = descriptor; + GlyphIndicesNew = new(glyphCount); } - readonly OpenTypeDescriptor _descriptor; public void AddChars(CodePointGlyphIndexPair[] codePoints) { @@ -30,7 +29,22 @@ public void AddChars(CodePointGlyphIndexPair[] codePoints) var item = codePoints[idx]; if (item.GlyphIndex == 0) continue; - +#if true // #PSGFX + if (!CodePointsToGlyphIndices.ContainsKey(item.CodePoint)) + { + CodePointsToGlyphIndices.Add(item.CodePoint, item.GlyphIndex); + GlyphIndices[item.GlyphIndex] = null; + GlyphIndicesNew[item.GlyphIndex] = true; + MinCodePoint = Math.Min(MinCodePoint, item.CodePoint); + MaxCodePoint = Math.Max(MaxCodePoint, item.CodePoint); + } + else + { + // In PDFsharp Graphics we also can draw glyphs without a code point. + GlyphIndices[item.GlyphIndex] = null; + GlyphIndicesNew[item.GlyphIndex] = true; + } +#else if (CodePointsToGlyphIndices.ContainsKey(item.CodePoint)) continue; @@ -38,6 +52,7 @@ public void AddChars(CodePointGlyphIndexPair[] codePoints) GlyphIndices[item.GlyphIndex] = default; MinCodePoint = Math.Min(MinCodePoint, item.CodePoint); MaxCodePoint = Math.Max(MaxCodePoint, item.CodePoint); +#endif } } @@ -55,6 +70,9 @@ public int[] Chars } } + /// + /// Gets an ordered array glyph indices. + /// public ushort[] GetGlyphIndices() { var indices = new ushort[GlyphIndices.Count]; @@ -63,17 +81,25 @@ public ushort[] GetGlyphIndices() return indices; } - public int MinCodePoint = Int32.MaxValue; + public int MinCodePoint = Int32.MaxValue; public int MaxCodePoint = Int32.MinValue; /// - /// Maps a Unicode code point to a glyph ID. + /// Maps a Unicode code point to a glyphs. + /// Contains all used codepoints and their glyphs. + /// Used for ToUnicode table and /W entry. /// - public Dictionary CodePointsToGlyphIndices = []; + public readonly Dictionary CodePointsToGlyphIndices = []; /// - /// Collects all used glyph IDs. Value is not used. + /// Collects all used glyph IDs. Value is now used by PDFsharp Graphics where it is possible + /// to draw only glyphs without associated code point. Therefore, this dictionary is now + /// relevant for computing the font subset. + /// We also use this dictionary to fast lookup if a glyph is already added. /// - public Dictionary GlyphIndices = []; + public readonly Dictionary GlyphIndices = []; + + // TODO #PSGFX Use a BitArray for collection the glyphs. + public readonly BitArray GlyphIndicesNew; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs index 19007492..4f61566e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontDescriptorCache.cs @@ -2,77 +2,89 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; -using PdfSharp.Fonts.OpenType; using PdfSharp.Internal; +using PdfSharp.Internal.OpenType; +using PdfSharp.Internal.Threading; namespace PdfSharp.Fonts { /// /// Global table of OpenType font descriptor objects. /// - static class FontDescriptorCache + class PsFontDescriptorCache { + internal PsFontDescriptorCache(PsGlobals.PsFontStorage storage) + { + _storage = storage; + } + /// /// 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) + public OpenTypeFontDescriptor GetOrCreateDescriptorFor(XFont font) { if (font == null) throw new ArgumentNullException(nameof(font)); font.GlyphTypeface.CheckVersion(); - //FontSelector1 selector = new FontSelector1(font); string fontDescriptorKey = font.GlyphTypeface.Key; try { - var cache = Globals.Global.Fonts.FontDescriptorCache; - Locks.EnterFontFactory(); + var cache = OtGlobals.Global.OTFonts.FontDescriptorCache; // TODO HACK + Locks.EnterFontManagement(); if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) return descriptor; - descriptor = new OpenTypeDescriptor(fontDescriptorKey, font); - cache.Add(fontDescriptorKey, descriptor); + descriptor = new OpenTypeFontDescriptor(fontDescriptorKey, + font.GlyphTypeface.OTFontFace, + font.GlyphTypeface.FamilyName, + font.GlyphTypeface.FaceName); + cache.TryAdd(fontDescriptorKey, descriptor); return descriptor; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } - public static FontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypeface) + public OpenTypeFontDescriptor GetOrCreateDescriptorFor(XGlyphTypeface glyphTypeface) { glyphTypeface.CheckVersion(); string fontDescriptorKey = glyphTypeface.Key; try { - var cache = Globals.Global.Fonts.FontDescriptorCache; - Locks.EnterFontFactory(); + var cache = OtGlobals.Global.OTFonts.FontDescriptorCache; + Locks.EnterFontManagement(); if (cache.TryGetValue(fontDescriptorKey, out var descriptor)) return descriptor; - descriptor = new OpenTypeDescriptor(fontDescriptorKey, glyphTypeface); - cache.Add(fontDescriptorKey, descriptor); + descriptor = new OpenTypeFontDescriptor(fontDescriptorKey, + glyphTypeface.OTFontFace, + glyphTypeface.FamilyName, + glyphTypeface.FontName, 42); + cache.TryAdd(fontDescriptorKey, descriptor); return descriptor; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } /// /// 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) + public OpenTypeFontDescriptor 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); + string fontDescriptorKey = KeyHelper.ComputeFdKey(fontFamilyName, (OTFontStyleHack)style); try { - var cache = Globals.Global.Fonts.FontDescriptorCache; - Locks.EnterFontFactory(); + //var cache = Globals.Global.Fonts.FontDescriptorCache; + var cache = OtGlobals.Global.OTFonts.FontDescriptorCache; + Locks.EnterFontManagement(); if (!cache.TryGetValue(fontDescriptorKey, out var descriptor)) { var font = new XFont(fontFamilyName, 10, style); @@ -81,30 +93,13 @@ 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; } - finally { Locks.ExitFontFactory(); } - } - - internal static void Reset() - { - Globals.Global.Fonts.FontDescriptorCache.Clear(); + finally { Locks.ExitFontManagement(); } } - } -} -namespace PdfSharp.Internal -{ - partial class Globals - { - partial class FontStorage - { - /// - /// Maps font descriptor key to font descriptor which is currently only an OpenTypeFontDescriptor. - /// - public readonly Dictionary FontDescriptorCache = []; - } + readonly PsGlobals.PsFontStorage _storage; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs index 78257168..31dcb09d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactory.cs @@ -1,7 +1,15 @@ // 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 PdfSharp.Internal.Threading; +using PdfSharp.Internal.OpenType; +using Microsoft.Extensions.Logging; +using PdfSharp.Logging; +using PdfSharp.Drawing; +using PdfSharp.Fonts.Internal; #if GDI using System.Drawing; using GdiFontFamily = System.Drawing.FontFamily; @@ -15,20 +23,11 @@ using WpfGlyphTypeface = System.Windows.Media.GlyphTypeface; using WpfTypeface = System.Windows.Media.Typeface; #endif -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; - -// Re-Sharper disable RedundantNameQualifier namespace PdfSharp.Fonts { /// - /// Provides functionality to map a font face request to a physical font. + /// Provides functionality to map a typeface request to a physical font face. /// static class FontFactory { @@ -49,10 +48,10 @@ static class FontFactory try { - var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; - var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; + var fontResolverInfosByName = PsGlobals.Global.Fonts.FontResolverInfosByName; + var fontSourcesByName = PsGlobals.Global.Fonts.FontSourcesByName; - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); // Was this typeface requested before? if (fontResolverInfosByName.TryGetValue(typefaceKey, out var fontResolverInfo)) return fontResolverInfo; @@ -111,10 +110,16 @@ static class FontFactory finally { _fallbackFontResolverInvoked = false; - Locks.ExitFontFactory(); + Locks.ExitFontManagement(); } } - static bool _fallbackFontResolverInvoked; + + /// + /// A flag that indicated that the fallback font resolver is invoked during a font resolution. + /// The flag can safely be a static because it is only temporarily used during font resolving + /// and that is always running protected by a lock. + /// + static bool _fallbackFontResolverInvoked; // Save to be static declaration. /// /// Register resolver info and font source for a custom font resolver . @@ -138,10 +143,10 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f #endif try { - var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; - var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; + var fontResolverInfosByName = PsGlobals.Global.Fonts.FontResolverInfosByName; + var fontSourcesByName = PsGlobals.Global.Fonts.FontSourcesByName; - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); // OverrideStyleSimulations is true only for internal quality tests. // With this code we can simulate bold and/or italic for a font face even if @@ -165,7 +170,7 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f // Discard new object and reuse previous one. fontResolverInfo = existingFontResolverInfo; // Associate existing resolver info with the new typeface key. - fontResolverInfosByName.Add(typefaceKey, fontResolverInfo); + fontResolverInfosByName.TryAdd(typefaceKey, fontResolverInfo); #if DEBUG_ // The font source should exist. Debug.Assert(fontResolverInfosByName.ContainsKey(fontResolverInfo.FaceName)); @@ -176,10 +181,10 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f // 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. + 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.Add(resolverInfoKey, fontResolverInfo); + 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. @@ -210,102 +215,26 @@ internal static void RegisterResolverResult(IFontResolver fontResolver, string f // 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, + // Add font source’s font resolver name if it is different to the face key. + if (String.Compare(fontResolverInfo.FaceName, fontSource.FontFaceKey, StringComparison.OrdinalIgnoreCase) != 0) - fontSourcesByName.Add(fontResolverInfo.FaceName, fontSource); + fontSourcesByName.TryAdd(fontResolverInfo.FaceName, fontSource); } } } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } -#if GDI - /// - /// Registers the font face. - /// - // 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; - - 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.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(); } - } -#endif - - /// - /// Gets the bytes of a physical font with specified face name. - /// - public static XFontSource GetFontSourceByFontName(string fontName) - { - if (Globals.Global.Fonts.FontSourcesByName.TryGetValue(fontName, out var fontSource)) - return fontSource; - - Debug.Assert(false, $"An XFontSource with the name '{fontName}' does not exist."); - return null; - } - - /// - /// Gets the bytes of a physical font with specified face name. - /// - public static XFontSource GetFontSourceByTypefaceKey(string typefaceKey) - { - if (Globals.Global.Fonts.FontSourcesByName.TryGetValue(typefaceKey, out var fontSource)) - return fontSource; - - Debug.Assert(false, $"An XFontSource with the typeface key '{typefaceKey}' does not exist."); - return null; - } - - public static bool TryGetFontSourceByKey(ulong key, [MaybeNullWhen(false)] out XFontSource fontSource) - { - return Globals.Global.Fonts.FontSourcesByKey.TryGetValue(key, out fontSource); - } - - /// - /// Gets a value indicating whether at least one font source was created. - /// - public static bool HasFontSources => Globals.Global.Fonts.FontSourcesByName.Count > 0; + // ========== FontResolverInfo ========== public static bool TryGetFontResolverInfoByTypefaceKey(string typeFaceKey, [MaybeNullWhen(false)] out FontResolverInfo info) { - return Globals.Global.Fonts.FontResolverInfosByName.TryGetValue(typeFaceKey, out info); + return PsGlobals.Global.Fonts.FontResolverInfosByName.TryGetValue(typeFaceKey, out info); } - public static bool TryGetFontSourceByTypefaceKey(string typefaceKey, [MaybeNullWhen(false)] out XFontSource source) - { - return Globals.Global.Fonts.FontSourcesByName.TryGetValue(typefaceKey, out source); - } - - //public static bool TryGetFontSourceByFaceName(string faceName, out XFontSource source) - //{ - // return FontSourcesByName.TryGetValue(faceName, out source); - //} - internal static void CacheFontResolverInfo(string typefaceKey, FontResolverInfo fontResolverInfo) { - var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; + var fontResolverInfosByName = PsGlobals.Global.Fonts.FontResolverInfosByName; // Check whether identical font is already registered. if (fontResolverInfosByName.TryGetValue(typefaceKey, out _)) { @@ -318,117 +247,23 @@ 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); - } - - /// - /// Caches a font source under its face name and its key. - /// - public static XFontSource CacheFontSource(XFontSource fontSource) - { - try - { - 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; - for (int idx = 0; idx < length; idx++) - { - if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx]) - { - //Debug.Assert(false,"Two fonts with identical checksum found."); - break; - //goto FontsAreNotIdentical; - } - } - Debug.Assert(existingFontSource.FontFace != null); -#endif - 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(); - } - - 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; - } - finally { Locks.ExitFontFactory(); } - } - - /// - /// Caches a font source under its face name and its key. - /// - public static XFontSource CacheNewFontSource(string typefaceKey, XFontSource fontSource) - { - // Debug.Assert(!FontSourcesByFaceName.ContainsKey(fontSource.FaceName)); - - // Check whether an identical font source with a different face name already exists. - if (Globals.Global.Fonts.FontSourcesByKey.TryGetValue(fontSource.Key, out var existingFontSource)) - { - //// Fonts have same length and check sum. Now check byte by byte identity. - //int length = fontSource.Bytes.Length; - //for (int idx = 0; idx < length; idx++) - //{ - // if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx]) - // { - // goto FontsAreNotIdentical; - // } - //} - return existingFontSource; - - ////// The bytes are really identical. Register font source again with the new face name - ////// but return the existing one to save memory. - ////FontSourcesByFaceName.Add(fontSource.FaceName, 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(); - } - - OpenTypeFontFace fontFace = fontSource.FontFace; - if (Equals(fontFace, null)) - { - fontFace = new OpenTypeFontFace(fontSource); - 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); - - return fontSource; + fontResolverInfosByName.TryAdd(typefaceKey, fontResolverInfo); + fontResolverInfosByName.TryAdd(fontResolverInfo.Key, fontResolverInfo); } public static void CacheExistingFontSourceWithNewTypefaceKey(string typefaceKey, XFontSource fontSource) { try { - Locks.EnterFontFactory(); - Globals.Global.Fonts.FontSourcesByName.Add(typefaceKey, fontSource); + Locks.EnterFontManagement(); + PsGlobals.Global.Fonts.FontSourcesByName.TryAdd(typefaceKey, fontSource); } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } public static void CheckInvocationOfPlatformFontResolver() { - if (!Locks.IsFontFactoryLookTaken()) + if (!Locks.IsFontManagementLookTaken()) throw new InvalidOperationException("You must not call PlatformFontResolver.ResolveTypeface if you are not calling from within a font resolver."); if (_fallbackFontResolverInvoked) @@ -437,14 +272,18 @@ public static void CheckInvocationOfPlatformFontResolver() internal static void Reset() { - Globals.Global.Fonts.FontResolverInfosByName.Clear(); - Globals.Global.Fonts.FontSourcesByName.Clear(); - Globals.Global.Fonts.FontSourcesByKey.Clear(); - + try + { + Locks.EnterFontManagement(); + PsGlobals.Global.Fonts.FontResolverInfosByName.Clear(); + PsGlobals.Global.Fonts.FontSourcesByName.Clear(); + PsGlobals.Global.Fonts.FontSourcesByKey.Clear(); #if CORE - // Also requires a reset. - PlatformFontResolver.Reset(); + // Also requires a reset. + PlatformFontResolver.Reset(); #endif + } + finally { Locks.ExitFontManagement(); } } internal static string GetFontCachesState() @@ -456,39 +295,39 @@ 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 = PsGlobals.Global.Fonts.FontResolverInfosByName.Keys; count = keyCollection.Count; keys = new string[count]; keyCollection.CopyTo(keys, 0); Array.Sort(keys, StringComparer.OrdinalIgnoreCase); foreach (string key in keys) - state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontResolverInfosByName[key].DebuggerDisplay); + state.AppendFormat(" {0}: {1}\n", key, PsGlobals.Global.Fonts.FontResolverInfosByName[key].DebuggerDisplay); state.Append('\n'); // FontSource by key. state.Append("Font source by key and name\n"); - Dictionary.KeyCollection fontSourceKeys = Globals.Global.Fonts.FontSourcesByKey.Keys; + var fontSourceKeys = PsGlobals.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; + state.AppendFormat(" {0}: {1}\n", ul, PsGlobals.Global.Fonts.FontSourcesByKey[ul].DebuggerDisplay); + var fontSourceNames = PsGlobals.Global.Fonts.FontSourcesByName.Keys; count = fontSourceNames.Count; keys = new string[count]; fontSourceNames.CopyTo(keys, 0); Array.Sort(keys, StringComparer.OrdinalIgnoreCase); foreach (string key in keys) - state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontSourcesByName[key].DebuggerDisplay); + state.AppendFormat(" {0}: {1}\n", key, PsGlobals.Global.Fonts.FontSourcesByName[key].DebuggerDisplay); state.Append("--------------------\n\n"); // FontFamilyInternal by name. - state.Append(FontFamilyCache.GetCacheState()); + state.Append(PsFontFamilyCache.GetCacheState()); // XGlyphTypeface by name. - state.Append(GlyphTypefaceCache.GetCacheState()); + state.Append(PsGlyphTypefaceCache.GetCacheState()); // OpenTypeFontFace by name. - state.Append(OpenTypeFontFaceCache.GetCacheState()); + //state.Append(OpenTypeFontFaceCache.GetCacheState()); return state.ToString(); } } @@ -498,26 +337,15 @@ namespace PdfSharp.Internal { using Fonts; - partial class Globals + partial class PsGlobals { - partial class FontStorage + partial class PsFontStorage { /// /// 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); - - /// - /// Maps typeface key or font name to font source. - /// - public readonly Dictionary 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 FontResolverInfosByName = new(StringComparer.Ordinal); } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactoryCaches.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactoryCaches.cs deleted file mode 100644 index 21f28626..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFactoryCaches.cs +++ /dev/null @@ -1,23 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -//using Microsoft.Extensions.Logging; -//using PdfSharp.Fonts.Internal; -//using PdfSharp.Fonts.OpenType; -//using PdfSharp.Logging; -//using PdfSharp.Pdf.Internal; - -//namespace PdfSharp.Fonts -//{ -// /// -// /// All font caches comes here. -// /// -// static class FontFactoryCaches -// { -// public static FontDescriptorCacheType FontDescriptorCache => default!; - -// public class FontDescriptorCacheType -// { -// } -// } -//} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs index de77d6a5..da0e2e38 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyCache.cs @@ -1,46 +1,52 @@ // 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 PdfSharp.Internal.Threading; +using PdfSharp.Fonts; #if GDI using GdiFontFamily = System.Drawing.FontFamily; -using PdfSharp.Fonts.OpenType; using PdfSharp.Drawing; #endif #if WPF using WpfFontFamily = System.Windows.Media.FontFamily; #endif -using PdfSharp.Internal; -using PdfSharp.Fonts; namespace PdfSharp.Fonts { /// /// Global cache of all internal font family objects. /// - static class FontFamilyCache + class PsFontFamilyCache { - public static FontFamilyInternal? GetFamilyByName(string familyName) + internal PsFontFamilyCache(PsGlobals.PsFontStorage storage) + { + _storage = storage; + } + + public FontFamilyInternal? GetFamilyByName(string familyName) { try { - Locks.EnterFontFactory(); - Globals.Global.Fonts.FontFamiliesByName.TryGetValue(familyName, out var family); + Locks.EnterFontManagement(); + _storage.FontFamiliesByName.TryGetValue(familyName, out var family); return family; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } /// /// Caches the font family or returns a previously cached one. /// - public static FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFamily) + public FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFamily) { try { - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); // 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 (_storage.FontFamiliesByName.TryGetValue(fontFamily.Name, out var existingFontFamily)) { #if DEBUG if (fontFamily.Name == "xxx") @@ -48,29 +54,31 @@ public static FontFamilyInternal CacheOrGetFontFamily(FontFamilyInternal fontFam #endif return existingFontFamily; } - Globals.Global.Fonts.FontFamiliesByName.Add(fontFamily.Name, fontFamily); + _storage.FontFamiliesByName.TryAdd(fontFamily.Name, fontFamily); return fontFamily; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } - internal static void Reset() - { - Globals.Global.Fonts.FontFamiliesByName.Clear(); - } + //internal static void Reset() + //{ + // PsGlobals.Global.Fonts.FontFamiliesByName.Clear(); + //} + + readonly PsGlobals.PsFontStorage _storage; 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 = PsGlobals.Global.Fonts.FontFamiliesByName.Keys; int count = familyKeys.Count; string[] keys = new string[count]; familyKeys.CopyTo(keys, 0); Array.Sort(keys, StringComparer.OrdinalIgnoreCase); foreach (string key in keys) - state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontFamiliesByName[key].DebuggerDisplay); + state.AppendFormat(" {0}: {1}\n", key, PsGlobals.Global.Fonts.FontFamiliesByName[key].DebuggerDisplay); state.Append("\n"); return state.ToString(); } @@ -79,14 +87,14 @@ internal static string GetCacheState() namespace PdfSharp.Internal { - partial class Globals + partial class PsGlobals { - partial class FontStorage + partial class PsFontStorage { /// /// 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..2ce30335 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontFamilyInternal.cs @@ -1,9 +1,11 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using PdfSharp.Drawing; using PdfSharp.Fonts.Internal; +using PdfSharp.Internal.Threading; +using PdfSharp.Internal.OpenType; using PdfSharp.Internal; + #if GDI using GdiFontFamily = System.Drawing.FontFamily; #endif @@ -20,7 +22,7 @@ namespace PdfSharp.Fonts /// Internal implementation class of XFontFamily. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public sealed class FontFamilyInternal + internal sealed class FontFamilyInternal { // Implementation Notes // FontFamilyInternal implements an XFontFamily. @@ -33,7 +35,9 @@ public sealed class FontFamilyInternal FontFamilyInternal(string familyName, bool createPlatformObjects) { - _sourceName = _name = familyName; + _sourceName = familyName; + _otFontFamily = OpenTypeFontFamily.GetOrCreateFrom(familyName); + _name = _otFontFamily.FamilyName; #if GDI if (createPlatformObjects) { @@ -69,6 +73,7 @@ public sealed class FontFamilyInternal { _sourceName = _name = gdiFontFamily.Name; _gdiFontFamily = gdiFontFamily; + _otFontFamily = OpenTypeFontFamily.GetOrCreateFrom(_name); #if WPF // Hybrid build only. _wpfFontFamily = new WpfFontFamily(gdiFontFamily.Name); @@ -82,6 +87,8 @@ public sealed class FontFamilyInternal _sourceName = wpfFontFamily.Source; _name = wpfFontFamily.FamilyNames[FontHelper.XmlLanguageEnUs]; _wpfFontFamily = wpfFontFamily; + _otFontFamily = OpenTypeFontFamily.GetOrCreateFrom(_name); + #if GDI // Hybrid build only. _gdiFontFamily = new GdiFontFamily(_sourceName); @@ -93,16 +100,17 @@ internal static FontFamilyInternal GetOrCreateFromName(string familyName, bool c { try { - Locks.EnterFontFactory(); - var family = FontFamilyCache.GetFamilyByName(familyName); + Locks.EnterFontManagement(); + var fontFamilyCache = PsGlobals.Global.Fonts.FontFamilyCache; + var family = fontFamilyCache.GetFamilyByName(familyName); if (family == null) { - family = new FontFamilyInternal(familyName, createPlatformObject); - family = FontFamilyCache.CacheOrGetFontFamily(family); + family = new(familyName, createPlatformObject); + family = fontFamilyCache.CacheOrGetFontFamily(family); } return family; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } #if GDI @@ -110,12 +118,13 @@ internal static FontFamilyInternal GetOrCreateFromGdi(GdiFontFamily gdiFontFamil { try { - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); FontFamilyInternal fontFamily = new FontFamilyInternal(gdiFontFamily); - fontFamily = FontFamilyCache.CacheOrGetFontFamily(fontFamily); + var fontFamilyCache = PsGlobals.Global.Fonts.FontFamilyCache; + fontFamily = fontFamilyCache.CacheOrGetFontFamily(fontFamily); return fontFamily; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } #endif @@ -123,7 +132,8 @@ internal static FontFamilyInternal GetOrCreateFromGdi(GdiFontFamily gdiFontFamil internal static FontFamilyInternal GetOrCreateFromWpf(WpfFontFamily wpfFontFamily) { FontFamilyInternal fontFamily = new FontFamilyInternal(wpfFontFamily); - fontFamily = FontFamilyCache.CacheOrGetFontFamily(fontFamily); + var fontFamilyCache = PsGlobals.Global.Fonts.FontFamilyCache; + fontFamily = fontFamilyCache.CacheOrGetFontFamily(fontFamily); return fontFamily; } #endif @@ -132,16 +142,15 @@ internal static FontFamilyInternal GetOrCreateFromWpf(WpfFontFamily wpfFontFamil /// Gets the family name this family was originally created with. /// public string SourceName => _sourceName; - readonly string _sourceName; /// /// Gets the name that uniquely identifies this font family. /// public string Name => _name; - readonly string _name; + readonly OpenTypeFontFamily _otFontFamily; #if GDI /// /// Gets the underlying GDI+ font family object. @@ -166,6 +175,6 @@ internal static FontFamilyInternal GetOrCreateFromWpf(WpfFontFamily wpfFontFamil /// Gets the DebuggerDisplayAttribute text. /// internal string DebuggerDisplay - => String.Format(CultureInfo.InvariantCulture, "FontFamily: '{0}'", Name); + => String.Format(CultureInfo.InvariantCulture, "FontFamily: '{0}'", _otFontFamily.FamilyName); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontSourceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontSourceCache.cs new file mode 100644 index 00000000..7dd9f92a --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/FontSourceCache.cs @@ -0,0 +1,200 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; +using PdfSharp.Internal; +using PdfSharp.Internal.OpenType; +using PdfSharp.Internal.Threading; +using System.Collections.Concurrent; +#if GDI +using GdiFontFamily = System.Drawing.FontFamily; +#endif +#if WPF +using WpfFontFamily = System.Windows.Media.FontFamily; +#endif + +namespace PdfSharp.Fonts +{ + /// + /// Global cache of all internal font family objects. + /// + class PsFontSourceCache + { + internal PsFontSourceCache(PsGlobals.PsFontStorage storage) + { + _storage = storage; + } + + /// + /// Gets the bytes of a physical font with specified face name. + /// + public XFontSource GetFontSourceByFontName(string uniqueFontSourceName) + { + if (_storage.FontSourcesByName.TryGetValue(uniqueFontSourceName, out var fontSource)) + return fontSource; + + Debug.Assert(false, $"An XFontSource with the name '{uniqueFontSourceName}' does not exist."); + return null; + } + + /// + /// Gets the bytes of a physical font with specified face name. + /// + public XFontSource GetFontSourceByTypefaceKey(string typefaceKey) + { + if (_storage.FontSourcesByName.TryGetValue(typefaceKey, out var fontSource)) + return fontSource; + + Debug.Assert(false, $"An XFontSource with the typeface key '{typefaceKey}' does not exist."); + return null; + } + + public bool TryGetFontSourceByKey(ulong key, [MaybeNullWhen(false)] out XFontSource fontSource) + { + return _storage.FontSourcesByKey.TryGetValue(key, out fontSource); + } + + public bool TryGetFontSourceByTypefaceKey(string typefaceKey, [MaybeNullWhen(false)] out XFontSource source) + { + return _storage.FontSourcesByName.TryGetValue(typefaceKey, out source); + } + + /// + /// Caches a font source under its face name and its key. + /// + public XFontSource CacheFontSource(XFontSource fontSource) + { + try + { + Locks.EnterFontManagement(); + // Check whether an identical font source with a different face name already exists. + if (_storage.FontSourcesByKey.TryGetValue(fontSource.ChecksumKey, out var existingFontSource)) + { +#if DEBUG + // Fonts have same length and check sum. Now check byte by byte identity. + int length = fontSource.Bytes.Length; + for (int idx = 0; idx < length; idx++) + { + if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx]) + { + //Debug.Assert(false,"Two fonts with identical checksum found."); + break; + //goto FontsAreNotIdentical; + } + } + Debug.Assert(existingFontSource.OTFontFace != null); +#endif + 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(); + } + + OpenTypeFontFace? fontFace = fontSource.OTFontFace; + if (fontFace == null!) + { + // Create OpenType font face for this font source. + fontSource.OTFontFace = OpenTypeFontFace.GetOrCreateFrom(fontSource.OTFontSource); + } + _storage.FontSourcesByKey.TryAdd(fontSource.ChecksumKey, fontSource); + _storage.FontSourcesByName.TryAdd(fontSource.FontFaceKey, fontSource); + return fontSource; + } + finally { Locks.ExitFontManagement(); } + } + + /// + /// Caches a font source under its face name and its key. + /// + public XFontSource CacheNewFontSource(string typefaceKey, XFontSource fontSource) + { + // Debug.Assert(!FontSourcesByFaceName.ContainsKey(fontSource.FaceName)); + + // Check whether an identical font source with a different face name already exists. + if (_storage.FontSourcesByKey.TryGetValue(fontSource.ChecksumKey, out var existingFontSource)) + { + //// Fonts have same length and check sum. Now check byte by byte identity. + //int length = fontSource.Bytes.Length; + //for (int idx = 0; idx < length; idx++) + //{ + // if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx]) + // { + // goto FontsAreNotIdentical; + // } + //} + return existingFontSource; + + ////// The bytes are really identical. Register font source again with the new face name + ////// but return the existing one to save memory. + ////FontSourcesByFaceName.Add(fontSource.FaceName, 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(); + } + + OpenTypeFontFace otFontFace = fontSource.OTFontFace; + if (Equals(otFontFace, null)) + { + otFontFace = OpenTypeFontFace.GetOrCreateFrom(fontSource.OTFontSource); + fontSource.OTFontFace = otFontFace; // Also sets the font name in fontSource + } + + _storage.FontSourcesByName.TryAdd(typefaceKey, fontSource); + _storage.FontSourcesByName.TryAdd(fontSource.FontFaceKey, fontSource); + _storage.FontSourcesByKey.TryAdd(fontSource.ChecksumKey, fontSource); + + return fontSource; + } + + /// + /// Gets a value indicating whether at least one font source was created. + /// + public bool HasFontSources => _storage.FontSourcesByName.Count > 0; + + + readonly PsGlobals.PsFontStorage _storage; + + //internal static string GetCacheState() + //{ + // var state = new StringBuilder(); + // state.Append("====================\n"); + // state.Append("Font families by name\n"); + // var familyKeys = PsGlobals.Global.Fonts.FontFamiliesByName.Keys; + // int count = familyKeys.Count; + // string[] keys = new string[count]; + // familyKeys.CopyTo(keys, 0); + // Array.Sort(keys, StringComparer.OrdinalIgnoreCase); + // foreach (string key in keys) + // state.AppendFormat(" {0}: {1}\n", key, _storage.FontFamiliesByName[key].DebuggerDisplay); + // state.Append("\n"); + // return state.ToString(); + //} + } +} + +namespace PdfSharp.Internal +{ + partial class PsGlobals + { + partial class PsFontStorage + { + /// + /// Maps font source key (FSK) to font source. + /// The key is a simple hash code of the font face data. + /// + public readonly ConcurrentDictionary FontSourcesByKey = []; + + /// + /// Maps typeface key or font name to font source. + /// + public readonly ConcurrentDictionary FontSourcesByName = new(StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs index 36a2f5a8..9f1fca85 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlobalFontSettings.cs @@ -2,8 +2,9 @@ // See the LICENSE file in the solution root for more information. using Microsoft.Extensions.Logging; +using PdfSharp.Internal.OpenType; +using PdfSharp.Internal.Threading; using PdfSharp.Fonts; -using PdfSharp.Fonts.OpenType; using PdfSharp.Internal; using PdfSharp.Logging; using PdfSharp.Pdf; @@ -19,7 +20,7 @@ public static class GlobalFontSettings /// The name of the default font. This name is obsolete and must not be used anymore. /// [Obsolete("DefaultFontName is deprecated. Do not use it anymore.")] - public const string DefaultFontName_ = "PlatformDefault"; + public const string DefaultFontName = "PlatformDefault"; /// /// Gets or sets the custom font resolver for the current application. @@ -29,20 +30,20 @@ public static class GlobalFontSettings /// public static IFontResolver? FontResolver { - get => Globals.Global.Fonts.FontResolver; + get => PsGlobals.Global.Fonts.FontResolver; set { // Cannot remove font resolver. if (value == null) throw new ArgumentNullException(nameof(value), "You cannot remove the font resolver."); - ref var fontResolver = ref Globals.Global.Fonts.FontResolver; + ref var fontResolver = ref PsGlobals.Global.Fonts.FontResolver; try { - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); SetFontResolver(value, ref fontResolver); } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } } @@ -54,20 +55,20 @@ public static IFontResolver? FontResolver /// public static IFontResolver? FallbackFontResolver { - get => Globals.Global.Fonts.FallbackFontResolver; + get => PsGlobals.Global.Fonts.FallbackFontResolver; set { // Cannot remove font resolver. if (value == null) throw new ArgumentNullException(nameof(value), "You cannot remove the fallback font resolver."); - ref var fontResolver = ref Globals.Global.Fonts.FallbackFontResolver; + ref var fontResolver = ref PsGlobals.Global.Fonts.FallbackFontResolver; try { - Locks.EnterFontFactory(); + Locks.EnterFontManagement(); SetFontResolver(value, ref fontResolver); } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } } @@ -89,7 +90,8 @@ static void SetFontResolver(IFontResolver value, ref IFontResolver? location) return; } - if (FontFactory.HasFontSources) + var fontSourceCache = PsGlobals.Global.Fonts.FontSourceCache; + if (fontSourceCache.HasFontSources) { #if DEBUG var config = Capabilities.Build.BuildName; @@ -140,15 +142,12 @@ internal static void ResetAll(bool calledFromResetFontManagement = false) if (calledFromResetFontManagement) PdfSharpLogHost.Logger.LogInformation("PDFsharp font management is about to be reset."); - Globals.Global.Fonts.FontResolver = null; - Globals.Global.Fonts.FallbackFontResolver = null; - Globals.Global.Fonts.UseWindowsFontsUnderWindows = null; - Globals.Global.Fonts.UseWindowsFontsUnderWsl2 = null; - GlyphTypefaceCache.Reset(); - FontDescriptorCache.Reset(); + PsGlobals.Global.Fonts.FontResolver = null; + PsGlobals.Global.Fonts.FallbackFontResolver = null; + PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows = null; + PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 = null; FontFactory.Reset(); - FontFamilyCache.Reset(); - OpenTypeFontFaceCache.Reset(); + OtGlobals.RecreateOtGlobals(); } /// @@ -162,27 +161,27 @@ public static PdfFontEncoding DefaultFontEncoding { get { - if (!Globals.Global.Fonts.FontEncodingInitialized) + if (!PsGlobals.Global.Fonts.FontEncodingInitialized) DefaultFontEncoding = PdfFontEncoding.Automatic; - return Globals.Global.Fonts.FontEncoding; + return PsGlobals.Global.Fonts.FontEncoding; } set { try { - Locks.EnterFontFactory(); - if (Globals.Global.Fonts.FontEncodingInitialized) + Locks.EnterFontManagement(); + if (PsGlobals.Global.Fonts.FontEncodingInitialized) { // Ignore multiple setting e.g. in a web application. - if (Globals.Global.Fonts.FontEncoding == value) + if (PsGlobals.Global.Fonts.FontEncoding == value) return; throw new InvalidOperationException("Must not change DefaultFontEncoding after it was set once."); } - Globals.Global.Fonts.FontEncoding = value; - Globals.Global.Fonts.FontEncodingInitialized = true; + PsGlobals.Global.Fonts.FontEncoding = value; + PsGlobals.Global.Fonts.FontEncodingInitialized = true; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } } @@ -198,26 +197,26 @@ public static bool UseWindowsFontsUnderWindows get { // If not opted-in we do not use Windows fonts anymore. - if (Globals.Global.Fonts.UseWindowsFontsUnderWindows == null) + if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows == null) UseWindowsFontsUnderWindows = false; - return Globals.Global.Fonts.UseWindowsFontsUnderWindows ?? false; + return PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows ?? false; } set { try { - Locks.EnterFontFactory(); - if (Globals.Global.Fonts.UseWindowsFontsUnderWindows.HasValue) + Locks.EnterFontManagement(); + if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows.HasValue) { // Ignore multiple setting e.g. in a web application. - if (Globals.Global.Fonts.UseWindowsFontsUnderWindows == value) + if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows == value) return; throw new InvalidOperationException("Must not change UseWindowsFontsUnderWindows after it was once set or got."); } - Globals.Global.Fonts.UseWindowsFontsUnderWindows = value; + PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows = value; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } } @@ -232,28 +231,29 @@ public static bool UseWindowsFontsUnderWsl2 get { // If not opted-in we do not use Windows fonts anymore. - if (Globals.Global.Fonts.UseWindowsFontsUnderWsl2 == null) + if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 == null) UseWindowsFontsUnderWsl2 = false; - return Globals.Global.Fonts.UseWindowsFontsUnderWsl2 ?? false; + return PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 ?? false; } set { try { - Locks.EnterFontFactory(); - if (Globals.Global.Fonts.UseWindowsFontsUnderWsl2.HasValue) + Locks.EnterFontManagement(); + if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2.HasValue) { // Ignore multiple setting e.g. in a web application. - if (Globals.Global.Fonts.UseWindowsFontsUnderWsl2 == value) + if (PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 == value) return; throw new InvalidOperationException("Must not change UseWindowsFontsUnderWsl2 after it was once set or got."); } - Globals.Global.Fonts.UseWindowsFontsUnderWsl2 = value; + PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 = value; } - finally { Locks.ExitFontFactory(); } + finally { Locks.ExitFontManagement(); } } } + #elif GDI || WPF /// /// This property has no effect under a GDI or WPF build of PDFsharp. @@ -292,21 +292,21 @@ public static bool UseWindowsFontsUnderWsl2 internal static void Reset() { // Reset font resolvers. - Globals.Global.Fonts.FontResolver = null; - Globals.Global.Fonts.FallbackFontResolver = null; + PsGlobals.Global.Fonts.FontResolver = null; + PsGlobals.Global.Fonts.FallbackFontResolver = null; // Reset mode of PlatformFontResolver. - Globals.Global.Fonts.UseWindowsFontsUnderWindows = null; - Globals.Global.Fonts.UseWindowsFontsUnderWsl2 = null; + PsGlobals.Global.Fonts.UseWindowsFontsUnderWindows = null; + PsGlobals.Global.Fonts.UseWindowsFontsUnderWsl2 = null; } } } namespace PdfSharp.Internal { - partial class Globals + partial class PsGlobals { - partial class FontStorage + partial class PsFontStorage { /// /// The globally set custom font resolver. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphHelper.cs index 8dca01a3..07136d28 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphHelper.cs @@ -1,6 +1,11 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Text; +using PdfSharp.Pdf.Internal; +using PdfSharp.Drawing; +using PdfSharp.Fonts.Internal; +using PdfSharp.Internal.OpenType; #if GDI using System.Drawing; using System.Drawing.Text; @@ -8,10 +13,6 @@ #if WPF using System.Windows.Media; #endif -using System.Text; -using PdfSharp.Drawing; -using PdfSharp.Fonts.Internal; -using PdfSharp.Pdf.Internal; namespace PdfSharp.Fonts { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphTypefaceCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphTypefaceCache.cs new file mode 100644 index 00000000..c37ca9bf --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/GlyphTypefaceCache.cs @@ -0,0 +1,76 @@ +// 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 PdfSharp.Internal.Threading; +using PdfSharp.Drawing; + +namespace PdfSharp.Fonts +{ + /// + /// Global table of all glyph typefaces. + /// + class PsGlyphTypefaceCache + { + internal PsGlyphTypefaceCache(PsGlobals.PsFontStorage storage) + { + _storage = storage; + } + + public bool TryGetGlyphTypeface(string key, [MaybeNullWhen(false)] out XGlyphTypeface glyphTypeface) + { + try + { + Locks.EnterFontManagement(); + bool result = _storage.GlyphTypefacesByKey.TryGetValue(key, out glyphTypeface); + return result; + } + finally { Locks.ExitFontManagement(); } + } + + public void AddGlyphTypeface(XGlyphTypeface glyphTypeface) + { + try + { + Locks.EnterFontManagement(); + Debug.Assert(!_storage.GlyphTypefacesByKey.ContainsKey(glyphTypeface.Key)); + _storage.GlyphTypefacesByKey.TryAdd(glyphTypeface.Key, glyphTypeface); + } + finally { Locks.ExitFontManagement(); } + } + + readonly PsGlobals.PsFontStorage _storage; + + internal static string GetCacheState() + { + var state = new StringBuilder(); + state.Append("====================\n"); + state.Append("Glyph typefaces by name\n"); + var familyKeys = PsGlobals.Global.Fonts.GlyphTypefacesByKey.Keys; + int count = familyKeys.Count; + string[] keys = new string[count]; + familyKeys.CopyTo(keys, 0); + Array.Sort(keys, StringComparer.OrdinalIgnoreCase); + foreach (string key in keys) + state.AppendFormat(" {0}: {1}\n", key, PsGlobals.Global.Fonts.GlyphTypefacesByKey[key].DebuggerDisplay); + state.Append("\n"); + return state.ToString(); + } + } +} + +namespace PdfSharp.Internal +{ + partial class PsGlobals + { + partial class PsFontStorage + { + /// + /// Maps typeface key to glyph typeface. + /// + public readonly ConcurrentDictionary GlyphTypefacesByKey = new(StringComparer.Ordinal); + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/PlatformFontResolver.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/PlatformFontResolver.cs index b392a126..eb3a1565 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/PlatformFontResolver.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/PlatformFontResolver.cs @@ -106,8 +106,8 @@ public static class PlatformFontResolver else { #if GDI && !WPF - bool mustSimulateBold = gdiFont.Bold && !fontSource.FontFace.os2.IsBold; - bool mustSimulateItalic = gdiFont.Italic && !fontSource.FontFace.os2.IsItalic; + bool mustSimulateBold = gdiFont.Bold && !fontSource.OTFontFace.os2.IsBold; + bool mustSimulateItalic = gdiFont.Italic && !fontSource.OTFontFace.os2.IsItalic; fontResolverInfo = new PlatformFontResolverInfo(typefaceKey, mustSimulateBold, mustSimulateItalic, gdiFont); #endif #if WPF @@ -168,8 +168,8 @@ public static class PlatformFontResolver typefaceKey = XGlyphTypeface.ComputeGtfKey(familyName, fontResolvingOptions); var style = fontResolvingOptions.FontStyle; - var fontResolverInfosByName = Globals.Global.Fonts.FontResolverInfosByName; - var fontSourcesByName = Globals.Global.Fonts.FontSourcesByName; + var fontResolverInfosByName = PsGlobals.Global.Fonts.FontResolverInfosByName; + var fontSourcesByName = PsGlobals.Global.Fonts.FontSourcesByName; // Was this typeface requested before? if (fontResolverInfosByName.TryGetValue(typefaceKey, out var fontResolverInfo)) @@ -314,11 +314,6 @@ internal static void Reset() typefaceKey = XGlyphTypeface.ComputeGtfKey(familyName, fontResolvingOptions); XFontStyleEx style = fontResolvingOptions.FontStyle; -#if DEBUG_ - if (StringComparer.OrdinalIgnoreCase.Compare(familyName, "Segoe UI Semilight") == 0 - && (style & XFontStyleEx.BoldItalic) == XFontStyleEx.Italic) - familyName.GetType(); -#endif wpfFontFamily = null; wpfTypeface = null; wpfGlyphTypeface = null; @@ -326,21 +321,6 @@ internal static void Reset() // Use WPF technique to create font data. // No more XPrivateFontCollection. //wpfTypeface = XPrivateFontCollection.TryCreateTypeface(familyName, style, out wpfFontFamily); -#if DEBUG_ - if (wpfTypeface != null) - { - WpfGlyphTypeface glyphTypeface; - ICollection list = wpfFontFamily.GetTypefaces(); - foreach (WpfTypeface tf in list) - { - if (!tf.TryGetGlyphTypeface(out glyphTypeface)) - Debug-Break.Break(); - } - - //if (!WpfTypeface.TryGetGlyphTypeface(out glyphTypeface)) - // throw new InvalidOperationException(PsMgs.CannotGetGlyphTypeface(familyName)); - } -#endif wpfFontFamily ??= new WpfFontFamily(familyName); wpfTypeface ??= FontHelper.CreateTypeface(wpfFontFamily, style); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/WindowsPlatformFontResolver.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/WindowsPlatformFontResolver.cs index 7c2887b1..286d765a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/WindowsPlatformFontResolver.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/WindowsPlatformFontResolver.cs @@ -106,7 +106,7 @@ public enum FontSimulation public byte[]? GetFont(string faceName) { - // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract because it can be null. + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract because it can yet be null. _fontsPath ??= Capabilities.OperatingSystem.IsWindows ? WindowsFontsPath : WindowsFontsPathUnderWsl2; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Calc.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Calc.cs index 620801d7..6ca4bc50 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Calc.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Calc.cs @@ -17,7 +17,7 @@ static class Calc /// /// Degree to radiant factor. /// - public const double Deg2Rad = Math.PI / 180; + public static double Deg2Rad => Math.PI / 180; /// /// Get page size in point from specified PageSize. @@ -48,6 +48,31 @@ public static XSize PageSizeToSize(PageSize value) PageSize.Quarto => new(576, 720), PageSize.Size10x14 => new(720, 1008), +#if true_ // Who needs this? Should be deleted. + PageSize.Undefined => expr, + PageSize.RA0 => expr, + PageSize.RA1 => expr, + PageSize.RA2 => expr, + PageSize.RA3 => expr, + PageSize.RA4 => expr, + PageSize.RA5 => expr, + PageSize.B0 => expr, + PageSize.B1 => expr, + PageSize.B2 => expr, + PageSize.B3 => expr, + PageSize.Foolscap => expr, + PageSize.GovernmentLetter => expr, + PageSize.Post => expr, + PageSize.Crown => expr, + PageSize.LargePost => expr, + PageSize.Demy => expr, + PageSize.Medium => expr, + PageSize.Royal => expr, + PageSize.Elephant => expr, + PageSize.DoubleDemy => expr, + PageSize.QuadDemy => expr, + PageSize.STMT => expr, +#endif _ => throw new ArgumentException("Invalid PageSize.") }; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Diagnostics.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Diagnostics.cs index 61b62773..8f6ddbe0 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Diagnostics.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Diagnostics.cs @@ -2,6 +2,7 @@ // See the LICENSE file in the solution root for more information. using System; +using System.Diagnostics.CodeAnalysis; using System.Globalization; using PdfSharp.Pdf; using PdfSharp.Pdf.Content; @@ -56,16 +57,19 @@ enum NotSupportedBehavior static class ParserDiagnostics // #CLEANUP { + [DoesNotReturn] public static void ThrowParserException(string message) { throw new PdfReaderException(message); } + [DoesNotReturn] public static void ThrowParserException(string message, Exception innerException) { throw new PdfReaderException(message, innerException); } + [DoesNotReturn] public static void HandleUnexpectedCharacter(char ch, string dump) { // Hex formatting does not work with type Char. It must be cast to integer. @@ -75,6 +79,7 @@ public static void HandleUnexpectedCharacter(char ch, string dump) dump; ThrowParserException(message); } + [DoesNotReturn] public static void HandleUnexpectedToken(string token, string dump) { string message = @@ -84,6 +89,7 @@ public static void HandleUnexpectedToken(string token, string dump) ThrowParserException(message); } + [DoesNotReturn] public static void CannotFindEndOfStream(PdfDictionary dict) { string message = $"Cannot find end of stream in object '{dict.ObjectID}'."; @@ -93,22 +99,26 @@ public static void CannotFindEndOfStream(PdfDictionary dict) static class ContentReaderDiagnostics // #CLEANUP { + [DoesNotReturn] public static void ThrowContentReaderException(string message) { throw new ContentReaderException(message); } + [DoesNotReturn] public static void ThrowContentReaderException(string message, Exception innerException) { throw new ContentReaderException(message, innerException); } + [DoesNotReturn] public static void ThrowNumberOutOfIntegerRange(long value) { string message = String.Format(CultureInfo.InvariantCulture, "Number '{0}' out of integer range.", value); ThrowContentReaderException(message); } + [DoesNotReturn] public static void HandleUnexpectedCharacter(char ch) { string message = String.Format(CultureInfo.InvariantCulture, diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DiagnosticsHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DiagnosticsHelper.cs index c72ebaaf..fceafd22 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DiagnosticsHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DiagnosticsHelper.cs @@ -122,8 +122,8 @@ public static (string Stretch, string Weight) TryGetStretchAndWeight(XGlyphTypef { var bold = glyphTypeface.IsBold ? " (bold)" : ""; - var stretch = XFontStretches.FontStretchFromFaceName(glyphTypeface.FaceName); - var weight = XFontWeights.FontWeightFromFaceName(glyphTypeface.FaceName); + var stretch = XFontStretches.FontStretchFromFaceName(glyphTypeface.FontName); + var weight = XFontWeights.FontWeightFromFaceName(glyphTypeface.FontName); return (stretch.ToString(), weight.ToString() + bold); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs index 1763d63a..9db47cd6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DotNetHelper.cs @@ -1,50 +1,60 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. +// DELETE -#if !NET6_0_OR_GREATER +////// PDFsharp - A .NET library for processing PDF +////// See the LICENSE file in the solution root for more information. -using System.Numerics; +////#if !NET8_0_OR_GREATER -namespace PdfSharp.Internal -{ - /// - /// Class containing replacements for net 6 methods missing in net framework 4.6.2. - /// - static class DotNetHelper - { - /// - /// Implements the net 6 BigInteger constructor missing in net framework 4.6.2. - /// Initializes a new instance of the BigInteger structure using the values in a read-only span of bytes, and optionally indicating the signing encoding and the endianness byte order. - /// - /// - /// - /// - public static BigInteger CreateBigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigEndian = false) - { - var bytes = value.ToArray(); +////using System; +////using System.Collections; +////using System.Collections.Generic; +////using System.Numerics; - // Convert to little endian, which is expected by BigInteger constructor. - if (isBigEndian) - bytes = ((IEnumerable)bytes).Reverse().ToArray(); +////namespace PdfSharp.Internal +////{ +//// /// +//// /// Class containing replacements for .NET 6 methods missing in .NET framework 4.6.2. +//// /// +//// static class DotNetHelper +//// { +//// /// +//// /// Implements the .NET 6 BigInteger constructor missing in .NET framework 4.6.2. +//// /// Initializes a new instance of the BigInteger structure using the values in a read-only span of bytes, and optionally indicating the signing encoding and the endianness byte order. +//// /// +//// /// +//// /// +//// /// +//// public static BigInteger CreateBigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigEndian = false) +//// { +//// var bytes = value.ToArray(); - // A leading bit of 1 defines a negative number. If the input should be interpreted as unsigned, prepend a new zero byte, if there’s a leading 1. - // As bytes is in little endian order, check the most significant bit of the last byte. If it is 1, append the zero byte. - if (isUnsigned && bytes.Length > 0 && (bytes.Last() & 0x80) > 0) - { -#if NET462 - var len = bytes.Length; - var bytes2 = new byte[len + 1]; - bytes.CopyTo(bytes2, 0); - bytes2[len] = 0; - bytes = bytes2; -#else - bytes = bytes.Append((byte)0).ToArray(); -#endif - } +//// // Convert to little endian, which is expected by BigInteger constructor. +//// if (isBigEndian) +//// { +//// //bytes = bytes.Reverse().ToArray(); +//// // In .NET 10 is for 'bytes.Reverse()' 'MemoryExtensions.Reverse(this Span)' resolved +//// // instead of 'Enumerable.Reverse(this IEnumerable)'. +//// // See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/breaking-changes/compiler%20breaking%20changes%20-%20dotnet%2010#enumerablereverse +//// // ReSharper disable once InvokeAsExtensionMethod because we must call the extension method explicitly in .NET 10. +//// bytes = Enumerable.Reverse(bytes).ToArray(); +//// } - return new BigInteger(bytes); - } - } -} - -#endif +//// // A leading bit of 1 defines a negative number. If the input should be interpreted as unsigned, prepend a new zero byte, if there’s a leading 1. +//// // As bytes is in little endian order, check the most significant bit of the last byte. If it is 1, append the zero byte. +//// if (isUnsigned && bytes.Length > 0 && (bytes.Last() & 0x80) != 0) +//// { +////#if NET462 +//// var len = bytes.Length; +//// var bytes2 = new byte[len + 1]; +//// bytes.CopyTo(bytes2, 0); +//// bytes2[len] = 0; +//// bytes = bytes2; +////#else +//// bytes = bytes.Append((byte)0).ToArray(); +////#endif +//// } +//// return new BigInteger(bytes); +//// } +//// } +////} +////#endif \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DoubleUtil.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DoubleUtil.cs index 16b338c8..56580429 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DoubleUtil.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/DoubleUtil.cs @@ -124,10 +124,10 @@ public static bool IsNaN(double value) { var t = new NanUnion { DoubleValue = value }; - ulong exp = t.UintValue & 0xfff0000000000000; - ulong man = t.UintValue & 0x000fffffffffffff; + ulong exp = t.UintValue & 0xfff0_0000_0000_0000; + ulong man = t.UintValue & 0x000f_ffff_ffff_ffff; - return exp is 0x7ff0000000000000 or 0xfff0000000000000 && man != 0; + return exp is 0x7ff0_0000_0000_0000 or 0xfff0_0000_0000_0000 && man != 0; } /// @@ -166,10 +166,10 @@ public static int DoubleToInt(double value) struct NanUnion { [FieldOffset(0)] - internal double DoubleValue; + public double DoubleValue; [FieldOffset(0)] - internal readonly ulong UintValue; + public readonly ulong UintValue; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/ErrorHelpers.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/ErrorHelpers.cs index 471812bd..aa36af50 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/ErrorHelpers.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/ErrorHelpers.cs @@ -13,7 +13,8 @@ namespace PdfSharp.Internal static class TH { const string SendUsTheFile = "\nPDFsharp cannot read this PDF file. " + - "If you think your file is a valid PDF file please send it to us so that we can fix this bug in the PDF parser."; + "If you think your file is a valid PDF file please send it to us so that we can fix this bug in the PDF parser. "+ + "See " + UrlLiterals.LinkToCannotOpenPdfFile; public static InvalidOperationException InvalidOperationException_CouldNotFindMetadataDictionary() => new("Could not find document’s metadata dictionary." + SendUsTheFile); @@ -23,9 +24,11 @@ public static InvalidOperationException InvalidOperationException_ReferenceMustN #region Reader Messages - public static ObjectNotAvailableException ObjectNotAvailableException_CannotRetrieveStreamLength(Exception? innerException = null) + public static ObjectNotAvailableException ObjectNotAvailableException_CannotRetrieveStreamLength(SizeType? streamStart, Exception? innerException = null) { - const string message = "Cannot retrieve stream length." + SendUsTheFile; + string message = "Cannot retrieve stream length." + + (streamStart.HasValue ? $" Stream begins at {streamStart.Value}." : null) + + SendUsTheFile; return innerException != null ? new(message, innerException) : new(message); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs new file mode 100644 index 00000000..8185fb27 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Lock.cs @@ -0,0 +1,70 @@ +////// 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; + +////namespace PdfSharp.Internal +////{ +//// /// +//// /// Static locking functions to make PDFsharp thread save. +//// /// POSSIBLE BUG_OLD: Having more than one lock can lead to a deadlock. +//// /// +//// static class Locks // Renamed from Lock because of the new Lock class in .NET 9. +//// { +//// public static void EnterGdiPlus() +//// { + +//// Monitor.Enter(GdiPlusLock); +//// //Interlocked.Increment(ref _gdiPlusLockCount); +//// _gdiPlusLockCount++; // Is atomic because we have the lock. + +//// if (IsFontFactoryLockedByCurrentThread) +//// { +//// // This should not happen by design. +//// PdfSharpLogHost.Logger.LogCritical("Entered GDI+ lock while FontFactory lock is also taken."); +//// } +//// } + +//// public static void ExitGdiPlus() +//// { +//// Interlocked.Decrement(ref _gdiPlusLockCount); +//// Monitor.Exit(GdiPlusLock); +//// } + +//// public static bool IsGdiPlusLockedByCurrentThread => Monitor.IsEntered(GdiPlusLock); + +//// public static bool IsGdiPlusLookTaken() => _gdiPlusLockCount > 0; + + +//// static readonly object GdiPlusLock = new(); +//// static int _gdiPlusLockCount; + +//// // ------------------------------------------------------------ + +//// public static void EnterFontManagement() +//// { +//// Monitor.Enter(FontFactoryLock); +//// _fontFactoryLockCount++; // Is atomic because we have the lock. + +//// if (IsGdiPlusLockedByCurrentThread) +//// { +//// // This should not happen by design. +//// PdfSharpLogHost.Logger.LogCritical("Entered FontFactory lock while GDI+ lock is also taken."); +//// } +//// } + +//// public static void ExitFontManagement() +//// { +//// _fontFactoryLockCount--; +//// Monitor.Exit(FontFactoryLock); +//// } + +//// public static bool IsFontFactoryLockedByCurrentThread => Monitor.IsEntered(FontFactoryLock); + +//// public static bool IsFontFactoryLookTaken() => _fontFactoryLockCount > 0; + +//// static readonly object FontFactoryLock = new(); +//// static int _fontFactoryLockCount; +//// } +////} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Globals.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/PsGlobals.cs similarity index 61% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Globals.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Internal/PsGlobals.cs index 18b6999d..9f8bbfe4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Globals.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/PsGlobals.cs @@ -2,23 +2,25 @@ // See the LICENSE file in the solution root for more information. using Microsoft.Extensions.Logging; +using PdfSharp.Fonts; +using PdfSharp.Internal.OpenType; using PdfSharp.Logging; namespace PdfSharp.Internal { /// - /// The one and only class that hold all PDFsharp global stuff. + /// The one and only class that holds all PDFsharp global stuff. /// - partial class Globals + partial class PsGlobals { - Globals() + PsGlobals() { - _version = _globalVersionCount++; + IncrementVersion(); } - public static Globals Global => _global; + public static PsGlobals Global => _global; - internal void RecreateGlobals() + internal static void RecreatePsGlobals() { _global = new(); } @@ -29,10 +31,9 @@ internal void RecreateGlobals() /// Gets the version of this instance. /// public int Version => _version; - /*readonly*/ int _version; - public readonly FontStorage Fonts = new(); + public readonly PsFontStorage Fonts = new(); /// /// The global version count gives every new instance of Globals a new unique @@ -42,15 +43,22 @@ internal void RecreateGlobals() /// /// The container of all global stuff in PDFsharp. + /// By creating a new instance of Globals all caches are reset + /// as if PDFsharp starts in a new process. /// - static Globals _global = new(); + static PsGlobals _global = new(); - public partial class FontStorage + public partial class PsFontStorage { // A partial class within a partial class 😨 - internal FontStorage() + internal PsFontStorage() { + FontSourceCache = new(this); + GlyphTypefaceCache = new(this); + FontDescriptorCache = new(this); + FontFamilyCache = new(this); + _globalFontStorageVersionCount++; } @@ -63,6 +71,11 @@ public void CheckVersion(int version) } } + public readonly PsFontSourceCache FontSourceCache; + public readonly PsGlyphTypefaceCache GlyphTypefaceCache; + public readonly PsFontDescriptorCache FontDescriptorCache; + public readonly PsFontFamilyCache FontFamilyCache; + /// /// Gets the version of this instance. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/TokenizerHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/TokenizerHelper.cs index 122a030a..280adaf1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/TokenizerHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Internal/TokenizerHelper.cs @@ -31,7 +31,7 @@ void Initialize(string? str, char quoteChar, char separator) _quoteChar = quoteChar; _argSeparator = separator; - // Skip any whitespace. + // Skip any white-space. while (_charIndex < _strLen) { if (!Char.IsWhiteSpace(_str!, _charIndex)) @@ -110,7 +110,7 @@ public bool NextToken(bool allowQuotedToken, char separator) int newTokenIndex = _charIndex; int newTokenLength = 0; - // Loop until hit end of string or hit a separator or whitespace. + // Loop until hit end of string or hit a separator or white-space. while (_charIndex < _strLen) { currentChar = _str[_charIndex]; @@ -166,11 +166,11 @@ void ScanToNextToken(char separator) { char currentChar = _str![_charIndex]; - // Ensure that currentChar is a white space or separator. + // Ensure that currentChar is a white-space or separator. if (currentChar != separator && !Char.IsWhiteSpace(currentChar)) throw new InvalidOperationException("ExtraDataEncountered"); //SR.Get(SRID.TokenizerHelperExtraDataEncountered, new object[0])); - // Loop until a character that isn’t the separator or white space. + // Loop until a character that isn’t the separator or white-space. int argSepCount = 0; while (_charIndex < _strLen) { @@ -186,7 +186,7 @@ void ScanToNextToken(char separator) } else if (Char.IsWhiteSpace(currentChar)) { - // Skip white space. + // Skip white-space. ++_charIndex; } else diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfSignatureField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfSignatureField.cs deleted file mode 100644 index 740d272b..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfSignatureField.cs +++ /dev/null @@ -1,177 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using PdfSharp.Pdf.IO; -using PdfSharp.Drawing; -using PdfSharp.Pdf.Annotations; - -namespace PdfSharp.Pdf.AcroForms -{ - /// - /// Represents the signature field. - /// - public sealed class PdfSignatureField : PdfAcroField - { - /// - /// Initializes a new instance of PdfSignatureField. - /// - internal PdfSignatureField(PdfDocument document) - : base(document) - { - CustomAppearanceHandler = null!; - } - - internal PdfSignatureField(PdfDictionary dict) - : base(dict) - { - CustomAppearanceHandler = null!; - } - - /// - /// Handler that creates the visual representation of the digital signature in PDF. - /// - public IAnnotationAppearanceHandler CustomAppearanceHandler { get; internal set; } - - /// - /// Creates the custom appearance form X object for the annotation that represents - /// this acro form text field. - /// - void RenderCustomAppearance() - { - var rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect); - - var visible = rect.X1 + rect.X2 + rect.Y1 + rect.Y2 != 0; - - if (!visible) - return; - - if (CustomAppearanceHandler == null) - throw new Exception("AppearanceHandler is not set."); - - var form = new XForm(_document, rect.Size); - var gfx = XGraphics.FromForm(form); - - CustomAppearanceHandler.DrawAppearance(gfx, rect.ToXRect()); - - form.DrawingFinished(); - - // Get existing or create new appearance dictionary - if (Elements[PdfAnnotation.Keys.AP] is not PdfDictionary ap) - { - ap = new PdfDictionary(_document); - Elements[PdfAnnotation.Keys.AP] = ap; - } - - // Set XRef to normal state - ap.Elements["/N"] = form.PdfForm.Reference; - - // PdfRenderer can be null. - form.PdfRenderer?.Close(); - } - - internal override void PrepareForSave() - { - base.PrepareForSave(); - if (CustomAppearanceHandler != null!) - RenderCustomAppearance(); - } - - /// - /// Writes a key/value pair of this signature field dictionary. - /// - internal override void WriteDictionaryElement(PdfWriter writer, PdfName key) - { - // Don’t encrypt Contents key’s value (PDF Reference 2.0: 7.6.2, Page 71). - if (key.Value == Keys.Contents) - { - var effectiveSecurityHandler = writer.EffectiveSecurityHandler; - writer.EffectiveSecurityHandler = null; - base.WriteDictionaryElement(writer, key); - writer.EffectiveSecurityHandler = effectiveSecurityHandler; - } - else - base.WriteDictionaryElement(writer, key); - } - - /// - /// Predefined keys of this dictionary. - /// The description comes from PDF 1.4 Reference. - /// - public new class Keys : PdfAcroField.Keys - { - /// - /// (Optional) The type of PDF object that this dictionary describes; if present, - /// must be Sig for a signature dictionary. - /// - [KeyInfo(KeyType.Name | KeyType.Optional)] - public const string Type = "/Type"; - - /// - /// (Required; inheritable) The name of the signature handler to be used for - /// authenticating the field’s contents, such as Adobe.PPKLite, Entrust.PPKEF, - /// CICI.SignIt, or VeriSign.PPKVS. - /// - [KeyInfo(KeyType.Name | KeyType.Required)] - public const string Filter = "/Filter"; - - /// - /// (Optional) The name of a specific submethod of the specified handler. - /// - [KeyInfo(KeyType.Name | KeyType.Optional)] - public const string SubFilter = "/SubFilter"; - - /// - /// (Required) An array of pairs of integers (starting byte offset, length in bytes) - /// describing the exact byte range for the digest calculation. Multiple discontinuous - /// byte ranges may be used to describe a digest that does not include the - /// signature token itself. - /// - [KeyInfo(KeyType.Array | KeyType.Required)] - public const string ByteRange = "/ByteRange"; - - /// - /// (Required) The encrypted signature token. - /// - [KeyInfo(KeyType.String | KeyType.Required)] - public const string Contents = "/Contents"; - - /// - /// (Optional) The name of the person or authority signing the document. - /// - [KeyInfo(KeyType.TextString | KeyType.Optional)] - public const string Name = "/Name"; - - /// - /// (Optional) The time of signing. Depending on the signature handler, this - /// may be a normal unverified computer time or a time generated in a verifiable - /// way from a secure time server. - /// - [KeyInfo(KeyType.Date | KeyType.Optional)] - public const string M = "/M"; - - /// - /// (Optional) The CPU host name or physical location of the signing. - /// - [KeyInfo(KeyType.TextString | KeyType.Optional)] - public const string Location = "/Location"; - - /// - /// (Optional) The reason for the signing, such as (I agree…). - /// - [KeyInfo(KeyType.TextString | KeyType.Optional)] - public const string Reason = "/Reason"; - - /// - /// Gets the KeysMeta for these keys. - /// - internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - - static DictionaryMeta? _meta; - } - - /// - /// Gets the KeysMeta of this dictionary type. - /// - internal override DictionaryMeta Meta => Keys.Meta; - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfAction.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfAction.cs index 7460e44c..c653480c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfAction.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfAction.cs @@ -1,6 +1,8 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +// v7.0.0 Ready + namespace PdfSharp.Pdf.Actions { /// @@ -8,6 +10,8 @@ namespace PdfSharp.Pdf.Actions /// public abstract class PdfAction : PdfDictionary { + // Reference 2.0: 12.6 Actions / Page 506 + /// /// Initializes a new instance of the class. /// @@ -26,29 +30,40 @@ protected PdfAction(PdfDocument document) Elements.SetName(Keys.Type, "/Action"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfAction(PdfDictionary dict) + : base(dict) + { } + /// /// Predefined keys of this dictionary. /// internal class Keys : KeysBase { + // Reference 2.0: Table 196 — Entries common to all action dictionaries / Page 506 + /// /// (Optional) The type of PDF object that this dictionary describes; - /// if present, must be Action for an action dictionary. + /// if present, shall be Action for an action dictionary. /// [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Action")] public const string Type = "/Type"; /// - /// (Required) The type of action that this dictionary describes. + /// (Required) The type of action that this dictionary describes; + /// see PdfActionTypes for specific values. /// [KeyInfo(KeyType.Name | KeyType.Required)] public const string S = "/S"; /// - /// (Optional; PDF 1.2) The next action or sequence of actions to be performed - /// after the action represented by this dictionary. The value is either a - /// single action dictionary or an array of action dictionaries to be performed - /// in order; see below for further discussion. + /// (Optional; PDF 1.2) The next action or sequence of actions that shall be performed after + /// the action represented by this dictionary. The value is either a single action dictionary + /// or an array of action dictionaries that shall be performed in order; see Note 1 for + /// further discussion. /// [KeyInfo(KeyType.ArrayOrDictionary | KeyType.Optional)] public const string Next = "/Next"; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfEmbeddedGoToAction.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfEmbeddedGoToAction.cs index 0a7066b6..f29bdff7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfEmbeddedGoToAction.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfEmbeddedGoToAction.cs @@ -1,10 +1,12 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; -using System.Diagnostics; using PdfSharp.Pdf.IO; +// v7.0.0 TODO + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + namespace PdfSharp.Pdf.Actions { /// @@ -30,6 +32,16 @@ public PdfEmbeddedGoToAction(PdfDocument document) Inititalize(); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfEmbeddedGoToAction(PdfDictionary dict) + : base(dict) + { + Inititalize(); + } + /// /// Creates a link to an embedded document. /// @@ -127,7 +139,7 @@ void ParseDestinationName() /// Separator for splitting destination path segments ans destination name. /// public const char Separator = '\\'; - + /// /// Path segment string used to move to the parent document. /// @@ -155,7 +167,8 @@ void ParseDestinationName() /// /// (Required) The destination in the target to jump to (see Section 8.2.1, “Destinations”). /// - [KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)] + //[KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)] + [KeyInfo(KeyType.NameOrByteStringOrArray | KeyType.Required)] // #US373 public const string D = "/D"; /// @@ -175,77 +188,85 @@ void ParseDestinationName() [KeyInfo(KeyType.Dictionary | KeyType.Optional)] public const string T = "/T"; } + } - internal class TargetDictionary : PdfDictionary + public class TargetDictionary : PdfDictionary + { + TargetDictionary() + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal TargetDictionary(PdfDictionary dict) + : base(dict) + { } + + public static TargetDictionary CreateTargetChild(string name) { - TargetDictionary() - { } + var target = new TargetDictionary(); - public static TargetDictionary CreateTargetChild(string name) - { - var target = new TargetDictionary(); + target.Elements.SetName(Keys.R, "/C"); + target.Elements.SetString(Keys.N, name); - target.Elements.SetName(Keys.R, "/C"); - target.Elements.SetString(Keys.N, name); + return target; + } - return target; - } + public static TargetDictionary CreateTargetParent() + { + var target = new TargetDictionary(); - public static TargetDictionary CreateTargetParent() - { - var target = new TargetDictionary(); + target.Elements.SetName(Keys.R, "/P"); - target.Elements.SetName(Keys.R, "/P"); + return target; + } - return target; - } + /// + /// Predefined keys of this dictionary. + /// + internal class Keys : KeysBase + { + /// + /// (Required) Specifies the relationship between the current document and the target + /// (which may be an intermediate target). Valid values are P (the target is the parent + /// of the current document) and C (the target is a child of the current document). + /// + [KeyInfo(KeyType.Name | KeyType.Required)] + public const string R = "/R"; /// - /// Predefined keys of this dictionary. + /// (Required if the value of R is C and the target is located in the EmbeddedFiles name tree; + /// otherwise, it must be absent) The name of the file in the EmbeddedFiles name tree. /// - internal class Keys : KeysBase - { - /// - /// (Required) Specifies the relationship between the current document and the target - /// (which may be an intermediate target). Valid values are P (the target is the parent - /// of the current document) and C (the target is a child of the current document). - /// - [KeyInfo(KeyType.Name | KeyType.Required)] - public const string R = "/R"; - - /// - /// (Required if the value of R is C and the target is located in the EmbeddedFiles name tree; - /// otherwise, it must be absent) The name of the file in the EmbeddedFiles name tree. - /// - [KeyInfo(KeyType.ByteString | KeyType.Optional)] - public const string N = "/N"; - - ///// - ///// (Required if the value of R is C and the target is associated with a file attachment annotation; - ///// otherwise, it must be absent) If the value is an integer, it specifies the page number (zero-based) - ///// in the current document containing the file attachment annotation. If the value is a string, - ///// it specifies a named destination in the current document that provides the page number of the - ///// file attachment annotation. - ///// - //[KeyInfo(KeyType.Integer | KeyType.ByteString | KeyType.Optional)] - //public const string P = "/P"; - - ///// - ///// (Required if the value of R is C and the target is associated with a file attachment annotation; - ///// otherwise, it must be absent) If the value is an integer, it specifies the index (zero-based) - ///// of the annotation in the Annots array (see Table 3.27) of the page specified by P. If the value - ///// is a text string, it specifies the value of NM in the annotation dictionary (see Table 8.15). - ///// - //[KeyInfo(KeyType.Integer | KeyType.TextString | KeyType.Optional)] - //public const string A = "/A"; - - /// - /// (Optional) A target dictionary specifying additional path information to the target document. - /// If this entry is absent, the current document is the target file containing the destination. - /// - [KeyInfo(KeyType.Dictionary | KeyType.Optional)] - public const string T = "/T"; - } + [KeyInfo(KeyType.ByteString | KeyType.Optional)] + public const string N = "/N"; + + ///// + ///// (Required if the value of R is C and the target is associated with a file attachment annotation; + ///// otherwise, it must be absent) If the value is an integer, it specifies the page number (zero-based) + ///// in the current document containing the file attachment annotation. If the value is a string, + ///// it specifies a named destination in the current document that provides the page number of the + ///// file attachment annotation. + ///// + //[KeyInfoBug(KeyType.Integer | KeyType.ByteString | KeyType.Optional)] // #US373 Cannot "|" types. + //public const string P = "/P"; + + ///// + ///// (Required if the value of R is C and the target is associated with a file attachment annotation; + ///// otherwise, it must be absent) If the value is an integer, it specifies the index (zero-based) + ///// of the annotation in the Annots array (see Table 3.27) of the page specified by P. If the value + ///// is a text string, it specifies the value of NM in the annotation dictionary (see Table 8.15). + ///// + //[KeyInfoBug(KeyType.Integer | KeyType.TextString | KeyType.Optional)] // #US373 Cannot "|" types. + //public const string A = "/A"; + + /// + /// (Optional) A target dictionary specifying additional path information to the target document. + /// If this entry is absent, the current document is the target file containing the destination. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string T = "/T"; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfGoToAction.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfGoToAction.cs index 1f2560c5..6bab204b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfGoToAction.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfGoToAction.cs @@ -3,6 +3,8 @@ using PdfSharp.Pdf.IO; +// v7.0.0 REVIEW + namespace PdfSharp.Pdf.Actions { /// @@ -10,6 +12,8 @@ namespace PdfSharp.Pdf.Actions /// public sealed class PdfGoToAction : PdfAction { + // Reference 2.0: 12.6.4.2 Go-To actions / Page 511 + /// /// Initializes a new instance of the class. /// @@ -28,6 +32,14 @@ public PdfGoToAction(PdfDocument document) Inititalize(); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfGoToAction(PdfDictionary dict) + : base(dict) + { } + /// /// Creates a link within the current document. /// @@ -44,8 +56,7 @@ public static PdfGoToAction CreateGoToAction(string destinationName) void Inititalize() { - Elements.SetName(PdfAction.Keys.Type, "/Action"); - Elements.SetName(PdfAction.Keys.S, "/GoTo"); + Elements.SetName(PdfAction.Keys.S, PdfNamedActionTypes.GoTo); } internal override void WriteObject(PdfWriter writer) @@ -60,9 +71,11 @@ internal override void WriteObject(PdfWriter writer) /// internal new class Keys : PdfAction.Keys { + // Reference 2.0: Table 202 — Additional entries specific to a go-to action / Page 511 + ///// ///// (Required) The type of action that this dictionary describes; - ///// must be GoTo for a go-to action. + ///// shall be GoTo for a go-to action. ///// //[KeyInfo(KeyType.Name | KeyType.Required, FixedValue = "GoTo")] //public const string S = "/S"; @@ -70,8 +83,15 @@ internal override void WriteObject(PdfWriter writer) /// /// (Required) The destination to jump to (see Section 8.2.1, “Destinations”). /// - [KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)] + //[KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)] // #US373 Cannot "|" types. + [KeyInfo(KeyType.NameOrByteStringOrArray | KeyType.Required)] public const string D = "/D"; + + /// + /// (Required) The destination to jump to (see Section 8.2.1, “Destinations”). + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string SD = "/SD"; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfRemoteGoToAction.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfRemoteGoToAction.cs index 308c7213..b62ca504 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfRemoteGoToAction.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/PdfRemoteGoToAction.cs @@ -3,6 +3,8 @@ using PdfSharp.Pdf.IO; +// v7.0.0 TODO + namespace PdfSharp.Pdf.Actions { /// @@ -28,6 +30,16 @@ public PdfRemoteGoToAction(PdfDocument document) Inititalize(); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfRemoteGoToAction(PdfDictionary dict) + : base(dict) + { + Inititalize(); + } + /// /// Creates a link to another document. /// @@ -90,7 +102,8 @@ string EncodePath(string path) /// /// (Required) The destination to jump to (see Section 8.5.3, “Action Types”). /// - [KeyInfo(KeyType.String | KeyType.Dictionary | KeyType.Required)] + //[KeyInfo(KeyType.String | KeyType.Dictionary | KeyType.Required)] // #US373 Cannot "|" types. + [KeyInfo(KeyType.StringOrDictionary | KeyType.Required)] // #US373 Cannot "|" types. //[KeyInfo(KeyType.FileSpecification | KeyType.Required)] // File Specifications are not yet implemented. public const string F = "/F"; @@ -100,7 +113,8 @@ string EncodePath(string path) /// its first element must be a page number within the remote document rather than an indirect reference to a page object /// in the current document. The first page is numbered 0. /// - [KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)] + //[KeyInfo(KeyType.Name | KeyType.ByteString | KeyType.Array | KeyType.Required)] // #US373 Cannot "|" types. + [KeyInfo(KeyType.NameOrByteStringOrArray | KeyType.Required)] // #US373 Cannot "|" types. public const string D = "/D"; /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionNames.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionNames.cs index bb9d0b76..d147d011 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionNames.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionNames.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. namespace PdfSharp.Pdf.Actions @@ -28,4 +28,4 @@ public enum PdfNamedActionNames /// LastPage } -} \ No newline at end of file +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionTypes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionTypes.cs new file mode 100644 index 00000000..4fa8b595 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Actions/enums/PdfNamedActionTypes.cs @@ -0,0 +1,116 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 Ready + +namespace PdfSharp.Pdf.Actions +{ + /// + /// Specifies the action types. + /// + public enum PdfNamedActionTypes + { + // Reference 2.0: 12.6.4 Action types / Page 510 + // Reference 2.0: Table 201 — Action types / Page 510 + + /// + /// Go to a destination in the current document. + /// + GoTo = 1, + + /// + /// ("Go-to remote") Go to a destination in another document. + /// + GoToR, + + /// + /// ("Go-to embedded"; PDF 1.6) Go to a destination in an embedded file. + /// + GoToE, + + /// + /// ("Go-to document part"; PDF 2.0) Go to a specified DPart in the current document. + /// + GoToDp, + + /// + /// Launch an application, usually to open a file. + /// + Launch, + + /// + /// Begin reading an article thread. + /// + Thread, + + /// + /// Resolve a uniform resource identifier. + /// + URI, + + /// + /// (PDF 1.2; deprecated in PDF 2.0) Play a sound. + /// + Sound, + + /// + /// (PDF 1.2; deprecated in PDF 2.0) Play a movie. + /// + Movie, + + /// + /// (PDF 1.2) Set an annotation’s Hidden flag. + /// + Hide, + + /// + /// (PDF 1.2) Execute a predefined action. + /// + Named, + + /// + /// (PDF 1.2) Send data to a uniform resource locator. + /// + SubmitForm, + + /// + /// (PDF 1.2) Set fields to their default values. + /// + ResetForm, + + /// + /// (PDF 1.2) Import field values from a file. + /// + ImportData, + + /// + /// (PDF 1.5) Set the states of optional content groups. + /// + SetOCGState, + + /// + /// (PDF 1.5) Controls the playing of multimedia content. + /// + Rendition, + + /// + /// (PDF 1.5) Updates the display of a document, using a transition dictionary. + /// + Trans, + + /// + /// (PDF 1.6) Set the current view of a 3D annotation. + /// + GoTo3DView, + + /// + /// (PDF 1.3) Execute an ECMAScript script. + /// + JavaScript, + + /// + /// (PDF 2.0; RichMedia annotation only) Specifies a command to be sent to the annotation’s handler. + /// + RichMediaExecute, + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs index 47ce5941..f7479828 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCIDFont.cs @@ -1,9 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using PdfSharp.Drawing; -using PdfSharp.Pdf.Filters; -using PdfSharp.Fonts.OpenType; +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Advanced { @@ -13,7 +11,7 @@ namespace PdfSharp.Pdf.Advanced /// PDFsharp only used CIDFontType2 which is a TrueType font program. /// // ReSharper disable once InconsistentNaming - class PdfCIDFont : PdfFont + public class PdfCIDFont : PdfFont { public PdfCIDFont(PdfDocument document) : base(document) @@ -31,12 +29,12 @@ public PdfCIDFont(PdfDocument document, PdfFontDescriptor fontDescriptor /*, XFo cid.Elements.SetInteger("/Supplement", 0); Elements.SetValue(Keys.CIDSystemInfo, cid); // #PDF-UA: 'Identity' or a stream must obviously be set for CIDFonts to satisfy PDF/UA requirements. - Elements.SetName(Keys.CIDToGIDMap, "Identity"); + Elements.SetName(Keys.CIDToGIDMap, "/Identity"); FontDescriptor = fontDescriptor; // ReSharper disable once DoNotCallOverridableMethodsInConstructor Owner.IrefTable.TryAdd(fontDescriptor); - Elements[Keys.FontDescriptor] = fontDescriptor.Reference; + Elements[Keys.FontDescriptor] = fontDescriptor.RequiredReference; //FontEncoding = font.PdfOptions.FontEncoding; FontEncoding = PdfFontEncoding.Unicode; @@ -59,11 +57,19 @@ public PdfCIDFont(PdfDocument document, PdfFontDescriptor fontDescriptor, byte[] // ReSharper disable once DoNotCallOverridableMethodsInConstructor //Owner.IrefTable.Add(fontDescriptor); Owner.IrefTable.TryAdd(fontDescriptor); - Elements[Keys.FontDescriptor] = fontDescriptor.Reference; + Elements[Keys.FontDescriptor] = fontDescriptor.RequiredReference; FontEncoding = PdfFontEncoding.Unicode; } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfCIDFont(PdfDictionary dict) + : base(dict) + { } + public string BaseFont { get => Elements.GetName(Keys.BaseFont); @@ -175,7 +181,6 @@ internal override void PrepareForSave() /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCatalog.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCatalog.cs index 7231ef8e..1b6b3ad3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCatalog.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCatalog.cs @@ -2,16 +2,21 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Pdf.IO; -using PdfSharp.Pdf.AcroForms; +using PdfSharp.Pdf.Forms; +using PdfSharp.Pdf.Metadata; using PdfSharp.Pdf.Structure; +// v7.0.0 TODO review and cleanup + namespace PdfSharp.Pdf.Advanced { /// - /// Represents the catalog dictionary. + /// Represents the PDF catalog dictionary. /// public sealed class PdfCatalog : PdfDictionary { + // Reference 2.0: / Page + /// /// Initializes a new instance of the class. /// @@ -21,6 +26,10 @@ public PdfCatalog(PdfDocument document) Elements.SetName(Keys.Type, "/Catalog"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfCatalog(PdfDictionary dictionary) : base(dictionary) { } @@ -33,6 +42,7 @@ public string Version get => _version; set { + // TODO Create: public enum PdfVersion { Pdf10, Pdf11, ..., Pdf20} switch (value) { case "1.0": @@ -49,33 +59,23 @@ public string Version case "1.6": throw new InvalidOperationException("Unsupported PDF version."); + case "1.7": + case "2.0": + _version = value; + break; + default: throw new ArgumentException("Invalid version."); } } } - string _version = "1.4"; + string _version = "1.7"; /// - /// Gets the pages collection of this document. + /// Gets the page tree root of this document. // TODO flat or not /// public PdfPages Pages - { - get - { - if (_pages == null) - { - _pages = (PdfPages?)Elements.GetValue(Keys.Pages, VCF.CreateIndirect) ?? NRT.ThrowOnNull(); - if (Owner.IsImported) - { - _pages.FlattenPageTree(); - //foreach (var page in _pages) - // page.InitPageSize(); - } - } - return _pages; - } - } + => _pages ??= Elements.GetRequiredDictionary(Keys.Pages, VCF.CreateIndirect); PdfPages? _pages; /// @@ -100,16 +100,13 @@ internal PdfPageMode PageMode /// Implementation of PdfDocument.ViewerPreferences. /// internal PdfViewerPreferences ViewerPreferences - { - get - { - if (_viewerPreferences == null) - _viewerPreferences = (PdfViewerPreferences?)Elements.GetValue(Keys.ViewerPreferences, VCF.CreateIndirect) ?? - NRT.ThrowOnNull(); - return _viewerPreferences; - } - } - PdfViewerPreferences? _viewerPreferences; + => Elements.GetRequiredDictionary(Keys.ViewerPreferences, VCF.CreateIndirect); + + internal bool HasOutline + => Elements.GetDictionary(Keys.Outlines, VCF.NoTransform) != null; + + internal PdfOutline Outline + => Elements.GetRequiredDictionary(Keys.Outlines, VCF.CreateIndirect); /// /// Implementation of PdfDocument.Outlines. @@ -118,82 +115,116 @@ internal PdfOutlineCollection Outlines { get { - if (_outline == null) - { - ////// Ensure that the page tree exists. - ////// ReSharper disable once UnusedVariable because we need dummy to call the getter. - ////PdfPages dummy = Pages; - - // Now create the outline item tree. - _outline = (PdfOutline?)Elements.GetValue(Keys.Outlines, VCF.CreateIndirect) ?? NRT.ThrowOnNull(); - } - return _outline.Outlines; + var outline = Elements.GetRequiredDictionary(Keys.Outlines, VCF.CreateIndirect); + return outline.Outlines; } } - PdfOutline? _outline; /// - /// Gets the name dictionary of this document. + /// Gets a value indicating whether the document has a /Metadata dictionary. + /// + public bool HasMetadata => Elements.GetDictionary(Keys.Metadata) != null; + + /// + /// Gets the metadata dictionary of this document. + /// An empty dictionary is created if it does not exist. + /// + [Obsolete("Use GetMetadata or GetRequiredMetadata.")] + public PdfMetadata Metadata + => Elements.GetRequiredDictionary(Keys.Metadata, VCF.CreateIndirect); + + /// + /// Gets the metadata dictionary of this document. + /// Returns null if it does not exist. + /// + public PdfMetadata? GetMetadata() + => Elements.GetDictionary(Keys.Metadata); + + /// + /// Gets the metadata dictionary of this document. + /// It is created if it does not exist. + /// + public PdfMetadata GetOrCreateMetadata() + => Elements.GetRequiredDictionary(Keys.Metadata, VCF.CreateIndirect); + + /// + /// Gets a value indicating whether the document has a /Names dictionary. + /// + public bool HasNames => Elements.GetDictionary(Keys.Names) != null; + + /// + /// Gets the /Names dictionary of this document. + /// An empty dictionary is created if it does not exist. /// public PdfNameDictionary Names - { - get - { - if (_names == null) - { - var dict = Elements.GetDictionary(Keys.Names); - if (dict != null) - _names = new PdfNameDictionary(dict); - else - { - _names = new PdfNameDictionary(Owner); - Owner.Internals.AddObject(_names); - Elements.SetReference(Keys.Names, _names.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull()); - } - } - return _names; - } - } - PdfNameDictionary? _names; + => Elements.GetRequiredDictionary(Keys.Names, VCF.CreateIndirect); + + /// + /// Gets the /Names dictionary of this document. + /// Returns null if it does not exist. + /// + public PdfNameDictionary? GetNames() + => Elements.GetDictionary(Keys.Metadata); /// - /// Gets the named destinations defined in the Catalog + /// Gets the /Names dictionary of this document. + /// It is created if it does not exist. /// + public PdfNameDictionary GetOrCreateNames() + => Elements.GetRequiredDictionary(Keys.Metadata, VCF.CreateIndirect); + + /// + /// Gets a value indicating whether the document has a /Dests dictionary. + /// + public bool HasDestinations + => Elements.GetDictionary(Keys.Dests) != null; + + /// + /// Gets the named destinations defined in the Catalog. + /// + [Obsolete("Use GetDestinations or GetRequiredDestinations.")] public PdfNamedDestinations Destinations - { - get - { - if (_dests == null) - { - var dict = Elements.GetDictionary(Keys.Dests); - if (dict != null) - _dests = new PdfNamedDestinations(dict); - else - { - _dests = new PdfNamedDestinations(); - _dests = new PdfNamedDestinations(); - Owner.Internals.AddObject(_dests); - Elements.SetReference(Keys.Dests, _dests.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull()); - } - } - return _dests; - } - } - PdfNamedDestinations? _dests; + => Elements.GetRequiredDictionary(Keys.Dests, VCF.CreateIndirect); /// - /// Gets the AcroForm dictionary of this document. + /// Gets the named destinations dictionary of this document. + /// Returns null if it does not exist. /// - public PdfAcroForm AcroForm - { - get - { - if (_acroForm == null) - _acroForm = (PdfAcroForm?)Elements.GetValue(Keys.AcroForm) ?? NRT.ThrowOnNull(); - return _acroForm; - } - } - PdfAcroForm? _acroForm; + public PdfNamedDestinations? GetDestinations() + => Elements.GetDictionary(Keys.Dests); + + /// + /// Gets the named destinations dictionary of this document. + /// It is created if it does not exist. + /// + public PdfNamedDestinations GetRequiredDestinations() + => Elements.GetRequiredDictionary(Keys.Dests, VCF.CreateIndirect); + + /// + /// Gets a value indicating whether the document has a /AcroForm dictionary. + /// + public bool HasAcroForm => Elements.GetDictionary(Keys.AcroForm) != null; + + /// + /// Gets the interactive form (AcroForm) dictionary of this document. + /// + [Obsolete("Use GetAcroForm or GetOrCreateAcroForm.")] + public PdfForm AcroForm + => Elements.GetRequiredDictionary(Keys.AcroForm, VCF.CreateIndirect); + + /// + /// Gets the acro form dictionary of this document. + /// Returns null if it does not exist. + /// + public PdfForm? GetAcroForm() + => Elements.GetDictionary(Keys.AcroForm); + + /// + /// Gets the acro form dictionary of this document. + /// It is created if it does not exist. + /// + public PdfForm GetOrCreateAcroForm() + => Elements.GetRequiredDictionary(Keys.AcroForm, VCF.CreateIndirect); /// /// Gets or sets the language identifier specifying the natural language for all text in the document. @@ -217,28 +248,49 @@ public string Language internal override void PrepareForSave() { // Prepare pages. - if (_pages != null) - _pages.PrepareForSave(); + _pages?.PrepareForSave(); + + // Prepare metadata. + var metadataManager = Document.GetMetadataManager(); + metadataManager.PrepareForSave(); + + var xxx = metadataManager.ToString(); + + // Prepare AcroForm objects. + var acroForm = GetAcroForm(); + var fields = acroForm?.Elements.GetArray(PdfForm.Keys.Fields); + if (fields != null) + { + foreach (var formRef in fields.Elements) + { + var form = (formRef as PdfReference)?.Value; + if (form is PdfFormField field) + { + field.PrepareForSave(); + } + } + } // Create outline objects. - //if (_outline != null && _outline.Outlines.Count > 0) - if (_outline is { Outlines.Count: > 0 }) + if (HasOutline && Outline is { Outlines.Count: > 0 } outline) { - if (Elements[Keys.PageMode] == null) + if (!Elements.HasValue(Keys.PageMode)) // #US373 GetValue() not needed, just checking existence. PageMode = PdfPageMode.UseOutlines; - _outline.PrepareForSave(); + outline.PrepareForSave(); } // Clean up structure tree root. - if (Elements.GetObject(Keys.StructTreeRoot) is PdfStructureTreeRoot str) - str.PrepareForSave(); + if (Elements.GetObject(Keys.StructTreeRoot) is PdfStructureTreeRoot structTree) + structTree.PrepareForSave(); } internal override void WriteObject(PdfWriter writer) { - if (_outline != null && _outline.Outlines.Count > 0) + var state = Document.State; + //if (_outline != null && _outline.Outlines.Count > 0) + if (HasOutline && Outline is { Outlines.Count: > 0 }) { - if (Elements[Keys.PageMode] == null) + if (!Elements.HasValue(Keys.PageMode)) // #US373 GetValue() not needed, just checking existence. PageMode = PdfPageMode.UseOutlines; } base.WriteObject(writer); @@ -247,7 +299,7 @@ internal override void WriteObject(PdfWriter writer) /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { // ReSharper disable InconsistentNaming @@ -286,6 +338,8 @@ internal sealed class Keys : KeysBase /// /// (Optional; PDF 1.2) The document’s name dictionary. + /// (PDF 2.0) For unencrypted wrapper documents for an encrypted payload document + /// the Names dictionary is required and shall contain the EmbeddedFiles name tree. /// [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)] public const string Names = "/Names"; @@ -371,7 +425,7 @@ internal sealed class Keys : KeysBase /// /// (Optional; PDF 1.2) The document’s interactive form (AcroForm) dictionary. /// - [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional, typeof(PdfAcroForm))] + [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional, typeof(PdfForm))] public const string AcroForm = "/AcroForm"; /// @@ -458,7 +512,7 @@ internal sealed class Keys : KeysBase public const string Collection = "/Collection"; /// - /// (Optional; PDF 1.7) A flag used to expedite the display of PDF documents containing XFA forms. + /// Optional; deprecated in PDF 2.0) A flag used to expedite the display of PDF documents containing XFA forms. /// It specifies whether the document must be regenerated when the document is first opened. /// If true, the viewer application treats the document as a shell and regenerates the content /// when the document is opened, regardless of any dynamic forms settings that appear in the XFA @@ -468,15 +522,30 @@ internal sealed class Keys : KeysBase /// See the XML Forms Architecture (XFA) Specification (Bibliography). /// Default value: false. /// - [KeyInfo("1.7", KeyType.Boolean | KeyType.Optional)] + [KeyInfo("1.7", KeyType.Boolean | KeyType.Optional | KeyType.DeprecatedIn20)] public const string NeedsRendering = "/NeedsRendering"; + // ----- New PDF 2.0 entries ----- + + // DSS - nyi + + /// + /// (Optional; PDF 2.0) An array of one or more file specification dictionaries which denote the associated + /// files for this PDF document. + /// For unencrypted wrapper documents for an encrypted payload document the AF key is required and shall + /// include a reference to the file specification dictionary for the encrypted payload document. + /// + [KeyInfo("2.0", KeyType.ArrayOfDictionaries | KeyType.Optional, ObjectType = typeof(PdfArrayOfDictionaries))] + public const string AF = "/AF"; + + // DPartRoot - nyi + // ReSharper restore InconsistentNaming /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs index 902f72b8..248d7a95 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs @@ -18,7 +18,7 @@ public sealed class PdfContent : PdfDictionary /// Initializes a new instance of the class. /// public PdfContent(PdfDocument document) - : base(document) + : base(document, true) { } /// @@ -31,10 +31,10 @@ internal PdfContent(PdfPage page) } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// - /// The dict. - public PdfContent(PdfDictionary dict) // HACK_OLD PdfContent + internal PdfContent(PdfDictionary dict) // HACK_OLD PdfContent : base(dict) { // A PdfContent dictionary is always unfiltered. @@ -50,13 +50,13 @@ public bool Compressed { if (value) { - var filter = Elements["/Filter"]; + var filter = Elements.GetValue(PdfStream.Keys.Filter); // #US373 if (filter == null && Stream is not null) { - byte[] bytes = Filtering.FlateDecode.Encode(Stream.Value, _document.Options.FlateEncodeMode); + byte[] bytes = Filtering.FlateDecode.Encode(Stream.Value, Document.Options.FlateEncodeMode); Stream.Value = bytes; - Elements.SetInteger("/Length", Stream.Length); - Elements.SetName("/Filter", "/FlateDecode"); + Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + Elements.SetInteger(PdfStream.Keys.Length, Stream.Length); } } } @@ -67,18 +67,18 @@ public bool Compressed /// void Decode() { - if (Stream is { Value: not null }) + if (Stream is not null) { - var item = Elements["/Filter"]; - if (item != null) + var filter = Elements.GetValue(PdfStream.Keys.Filter); + if (filter != null) { - var decodeParams = Elements[PdfStream.Keys.DecodeParms]; - var bytes = Filtering.Decode(Stream.Value, item, decodeParams); + var decodeParams = Elements.GetValue(PdfStream.Keys.DecodeParms); + var bytes = Filtering.Decode(Stream.Value, filter, decodeParams); if (bytes != null!) { Stream.Value = bytes; - Elements.Remove("/Filter"); - Elements.SetInteger("/Length", Stream.Length); + Elements.Remove(PdfStream.Keys.Filter); + Elements.SetInteger(PdfStream.Keys.Length, Stream.Length); } } } @@ -121,36 +121,57 @@ internal override void WriteObject(PdfWriter writer) { // GetContent also disposes the underlying XGraphics object, if one exists //Stream = new PdfStream(PdfEncoders.RawEncoding.GetBytes(pdfRenderer.GetContent()), this); - _pdfRenderer.Close(); + if (_pdfRenderer is XGraphicsPdfRenderer xgfxRenderer) + { + xgfxRenderer.Close(); + } + else + { + // Case: Renderer is a PDFsharp Graphics DrawingContext for PDF. + // No automatic close, throw. + throw new InvalidOperationException( + $"A renderer of type {_pdfRenderer.GetType().FullName} is still open for this content stream."); + } Debug.Assert(_pdfRenderer == null); } if (Stream != null!) { - if (Owner.Options.CompressContentStreams && Elements.GetName("/Filter").Length == 0) + // Acrobat crashes if a PDF file contains an empty stream that is compressed and + // therefore about 2 to 4 bytes long. So we do not compress very short streams + // at all. + const int streamLengthCompressionThreshold = 32; + + // Compress the stream if it has a minimum length and has no filter set yet. + // Short streams are smaller without compression. + if (Owner.Options.CompressContentStreams && !Elements.HasValue(PdfStream.Keys.Filter) && + Stream.Value.Length > streamLengthCompressionThreshold) { - Stream.Value = Filtering.FlateDecode.Encode(Stream.Value, _document.Options.FlateEncodeMode); - //Elements["/Filter"] = new PdfName("/FlateDecode"); - Elements.SetName("/Filter", "/FlateDecode"); + Stream.Value = Filtering.FlateDecode.Encode(Stream.Value, Document.Options.FlateEncodeMode); + Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); } - Elements.SetInteger("/Length", Stream.Length); + Elements.SetInteger(PdfStream.Keys.Length, Stream.Length); } base.WriteObject(writer); } - internal void SetRenderer(XGraphicsPdfRenderer? renderer) => _pdfRenderer = renderer; - XGraphicsPdfRenderer? _pdfRenderer; + // Sets the renderer that currently renders this content stream. + internal void SetRenderer(IPageContentRenderer? renderer) => _pdfRenderer = renderer; + + internal IPageContentRenderer? Renderer => _pdfRenderer; + + IPageContentRenderer? _pdfRenderer; /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : PdfStream.Keys + public sealed class Keys : PdfStream.Keys { /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs index 34583149..c418b01c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs @@ -2,9 +2,10 @@ // See the LICENSE file in the solution root for more information. using System.Collections; +using PdfSharp.Internal; using PdfSharp.Pdf.Content.Objects; +using PdfSharp.Pdf.Internal; using PdfSharp.Pdf.IO; -using static PdfSharp.Pdf.PdfDictionary; namespace PdfSharp.Pdf.Advanced { @@ -18,7 +19,7 @@ public sealed class PdfContents : PdfArray /// /// The document. public PdfContents(PdfDocument document) - : base(document) + : base(document, false) { } internal PdfContents(PdfArray array) @@ -51,7 +52,6 @@ public PdfContent AppendContent() SetModified(); PdfContent content = new PdfContent(Owner); - Owner.IrefTable.Add(content); Debug.Assert(content.Reference != null); Elements.Add(content.Reference); return content; @@ -79,19 +79,17 @@ public PdfContent PrependContent() public PdfContent CreateSingleContent() { byte[] bytes = []; - byte[] bytes1; - byte[] bytes2; foreach (PdfItem iref in Elements) { PdfDictionary cont = (PdfDictionary)((PdfReference)iref).Value; - bytes1 = bytes; - bytes2 = cont.Stream!.UnfilteredValue; + var bytes1 = bytes; + var bytes2 = cont.Stream!.UnfilteredValue; bytes = new byte[bytes1.Length + bytes2.Length + 1]; bytes1.CopyTo(bytes, 0); bytes[bytes1.Length] = (byte)'\n'; bytes2.CopyTo(bytes, bytes1.Length + 1); } - PdfContent content = new PdfContent(Owner); + var content = new PdfContent(Owner); content.Stream = new PdfDictionary.PdfStream(bytes, content); return content; } @@ -99,28 +97,27 @@ public PdfContent CreateSingleContent() /// /// Replaces the current content of the page with the specified content sequence. /// - public PdfContent ReplaceContent(CSequence cseq) + public PdfContent ReplaceContent(CSequence seq) { - if (cseq == null) - throw new ArgumentNullException(nameof(cseq)); + if (seq == null) + throw new ArgumentNullException(nameof(seq)); - return ReplaceContent(cseq.ToContent()); + var bytes = PdfEncoders.RawEncoding.GetBytes(seq.ToContent()); + return ReplaceContent(bytes); } /// /// Replaces the current content of the page with the specified bytes. /// - PdfContent ReplaceContent(byte[] contentBytes) + public PdfContent ReplaceContent(byte[] contentBytes) { Debug.Assert(Owner != null); - PdfContent content = new PdfContent(Owner); - + var content = new PdfContent(Owner); content.CreateStream(contentBytes); - Owner.IrefTable.Add(content); Elements.Clear(); - Elements.Add(content.ReferenceNotNull); + Elements.Add(content.RequiredReference); return content; } @@ -143,7 +140,7 @@ void SetModified() byte[] value; int length; PdfContent content = (PdfContent)((PdfReference)Elements[0]).Value; - if (content != null && content.Stream != null) + if (content is { Stream: not null }) { length = content.Stream.Length; value = new byte[length + 2]; @@ -154,7 +151,7 @@ void SetModified() content.Elements.SetInteger("/Length", length + 2); } content = (PdfContent)((PdfReference)Elements[count - 1]).Value; - if (content != null && content.Stream != null) + if (content is { Stream: not null }) { length = content.Stream.Length; value = new byte[length + 3]; @@ -175,7 +172,10 @@ internal override void WriteObject(PdfWriter writer) { // Save two bytes in PDF stream... if (Elements.Count == 1) + { + var xxx = Elements[0]; Elements[0].WriteObject(writer); + } else base.WriteObject(writer); } @@ -222,7 +222,7 @@ public PdfContent Current { if (_index == -1 || _index >= _contents.Elements.Count) throw new InvalidOperationException(PsMsgs.ListEnumCurrentOutOfRange); - return _currentElement??throw new InvalidOperationException("Current called before MoveNext."); + return _currentElement ?? throw new InvalidOperationException("Current called before MoveNext."); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceStream.cs index 091f1197..867f31fd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceStream.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceStream.cs @@ -1,15 +1,14 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Collections.Generic; -using System.Diagnostics; +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Advanced { /// /// Represents a PDF cross-reference stream. /// - sealed class PdfCrossReferenceStream : PdfTrailer // Reference: 3.4.7 Cross-Reference Streams / Page 106 + public sealed class PdfCrossReferenceStream : PdfTrailer // Reference: 3.4.7 Cross-Reference Streams / Page 106 { /// /// Initializes a new instance of the class. @@ -25,6 +24,14 @@ public PdfCrossReferenceStream(PdfDocument document) #endif } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfCrossReferenceStream(PdfDictionary dict) + : base(dict) + { } + public readonly List Entries = new List(); public struct CrossReferenceStreamEntry @@ -114,7 +121,7 @@ public struct CrossReferenceStreamEntry /// /// Gets the KeysMeta for these keys. /// - public new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs index 327c212c..e15b6b0b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs @@ -10,7 +10,9 @@ using System.Collections; using Microsoft.Extensions.Logging; +using PdfSharp.Internal; using PdfSharp.Logging; +using PdfSharp.Pdf.Forms; using PdfSharp.Pdf.IO; namespace PdfSharp.Pdf.Advanced @@ -48,6 +50,15 @@ sealed class PdfCrossReferenceTable(PdfDocument document) // Must not be derived /// public void Add(PdfReference iref) { +#if DEBUG_ + //if (this is PdfObject { ObjectNumber: 96049 }) + // _ = typeof(int); + if (iref.ObjectID.ObjectNumber == 96049) + { + _ = typeof(int); + var number = iref.ObjectNumber; + } +#endif if (iref.ObjectID.IsEmpty) { // When happens this? @@ -69,6 +80,7 @@ public void Add(PdfReference iref) $"This should not occur. If you think this is a bug in PDFsharp, please visit {UrlLiterals.LinkToCannotOpenPdfFile} for further information.", oldIref.ObjectID, oldIref.Position, iref.Position); _objectTable.Remove(iref.ObjectID); + // TODO Set old object to dead. } _objectTable.Add(iref.ObjectID, iref); @@ -78,51 +90,73 @@ public void Add(PdfReference iref) /// /// Adds a PdfObject to the table. + /// This makes a direct object an indirect one. /// - public void Add(PdfObject value) + public void Add(PdfObject obj) { +#if DEBUG + if (obj.ObjectID.ObjectNumber == 96049) + _ = typeof(int); +#endif + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + + if (obj.ParentInfo != null) + throw new InvalidOperationException( + "You cannot convert a PDF object to an indirect object when it was already used as a direct one."); + // ReSharper disable once NullableWarningSuppressionIsUsed - if (value.Owner == null!) + if (obj.Owner == null!) { PdfSharpLogHost.PdfReadingLogger.LogWarning("Object without owner gets owned by the document it was added to."); - value.Document = document; + obj.Document = document; } else { - Debug.Assert(value.Owner == document); - if (value.Owner != document) + Debug.Assert(obj.Owner == document); + if (obj.Owner != document) { PdfSharpLogHost.PdfReadingLogger.LogError("Object not owned by the document it was added to."); + throw new InvalidOperationException("PDF object does not belong to this document."); } } - if (value.ObjectID.IsEmpty) + if (obj.ObjectID.IsEmpty) { - // Create new object number. - value.SetObjectID(GetNewObjectNumber(), 0); + // TODO: Check when this happens. + // Create new object number and create reference. + obj.SetObjectID(GetNewObjectNumber(), 0); +#if DEBUG_ + if (obj.ObjectNumber == 5) + _ = typeof(int); +#endif } - if (_objectTable.ContainsKey(value.ObjectID)) + if (_objectTable.ContainsKey(obj.ObjectID)) { // This must not happen. throw new InvalidOperationException("Object already in table."); } - _objectTable.Add(value.ObjectID, value.ReferenceNotNull); + _objectTable.Add(obj.ObjectID, obj.RequiredReference); // Always adjust MaxObjectNumber when a new object is added. - MaxObjectNumber = Math.Max(MaxObjectNumber, value.ObjectNumber); + MaxObjectNumber = Math.Max(MaxObjectNumber, obj.ObjectNumber); } /// /// Adds a PdfObject to the table if it was not already in. /// Returns true if it was added, false otherwise. /// - public bool TryAdd(PdfObject value) + public bool TryAdd(PdfObject obj) { - if (value.ObjectID.IsEmpty || !_objectTable.ContainsKey(value.ObjectID)) +#if DEBUG + if (obj.ObjectID.ObjectNumber == 96049) + _ = typeof(int); +#endif + if (obj.ObjectID.IsEmpty || !_objectTable.ContainsKey(obj.ObjectID)) { - Add(value); + Add(obj); return true; } return false; @@ -324,6 +358,13 @@ internal void Renumber() iref.ObjectID = new PdfObjectID(idx + 1); // Rehash with new number. _objectTable.Add(iref.ObjectID, iref); + + // Handle special case where a form field also is a widget annotation. + // We have to renumber the widget too. + if (iref.Value is PdfFormField { _widget: not null } formField) + { + formField._widget.Reference!.ObjectID = iref.ObjectID; + } } MaxObjectNumber = count; //CheckConsistence(); @@ -581,6 +622,8 @@ void TransitiveClosureImplementation(Dictionary reference // _ = typeof(int); //} #endif + if (pivot.IsDead) + Debugger.Break(); FindReferencedItems(pivot); } #if TEST_CODE @@ -606,6 +649,11 @@ void FindReferencedItems(PdfObject pdfObj) { Debug.Assert(pdfObj is PdfDictionary or PdfArray, "Call with dictionary or array only."); +#if DEBUG + if (pdfObj.ObjectNumber == 18) + _ = typeof(int); +#endif + IEnumerable? items = null; PdfDictionary? dict; PdfArray? array; @@ -618,8 +666,15 @@ void FindReferencedItems(PdfObject pdfObj) foreach (PdfItem item in items) { + if (item.IsDead) + Debugger.Break(); + if (item is PdfReference iref) { +#if DEBUG + if (iref.ObjectNumber == 18) + _ = typeof(int); +#endif // Case: The item is an indirect object. // Check if the reference belongs to the current document. @@ -641,6 +696,29 @@ void FindReferencedItems(PdfObject pdfObj) } var newObject = iref.Value; + // Handle a rare case here. If an Acro field (interactive field) has only one + // child (one entry in /Kids array) that is a widget annotation it can merge + // the widget keys with its own keys. + // In this case PDFsharp creates a PdfArcoFieldWidget and a PdfWidgetAnnotation + // object that share the same elements container. Both have their own + // PdfReference, but only the reference of the PdfArcoFieldWidget is in the + // IRefTable, while PdfWidgetAnnotation has a reference not owned by its document. + + // TODO: Fields with type (/FT) can also be widgets. + + // It works, because the object IDs are the same for both references. + // So one PDF dictionary has two different .NET type. If you came from /Kits in + // PdfFormField, it is of type PdfArcoFieldWidget; if you came from /Annots in + // PdfPage, it is of type PdfWidgetAnnotation. + // The following code ensures that always the PdfReference of the PdfArcoFieldWidget + // is taken. This is the only use of the new internal virtual function ActualReference. + var newIref = newObject?.ActualReference ?? null; // TODO newObject can be null here. + if (newIref != null && !ReferenceEquals(iref, newIref)) + { + if (references.ContainsKey(newIref)) + continue; + iref = newIref; + } // Ignore unreachable objects. if (iref.Document != null) @@ -657,15 +735,15 @@ void FindReferencedItems(PdfObject pdfObj) Debug.Assert(ReferenceEquals(iref.Document, document)); if (newObject.ObjectID.ObjectNumber != 0) references.Add(iref, null); -#if TEST_CODE__ - // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because auf .NET Framework / Standard +#if TEST_CODE_ + // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because of .NET Framework / Standard if (!doubleCheckReferences.ContainsKey(value)) doubleCheckReferences.Add(value, null); else _ = typeof(int); #endif - if (newObject is PdfDictionary or PdfArray) + if (newObject is PdfDictionary or PdfArray) // TODO PdfContainer { stack.Push(newObject); #if TEST_CODE @@ -682,7 +760,12 @@ void FindReferencedItems(PdfObject pdfObj) else { // Case: The item is a direct object. - +#if DEBUG + // TODO: Now we have PdfContainer. + var f1 = item is PdfObject and (PdfDictionary or PdfArray); + var f2 = item is PdfContainer; + Debug.Assert(f1 == f2); +#endif if (item is PdfObject pdfDictionaryOrArray and (PdfDictionary or PdfArray)) { #if TEST_CODE_ @@ -714,7 +797,7 @@ public PdfReference DeadObject Add(_deadObject); _deadObject.Elements.Add("/DeadObjectCount", new PdfInteger()); } - return _deadObject.ReferenceNotNull; + return _deadObject.RequiredReference; } } PdfDictionary? _deadObject; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDictionaryWithContentStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDictionaryWithContentStream.cs index 51080774..0fcb31b2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDictionaryWithContentStream.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDictionaryWithContentStream.cs @@ -55,18 +55,19 @@ internal PdfResources Resources internal string GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) { - pdfFont = _document.FontTable.GetOrCreateFont(glyphTypeface, fontType); + pdfFont = Document.FontTable.GetOrCreateFont(glyphTypeface, fontType); Debug.Assert(pdfFont != null); string name = Resources.AddFont(pdfFont); return name; } - string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) + string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) => GetFontName(glyphTypeface, fontType, out pdfFont); internal string GetFontName(string idName, byte[] fontData, out PdfFont pdfFont) { - pdfFont = _document.FontTable.GetFont(idName, fontData); + Debug.Assert(ReferenceEquals(_document2, Document)); + pdfFont = Document.FontTable.GetFont(idName, fontData); Debug.Assert(pdfFont != null); string name = Resources.AddFont(pdfFont); return name; @@ -82,7 +83,8 @@ string IContentStream.GetFontName(string idName, byte[] fontData, out PdfFont pd /// internal string GetImageName(XImage image) { - PdfImage pdfImage = _document.ImageTable.GetImage(image); + Debug.Assert(ReferenceEquals(_document2, Document)); + PdfImage pdfImage = Document.ImageTable.GetImage(image); Debug.Assert(pdfImage != null); string name = Resources.AddImage(pdfImage); return name; @@ -101,7 +103,7 @@ string IContentStream.GetImageName(XImage image) /// internal string GetFormName(XForm form) { - PdfFormXObject pdfForm = _document.FormTable.GetForm(form); + PdfFormXObject pdfForm = Document.FormTable.GetForm(form); Debug.Assert(pdfForm != null); string name = Resources.AddForm(pdfForm); return name; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfInternals.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDocumentInternals.cs similarity index 82% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfInternals.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDocumentInternals.cs index 376520ee..79e5ec84 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfInternals.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfDocumentInternals.cs @@ -11,17 +11,17 @@ namespace PdfSharp.Pdf.Advanced /// Provides access to the internal document data structures. /// This class prevents the public interfaces from pollution with too many internal functions. /// - public class PdfInternals // TODO_OLD: PdfDocumentInternals... PdfPageInternals etc. + public class PdfDocumentInternals { - internal PdfInternals(PdfDocument document) + internal PdfDocumentInternals(PdfDocument document) { _document = document; } - readonly PdfDocument _document; /// /// Gets or sets the first document identifier. + /// Sometimes called the document ID. /// public string FirstDocumentID { @@ -36,7 +36,8 @@ public string FirstDocumentID /// /// Gets or sets the second document identifier. - /// + /// Sometimes called the instance IC. + /// /// public string SecondDocumentID { get => _document.Trailer.GetDocumentID(1); @@ -76,7 +77,7 @@ Guid GuidFromString(string id) // ReSharper disable once InconsistentNaming // #PDF-UA public object? UAManager - => _document._uaManager; + => _document.UAManager; /// /// Returns the object with the specified Identifier, or null if no such object exists. @@ -155,31 +156,20 @@ public PdfObject[] GetAllObjects() /// /// Gets all indirect objects ordered by their object identifier. /// - [Obsolete("Use GetAllObjects.")] // Properties should not return arrays + [Obsolete("Use GetAllObjects.")] // Because a properties should not return an array. public PdfObject[] AllObjects => GetAllObjects(); /// /// Creates the indirect object of the specified type, adds it to the document, /// and returns the object. /// - public T CreateIndirectObject<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicParameterlessConstructor)] T>() + public T CreateIndirectObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + T>() where T : PdfObject { -#if true T obj = Activator.CreateInstance(); _document.IrefTable.Add(obj); -#else - T result = null; - ConstructorInfo ctorInfo = typeof(T).GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic | BindingFlags.ExactBinding, - null, new Type[] { typeof(PdfDocument) }, null); - if (ctorInfo != null) - { - result = (T)ctorInfo.Invoke(new object[] { _document }); - Debug.Assert(result != null); - AddObject(result); - } - Debug.Assert(result != null, "CreateIndirectObject failed with type " + typeof(T).FullName); -#endif return obj; } @@ -189,12 +179,26 @@ public PdfObject[] GetAllObjects() /// public void AddObject(PdfObject obj) { - if (obj == null) - throw new ArgumentNullException(nameof(obj)); - if (obj.Owner == null!) - obj.Document = _document; - else if (obj.Owner != _document) - throw new InvalidOperationException("Object does not belong to this document."); + // TODO: DELETE, moved to IrefTable + //if (obj == null) + // throw new ArgumentNullException(nameof(obj)); + + //// TODO + //if (obj.ParentInfo != null) + // throw new InvalidOperationException( + // "You cannot convert a PDF object to an indirect object when it was already used as a direct one."); + + //if (obj.Owner == null!) + // obj.Document = _document; + //else if (obj.Owner != _document) + // throw new InvalidOperationException("PDF object does not belong to this document."); + + // Make it an indirect object. + _document.IrefTable.Add(obj); + } + + internal void TryAddObject(PdfObject obj) // TODO: remove + { _document.IrefTable.Add(obj); } @@ -248,6 +252,16 @@ public void WriteObject(Stream stream, PdfItem item) /// /// The name of the custom value key. /// - public string CustomValueKey = "/PdfSharp.CustomValue"; + public readonly string CustomValueKey = "/PdfSharp.CustomValue"; + } + + /// + /// The class PdfInternals was renamed to PdfDocumentInternals. + /// Just rename it. + /// + [Obsolete("PDFsharp 6.4: PdfInternals was renamed to PdfDocumentInternals.")] + public class PdfInternals + { + // Just a stub for a better compiler message. } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileParameters.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileParameters.cs new file mode 100644 index 00000000..3d20b523 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileParameters.cs @@ -0,0 +1,75 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Security.Cryptography; +using PdfSharp.Pdf.PdfItemExtensions; +using PdfSharp.Pdf.Security; + +namespace PdfSharp.Pdf.Advanced +{ + /// + /// Represents an embedded file parameter dictionary. + /// + public sealed class PdfEmbeddedFileParameters : PdfDictionary + { + // Reference 2.0: 7.11.4 Embedded file streams / Page 138 + + /// + /// Initializes a new instance of PdfEmbeddedFileParameters. + /// + public PdfEmbeddedFileParameters(PdfDocument document) : base(document) + { + var now = new PdfDate(DateTimeOffset.Now); + Elements[Keys.CreationDate] = now; + Elements[Keys.ModDate] = now; + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfEmbeddedFileParameters(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : PdfStream.Keys + { + /// + /// (Optional) The size of the uncompressed embedded file, in bytes. + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string Size = "/Size"; + + /// + /// (Optional) The date and time when the embedded file was created. + /// + [KeyInfo(KeyType.Date | KeyType.Optional)] + public const string CreationDate = "/CreationDate"; + + /// + /// (Optional, required in the case of an embedded file stream used as an associated file) + /// The date and time when the embedded file was last modified. + /// + [KeyInfo(KeyType.Date | KeyType.Optional)] + public const string ModDate = "/ModDate"; + + /// + /// (Optional; deprecated in PDF 2.0) + /// A subdictionary containing additional information specific to Mac OS files. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string Mac = "/Mac"; + + /// + ///(Optional) A 16-byte string that is the checksum of the bytes of the uncompressed embedded file. + /// The checksum shall be calculated by applying the standard MD5 message-digest algorithm + /// (defined in Internet RFC 1321) to the bytes of the embedded file stream. + /// + [KeyInfo(KeyType.String | KeyType.Optional)] + public const string CheckSum = "/CheckSum"; + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs index 34fa0b8d..cc32c03c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfEmbeddedFileStream.cs @@ -1,40 +1,77 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Pdf.PdfItemExtensions; +using PdfSharp.Pdf.Security; + namespace PdfSharp.Pdf.Advanced { /// - /// Represents an embedded file stream. - /// PDF 1.3. + /// Represents a PDF embedded file stream. /// public class PdfEmbeddedFileStream : PdfDictionary { + // Reference 2.0: 7.11.4 Embedded file streams / Page 137 + /// /// Initializes a new instance of PdfEmbeddedFileStream from a stream. /// - public PdfEmbeddedFileStream(PdfDocument document, Stream stream) : base(document) + public PdfEmbeddedFileStream(PdfDocument document, Stream stream, string? subType = null, DateTimeOffset? modDate = null) + : base(document) { - _data = new byte[stream.Length]; + // TODO: Use TryGetBuffer if stream is MemoryStream. + // use the byte array from stream function here. + + var length = stream.Length; + var bytes = new byte[length]; using (stream) { - _ = stream.Read(_data, 0, (int)stream.Length); + var bytesRead = stream.Read(bytes, 0, (int)length); + // TODO: bytesRead may be smaller than length. + //Debug.Assert(bytesRead != length); } - Initialize(); + InitializeNew(bytes, subType, modDate); } - void Initialize() + /// + /// Initializes a new instance of PdfEmbeddedFileStream from a stream. + /// + public PdfEmbeddedFileStream(PdfDocument document, byte[] bytes, string? subType = null, DateTimeOffset? modDate = null) + : base(document) + { + InitializeNew(bytes, subType, modDate); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfEmbeddedFileStream(PdfDictionary dict) + : base(dict) + { } + + void InitializeNew(byte[] bytes, string? subType, DateTimeOffset? modDate) { Elements.SetName(Keys.Type, TypeValue); - Elements.SetInteger("/Length", _data.Length); + Elements.SetInteger("/Length", bytes.Length); + + // HACK TODO remove + Elements.SetName(Keys.Subtype, "text/xml"); + if (subType != null) + Elements.SetName(Keys.Subtype, subType); + + var now = DateTimeOffset.Now; + modDate ??= now; - var objParams = new PdfDictionary(_document); - objParams.Elements.SetInteger("/Size", _data.Length); - var now = DateTime.Now; - objParams.Elements.SetDateTime("/CreationDate", now); - objParams.Elements.SetDateTime("/ModDate", now); - Elements.SetObject(Keys.Params, objParams); + //var fileParams = Elements.GetValue(Keys.Params, VCF.Create).AsDictionary(); + var fileParams = Elements.GetRequiredDictionary(Keys.Params, VCF.Create); - Stream = new PdfStream(_data, this); + fileParams.Elements.SetInteger(PdfEmbeddedFileParameters.Keys.Size, bytes.Length); + fileParams.Elements.SetDateTime(PdfEmbeddedFileParameters.Keys.CreationDate, now); + fileParams.Elements.SetDateTime(PdfEmbeddedFileParameters.Keys.ModDate, modDate.Value); + fileParams.Elements.SetString(PdfEmbeddedFileParameters.Keys.CheckSum, MD5Managed.ComputeHashHex(bytes)); + + Stream = new(bytes, this); } /// @@ -45,8 +82,6 @@ public static bool IsEmbeddedFile(PdfDictionary dictionary) return dictionary.Elements.GetName(Keys.Type) == TypeValue; } - readonly byte[] _data; - const string TypeValue = "/EmbeddedFile"; /// @@ -75,8 +110,17 @@ public class Keys : PdfStream.Keys /// (Optional) An embedded file parameter dictionary containing additional, /// file-specific information (see Table 3.43). /// - [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(PdfEmbeddedFileParameters))] public const string Params = "/Params"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; } + + internal override DictionaryMeta Meta => Keys.Meta; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfExtGState.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfExtGState.cs index 8c8eef34..8627e2cf 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfExtGState.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfExtGState.cs @@ -25,7 +25,6 @@ public PdfExtGState(PdfDocument document) : base(document) { Elements.SetName(Keys.Type, "/ExtGState"); - #if true_ //AIS false //BM /Normal @@ -53,6 +52,14 @@ public PdfExtGState(PdfDocument document) //#endif } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfExtGState(PdfDictionary dict) + : base(dict) + { } + /// /// Used in Edf.Xps. /// @@ -118,7 +125,8 @@ public double StrokeAlpha { // #PDF-A // ReSharper disable once CompareOfFloatsByEqualityOperator - if (_document.IsPdfA && value != 1.0) + Debug.Assert(ReferenceEquals(_document2, Document)); + if (Document.IsPdfA && value != 1.0) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Stroke alpha value set to 1."); value = 1.0; @@ -139,8 +147,9 @@ public double NonStrokeAlpha set { // #PDF-A + Debug.Assert(ReferenceEquals(_document2, Document)); // ReSharper disable once CompareOfFloatsByEqualityOperator - if (_document.IsPdfA && value != 1.0) + if (Document.IsPdfA && value != 1.0) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Non-stroke alpha value set to 1."); value = 1.0; @@ -188,7 +197,7 @@ public bool NonStrokeOverprint /// public PdfSoftMask SoftMask { - set => Elements.SetReference(Keys.SMask, value); + set => Elements.SetObject(Keys.SMask, value); } internal string Key => _key; @@ -211,7 +220,7 @@ internal static string MakeKey(double alpha, bool overPaint) /// /// Common keys for all streams. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { // ReSharper disable InconsistentNaming @@ -384,7 +393,6 @@ internal sealed class Keys : KeysBase /// Gets the KeysMeta for these keys. ///
internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFont.cs index c3cceb6a..53853243 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFont.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFont.cs @@ -1,8 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal.OpenType; using PdfSharp.Fonts; -using PdfSharp.Fonts.Internal; +using CodePointGlyphIndexPair = PdfSharp.Internal.OpenType.CodePointGlyphIndexPair; namespace PdfSharp.Pdf.Advanced { @@ -16,7 +17,19 @@ public class PdfFont : PdfDictionary ///
protected PdfFont(PdfDocument document) : base(document) - { } + { + CMapInfo = null!; + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFont(PdfDictionary dict) + : base(dict) + { + CMapInfo = null!; + } internal PdfFontDescriptor FontDescriptor { @@ -25,9 +38,9 @@ internal PdfFontDescriptor FontDescriptor Debug.Assert(_fontDescriptor != null); return _fontDescriptor ?? NRT.ThrowOnNull(); } - set => _fontDescriptor = value; + init => _fontDescriptor = value; } - PdfFontDescriptor _fontDescriptor = default!; + readonly PdfFontDescriptor _fontDescriptor = null!; internal PdfFontEncoding FontEncoding { get; init; } @@ -38,20 +51,15 @@ internal PdfFontDescriptor FontDescriptor internal void AddChars(CodePointGlyphIndexPair[] codePoints) { - _cmapInfo.AddChars(codePoints); + CMapInfo.AddChars(codePoints); _fontDescriptor.CMapInfo.AddChars(codePoints); } /// /// Gets or sets the CMapInfo of a PDF font. - /// For a Unicode font only this characters come to the ToUnicode map. + /// For a Unicode font only these characters come to the ToUnicode map. /// - internal CMapInfo CMapInfo - { - get => _cmapInfo ?? NRT.ThrowOnNull(); - set => _cmapInfo = value; - } - internal CMapInfo _cmapInfo = default!; + internal CMapInfo CMapInfo { get; init; } /// /// Gets or sets ToUnicodeMap. @@ -61,6 +69,7 @@ internal PdfToUnicodeMap ToUnicodeMap get => _toUnicodeMap ?? NRT.ThrowOnNull(); set => _toUnicodeMap = value; } + // ReSharper disable once InconsistentNaming internal PdfToUnicodeMap? _toUnicodeMap; @@ -100,3 +109,23 @@ public class Keys : KeysBase } } } + +namespace PdfSharp.Pdf.Advanced +{ + /// + /// An internal hack to allow to interact PdfSharp.Graphics.FontFace with + /// PDFsharp core lib. + /// + interface IFontProxy // #PSGFX IXGlyphTypeFaceProxy + { + void CheckVersion(); + + string Key { get; } + + OpenTypeFontFace FontFace { get; } + + string FaceName { get; } + + string GetBaseName(); + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptor.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptor.cs index fe9bfdf1..1ef7f64d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptor.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptor.cs @@ -1,10 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; using System.Text; +using PdfSharp.Internal.OpenType; using PdfSharp.Fonts; -using PdfSharp.Fonts.OpenType; namespace PdfSharp.Pdf.Advanced { @@ -75,11 +74,11 @@ enum PdfFontDescriptorFlags /// public sealed class PdfFontDescriptor : PdfDictionary { - internal PdfFontDescriptor(PdfDocument document, OpenTypeDescriptor otDescriptor) + internal PdfFontDescriptor(PdfDocument document, OpenTypeFontDescriptor otDescriptor) : base(document) { Descriptor = otDescriptor; - _cmapInfo = new CMapInfo(otDescriptor); + _cmapInfo = new CMapInfo(otDescriptor.FontFace.GlyphCount); Elements.SetName(Keys.Type, "/FontDescriptor"); @@ -97,9 +96,26 @@ internal PdfFontDescriptor(PdfDocument document, OpenTypeDescriptor otDescriptor Elements.SetReal(Keys.ItalicAngle, Descriptor.ItalicAngle); Elements.SetInteger(Keys.StemV, Descriptor.StemV); Elements.SetInteger(Keys.XHeight, Descriptor.DesignUnitsToPdf(Descriptor.XHeight)); + + //var x = Elements.GetValue(Keys.FontBBox); + //if (x is { IsDead: true }) + // Debugger.Break(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFontDescriptor(PdfDictionary dict) + : base(dict) + { + // Not implemented. Cannot create an OpenType descriptor from a font in a PDF file. + // Ctor exists only for type transformation. + Descriptor = null!; // TO/DO Connect with OT descriptor. + _cmapInfo = null!; // TO/DO Connect with OT descriptor. } - internal OpenTypeDescriptor Descriptor { get; } + internal OpenTypeFontDescriptor Descriptor { get; } /// /// Gets or sets the name of the font. @@ -107,7 +123,7 @@ internal PdfFontDescriptor(PdfDocument document, OpenTypeDescriptor otDescriptor public string FontName { get => Elements.GetName(Keys.FontName); - set => Elements.SetName(Keys.FontName, value); + set => Elements.SetName(Keys.FontName, Name.MakeName(value)); } /// @@ -115,7 +131,7 @@ public string FontName /// public bool IsSymbolFont { get; private set; } - PdfFontDescriptorFlags FlagsFromDescriptor(OpenTypeDescriptor descriptor) + PdfFontDescriptorFlags FlagsFromDescriptor(OpenTypeFontDescriptor descriptor) { PdfFontDescriptorFlags flags = 0; IsSymbolFont = descriptor.IsSymbolFont; @@ -128,17 +144,13 @@ PdfFontDescriptorFlags FlagsFromDescriptor(OpenTypeDescriptor descriptor) /// font subset. /// internal bool AddCmapTable { get; set; } - + /// /// Gets the CMapInfo for PDF font descriptor. /// It contains all characters, ANSI and Unicode. /// - internal CMapInfo CMapInfo - { - get => _cmapInfo ?? NRT.ThrowOnNull(); - //set => _cmapInfo2 = value; - } - CMapInfo _cmapInfo; + internal CMapInfo CMapInfo => _cmapInfo; + readonly CMapInfo _cmapInfo; internal override void PrepareForSave() { @@ -146,7 +158,7 @@ internal override void PrepareForSave() if (_prepared) return; _prepared = true; - + var pdfFontFile = new PdfFontProgram(Owner); pdfFontFile.CreateFontFileAndAddToDescriptor(this, _cmapInfo, !AddCmapTable); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptorCache.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptorCache.cs index 269d5da3..422eb68c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptorCache.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontDescriptorCache.cs @@ -1,11 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; -using PdfSharp.Drawing; -using PdfSharp.Fonts; -using PdfSharp.Fonts.OpenType; -using PdfSharp.Internal; +using System.Collections.Concurrent; +using PdfSharp.Internal.OpenType; namespace PdfSharp.Pdf.Advanced { @@ -21,13 +18,13 @@ sealed class PdfFontDescriptorCache(PdfDocument doc) /// Gets the FontDescriptor identified by the specified XFont. If no such object /// exists, a new FontDescriptor is created and added to the cache. ///
- public PdfFontDescriptor GetOrCreatePdfDescriptorFor(OpenTypeDescriptor otDescriptor, string baseName) + public PdfFontDescriptor GetOrCreatePdfDescriptorFor(OpenTypeFontDescriptor otDescriptor, string baseName) { if (!_cache.TryGetValue(otDescriptor.Key, out var pdfDescriptor)) { pdfDescriptor = new PdfFontDescriptor(Owner, otDescriptor); pdfDescriptor.FontName = pdfDescriptor.CreateEmbeddedFontSubsetName(baseName); - _cache.Add(otDescriptor.Key, pdfDescriptor); + _cache.TryAdd(otDescriptor.Key, pdfDescriptor); } return pdfDescriptor; } @@ -37,6 +34,6 @@ public PdfFontDescriptor GetOrCreatePdfDescriptorFor(OpenTypeDescriptor otDescri /// /// Maps OpenType descriptor to document specific PDF font descriptor. /// - readonly Dictionary _cache = []; + readonly ConcurrentDictionary _cache = []; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontProgram.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontProgram.cs index 0c362698..3446a55e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontProgram.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontProgram.cs @@ -1,9 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Text; +using PdfSharp.Internal.OpenType; using PdfSharp.Fonts; -using PdfSharp.Fonts.OpenType; using PdfSharp.Pdf.Filters; namespace PdfSharp.Pdf.Advanced @@ -20,23 +19,32 @@ internal PdfFontProgram(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFontProgram(PdfDictionary dict) + : base(dict) + { } + internal void CreateFontFileAndAddToDescriptor(PdfFontDescriptor pdfFontDescriptor, CMapInfo cmapInfo, bool cidFont) { - var x = pdfFontDescriptor.Elements[PdfFontDescriptor.Keys.FontFile2]; + //var x = pdfFontDescriptor.Elements[PdfFontDescriptor.Keys.FontFile2]; + var x = pdfFontDescriptor.Elements.GetValue(PdfFontDescriptor.Keys.FontFile2); // #US373 OpenTypeFontFace subSet = pdfFontDescriptor.Descriptor.FontFace.CreateFontSubset(cmapInfo.GlyphIndices, cidFont); - byte[] fontData = subSet.FontSource.Bytes; + byte[] fontData = subSet.OTFontSource.Bytes; Owner.Internals.AddObject(this); - pdfFontDescriptor.Elements[PdfFontDescriptor.Keys.FontFile2] = Reference; + pdfFontDescriptor.Elements[PdfFontDescriptor.Keys.FontFile2] = RequiredReference; Elements["/Length1"] = new PdfInteger(fontData.Length); if (!Owner.Options.NoCompression) { - fontData = Filtering.FlateDecode.Encode(fontData, _document.Options.FlateEncodeMode); - Elements["/Filter"] = new PdfName("/FlateDecode"); + fontData = Filtering.FlateDecode.Encode(fontData, Document.Options.FlateEncodeMode); + Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); } - Elements["/Length"] = new PdfInteger(fontData.Length); + Elements.SetInteger(PdfStream.Keys.Length, fontData.Length); CreateStream(fontData); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontTable.cs index 791d35dc..e2003cd4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFontTable.cs @@ -11,11 +11,13 @@ enum FontType /// /// TrueType with WinAnsi encoding. + /// PDF subtype is TrueType. /// TrueTypeWinAnsi = 1, // #RENAME better name /// /// TrueType with Identity-H or Identity-V encoding (Unicode). + /// PDF subtype is CIDFontType2. /// Type0Unicode = 2, // #RENAME better name } @@ -33,7 +35,7 @@ public PdfFontTable(PdfDocument document) { } /// - /// Gets a PdfFont from an XFont. If no PdfFont already exists, a new one is created. + /// Gets a PdfFont from an XGlyphTypeface. If no PdfFont already exists, a new one is created. /// public PdfFont GetOrCreateFont(XGlyphTypeface glyphTypeface, FontType fontType) { @@ -106,7 +108,7 @@ internal static string ComputePdfFontKey(XGlyphTypeface glyphTypeface, FontType // #NFM Use gtk here. But the gtk without simulation flags. - var faceName = glyphTypeface.FontFace.FullFaceName.ToLowerInvariant(); + var faceName = glyphTypeface.OTFontFace.FullFaceName.ToLowerInvariant(); var bold = glyphTypeface.IsBold; var italic = glyphTypeface.IsItalic; var type = fontType == FontType.TrueTypeWinAnsi ? "+A" : "+U"; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObject.cs index 98f89b6c..bb68002a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObject.cs @@ -1,6 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; +using PdfSharp.Drawing; +using PdfSharp.Pdf.IO; #if GDI using System.Drawing; using System.Drawing.Imaging; @@ -8,7 +11,10 @@ #if WPF using System.Windows.Media; #endif -using PdfSharp.Drawing; + +// v7.0.0 REVIEW + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Advanced { @@ -20,21 +26,31 @@ public sealed class PdfFormXObject : PdfXObject, IContentStream internal PdfFormXObject(PdfDocument thisDocument) : base(thisDocument) { - Elements.SetName(Keys.Type, "/XObject"); - Elements.SetName(Keys.Subtype, "/Form"); + SetTypeKeys(); + _form = null; } internal PdfFormXObject(PdfDocument thisDocument, XForm form) : base(thisDocument) { // BUG_OLD: form is not used - not implemented. - Elements.SetName(Keys.Type, "/XObject"); - Elements.SetName(Keys.Subtype, "/Form"); + SetTypeKeys(); + _form = form; //if (form.IsTemplate) //{ } } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormXObject(PdfDictionary dict) + : base(dict) + { + _form = null; + } + internal double DpiX { get; set; } = 72; internal double DpiY { get; set; } = 72; @@ -44,8 +60,7 @@ internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importe { Debug.Assert(importedObjectTable != null); Debug.Assert(ReferenceEquals(thisDocument, importedObjectTable.Owner)); - Elements.SetName(Keys.Type, "/XObject"); - Elements.SetName(Keys.Subtype, "/Form"); + SetTypeKeys(); if (form.IsTemplate) { @@ -62,16 +77,19 @@ internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importe PdfPage importPage = importPages[pdfForm.PageNumber - 1]; // Import resources - var res = importPage.Elements["/Resources"]; - if (res != null) // unlikely but possible + //var res = importPage.Elements["/Resources"]; + var res = importPage.Elements.GetValue("/Resources"); // #US373 + if (res != null) // Unlikely but possible. { #if true // Get root object. + // #US373 begin PdfObject root; - if (res is PdfReference reference) - root = reference.Value; - else + //if (res is PdfReference reference) + // root = reference.Value; + //else root = (PdfDictionary)res; + // #US373 end root = ImportClosure(importedObjectTable, thisDocument, root); // If the root was a direct object, make it indirect. @@ -160,7 +178,7 @@ internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importe } // Take /Rotate into account. - PdfRectangle rect = importPage.Elements.GetRectangle(PdfPage.InheritablePageKeys.MediaBox); + PdfRectangle rect = importPage.Elements.GetRequiredRectangle(PdfPage.InheritablePageKeys.MediaBox); // Reduce rotation to 0, 90, 180, or 270. int rotate = (importPage.Elements.GetInteger(PdfPage.InheritablePageKeys.Rotate) % 360 + 360) % 360; //rotate = 0; @@ -209,13 +227,25 @@ internal PdfFormXObject(PdfDocument thisDocument, PdfImportedObjectTable importe #if !DEBUG content.Compressed = true; #endif - var filter = content.Elements["/Filter"]; + //var filter = content.Elements["/Filter"]; + var filter = content.Elements.GetValue(PdfStream.Keys.Filter); // #US373 if (filter != null) - Elements["/Filter"] = filter.Clone(); + Elements[PdfStream.Keys.Filter] = filter.Clone(); // #US373: Should we expect references here? // (no cloning needed because the bytes keep untouched) Stream = content.Stream; // new PdfStream(bytes, this); - Elements.SetInteger("/Length", content.Stream.Value.Length); + Elements.SetInteger("/Length", content.Stream!.Value.Length); + } + + public void SetTypeKeys() + { + Elements.SetName(Keys.Type, "/XObject"); + Elements.SetName(Keys.Subtype, "/Form"); + } + + internal void SetForm(XForm form) + { + _form = form; } /// @@ -236,13 +266,14 @@ public PdfResources Resources internal string GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) { - pdfFont = _document.FontTable.GetOrCreateFont(glyphTypeface, fontType); + Debug.Assert(ReferenceEquals(_document2, Document)); + pdfFont = Document.FontTable.GetOrCreateFont(glyphTypeface, fontType); Debug.Assert(pdfFont != null); string name = Resources.AddFont(pdfFont); return name; } - string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) + string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) => GetFontName(glyphTypeface, fontType, out pdfFont); /// @@ -250,7 +281,8 @@ string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontTyp /// internal string GetFontName(string idName, byte[] fontData, out PdfFont pdfFont) { - pdfFont = _document.FontTable.GetFont(idName, fontData); + Debug.Assert(ReferenceEquals(_document2, Document)); + pdfFont = Document.FontTable.GetFont(idName, fontData); Debug.Assert(pdfFont != null); string name = Resources.AddFont(pdfFont); return name; @@ -363,6 +395,26 @@ void FixUpObject_old(PdfImportedObjectTable iot, PdfObject value) } } #endif + public static bool IsFormXObject(PdfDictionary dict) + { + // Subtype is required. + if (dict.Elements.GetName(Keys.Subtype) == "/Form") + return true; + + // Type is optional. + if (dict.Elements.GetName(Keys.Type) == "/XObject") + return true; + + return false; + } + + internal override void WriteObject(PdfWriter writer) + { + _form?.DrawingFinished(); + base.WriteObject(writer); + } + + XForm? _form; /// /// Predefined keys of this dictionary. @@ -441,7 +493,6 @@ void FixUpObject_old(PdfImportedObjectTable iot, PdfObject value) /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObjectTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObjectTable.cs index 188c9aa9..c85dbae0 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObjectTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFormXObjectTable.cs @@ -69,15 +69,15 @@ public PdfFormXObject GetForm(XForm form) } /// - /// Gets the imported object table. + /// Gets the imported object table of the specified page. /// public PdfImportedObjectTable GetImportedObjectTable(PdfPage page) { // Is the external PDF file from which is imported already known for the current document? - Selector selector = new Selector(page); + var selector = new Selector(page); if (!_forms.TryGetValue(selector, out var importedObjectTable)) { - importedObjectTable = new PdfImportedObjectTable(Owner, page.Owner); + importedObjectTable = new(Owner, page.Owner); _forms[selector] = importedObjectTable; } return importedObjectTable; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfGroupAttributes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfGroupAttributes.cs index 97cea4d1..9707a50b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfGroupAttributes.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfGroupAttributes.cs @@ -22,6 +22,16 @@ internal PdfGroupAttributes(PdfDocument thisDocument) Elements.SetName(Keys.Type, "/Group"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfGroupAttributes(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.Type, "/Group"); + } + /// /// Predefined keys of this dictionary. /// @@ -47,7 +57,6 @@ public class Keys : KeysBase /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.FaxEncode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.FaxEncode.cs index e923780e..b7a51adc 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.FaxEncode.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.FaxEncode.cs @@ -252,42 +252,42 @@ partial class PdfImage static readonly uint[] _zeroRuns = [ - 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, /* 0x00 - 0x0f */ - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0x10 - 0x1f */ - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x20 - 0x2f */ - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0x30 - 0x3f */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x40 - 0x4f */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x50 - 0x5f */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x60 - 0x6f */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x70 - 0x7f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x80 - 0x8f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x90 - 0x9f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xa0 - 0xaf */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xb0 - 0xbf */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xc0 - 0xcf */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xd0 - 0xdf */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0xe0 - 0xef */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 /* 0xf0 - 0xff */ - ]; - - static readonly uint[] _oneRuns = - [ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x00 - 0x0f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x10 - 0x1f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x20 - 0x2f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x30 - 0x3f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x40 - 0x4f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x50 - 0x5f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x60 - 0x6f */ - 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, /* 0x70 - 0x7f */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x80 - 0x8f */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0x90 - 0x9f */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xa0 - 0xaf */ - 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, /* 0xb0 - 0xbf */ - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xc0 - 0xcf */ - 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, /* 0xd0 - 0xdf */ - 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, /* 0xe0 - 0xef */ - 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8 /* 0xf0 - 0xff */ + 8, 7, 6, 6, 5, 5, 5, 5, 4, 4, 4, 4, 4, 4, 4, 4, // 0x00 - 0x0f + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0x10 - 0x1f + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x20 - 0x2f + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0x30 - 0x3f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x40 - 0x4f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x50 - 0x5f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x60 - 0x6f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x70 - 0x7f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x80 - 0x8f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x90 - 0x9f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xa0 - 0xaf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xb0 - 0xbf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xc0 - 0xcf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xd0 - 0xdf + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0xe0 - 0xef + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0 // 0xf0 - 0xff + ]; + + static readonly uint[] _oneRuns = + [ + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x00 - 0x0f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x10 - 0x1f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x20 - 0x2f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x30 - 0x3f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x40 - 0x4f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x50 - 0x5f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x60 - 0x6f + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, // 0x70 - 0x7f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x80 - 0x8f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0x90 - 0x9f + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xa0 - 0xaf + 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, 1, // 0xb0 - 0xbf + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xc0 - 0xcf + 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, 2, // 0xd0 - 0xdf + 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, 3, // 0xe0 - 0xef + 4, 4, 4, 4, 4, 4, 4, 4, 5, 5, 5, 5, 6, 6, 7, 8 // 0xf0 - 0xff ]; /// @@ -298,7 +298,7 @@ partial class PdfImage static uint CountOneBits(BitReader reader, uint bitsLeft) { uint found = 0; - for (;;) + for (; ; ) { uint bits; int @byte = reader.PeekByte(out bits); @@ -325,7 +325,7 @@ static uint CountOneBits(BitReader reader, uint bitsLeft) static uint CountZeroBits(BitReader reader, uint bitsLeft) { uint found = 0; - for (;;) + for (; ; ) { uint bits; int @byte = reader.PeekByte(out bits); @@ -418,7 +418,7 @@ static void FaxEncode2DRow(BitWriter writer, uint bytesFileOffset, byte[] imageB uint a2, b2; // ReSharper restore TooWideLocalVariableScope - for (;;) + for (; ; ) { b2 = FindDifferenceWithCheck(readerReference, b1, width, readerReference.GetBit(b1)); if (b2 >= a1) @@ -699,13 +699,13 @@ internal void FlushBuffer() internal void WriteBits(uint value, uint bits) { #if true - // TODO_OLD: Try to make this faster! + // TODO_OLD: Try to make this faster! - // If we have to write more bits than fit into the buffer, we fill - // the buffer and call the same routine recursively for the rest. + // If we have to write more bits than fit into the buffer, we fill + // the buffer and call the same routine recursively for the rest. #if USE_GOTO - // Use GOTO instead of end recursion: (is this faster?) - SimulateRecursion: + // Use GOTO instead of end recursion: (is this faster?) + SimulateRecursion: #endif if (bits + _bitsInBuffer > 8) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs index 535bbb3d..1c9afe14 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImage.cs @@ -10,7 +10,7 @@ #endif using Microsoft.Extensions.Logging; using PdfSharp.Drawing; -using PdfSharp.Drawing.Internal; +using PdfSharp.Internal.Imaging; using PdfSharp.Logging; using PdfSharp.Pdf.Filters; @@ -31,6 +31,7 @@ public PdfImage(PdfDocument document, XImage image) Elements.SetName(Keys.Subtype, "/Image"); _image = image; + ImportedImage = _image._importedImage; ////// TODO_OLD: identify images used multiple times. If the image already exists use the same XRef. ////_defaultName = PdfImageTable.NextImageName; @@ -39,7 +40,7 @@ public PdfImage(PdfDocument document, XImage image) { // Pdf supports Jpeg, therefore we can write what we’ve read: case "{B96B3CAE-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Jpeg - InitializeJpeg(document.Options); + InitializeJpeg(); break; // All other image formats are converted to PDF bitmaps: @@ -48,7 +49,7 @@ public PdfImage(PdfDocument document, XImage image) case "{B96B3CB1-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Tiff case "{B96B3CB5-0728-11D3-9D7B-0000F81EF32E}": //XImageFormat.Icon // Future improvement: try Jpeg for size optimization??? - InitializeNonJpeg(document.Options); + InitializeNonJpeg(); break; case "{84570158-DBF0-4C6B-8368-62D6A3CA76E0}": //XImageFormat.Pdf: @@ -61,148 +62,39 @@ public PdfImage(PdfDocument document, XImage image) } } -#if true_ - //private void InitializeAg() - //{ - // ReadTrueColorMemoryBitmapAg(3, 8, true); - //} - - //private void ReadTrueColorMemoryBitmapAg(int components, int bits, bool hasAlpha) - //{ - // int pdfVersion = Owner.Version; - // MemoryStream memory = new MemoryStream(); - - // WriteableBitmap bitmap = null; - // if (_image._wpfImage is WriteableBitmap) - // bitmap = (WriteableBitmap)_image._wpfImage; - // else if (_image._wpfImage is BitmapImage) - // bitmap = new WriteableBitmap(_image._wpfImage); - - // if (bitmap != null) - // { - // int height = _image.PixelHeight; - // int width = _image.PixelWidth; - - // int logicalComponents = components; - // if (components == 4) - // logicalComponents = 3; - - // byte[] imageData = new byte[components * width * height]; - - // bool hasMask = false; - // bool hasAlphaMask = false; - // byte[] alphaMask = hasAlpha ? new byte[width * height] : null; - // MonochromeMask mask = hasAlpha ? new MonochromeMask(width, height) : null; - - // int nOffsetRead = 0; - // if (logicalComponents == 3) - // { - // for (int y = 0; y < height; ++y) - // { - // int nOffsetWrite = 3 * y * width; // 3*(height - 1 - y)*width; - // int nOffsetWriteAlpha = 0; - // if (hasAlpha) - // { - // mask.StartLine(y); - // nOffsetWriteAlpha = y * width; // (height - 1 - y) * width; - // } - - // for (int x = 0; x < width; ++x) - // { - // uint pixel = (uint)bitmap.Pixels[nOffsetRead]; - // imageData[nOffsetWrite] = (byte)(pixel >> 16); - // imageData[nOffsetWrite + 1] = (byte)(pixel >> 8); - // imageData[nOffsetWrite + 2] = (byte)(pixel); - // if (hasAlpha) - // { - // byte pel = (byte)(pixel >> 24); - // mask.AddPel(pel); - // alphaMask[nOffsetWriteAlpha] = pel; - // if (!hasMask || !hasAlphaMask) - // { - // if (pel != 255) - // { - // hasMask = true; - // if (pel != 0) - // hasAlphaMask = true; - // } - // } - // ++nOffsetWriteAlpha; - // } - // //nOffsetRead += hasAlpha ? 4 : components; - // ++nOffsetRead; - // nOffsetWrite += 3; - // } - // //nOffsetRead = 4*((nOffsetRead + 3)/4); // Align to 32 bit boundary - // } - // } - // else if (components == 1) - // { - // // Grayscale - // throw new NotImplementedException("Image format not supported (grayscales)."); - // } - - // FlateDecode fd = new FlateDecode(); - // if (hasMask) - // { - // // monochrome mask is either sufficient or - // // provided for compatibility with older reader versions - // byte[] maskDataCompressed = fd.Encode(mask.MaskData, _document.Options.FlateEncodeMode); - // PdfDictionary pdfMask = new PdfDictionary(_document); - // pdfMask.Elements.SetName(Keys.Type, "/XObject"); - // pdfMask.Elements.SetName(Keys.Subtype, "/Image"); - - // Owner._irefTable.Add(pdfMask); - // pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask); - // pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length); - // pdfMask.Elements["/Filter"] = new PdfName("/FlateDecode"); - // pdfMask.Elements[Keys.Width] = new PdfInteger(width); - // pdfMask.Elements[Keys.Height] = new PdfInteger(height); - // pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1); - // pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true); - // Elements[Keys.Mask] = pdfMask.Reference; - // } - // if (hasMask && hasAlphaMask && pdfVersion >= 14) - // { - // // The image provides an alpha mask (requires Arcrobat 5.0 or higher) - // byte[] alphaMaskCompressed = fd.Encode(alphaMask, _document.Options.FlateEncodeMode); - // PdfDictionary smask = new PdfDictionary(_document); - // smask.Elements.SetName(Keys.Type, "/XObject"); - // smask.Elements.SetName(Keys.Subtype, "/Image"); - - // Owner._irefTable.Add(smask); - // smask.Stream = new PdfStream(alphaMaskCompressed, smask); - // smask.Elements["/Length"] = new PdfInteger(alphaMaskCompressed.Length); - // smask.Elements["/Filter"] = new PdfName("/FlateDecode"); - // smask.Elements[Keys.Width] = new PdfInteger(width); - // smask.Elements[Keys.Height] = new PdfInteger(height); - // smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8); - // smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); - // Elements[Keys.SMask] = smask.Reference; - // } - - // byte[] imageDataCompressed = fd.Encode(imageData, _document.Options.FlateEncodeMode); - - // Stream = new PdfStream(imageDataCompressed, this); - // Elements["/Length"] = new PdfInteger(imageDataCompressed.Length); - // Elements["/Filter"] = new PdfName("/FlateDecode"); - // Elements[Keys.Width] = new PdfInteger(width); - // Elements[Keys.Height] = new PdfInteger(height); - // Elements[Keys.BitsPerComponent] = new PdfInteger(8); - // // Anything needed for CMYK? Do we have sample images? - // Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); - // if (_image.Interpolate) - // Elements[Keys.Interpolate] = PdfBoolean.True; - // } - //} +#if CORE + internal PdfImage(PdfDocument document, ImportedImage importedImage) + : base(document) + { + Elements.SetName(Keys.Type, "/XObject"); + Elements.SetName(Keys.Subtype, "/Image"); + _image = null!; // Image is not used in this case. + ImportedImage = importedImage; + if (ImportedImage is ImportedJpegImage) + InitializeJpeg(); + else + InitializeNonJpeg(); + } #endif + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfImage(PdfDictionary dict) + : base(dict) + { + _image = null!; + } + /// /// Gets the underlying XImage object. /// - public XImage Image => _image; + public XImage? Image => _image; - readonly XImage _image; + readonly XImage? _image; + + ImportedImage? ImportedImage { get; set; } /// /// Returns 'Image'. @@ -215,7 +107,7 @@ public override string ToString() /// /// Creates the keys for a JPEG image. /// - void InitializeJpeg(PdfDocumentOptions options) + void InitializeJpeg() { // PDF supports JPEG, so there’s not much to be done. MemoryStream? memory = null; @@ -226,16 +118,16 @@ void InitializeJpeg(PdfDocumentOptions options) int streamLength = 0; #if CORE || GDI || WPF - if (_image._importedImage != null) + if (ImportedImage != null) { - var idd = (ImageDataDct)_image._importedImage.ImageData(options); - imageBits = idd.Data; + var idd = ImportedImage.ImageData; + imageBits = idd; streamLength = idd.Length; } #endif #if CORE || GDI - if (_image._importedImage == null) + if (_image != null && ImportedImage == null) { if (!_image._path.StartsWith("*", StringComparison.Ordinal)) { @@ -278,7 +170,7 @@ void InitializeJpeg(PdfDocumentOptions options) // Save the image to a memory stream. memory = new MemoryStream(); ownMemory = true; - _image._gdiImage.Save(memory, ImageFormat.Jpeg); + _image._gdiImage.Save(memory, System.Drawing.Imaging.ImageFormat.Jpeg); #endif } } @@ -288,38 +180,39 @@ void InitializeJpeg(PdfDocumentOptions options) Debug.Assert(false, "Internal error? JPEG image, but file not found!"); throw new InvalidOperationException("JPEG image used, but cannot access image data!"); } - #if GDI { // Use ImageImporter to get meta information about JPEG image. GDI does not return correct information for CMYK images. - var ii = ImageImporter.GetImageImporter(); - var i = ii.ImportImage(memory); - if (i != null && i is ImportedImageJpeg) + var ii = ToBeNamed.TryImportImage(memory, out var image); + if (ii && image is ImportedJpegImage) { - _image._importedImage = i; + _image._importedImage = image; + ImportedImage = image; } } #endif } #endif #if WPF - if (!_image._path.StartsWith("*", StringComparison.Ordinal)) + if (_image != null && !_image._path.StartsWith("*", StringComparison.Ordinal)) { // Image does not come from a stream, so we have the path to the file - just use the path. // If the image was modified in memory, those changes will be lost and the original image, as it was read from the file, will be added to the PDF. if (imageBits == null) { - // Use ImageImporterJpeg to read the image. + // Use ImageImporter to read the image. string filename = XImage.GetImageFilename(_image._wpfImage); - ImageImporter ii = ImageImporter.GetImageImporter(); - var i = ii.ImportImage(filename); + var worker = new StreamReaderWorker(filename); + var success = ToBeNamed.TryImportImage(worker.Data, out var image); - ImageDataDct idd2 = (ImageDataDct)i!.ImageData(options); - imageBits = idd2.Data; - streamLength = idd2.Length; + imageBits = image!.GetPdfImageData(); + streamLength = imageBits.Length; if (_image._importedImage == null) - _image._importedImage = i; + { + _image._importedImage = image; + ImportedImage = image; + } } memory = _image.Memory; @@ -327,7 +220,7 @@ void InitializeJpeg(PdfDocumentOptions options) else { // If we have a stream, copy data from the stream. - if (_image._stream != null && _image._stream.CanSeek) + if (_image != null && _image._stream != null! && _image._stream.CanSeek) { memory = new MemoryStream(); ownMemory = true; @@ -340,18 +233,9 @@ void InitializeJpeg(PdfDocumentOptions options) memory.Write(buffer, 0, bytesRead); } } - else - { - // TODO_OLD Anything we can do here? - //#if CORE _WITH _GDI - // // No stream, no filename, get image data. - // // Save the image to a memory stream. - // _image._gdiImage.Save(memory, ImageFormat.Jpeg); - //#endif - } } - if (imageBits == null && memory == null && _image._wpfImage != null) + if (imageBits == null && memory == null && _image != null && _image._wpfImage != null) { Debug.Assert(false, "Internal error? JPEG image, but file not read!"); } @@ -360,58 +244,57 @@ void InitializeJpeg(PdfDocumentOptions options) memory = new MemoryStream(); ownMemory = true; #endif - // Idea: Use ImageImporterJPEG here to avoid redundant code. - if (imageBits == null) { streamLength = (int)memory!.Length; // NRT imageBits = new byte[streamLength]; memory.Seek(0, SeekOrigin.Begin); - memory.Read(imageBits, 0, streamLength); + _ = memory.Read(imageBits, 0, streamLength); // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (ownMemory) memory.Dispose(); } - - bool tryFlateDecode = _document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Automatic; - bool useFlateDecode = _document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Always; + Debug.Assert(ReferenceEquals(_document2, Document)); + bool tryFlateDecode = Document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Automatic; + bool useFlateDecode = Document.Options.UseFlateDecoderForJpegImages == PdfUseFlateDecoderForJpegImages.Always; FlateDecode fd = new FlateDecode(); - byte[]? imageDataCompressed = useFlateDecode || tryFlateDecode ? fd.Encode(imageBits, _document.Options.FlateEncodeMode) : null; - if (imageDataCompressed != null && (useFlateDecode || tryFlateDecode && imageDataCompressed.Length < imageBits.Length)) + byte[]? imageDataCompressed = useFlateDecode || tryFlateDecode ? fd.Encode(imageBits, Document.Options.FlateEncodeMode) : null; + if (imageDataCompressed != null && (useFlateDecode || (tryFlateDecode && imageDataCompressed.Length < imageBits.Length))) { Stream = new PdfStream(imageDataCompressed, this); - Elements["/Length"] = new PdfInteger(imageDataCompressed.Length); - PdfArray arrayFilters = new(_document); + Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length); + PdfArray arrayFilters = new(Document); arrayFilters.Elements.Add(new PdfName("/FlateDecode")); arrayFilters.Elements.Add(new PdfName("/DCTDecode")); - Elements["/Filter"] = arrayFilters; + Elements[PdfStream.Keys.Filter] = arrayFilters; } else { Stream = new PdfStream(imageBits, this); - Elements["/Length"] = new PdfInteger(streamLength); - Elements["/Filter"] = new PdfName("/DCTDecode"); + Elements.SetInteger(PdfStream.Keys.Length, streamLength); + Elements.SetName(PdfStream.Keys.Filter, "/DCTDecode"); } + // #PDF-A - if (_image.Interpolate && !_document.IsPdfA) + if (_image is { Interpolate: true } && !Document.IsPdfA) Elements[Keys.Interpolate] = PdfBoolean.True; - Elements[Keys.Width] = new PdfInteger(_image.PixelWidth); - Elements[Keys.Height] = new PdfInteger(_image.PixelHeight); + Elements[Keys.Width] = new PdfInteger(_image?.PixelWidth ?? ImportedImage!.PixelWidth); + Elements[Keys.Height] = new PdfInteger(_image?.PixelHeight ?? ImportedImage!.PixelHeight); Elements[Keys.BitsPerComponent] = new PdfInteger(8); #if CORE || GDI || WPF - if (_image._importedImage != null) + if (ImportedImage != null) { - if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGCMYK || - _image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGRGBW) + if (ImportedImage.ImageFormat == PdfSharp.Internal.Imaging.ImageFormats.JPEGCMYK || + ImportedImage.ImageFormat == PdfSharp.Internal.Imaging.ImageFormats.JPEGRGBW) { // TODO_OLD: Test with CMYK JPEG files (so far I only found ImageFlags.ColorSpaceYcck JPEG files ...) Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK"); - if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGRGBW) + if (ImportedImage.ImageFormat == PdfSharp.Internal.Imaging.ImageFormats.JPEGRGBW) Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors because YCCK is inverted CMYK. } - else if (_image._importedImage.Information.ImageFormat == ImageInformation.ImageFormats.JPEGGRAY) + else if (ImportedImage.ImageFormat == PdfSharp.Internal.Imaging.ImageFormats.JPEGGRAY) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } @@ -422,14 +305,15 @@ void InitializeJpeg(PdfDocumentOptions options) } #endif #if GDI - if (_image._importedImage == null) + if (_image != null && ImportedImage == null) { if ((_image._gdiImage.Flags & ((int)ImageFlags.ColorSpaceCmyk | (int)ImageFlags.ColorSpaceYcck)) != 0) { // TODO_OLD: Test with CMYK JPEG files (so far I only found ImageFlags.ColorSpaceYcck JPEG files ...) Elements[Keys.ColorSpace] = new PdfName("/DeviceCMYK"); if ((_image._gdiImage.Flags & (int)ImageFlags.ColorSpaceYcck) != 0) - Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors because YCCK is inverted CMYK. + //Elements["/Decode"] = new PdfLiteral("[1 0 1 0 1 0 1 0]"); // Invert colors because YCCK is inverted CMYK. #US309 + Elements["/Decode"] = new PdfArray(1, 0, 1, 0, 1, 0, 1, 0); // Invert colors because YCCK is inverted CMYK. } else if ((_image._gdiImage.Flags & (int)ImageFlags.ColorSpaceGray) != 0) { @@ -442,7 +326,7 @@ void InitializeJpeg(PdfDocumentOptions options) } #endif #if WPF - if (_image._importedImage == null) + if (_image != null && ImportedImage == null) { string pixelFormat = _image._wpfImage?.Format.ToString() ?? ""; bool isCmyk = _image.IsCmyk; @@ -468,45 +352,46 @@ void InitializeJpeg(PdfDocumentOptions options) /// /// Creates the keys for a FLATE image. /// - void InitializeNonJpeg(PdfDocumentOptions options) + void InitializeNonJpeg() { #if CORE || GDI || WPF - if (_image._importedImage != null) + if (ImportedImage != null) { - switch (_image._importedImage.Information.ImageFormat) + switch (ImportedImage.ImageFormat) { - case ImageInformation.ImageFormats.ARGB32: - CreateTrueColorMemoryBitmap(3, 8, true, options); + case PdfSharp.Internal.Imaging.ImageFormats.Bgra32: + CreateTrueColorMemoryBitmap(3, 8, true); break; - case ImageInformation.ImageFormats.RGB24: - CreateTrueColorMemoryBitmap(3, 8, false, options); + case PdfSharp.Internal.Imaging.ImageFormats.Bgr32: + CreateTrueColorMemoryBitmap(3, 8, false); break; - case ImageInformation.ImageFormats.Grayscale8: - CreateTrueColorMemoryBitmap(1, 8, false, options); + case PdfSharp.Internal.Imaging.ImageFormats.Gray8: + CreateTrueColorMemoryBitmap(1, 8, false); break; - case ImageInformation.ImageFormats.Palette8: - CreateIndexedMemoryBitmap(8, options); + case PdfSharp.Internal.Imaging.ImageFormats.Indexed8: + CreateIndexedMemoryBitmap(8); break; - case ImageInformation.ImageFormats.Palette4: - CreateIndexedMemoryBitmap(4, options); + case PdfSharp.Internal.Imaging.ImageFormats.Indexed4: + CreateIndexedMemoryBitmap(4); break; - case ImageInformation.ImageFormats.Palette1: - CreateIndexedMemoryBitmap(1, options); + case PdfSharp.Internal.Imaging.ImageFormats.Indexed1: + CreateIndexedMemoryBitmap(1); break; default: - throw new NotImplementedException("Image format not supported."); + throw new NotSupportedException("Image format not supported."); } + return; } #endif #if GDI && !WPF - switch (_image._gdiImage.PixelFormat) + switch (_image!._gdiImage.PixelFormat) { case PixelFormat.Format24bppRgb: ReadTrueColorMemoryBitmap(3, 8, false); @@ -537,11 +422,14 @@ void InitializeNonJpeg(PdfDocumentOptions options) //#if DEBUGxxx // image.image.Save("$$$.bmp", ImageFormat.Bmp); //#endif - throw new NotImplementedException("Image format not supported."); + throw new NotSupportedException("Image format not supported."); } #endif #if WPF - string format = _image._wpfImage.Format.ToString(); + // Only called for GDI and WPF. + // _image is not null here. + + string format = _image!._wpfImage.Format.ToString(); switch (format) { case "Bgr24": //Format24bppRgb: @@ -588,31 +476,41 @@ void InitializeNonJpeg(PdfDocumentOptions options) #if DEBUG_ image.image.Save("$$$.bmp", ImageFormat.Bmp); #endif - throw new NotImplementedException("Image format \"" + format + "\" not supported."); + throw new NotSupportedException("Image format '" + format + "' not supported."); } #endif } #if CORE || GDI || WPF - void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options) + void CreateIndexedMemoryBitmap(int bits) { - var idb = (ImageDataBitmap)_image._importedImage!.ImageData(options); - var ii = _image._importedImage.Information; + var ii = (ImportedRasterImage)ImportedImage!; + var ibi = ii as ImportedBmpImage; int pdfVersion = Owner.Version; - int firstMaskColor = -1, lastMaskColor = -1; + int firstMaskColor = ibi?.FirstMaskColor ?? -1, lastMaskColor = ibi?.LastMaskColor ?? -1; // TODO_OLD MaskColor transparency. - bool segmentedColorMask = idb.SegmentedColorMask; - bool hasAlphaMask = idb.AlphaMaskLength > 0; + bool segmentedColorMask = ibi?.SegmentedColorMask ?? false; + bool isGray = ibi?.IsGray ?? false; + int bitonal = ibi?.Bitonal ?? 0; + bool hasAlphaMask = ii.AlphaMaskData != null; + bool hasBitmapMask = ii.BitmapMaskData != null; + + bool isFaxEncoding = false; + byte[]? imageDataFax = null; + int k = 0; { FlateDecode fd = new FlateDecode(); + + // Color mask: currently disabled. + // Color mask. if (firstMaskColor != -1 && lastMaskColor != -1) { // Color mask requires Reader 4.0 or higher. - if (!segmentedColorMask && pdfVersion >= 13 && !idb.IsGray) + if (!segmentedColorMask && pdfVersion >= 13 && !isGray) { - PdfArray array = new PdfArray(_document); + PdfArray array = new PdfArray(Document); array.Elements.Add(new PdfInteger(firstMaskColor)); array.Elements.Add(new PdfInteger(lastMaskColor)); Elements[Keys.Mask] = array; @@ -620,96 +518,117 @@ void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options) else { // Monochrome mask. - byte[] maskDataCompressed = fd.Encode(idb.BitmapMask, _document.Options.FlateEncodeMode); - var pdfMask = new PdfDictionary(_document); + byte[] maskDataCompressed = fd.Encode(ii.GetPdfBitmapMaskData()!, Document.Options.FlateEncodeMode); + var pdfMask = new PdfDictionary(Document); pdfMask.Elements.SetName(Keys.Type, "/XObject"); pdfMask.Elements.SetName(Keys.Subtype, "/Image"); Owner.IrefTable.Add(pdfMask); pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask); - pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length); - pdfMask.Elements["/Filter"] = new PdfName("/FlateDecode"); - pdfMask.Elements[Keys.Width] = new PdfInteger((int)ii.Width); - pdfMask.Elements[Keys.Height] = new PdfInteger((int)ii.Height); - pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1); - pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true); - Elements[Keys.Mask] = pdfMask.Reference; + pdfMask.Elements.SetInteger(PdfStream.Keys.Length, maskDataCompressed.Length); + pdfMask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + pdfMask.Elements.SetInteger(Keys.Width, ii.PixelWidth); + pdfMask.Elements.SetInteger(Keys.Height, ii.PixelHeight); + pdfMask.Elements.SetInteger(Keys.BitsPerComponent, 1); + pdfMask.Elements.SetBoolean(Keys.ImageMask, true); + Elements[Keys.Mask] = pdfMask.RequiredReference; } } if (hasAlphaMask && pdfVersion >= 14) { // #PDF-A - if (_document.IsPdfA) + if (Document.IsPdfA) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Alpha mask of PdfImage suppressed."); } else { // The image provides an alpha mask (requires Arcrobat 5.0 or higher). - byte[] alphaMaskCompressed = fd.Encode(idb.AlphaMask, _document.Options.FlateEncodeMode); - var smask = new PdfDictionary(_document); + byte[] alphaMaskCompressed = fd.Encode(ii.GetPdfAlphaMaskData()!, Document.Options.FlateEncodeMode); + var smask = new PdfDictionary(Document); smask.Elements.SetName(Keys.Type, "/XObject"); smask.Elements.SetName(Keys.Subtype, "/Image"); Owner.IrefTable.Add(smask); smask.Stream = new PdfStream(alphaMaskCompressed, smask); - smask.Elements["/Length"] = new PdfInteger(alphaMaskCompressed.Length); - smask.Elements["/Filter"] = new PdfName("/FlateDecode"); - smask.Elements[Keys.Width] = new PdfInteger((int)ii.Width); - smask.Elements[Keys.Height] = new PdfInteger((int)ii.Height); - smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8); - smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); - Elements[Keys.SMask] = smask.Reference; + smask.Elements.SetInteger(PdfStream.Keys.Length, alphaMaskCompressed.Length); + smask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + smask.Elements.SetInteger(Keys.Width, ii.PixelWidth); + smask.Elements.SetInteger(Keys.Height, ii.PixelHeight); + smask.Elements.SetInteger(Keys.BitsPerComponent, 8); + smask.Elements.SetName(Keys.ColorSpace, "/DeviceGray"); + Elements[Keys.SMask] = smask.RequiredReference; } } - byte[] imageDataCompressed = fd.Encode(idb.Data, _document.Options.FlateEncodeMode); - byte[]? imageDataFaxCompressed = idb.DataFax != null ? fd.Encode(idb.DataFax, _document.Options.FlateEncodeMode) : null; + var imageBits = ii.ImageData; + var imageData = ii.GetPdfImageData()!; + + // If fax encoding is allowed, try if fax encoding reduces the size. + if (bits == 1 && Document.Options.EnableCcittCompressionForBilevelImages && + ibi != null) + { + // Only try Group 4 encoding. + // It seems that Group 3 2D encoding never beats both other encodings, therefore we don’t call it here. + + byte[] tempG4 = new byte[imageData.Length]; + int ccittSizeG4 = DoFaxEncodingGroup4(ref tempG4, imageBits, (uint)ibi.BytesFileOffset, (uint)ibi.PixelWidth, (uint)ibi.PixelHeight); + + isFaxEncoding = ccittSizeG4 > 0; + if (isFaxEncoding) + { + Array.Resize(ref tempG4, ccittSizeG4); + imageDataFax = tempG4; + k = -1; + } + } + byte[] imageDataCompressed = fd.Encode(imageData, Document.Options.FlateEncodeMode); + byte[]? imageDataFaxCompressed = imageDataFax != null ? fd.Encode(imageDataFax, Document.Options.FlateEncodeMode) : null; bool usesCcittEncoding = false; - if (idb.DataFax != null && imageDataFaxCompressed != null && - (idb.LengthFax < imageDataCompressed.Length || + if (imageDataFax != null && imageDataFaxCompressed != null && + (imageDataFax.Length < imageDataCompressed.Length || imageDataFaxCompressed.Length < imageDataCompressed.Length)) { // /CCITTFaxDecode creates the smaller file (with or without /FlateDecode). usesCcittEncoding = true; - if (idb.LengthFax < imageDataCompressed.Length) + if (imageDataFax.Length < imageDataCompressed.Length) { - Stream = new PdfStream(idb.DataFax, this); - Elements["/Length"] = new PdfInteger(idb.LengthFax); - Elements["/Filter"] = new PdfName("/CCITTFaxDecode"); + Stream = new PdfStream(imageDataFax, this); + Elements["/Length"] = new PdfInteger(imageDataFax.Length); + Elements[PdfStream.Keys.Filter] = new PdfName("/CCITTFaxDecode"); var dictionary = new PdfDictionary(); - if (idb.K != 0) - dictionary.Elements.Add("/K", new PdfInteger(idb.K)); - if (idb.IsBitonal < 0) + if (k != 0) + dictionary.Elements.Add("/K", new PdfInteger(k)); + if (bitonal < 0) dictionary.Elements.Add("/BlackIs1", new PdfBoolean(true)); dictionary.Elements.Add("/EndOfBlock", new PdfBoolean(false)); - dictionary.Elements.Add("/Columns", new PdfInteger((int)ii.Width)); - dictionary.Elements.Add("/Rows", new PdfInteger((int)ii.Height)); + dictionary.Elements.Add("/Columns", new PdfInteger(ii.PixelWidth)); + dictionary.Elements.Add("/Rows", new PdfInteger(ii.PixelHeight)); Elements[PdfStream.Keys.DecodeParms] = dictionary; } else { Stream = new PdfStream(imageDataFaxCompressed, this); Elements["/Length"] = new PdfInteger(imageDataFaxCompressed.Length); - var arrayFilters = new PdfArray(_document); + var arrayFilters = new PdfArray(Document); arrayFilters.Elements.Add(new PdfName("/FlateDecode")); arrayFilters.Elements.Add(new PdfName("/CCITTFaxDecode")); - Elements["/Filter"] = arrayFilters; - var arrayDecodeParms = new PdfArray(_document); + Elements[PdfStream.Keys.Filter] = arrayFilters; + var arrayDecodeParms = new PdfArray(Document); var dictFlateDecodeParms = new PdfDictionary(); var dictCcittFaxDecodeParms = new PdfDictionary(); - if (idb.K != 0) - dictCcittFaxDecodeParms.Elements.Add("/K", new PdfInteger(idb.K)); - if (idb.IsBitonal < 0) + if (k != 0) + dictCcittFaxDecodeParms.Elements.Add("/K", new PdfInteger(k)); + if (bitonal < 0) dictCcittFaxDecodeParms.Elements.Add("/BlackIs1", new PdfBoolean(true)); dictCcittFaxDecodeParms.Elements.Add("/EndOfBlock", new PdfBoolean(false)); - dictCcittFaxDecodeParms.Elements.Add("/Columns", new PdfInteger((int)ii.Width)); - dictCcittFaxDecodeParms.Elements.Add("/Rows", new PdfInteger((int)ii.Height)); + dictCcittFaxDecodeParms.Elements.Add("/Columns", new PdfInteger(ii.PixelWidth)); + dictCcittFaxDecodeParms.Elements.Add("/Rows", new PdfInteger(ii.PixelHeight)); arrayDecodeParms.Elements.Add(dictFlateDecodeParms); // How to add the "null object"? arrayDecodeParms.Elements.Add(dictCcittFaxDecodeParms); @@ -720,38 +639,39 @@ void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options) { // /FlateDecode creates the smaller file (or no monochrome bitmap). Stream = new PdfStream(imageDataCompressed, this); - Elements["/Length"] = new PdfInteger(imageDataCompressed.Length); - Elements["/Filter"] = new PdfName("/FlateDecode"); + Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length); + Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); } - Elements[Keys.Width] = new PdfInteger((int)ii.Width); - Elements[Keys.Height] = new PdfInteger((int)ii.Height); + Elements[Keys.Width] = new PdfInteger(ii.PixelWidth); + Elements[Keys.Height] = new PdfInteger(ii.PixelHeight); Elements[Keys.BitsPerComponent] = new PdfInteger(bits); // Anything needed for CMYK? Do we have sample images? // CCITT encoding: we need color palette for isBitonal == 0. // FlateDecode: we need color palette for isBitonal <= 0 unless we have grayscales. - if ((usesCcittEncoding && idb.IsBitonal == 0) || - (!usesCcittEncoding && idb.IsBitonal <= 0 && !idb.IsGray)) + if ((usesCcittEncoding && bitonal == 0) || + (!usesCcittEncoding && bitonal <= 0 && !isGray)) { - var colorPalette = new PdfDictionary(_document); - byte[]? packedPaletteData = idb.PaletteDataLength >= 48 ? fd.Encode(idb.PaletteData, _document.Options.FlateEncodeMode) : null; // Don’t compress small palettes. - if (packedPaletteData != null && packedPaletteData.Length + 20 < idb.PaletteDataLength) // +20: compensate for the overhead (estimated value). + var colorPalette = new PdfDictionary(Document); + var paletteData = ii.GetPdfPaletteData(); + byte[]? packedPaletteData = paletteData != null && paletteData!.Length >= 48 ? fd.Encode(paletteData, Document.Options.FlateEncodeMode) : null; // Don’t compress small palettes. + if (packedPaletteData != null && packedPaletteData.Length + 20 < paletteData!.Length) // +20: compensate for the overhead (estimated value). { // Create compressed color palette. colorPalette.CreateStream(packedPaletteData); - colorPalette.Elements["/Length"] = new PdfInteger(packedPaletteData.Length); - colorPalette.Elements["/Filter"] = new PdfName("/FlateDecode"); + colorPalette.Elements.SetInteger(PdfStream.Keys.Length, packedPaletteData.Length); + colorPalette.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); } else { // Create uncompressed color palette. - colorPalette.CreateStream(idb.PaletteData); - colorPalette.Elements["/Length"] = new PdfInteger(idb.PaletteDataLength); + colorPalette.CreateStream(paletteData!); + colorPalette.Elements.SetInteger(PdfStream.Keys.Length, paletteData!.Length); } Owner.IrefTable.Add(colorPalette); - var arrayColorSpace = new PdfArray(_document); + var arrayColorSpace = new PdfArray(Document); arrayColorSpace.Elements.Add(new PdfName("/Indexed")); arrayColorSpace.Elements.Add(new PdfName("/DeviceRGB")); arrayColorSpace.Elements.Add(new PdfInteger((int)ii.ColorsUsed - 1)); @@ -762,10 +682,11 @@ void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } - if (_image.Interpolate) + + if (_image is { Interpolate: true }) { // #PDF-A - if (_document.IsPdfA) + if (Document.IsPdfA) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Image interpolation suppressed."); } @@ -777,76 +698,78 @@ void CreateIndexedMemoryBitmap(int bits, PdfDocumentOptions options) } } - void CreateTrueColorMemoryBitmap(int components, int bits, bool hasAlpha, PdfDocumentOptions options) + void CreateTrueColorMemoryBitmap(int components, int bits, bool hasAlpha) { - // TODO_OLD Use hasAlpha? Or is ot superfluous? + // TODO_OLD Use hasAlpha? Or is it superfluous? int pdfVersion = Owner.Version; var fd = new FlateDecode(); - var idb = (ImageDataBitmap?)_image._importedImage?.ImageData(options) ?? NRT.ThrowOnNull(); - var ii = _image._importedImage?.Information ?? NRT.ThrowOnNull(); - bool hasMask = idb.AlphaMaskLength > 0 || idb.BitmapMaskLength > 0; - bool hasAlphaMask = idb.AlphaMaskLength > 0; + var ii = (ImportedRasterImage)ImportedImage!; + var ibi = ii as ImportedBmpImage; + bool hasAlphaMask = ii.AlphaMaskData != null; + bool hasBitmapMask = ii.BitmapMaskData != null; + bool hasMask = hasAlphaMask || hasBitmapMask; - if (hasMask && idb.BitmapMask != null!) + if (hasMask && hasBitmapMask) { // Monochrome mask is either sufficient or // provided for compatibility with older reader versions. - byte[] maskDataCompressed = fd.Encode(idb.BitmapMask, _document.Options.FlateEncodeMode); - var pdfMask = new PdfDictionary(_document); + byte[] maskDataCompressed = fd.Encode(ii.GetPdfBitmapMaskData()!, Document.Options.FlateEncodeMode); + var pdfMask = new PdfDictionary(Document); pdfMask.Elements.SetName(Keys.Type, "/XObject"); pdfMask.Elements.SetName(Keys.Subtype, "/Image"); Owner.IrefTable.Add(pdfMask); pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask); - pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length); - pdfMask.Elements["/Filter"] = new PdfName("/FlateDecode"); - pdfMask.Elements[Keys.Width] = new PdfInteger((int)ii.Width); - pdfMask.Elements[Keys.Height] = new PdfInteger((int)ii.Height); + pdfMask.Elements.SetInteger(PdfStream.Keys.Length, maskDataCompressed.Length); + pdfMask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + pdfMask.Elements[Keys.Width] = new PdfInteger(ii.PixelWidth); + pdfMask.Elements[Keys.Height] = new PdfInteger(ii.PixelHeight); pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1); pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true); - Elements[Keys.Mask] = pdfMask.Reference; + Elements[Keys.Mask] = pdfMask.RequiredReference; } if (hasMask && hasAlphaMask && pdfVersion >= 14) { // #PDF-A - if (_document.IsPdfA) + if (Document.IsPdfA) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Alpha mask of PdfImage suppressed."); } else { // The image provides an alpha mask (requires Acrobat 5.0 or higher). - byte[] alphaMaskCompressed = fd.Encode(idb.AlphaMask, _document.Options.FlateEncodeMode); - var smask = new PdfDictionary(_document); + byte[] alphaMaskCompressed = fd.Encode(ii.GetPdfAlphaMaskData()!, Document.Options.FlateEncodeMode); + var smask = new PdfDictionary(Document); smask.Elements.SetName(Keys.Type, "/XObject"); smask.Elements.SetName(Keys.Subtype, "/Image"); Owner.IrefTable.Add(smask); smask.Stream = new PdfStream(alphaMaskCompressed, smask); - smask.Elements["/Length"] = new PdfInteger(alphaMaskCompressed.Length); - smask.Elements["/Filter"] = new PdfName("/FlateDecode"); - smask.Elements[Keys.Width] = new PdfInteger((int)ii.Width); - smask.Elements[Keys.Height] = new PdfInteger((int)ii.Height); + smask.Elements.SetInteger(PdfStream.Keys.Length, alphaMaskCompressed.Length); + smask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + smask.Elements[Keys.Width] = new PdfInteger(ii.PixelWidth); + smask.Elements[Keys.Height] = new PdfInteger(ii.PixelHeight); smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8); smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); - Elements[Keys.SMask] = smask.Reference; + Elements[Keys.SMask] = smask.RequiredReference; } } - byte[] imageDataCompressed = fd.Encode(idb.Data, _document.Options.FlateEncodeMode); + byte[] imageDataCompressed = fd.Encode(ii.GetPdfImageData(), Document.Options.FlateEncodeMode); Stream = new PdfStream(imageDataCompressed, this); - Elements["/Length"] = new PdfInteger(imageDataCompressed.Length); - Elements["/Filter"] = new PdfName("/FlateDecode"); - Elements[Keys.Width] = new PdfInteger((int)ii.Width); - Elements[Keys.Height] = new PdfInteger((int)ii.Height); + Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length); + Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + Elements[Keys.Width] = new PdfInteger(ii.PixelWidth); + Elements[Keys.Height] = new PdfInteger(ii.PixelHeight); Elements[Keys.BitsPerComponent] = new PdfInteger(8); // Anything needed for CMYK? Do we have sample images? Elements[Keys.ColorSpace] = new PdfName(components == 1 ? "/DeviceGray" : "/DeviceRGB"); - if (_image.Interpolate) + + if (_image is { Interpolate: true }) { // #PDF-A - if (_document.IsPdfA) + if (Document.IsPdfA) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Image interpolation suppressed."); } @@ -888,20 +811,22 @@ static int ReadDWord(ReadOnlySpan bytes, int offset) /// true (ARGB), false (RGB) void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha) { + // Only called for GDI and WPF. + // _image is not null here. + //#if DEBUG_ // image.image.Save("$$$.bmp", ImageFormat.Bmp); //#endif int pdfVersion = Owner.Version; var memory = new MemoryStream(); #if GDI - _image._gdiImage.Save(memory, ImageFormat.Bmp); + _image!._gdiImage.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp); #endif #if WPF BmpBitmapEncoder encoder = new BmpBitmapEncoder(); - encoder.Frames.Add(BitmapFrame.Create(_image._wpfImage)); + encoder.Frames.Add(BitmapFrame.Create(_image!._wpfImage)); encoder.Save(memory); #endif - // Idea: Use ImageImporterBMP here to avoid redundant code. int streamLength = (int)memory.Length; Debug.Assert(streamLength > 0, "Bitmap image encoding failed."); @@ -909,7 +834,7 @@ void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha) { #if !WUI // Available with wrt, but not with wrt81. - // Note: imageBits.Length can be larger than streamLength. Do not use these extra bytes! + // Note that imageBits.Length can be larger than streamLength. Do not use these extra bytes. byte[] imageBits = memory.GetBuffer(); #else byte[] imageBits = new byte[streamLength]; @@ -918,152 +843,86 @@ void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha) memory.Dispose(); #endif - int height = _image.PixelHeight; - int width = _image.PixelWidth; - - // We could define structures for - // BITMAPFILEHEADER - // { BITMAPINFO } - // BITMAPINFOHEADER - // to avoid ReadWord and ReadDWord ... (but w/o pointers this doesn’t help much) - - if (ReadWord(imageBits, 0) != 0x4d42 || // "BM" - ReadDWord(imageBits, 2) != streamLength || - ReadDWord(imageBits, 14) != 40 || // sizeof BITMAPINFOHEADER - ReadDWord(imageBits, 18) != width || - ReadDWord(imageBits, 22) != height) - { - throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format"); - } - if (ReadWord(imageBits, 26) != 1 || - (!hasAlpha && ReadWord(imageBits, 28) != components * bits || - hasAlpha && ReadWord(imageBits, 28) != (components + 1) * bits) || - ReadDWord(imageBits, 30) != 0) - { - throw new NotImplementedException("ReadTrueColorMemoryBitmap: unsupported format #2"); - } - - int nFileOffset = ReadDWord(imageBits, 10); - int logicalComponents = components; - if (components == 4) - logicalComponents = 3; - - byte[] imageData = new byte[components * width * height]; + int height = _image!.PixelHeight; + int width = _image!.PixelWidth; - bool hasMask = false; - bool hasAlphaMask = false; - byte[]? alphaMask = hasAlpha ? new byte[width * height] : null; - MonochromeMask? mask = hasAlpha ? new MonochromeMask(width, height) : null; + // We use TryImportImageBmp here to avoid redundant code. - int nOffsetRead = 0; - if (logicalComponents == 3) - { - Debug.Assert(mask != null || (mask == null && !hasAlpha)); - for (int y = 0; y < height; ++y) - { - int nOffsetWrite = 3 * (height - 1 - y) * width; - int nOffsetWriteAlpha = 0; - if (hasAlpha) - { - Debug.Assert(mask != null); - mask.StartLine(y); - nOffsetWriteAlpha = (height - 1 - y) * width; - } + var ii = ToBeNamed.TryImportImageBmp(imageBits, out var image); + if (!ii) + throw new NotSupportedException("ReadTrueColorMemoryBitmap: unsupported format"); + var bmpImage = image as ImportedBmpImage; + if (bmpImage == null) + throw new NotSupportedException("ReadTrueColorMemoryBitmap: unsupported format"); - for (int x = 0; x < width; ++x) - { - imageData[nOffsetWrite] = imageBits[nFileOffset + nOffsetRead + 2]; - imageData[nOffsetWrite + 1] = imageBits[nFileOffset + nOffsetRead + 1]; - imageData[nOffsetWrite + 2] = imageBits[nFileOffset + nOffsetRead]; - if (hasAlpha && alphaMask != null) - { - Debug.Assert(mask != null); - mask.AddPel(imageBits[nFileOffset + nOffsetRead + 3]); - alphaMask[nOffsetWriteAlpha] = imageBits[nFileOffset + nOffsetRead + 3]; - if (!hasMask || !hasAlphaMask) - { - if (imageBits[nFileOffset + nOffsetRead + 3] != 255) - { - hasMask = true; - if (imageBits[nFileOffset + nOffsetRead + 3] != 0) - hasAlphaMask = true; - } - } - ++nOffsetWriteAlpha; - } - nOffsetRead += hasAlpha ? 4 : components; - nOffsetWrite += 3; - } - nOffsetRead = 4 * ((nOffsetRead + 3) / 4); // Align to 32 bit boundary. - } - } - else if (components == 1) - { - // Grayscale - throw new NotImplementedException("Image format not supported (grayscales)."); - } + var imageData = bmpImage!.GetPdfImageData(); + var hasAlphaMask = bmpImage.AlphaMaskData != null; + var hasMask = bmpImage.BitmapMaskData != null; + var alphaMask = bmpImage.GetPdfAlphaMaskData(); + var mask = bmpImage.GetPdfBitmapMask(); var fd = new FlateDecode(); if (hasMask && mask != null && mask.MaskUsed) { // Monochrome mask is either sufficient or // provided for compatibility with older reader versions. - byte[] maskDataCompressed = fd.Encode(mask.MaskData, _document.Options.FlateEncodeMode); - var pdfMask = new PdfDictionary(_document); + byte[] maskDataCompressed = fd.Encode(mask.MaskData, Document.Options.FlateEncodeMode); + var pdfMask = new PdfDictionary(Document); pdfMask.Elements.SetName(Keys.Type, "/XObject"); pdfMask.Elements.SetName(Keys.Subtype, "/Image"); Owner.IrefTable.Add(pdfMask); pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask); - pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length); - pdfMask.Elements["/Filter"] = new PdfName("/FlateDecode"); + pdfMask.Elements.SetInteger(PdfStream.Keys.Length, maskDataCompressed.Length); + pdfMask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); pdfMask.Elements[Keys.Width] = new PdfInteger(width); pdfMask.Elements[Keys.Height] = new PdfInteger(height); pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1); pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true); - Elements[Keys.Mask] = pdfMask.Reference; + Elements[Keys.Mask] = pdfMask.RequiredReference; } - if (hasMask && hasAlphaMask && pdfVersion >= 14 && alphaMask != null) + if (/*hasMask &&*/ hasAlphaMask && pdfVersion >= 14 && alphaMask != null) { // #PDF-A - if (_document.IsPdfA) + if (Document.IsPdfA) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Alpha mask of PdfImage suppressed."); } else { // The image provides an alpha mask (requires Acrobat 5.0 or higher). - byte[] alphaMaskCompressed = fd.Encode(alphaMask, _document.Options.FlateEncodeMode); - var smask = new PdfDictionary(_document); + byte[] alphaMaskCompressed = fd.Encode(alphaMask, Document.Options.FlateEncodeMode); + var smask = new PdfDictionary(Document); smask.Elements.SetName(Keys.Type, "/XObject"); smask.Elements.SetName(Keys.Subtype, "/Image"); Owner.IrefTable.Add(smask); smask.Stream = new PdfStream(alphaMaskCompressed, smask); - smask.Elements[PdfStream.Keys.Length] = new PdfInteger(alphaMaskCompressed.Length); - smask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode"); + smask.Elements.SetInteger(PdfStream.Keys.Length, alphaMaskCompressed.Length); + smask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); smask.Elements[Keys.Width] = new PdfInteger(width); smask.Elements[Keys.Height] = new PdfInteger(height); smask.Elements[Keys.BitsPerComponent] = new PdfInteger(8); smask.Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); - Elements[Keys.SMask] = smask.Reference; + Elements[Keys.SMask] = smask.RequiredReference; } } - byte[] imageDataCompressed = fd.Encode(imageData, _document.Options.FlateEncodeMode); + byte[] imageDataCompressed = fd.Encode(imageData, Document.Options.FlateEncodeMode); Stream = new PdfStream(imageDataCompressed, this); - Elements["/Length"] = new PdfInteger(imageDataCompressed.Length); - Elements["/Filter"] = new PdfName("/FlateDecode"); + Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length); + Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); Elements[Keys.Width] = new PdfInteger(width); Elements[Keys.Height] = new PdfInteger(height); Elements[Keys.BitsPerComponent] = new PdfInteger(8); // Anything needed for CMYK? Do we have sample images? Elements[Keys.ColorSpace] = new PdfName("/DeviceRGB"); + if (_image.Interpolate) { // #PDF-A - if (_document.IsPdfA) + if (Document.IsPdfA) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Image interpolation suppressed."); } @@ -1075,264 +934,93 @@ void ReadTrueColorMemoryBitmap(int components, int bits, bool hasAlpha) } } - /* BITMAPINFOHEADER struct and byte offsets: - typedef struct tagBITMAPINFOHEADER{ - DWORD biSize; // 14 - LONG biWidth; // 18 - LONG biHeight; // 22 - WORD biPlanes; // 26 - WORD biBitCount; // 28 - DWORD biCompression; // 30 - DWORD biSizeImage; // 34 - LONG biXPelsPerMeter; // 38 - LONG biYPelsPerMeter; // 42 - DWORD biClrUsed; // 46 - DWORD biClrImportant; // 50 - } BITMAPINFOHEADER, *PBITMAPINFOHEADER; - */ - void ReadIndexedMemoryBitmap(int bits) { + // Only called for GDI and WPF. + // _image is not null here. + int pdfVersion = Owner.Version; int firstMaskColor = -1, lastMaskColor = -1; bool segmentedColorMask = false; MemoryStream memory = new MemoryStream(); #if GDI - _image._gdiImage.Save(memory, ImageFormat.Bmp); + _image!._gdiImage.Save(memory, System.Drawing.Imaging.ImageFormat.Bmp); #endif #if WPF BmpBitmapEncoder encoder = new BmpBitmapEncoder(); //if (!_image._path.StartsWith("*", StringComparison.Ordinal)) // encoder.Frames.Add(BitmapFrame.Create(new Uri(_image._path), BitmapCreateOptions.PreservePixelFormat, BitmapCacheOption.OnLoad)); //else - encoder.Frames.Add(BitmapFrame.Create(_image._wpfImage)); + encoder.Frames.Add(BitmapFrame.Create(_image!._wpfImage)); encoder.Save(memory); #endif - // Idea: Use ImageImporterBMP here to avoid redundant code. - int streamLength = (int)memory.Length; Debug.Assert(streamLength > 0, "Bitmap image encoding failed."); if (streamLength > 0) { + int height = _image!.PixelHeight; + int width = _image!.PixelWidth; + byte[] imageBits = new byte[streamLength]; memory.Seek(0, SeekOrigin.Begin); - memory.Read(imageBits, 0, streamLength); + _ = memory.Read(imageBits, 0, streamLength); #if !WUI memory.Close(); #else memory.Dispose(); #endif - int height = _image.PixelHeight; - int width = _image.PixelWidth; - - if (ReadWord(imageBits, 0) != 0x4d42 || // "BM" - ReadDWord(imageBits, 2) != streamLength || - ReadDWord(imageBits, 14) != 40 || // sizeof BITMAPINFOHEADER -#if WPF - ReadDWord(imageBits, 18) != width || - ReadDWord(imageBits, 22) != height) -#else - ReadDWord(imageBits, 18) != width || - ReadDWord(imageBits, 22) != height) -#endif - { - throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format"); - } - - //#if WPF - // // These two lines should be superfluous, otherwise an exception would have been thrown above. - // width = ReadDWord(imageBits, 18); - // height = ReadDWord(imageBits, 22); - //#endif - - int fileBits = ReadWord(imageBits, 28); - if (fileBits != bits) - { - if (fileBits == 1 || fileBits == 4 || fileBits == 8) - bits = fileBits; - } - - if (ReadWord(imageBits, 26) != 1 || - ReadWord(imageBits, 28) != bits || - ReadDWord(imageBits, 30) != 0) - { - throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #2"); - } - - int bytesFileOffset = ReadDWord(imageBits, 10); - const int bytesColorPaletteOffset = 0x36; // GDI+ always returns Windows bitmaps: sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER - int paletteColors = ReadDWord(imageBits, 46); - if ((bytesFileOffset - bytesColorPaletteOffset) / 4 != paletteColors) - { - throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3"); - } - - MonochromeMask mask = new MonochromeMask(width, height); - - bool isGray = bits == 8 && (paletteColors == 256 || paletteColors == 0); - int isBitonal = 0; // 0: false; >0: true; <0: true (inverted). - byte[] paletteData = new byte[3 * paletteColors]; - for (int color = 0; color < paletteColors; ++color) - { - paletteData[3 * color] = imageBits[bytesColorPaletteOffset + 4 * color + 2]; - paletteData[3 * color + 1] = imageBits[bytesColorPaletteOffset + 4 * color + 1]; - paletteData[3 * color + 2] = imageBits[bytesColorPaletteOffset + 4 * color + 0]; - if (isGray) - isGray = paletteData[3 * color] == paletteData[3 * color + 1] && - paletteData[3 * color] == paletteData[3 * color + 2]; - - if (imageBits[bytesColorPaletteOffset + 4 * color + 3] < 128) - { - // We treat this as transparency. - if (firstMaskColor == -1) - firstMaskColor = color; - if (lastMaskColor == -1 || lastMaskColor == color - 1) - lastMaskColor = color; - if (lastMaskColor != color) - segmentedColorMask = true; - } - //else - //{ - // // We treat this as opacity. - //} - } - - if (bits == 1) - { - if (paletteColors == 0) - isBitonal = 1; - if (paletteColors == 2) - { - if (paletteData[0] == 0 && - paletteData[1] == 0 && - paletteData[2] == 0 && - paletteData[3] == 255 && - paletteData[4] == 255 && - paletteData[5] == 255) - isBitonal = 1; // Black on white - if (paletteData[5] == 0 && - paletteData[4] == 0 && - paletteData[3] == 0 && - paletteData[2] == 255 && - paletteData[1] == 255 && - paletteData[0] == 255) - isBitonal = -1; // White on black - } - } - - bool hasMask = firstMaskColor != -1 && lastMaskColor != -1; - - // NYI: (no sample found where this was required) - // if (segmentedColorMask = true) - // { ... } + // We use TryImportImageBmp here to avoid redundant code. bool isFaxEncoding = false; - byte[] imageData = new byte[((width * bits + 7) / 8) * height]; byte[]? imageDataFax = null; int k = 0; - // If fax encoding is allowed, try if fax encoding reduces the size. - if (bits == 1 && _document.Options.EnableCcittCompressionForBilevelImages) + var ii = ToBeNamed.TryImportImageBmp(imageBits, out var image); + if (!ii) + throw new NotSupportedException("ReadIndexedMemoryBitmap: unsupported format"); + var bmpImage = image as ImportedBmpImage; + if (bmpImage == null) + throw new NotSupportedException("ReadIndexedMemoryBitmap: unsupported format"); + if (bmpImage.PaletteData == null) + throw new NotSupportedException("ReadIndexedMemoryBitmap: unsupported format"); + + var imageData = bmpImage!.GetPdfImageData(); + var paletteData = bmpImage.GetPdfPaletteData()!; + var paletteColors = bmpImage.PaletteColors; + //var hasAlphaMask = bmpImage.AlphaMaskData != null; + var hasMask = bmpImage.BitmapMaskData != null; + //var alphaMask = bmpImage.GetPdfAlphaMaskData(); + var mask = bmpImage.GetPdfBitmapMask(); + var isGray = bmpImage.IsGray; + var bytesFileOffset = bmpImage.BytesFileOffset; + segmentedColorMask = bmpImage.SegmentedColorMask; + firstMaskColor = bmpImage.FirstMaskColor; + lastMaskColor = bmpImage.LastMaskColor; + hasMask |= firstMaskColor != -1 && lastMaskColor != -1; + var isBitonal = bmpImage.Bitonal; + if (bits != bmpImage.BitCount) { - // Future improvement: flag/option to select a specific format? - // Is: Only try Group 4 encoding. - - //// We try Group 3 1D and Group 4 (2D) encoding here and keep the smaller byte array. - ////byte[] temp = new byte[imageData.Length]; - ////int ccittSize = DoFaxEncoding(ref temp, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height); + if (bmpImage.BitCount == 1 || bmpImage.BitCount == 4 || bmpImage.BitCount == 8) + bits = bmpImage.BitCount; + } - //// It seems that Group 3 2D encoding never beats both other encodings, therefore we don’t call it here. - ////byte[] temp2D = new byte[imageData.Length]; - ////uint dpiY = (uint)image.VerticalResolution; - ////uint kTmp = 0; - ////int ccittSize2D = DoFaxEncoding2D((uint)bytesFileOffset, ref temp2D, imageBits, (uint)width, (uint)height, dpiY, out kTmp); - ////k = (int) kTmp; + // If fax encoding is allowed, try if fax encoding reduces the size. + if (bits == 1 && Document.Options.EnableCcittCompressionForBilevelImages) + { + // Only try Group 4 encoding. + // It seems that Group 3 2D encoding never beats both other encodings, therefore we don’t call it here. byte[] tempG4 = new byte[imageData.Length]; int ccittSizeG4 = DoFaxEncodingGroup4(ref tempG4, imageBits, (uint)bytesFileOffset, (uint)width, (uint)height); - isFaxEncoding = /*ccittSize > 0 ||*/ ccittSizeG4 > 0; + isFaxEncoding = ccittSizeG4 > 0; if (isFaxEncoding) { - //if (ccittSize == 0) - // ccittSize = 0x7fffffff; - if (ccittSizeG4 == 0) - ccittSizeG4 = 0x7fffffff; - //if (ccittSize <= ccittSizeG4) - //{ - // Array.Resize(ref temp, ccittSize); - // imageDataFax = temp; - // k = 0; - //} - //else - { - Array.Resize(ref tempG4, ccittSizeG4); - imageDataFax = tempG4; - k = -1; - } - } - } - - //if (hasMask) - { - int bytesOffsetRead = 0; - if (bits == 8 || bits == 4 || bits == 1) - { - int bytesPerLine = (width * bits + 7) / 8; - for (int y = 0; y < height; ++y) - { - mask.StartLine(y); - int bytesOffsetWrite = (height - 1 - y) * ((width * bits + 7) / 8); - for (int x = 0; x < bytesPerLine; ++x) - { - if (isGray) - { - // Lookup the gray value from the palette: - imageData[bytesOffsetWrite] = paletteData[3 * imageBits[bytesFileOffset + bytesOffsetRead]]; - } - else - { - // Store the palette index. - imageData[bytesOffsetWrite] = imageBits[bytesFileOffset + bytesOffsetRead]; - } - if (firstMaskColor != -1) - { - int n = imageBits[bytesFileOffset + bytesOffsetRead]; - if (bits == 8) - { - // TODO_OLD???: segmentedColorMask == true => bad mask NYI - mask.AddPel((n >= firstMaskColor) && (n <= lastMaskColor)); - } - else if (bits == 4) - { - // TODO_OLD???: segmentedColorMask == true => bad mask NYI - int n1 = (n & 0xf0) / 16; - int n2 = (n & 0x0f); - mask.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor)); - mask.AddPel((n2 >= firstMaskColor) && (n2 <= lastMaskColor)); - } - else if (bits == 1) - { - // TODO_OLD???: segmentedColorMask == true => bad mask NYI - for (int bit = 1; bit <= 8; ++bit) - { - int n1 = (n & 0x80) / 128; - mask.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor)); - n *= 2; - } - } - } - bytesOffsetRead += 1; - bytesOffsetWrite += 1; - } - bytesOffsetRead = 4 * ((bytesOffsetRead + 3) / 4); // Align to 32 bit boundary. - } - } - else - { - throw new NotImplementedException("ReadIndexedMemoryBitmap: unsupported format #3"); + Array.Resize(ref tempG4, ccittSizeG4); + imageDataFax = tempG4; + k = -1; } } @@ -1342,7 +1030,7 @@ void ReadIndexedMemoryBitmap(int bits) // Color mask requires Reader 4.0 or higher. if (!segmentedColorMask && pdfVersion >= 13 && !isGray) { - PdfArray array = new PdfArray(_document); + PdfArray array = new PdfArray(Document); array.Elements.Add(new PdfInteger(firstMaskColor)); array.Elements.Add(new PdfInteger(lastMaskColor)); Elements[Keys.Mask] = array; @@ -1350,28 +1038,28 @@ void ReadIndexedMemoryBitmap(int bits) else { // Monochrome mask. - byte[] maskDataCompressed = flateDecode.Encode(mask.MaskData, _document.Options.FlateEncodeMode); - var pdfMask = new PdfDictionary(_document); + byte[] maskDataCompressed = flateDecode.Encode(mask!.MaskData, Document.Options.FlateEncodeMode); + var pdfMask = new PdfDictionary(Document); pdfMask.Elements.SetName(Keys.Type, "/XObject"); pdfMask.Elements.SetName(Keys.Subtype, "/Image"); Owner.IrefTable.Add(pdfMask); pdfMask.Stream = new PdfStream(maskDataCompressed, pdfMask); //pdfMask.Elements["/Length"] = new PdfInteger(maskDataCompressed.Length); - pdfMask.Elements[PdfStream.Keys.Length] = new PdfInteger(maskDataCompressed.Length); - pdfMask.Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode"); + pdfMask.Elements.SetInteger(PdfStream.Keys.Length, maskDataCompressed.Length); + pdfMask.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); pdfMask.Elements[Keys.Width] = new PdfInteger(width); pdfMask.Elements[Keys.Height] = new PdfInteger(height); pdfMask.Elements[Keys.BitsPerComponent] = new PdfInteger(1); pdfMask.Elements[Keys.ImageMask] = new PdfBoolean(true); - Elements[Keys.Mask] = pdfMask.Reference; + Elements[Keys.Mask] = pdfMask.RequiredReference; } } Debug.Assert((isFaxEncoding && imageDataFax != null) || (!isFaxEncoding && imageDataFax == null)); - byte[] imageDataCompressed = flateDecode.Encode(imageData, _document.Options.FlateEncodeMode); - byte[]? imageDataFaxCompressed = isFaxEncoding ? flateDecode.Encode(imageDataFax!, _document.Options.FlateEncodeMode) : null; + byte[] imageDataCompressed = flateDecode.Encode(imageData, Document.Options.FlateEncodeMode); + byte[]? imageDataFaxCompressed = isFaxEncoding ? flateDecode.Encode(imageDataFax!, Document.Options.FlateEncodeMode) : null; bool usesCcittEncoding = false; if (isFaxEncoding && @@ -1404,11 +1092,11 @@ void ReadIndexedMemoryBitmap(int bits) Stream = new PdfStream(imageDataFaxCompressed, this); Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataFaxCompressed.Length); - var arrayFilters = new PdfArray(_document); + var arrayFilters = new PdfArray(Document); arrayFilters.Elements.Add(new PdfName("/FlateDecode")); arrayFilters.Elements.Add(new PdfName("/CCITTFaxDecode")); Elements[PdfStream.Keys.Filter] = arrayFilters; - var arrayDecodeParms = new PdfArray(_document); + var arrayDecodeParms = new PdfArray(Document); var dictFlateDecodeParms = new PdfDictionary(); var dictCcittFaxDecodeParms = new PdfDictionary(); @@ -1429,8 +1117,8 @@ void ReadIndexedMemoryBitmap(int bits) { // /FlateDecode creates the smaller file (or no monochrome bitmap). Stream = new PdfStream(imageDataCompressed, this); - Elements[PdfStream.Keys.Length] = new PdfInteger(imageDataCompressed.Length); - Elements[PdfStream.Keys.Filter] = new PdfName("/FlateDecode"); + Elements.SetInteger(PdfStream.Keys.Length, imageDataCompressed.Length); + Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); } Elements[Keys.Width] = new PdfInteger(width); @@ -1443,14 +1131,14 @@ void ReadIndexedMemoryBitmap(int bits) if ((usesCcittEncoding && isBitonal == 0) || (!usesCcittEncoding && isBitonal <= 0 && !isGray)) { - var colorPalette = new PdfDictionary(_document); - byte[]? packedPaletteData = paletteData.Length >= 48 ? flateDecode.Encode(paletteData, _document.Options.FlateEncodeMode) : null; // Don’t compress small palettes. + var colorPalette = new PdfDictionary(Document); + byte[]? packedPaletteData = paletteData.Length >= 48 ? flateDecode.Encode(paletteData, Document.Options.FlateEncodeMode) : null; // Don’t compress small palettes. if (packedPaletteData != null && packedPaletteData.Length + 20 < paletteData.Length) // +20: compensate for the overhead (estimated value). { // Create compressed color palette. colorPalette.CreateStream(packedPaletteData); - colorPalette.Elements[PdfStream.Keys.Length] = new PdfInteger(packedPaletteData.Length); - colorPalette.Elements["/Filter"] = new PdfName("/FlateDecode"); + colorPalette.Elements.SetInteger(PdfStream.Keys.Length, packedPaletteData.Length); + colorPalette.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); } else { @@ -1460,7 +1148,7 @@ void ReadIndexedMemoryBitmap(int bits) } Owner.IrefTable.Add(colorPalette); - var arrayColorSpace = new PdfArray(_document); + var arrayColorSpace = new PdfArray(Document); arrayColorSpace.Elements.Add(new PdfName("/Indexed")); arrayColorSpace.Elements.Add(new PdfName("/DeviceRGB")); arrayColorSpace.Elements.Add(new PdfInteger(paletteColors - 1)); @@ -1471,10 +1159,11 @@ void ReadIndexedMemoryBitmap(int bits) { Elements[Keys.ColorSpace] = new PdfName("/DeviceGray"); } + if (_image.Interpolate) { // #PDF-A - if (_document.IsPdfA) + if (Document.IsPdfA) { PdfSharpLogHost.Logger.LogWarning("PDF/A: Image interpolation suppressed."); } @@ -1673,92 +1362,4 @@ void ReadIndexedMemoryBitmap(int bits) // ReSharper restore InconsistentNaming } } - - /// - /// Helper class for creating bitmap masks (8 pels per byte). - /// - class MonochromeMask - { - /// - /// Returns the bitmap mask that will be written to PDF. - /// - public byte[] MaskData => _maskData; - - readonly byte[] _maskData; - - /// - /// Indicates whether the mask has transparent pels. - /// - public bool MaskUsed => _used; - - /// - /// Creates a bitmap mask. - /// - public MonochromeMask(int sizeX, int sizeY) - { - _sizeX = sizeX; - _sizeY = sizeY; - int byteSize = ((sizeX + 7) / 8) * sizeY; - _maskData = new byte[byteSize]; - StartLine(0); - } - - /// - /// Starts a new line. - /// - public void StartLine(int newCurrentLine) - { - _bitsWritten = 0; - _byteBuffer = 0; - _writeOffset = ((_sizeX + 7) / 8) * (_sizeY - 1 - newCurrentLine); - } - - /// - /// Adds a pel to the current line. - /// - /// - public void AddPel(bool isTransparent) - { - if (_bitsWritten < _sizeX) - { - // Mask: 0: opaque, 1: transparent (default mapping) - if (isTransparent) - { - _byteBuffer = (_byteBuffer << 1) + 1; - _used = true; - } - else - _byteBuffer = _byteBuffer << 1; - ++_bitsWritten; - if ((_bitsWritten & 7) == 0) - { - _maskData[_writeOffset] = (byte)_byteBuffer; - ++_writeOffset; - _byteBuffer = 0; - } - else if (_bitsWritten == _sizeX) - { - int n = 8 - (_bitsWritten & 7); - _byteBuffer = _byteBuffer << n; - _maskData[_writeOffset] = (byte)_byteBuffer; - } - } - } - - /// - /// Adds a pel from an alpha mask value. - /// - public void AddPel(int shade) - { - // NYI: dithering. - AddPel(shade < 128); - } - - readonly int _sizeX; - readonly int _sizeY; - int _writeOffset; - int _byteBuffer; - int _bitsWritten; - bool _used; - } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs index e9ef262e..c3e3afdb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImageTable.cs @@ -1,9 +1,11 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using PdfSharp.Drawing; -using PdfSharp.Drawing.Internal; using System.Runtime.InteropServices; +using PdfSharp.Drawing; +#if CORE +using PdfSharp.Internal.Imaging; +#endif using System.Security.Cryptography; using System.Text; @@ -43,6 +45,23 @@ public PdfImage GetImage(XImage image) return pdfImage; } +#if CORE + public PdfImage GetImage(ImportedImage importedImage) + { + byte[] bytes = importedImage.ImageData; + + var selector = new ImageSelector(bytes); + + if (!_images.TryGetValue(selector, out var pdfImage)) + { + pdfImage = new(Owner, importedImage); + Debug.Assert(pdfImage.Owner == Owner); + _images[selector] = pdfImage; + } + return pdfImage; + } +#endif + /// /// Map from ImageSelector to PdfImage. /// @@ -64,39 +83,14 @@ public ImageSelector(XImage image, PdfDocumentOptions options) // 3. Otherwise, create a GUID. // TODO_OLD: Create hashes also for other image sources. var selector = image._path; -#if true if (image._path == null! || image._importedImage != null) { if (image._importedImage != null) { - var iid = image._importedImage.ImageData(options); - if (iid is ImageDataDct jpeg) - { - var hashCreator = GetHashCreator(); - var hash = hashCreator.ComputeHash(jpeg.Data, 0, jpeg.Length); - selector = GetHashSelectorPrefix(image) + HashToString(hash); - } - else if (iid is ImageDataBitmap bmp) - { - var hashCreator = GetHashCreator(); - var hash = hashCreator.ComputeHash(bmp.Data, 0, bmp.Length); - if (bmp.AlphaMask != null! && bmp.AlphaMaskLength > 0) - { - var hash2 = hashCreator.ComputeHash(bmp.AlphaMask, 0, bmp.AlphaMaskLength); - selector = GetHashSelectorPrefix(image) + HashToString(hash) + HashToString(hash2); - } - else if (bmp.BitmapMask != null! && bmp.BitmapMaskLength > 0) - { - var hash2 = hashCreator.ComputeHash(bmp.BitmapMask, 0, bmp.BitmapMaskLength); - selector = GetHashSelectorPrefix(image) + HashToString(hash) + HashToString(hash2); - } - else - { - selector = GetHashSelectorPrefix(image) + HashToString(hash); - } - } - else - selector = "*" + Guid.NewGuid().ToString("B"); + var iid2 = image._importedImage.ImageData; + var hashCreator = GetHashCreator(); + var hash = hashCreator.ComputeHash(iid2, 0, iid2.Length); + selector = GetHashSelectorPrefix(image) + HashToString(hash); } else if (image._stream != null!) { @@ -156,19 +150,27 @@ public ImageSelector(XImage image, PdfDocumentOptions options) // Path must be set. Leading '*' indicates pseudo-paths. if (image._path == null!) image._path = Path; -#else - // HACK_OLD: implement a way to identify images when they are reused - if (image._path == null!) - image._path = "*" + Guid.NewGuid().ToString("B"); -#endif + + } + + ///// + ///// Initializes a new instance of ImageSelector from an XImage. + ///// + public ImageSelector(byte[] bytes) + { + var hashCreator = GetHashCreator(); + var hash = hashCreator.ComputeHash(bytes, 0, bytes.Length); + var selector = HashToString(hash); + + Path = GetHashSelectorPrefix() + selector; } /// - /// Creates an instance of HashAlgorithm für use in ImageSelector. + /// Creates an instance of HashAlgorithm for use in ImageSelector. /// - private HashAlgorithm GetHashCreator() + HashAlgorithm GetHashCreator() { - // Note: Beginning with PDFsharp 6.2.0 Preview 2, we use SHA1 here. + // Note hat beginning with PDFsharp 6.2.0 Preview 2, we use SHA1 here. // Earlier versions used MD5. // MD5 is deprecated and not FIPS compliant, so we decided to avoid MD5 here. // SHA1 is not deprecated and similarly efficient as MD5. @@ -182,12 +184,21 @@ private HashAlgorithm GetHashCreator() /// Image selectors that are no path names start with an asterisk. /// We combine image dimensions with the hashcode of the image bits to reduce chance af ambiguity. /// - private string GetHashSelectorPrefix(XImage image) + string GetHashSelectorPrefix(XImage image) { return "*hash:" + image.PixelWidth + ':' + image.PixelHeight + ':'; } - private String HashToString(Byte[] hash) + /// + /// Image selectors that are no path names start with an asterisk. + /// We combine image dimensions with the hashcode of the image bits to reduce chance af ambiguity. + /// + string GetHashSelectorPrefix() + { + return "*hash:"; + } + + string HashToString(byte[] hash) { StringBuilder sb = new StringBuilder(); for (int i = 0; i < hash.Length; i++) @@ -197,7 +208,7 @@ private String HashToString(Byte[] hash) return sb.ToString(); } - public string Path { get; } + public string Path { get; } // string Selection Key public override bool Equals(object? obj) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImportedObjectTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImportedObjectTable.cs index 26ca154f..92b9af25 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImportedObjectTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfImportedObjectTable.cs @@ -7,7 +7,7 @@ namespace PdfSharp.Pdf.Advanced /// Represents the imported objects of an external document. Used to cache objects that are /// already imported when a PdfFormXObject is added to a page. /// - sealed class PdfImportedObjectTable + sealed class PdfImportedObjectTable // TODO review { /// /// Initializes a new instance of this class with the document the objects are imported from. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNameDictionary.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNameDictionary.cs index 760122f3..1f819d4d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNameDictionary.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNameDictionary.cs @@ -1,10 +1,13 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; +using PdfSharp.Pdf.Attachments; + namespace PdfSharp.Pdf.Advanced { /// - /// Represents the name dictionary. + /// Represents the name dictionary of the catalog. /// public sealed class PdfNameDictionary : PdfDictionary { @@ -12,16 +15,25 @@ public sealed class PdfNameDictionary : PdfDictionary /// Initializes a new instance of the class. /// public PdfNameDictionary(PdfDocument document) - : base(document) - { } + : base(document, true) + { + // Is always an indirect object. + //document.Internals.AddObject(this); + } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfNameDictionary(PdfDictionary dictionary) : base(dictionary) { var dests = Elements.GetDictionary(Keys.Dests); if (dests != null) { - _dests = new PdfNameTreeNode(dests); + if (dests is not PdfNameTreeNode dests2) + dests2 = new PdfNameTreeNode(dests); + _dests = dests2; } } @@ -59,31 +71,44 @@ internal void AddNamedDestination(string destinationName, int destinationPage, P _dests.AddName(destinationName, destinationDict.Reference); #endif } - PdfNameTreeNode? _dests; - internal void AddEmbeddedFile(string name, Stream stream) - { - if (_embeddedFiles == null) - { - _embeddedFiles = new PdfNameTreeNode(); - Owner.Internals.AddObject(_embeddedFiles); - Elements.SetReference(Keys.EmbeddedFiles, _embeddedFiles.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull()); - } + //// TODO: Remove + //internal void AddEmbeddedFile(string name, Stream stream, out PdfFileSpecification fileSpecification, string? subType = null) + //{ + // var embeddedFiles = GetEmbeddedFiles(); + // if (embeddedFiles == null) + // { + // // Create a direct object. + // embeddedFiles = new PdfEmbeddedFiles(); + // Elements.Add(Keys.EmbeddedFiles, embeddedFiles); + // } - var embeddedFileStream = new PdfEmbeddedFileStream(Owner, stream); - var fileSpecification = new PdfFileSpecification(Owner, embeddedFileStream, name); - Owner.Internals.AddObject(fileSpecification); + // var embeddedFileStream = new PdfEmbeddedFileStream(Owner, stream, subType); + // fileSpecification = new PdfFileSpecification(Owner, embeddedFileStream, name); + // Owner.Internals.AddObject(fileSpecification); - _embeddedFiles.AddName(name, fileSpecification.ReferenceNotNull); - } + // embeddedFiles.AddName(name, fileSpecification.RequiredReference); + //} + + internal bool HasEmbeddedFiles + => Elements.TryGetValue(Keys.EmbeddedFiles, out _); - PdfNameTreeNode? _embeddedFiles; + /// + /// Get or created... TODO + /// + [return: NotNullIfNotNull(nameof(create))] + internal PdfEmbeddedFiles? GetEmbeddedFiles(bool create = false) // TODO: null or not null? => Use NotNullIfNotNullAttribute + { + var ef = Elements.GetRequiredDictionary(Keys.EmbeddedFiles, + create ? VCF.Create : VCF.None); + return ef; + } /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { // ReSharper disable InconsistentNaming @@ -139,7 +164,7 @@ internal sealed class Keys : KeysBase /// (Optional; PDF 1.4) A name tree mapping name strings to file specifications for embedded file streams /// (see Section 3.10.3, “Embedded File Streams”). ///
- [KeyInfo("1.4", KeyType.NameTree | KeyType.Optional)] + [KeyInfo("1.4", KeyType.NameTree | KeyType.Optional, typeof(PdfEmbeddedFiles))] public const string EmbeddedFiles = "/EmbeddedFiles"; ///// @@ -157,7 +182,19 @@ internal sealed class Keys : KeysBase //public const string Renditions = "/Renditions"; // ReSharper restore InconsistentNaming + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinationParameters.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinationParameters.cs index 1f06400b..045fedf1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinationParameters.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinationParameters.cs @@ -20,7 +20,7 @@ public class PdfNamedDestinationParameters static PdfNamedDestinationParameters CreateXYZ(double? left, double? top, double? zoom) { - return new PdfNamedDestinationParameters(Format("/XYZ {0} {1} {2}", left, top, zoom)); + return new(Format("/XYZ {0} {1} {2}", left, top, zoom)); } /// @@ -170,7 +170,6 @@ static string Format(string format, params double?[] values) { objValues[i] = values[i] ?? (object)"null"; } - return PdfEncoders.Format(format, objValues); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinations.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinations.cs index 6e6f809a..53a36cd3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinations.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfNamedDestinations.cs @@ -2,7 +2,6 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Pdf.Actions; -using System.Xml.Linq; namespace PdfSharp.Pdf.Advanced { @@ -12,13 +11,15 @@ namespace PdfSharp.Pdf.Advanced public sealed class PdfNamedDestinations : PdfDictionary { internal PdfNamedDestinations() - { - } + { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfNamedDestinations(PdfDictionary dict) : base(dict) - { - } + { } /// /// Gets all the destination names. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs index 63ae5adb..a4ed1a33 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfObjectStream.cs @@ -30,16 +30,28 @@ public PdfObjectStream(PdfDocument document) } /// - /// Initializes a new instance from an existing dictionary. Used for object type transformation. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfObjectStream(PdfDictionary dict) + : base(dict) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// internal PdfObjectStream(PdfDictionary dict, Parser documentParser) : base(dict) { int n = Elements.GetInteger(Keys.N); int first = Elements.GetInteger(Keys.First); - Stream.TryUncompress(); + Stream?.TryUncompress(); + + if (Stream == null) + throw new InvalidOperationException("PdfObjectStream has no stream. Unexpected error."); - var parser = new Parser(_document, new MemoryStream(Stream.UnfilteredValue), documentParser); + var parser = new Parser(base.Document, new MemoryStream(Stream.UnfilteredValue), documentParser); _header = parser.ReadObjectStreamHeader(n, first); #if DEBUG_ && CORE diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPageInheritableObjects.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPageInheritableObjects.cs index 48c88455..ae4bae66 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPageInheritableObjects.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPageInheritableObjects.cs @@ -5,6 +5,7 @@ namespace PdfSharp.Pdf.Advanced { +#if true_ /// /// Represents a PDF page object. /// @@ -23,14 +24,14 @@ public PdfRectangle MediaBox get => _mediaBox; set => _mediaBox = value; } - PdfRectangle _mediaBox = default!; + PdfRectangle _mediaBox = null!; public PdfRectangle CropBox { get => _cropBox; set => _cropBox = value; } - PdfRectangle _cropBox = default!; + PdfRectangle _cropBox = null!; public int Rotate { @@ -44,4 +45,5 @@ public int Rotate } int _rotate; } +#endif } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPlaceholder.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPlaceholder.cs new file mode 100644 index 00000000..9130bd33 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfPlaceholder.cs @@ -0,0 +1,96 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Internal; +using PdfSharp.Pdf.IO; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Advanced +{ + /// + /// Represents text that is written ‘as it is’ into the PDF stream. + /// Using this class can lead to invalid PDF files. + /// E.g. strings in a literal are not encrypted when the document is saved with a password. + /// + sealed class PdfPlaceholder(int length, char chPlaceholder = '?') : PdfPrimitive + { + public void SetValue(string value) + { + EnsureLength(value.Length); + Value = value; + _isEffectiveValueSet = true; + } + + public void SetValue(byte[] bytes) + { + EnsureLength(bytes.Length); + Value = PdfEncoders.RawEncoding.GetString(bytes); + _isEffectiveValueSet = true; + } + + /// + /// Gets the number of bytes of the signature. + /// + public int Length { get; init; } = length; + + /// + /// Gets the value as literal string. + /// + public string Value { get; private set; } = new(chPlaceholder, length); + + /// + /// Returns the current value. + /// + public override string ToString() => Value; + + /// + /// Writes the placeholder item. + /// + internal override void WriteObject(PdfWriter writer) + => writer.Write(this, out _startPosition, out _endPosition); + + /// + /// Writes the effective value at the placeholder position. + /// + public void WriteEffectiveValue(PdfWriter writer) + { + if (!_isEffectiveValueSet) + throw new InvalidOperationException("PdfPlaceholder cannot write the effective value because it is not set."); + + // Save current writer position. + var oldPosition = writer.Position; + + // Write Value at StartPosition. + Debug.Assert(Value.Length == Length); + writer.Stream.Position = StartPosition; + writer.WriteRaw(Value); + + // Restore old writer position. + writer.Stream.Position = oldPosition; + } + + /// + /// Position of the first byte of this item in PdfWriter’s stream. + /// + public SizeType StartPosition => _startPosition; + SizeType _startPosition; + + /// + /// Position of the last byte of this item in PdfWriter’s stream. + /// + public SizeType EndPosition => _endPosition; + SizeType _endPosition; + + bool _isEffectiveValueSet; + + void EnsureLength(int length) + { + if (Length != length) + { + throw new InvalidOperationException( + Invariant($"The length of the item must be '{Length}', but it is '{length}'.")); + } + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs index 7240a8b5..e4327e93 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs @@ -2,12 +2,15 @@ // See the LICENSE file in the solution root for more information. // With this define each iref object gets a unique number (uid) to make them distinguishable in the debugger. -#define UNIQUE_IREF_ +#define UNIQUE_IREF_xxx // TODO Move to Directory.Build.targets using Microsoft.Extensions.Logging; using PdfSharp.Logging; using PdfSharp.Pdf.IO; +// v7.0.0 TODO review MakeDocument and proxy objects + + namespace PdfSharp.Pdf.Advanced { /// @@ -16,30 +19,15 @@ namespace PdfSharp.Pdf.Advanced [DebuggerDisplay("iref({ObjectNumber}, {GenerationNumber})")] public sealed class PdfReference : PdfItem { - // About PdfReference - // - // * A PdfReference holds either the ObjectID or the PdfObject or both. - // - // * Each PdfObject has a PdfReference if and only if it is an indirect object. Direct objects have - // no PdfReference, because they are embedded in a parent object. - // - // * PdfReference objects are used to reference PdfObject instances. A value in a PDF dictionary - // or array that is a PdfReference represents an indirect reference. A value in a PDF dictionary - // or array that is a PdfObject represents a direct (or embedded) object. - // - // * When a PDF file is imported, the PdfXRefTable is filled with PdfReference objects keeping the - // ObjectsIDs and file positions (offsets) of all indirect objects. - // - // * Indirect objects can easily be renumbered because they do not rely on their ObjectsIDs. - // - // * During modification of a document the ObjectID of an indirect object has no meaning, - // except that they must be different in pairs. - /// /// Initializes a new PdfReference instance for the specified indirect object. /// An indirect PDF object has one and only one reference. /// You cannot create an instance of PdfReference. /// + /// A direct PDF object. + /// The object ID. Can be undefined. + /// The position in the PDF file stream, or -1, if unknown. + /// PdfReference(PdfObject pdfObject, PdfObjectID objectID, SizeType position) { if (pdfObject.Reference != null) @@ -50,19 +38,14 @@ public sealed class PdfReference : PdfItem if (position != 0) _position = position; - //else - //{ - // // Can be 0 or -1. - // //_ = typeof(int); - // //PdfSharpLogHost.DocumentProcessingLogger.LogError("Position is 0."); - //} #if UNIQUE_IREF && DEBUG _uid = ++s_counter; #endif } /// - /// Initializes a new PdfReference instance from the specified object identifier and file position. + /// Initializes a new instance of the class from the specified + /// object identifier and file position. /// PdfReference(PdfObjectID objectID, SizeType position) { @@ -73,15 +56,45 @@ public sealed class PdfReference : PdfItem #endif } + /// + /// Initializes a new instance of the class for a proxy container. + /// Used for objects that represents two different types, e.g. a PDF dictionary that is both + /// an interactive field and a widget annotation. + /// + /// The dominant container which is in the IRefTable. + /// The proxy container which will use the Elements of the master. + internal PdfReference(PdfContainer cont, PdfContainer proxy) + : base(cont) + { + Debug.Assert(cont.IsIndirect, "The dominant container must be an indirect object."); + Debug.Assert(!proxy.IsIndirect, "The proxy container must be a direct object."); + + // DELETE ItemFlags = ItemFlags.IsProxyReference; + + var xref = cont.RequiredReference; + _document = xref.Document; + _objectID = xref.ObjectID; + _position = xref.Position; + _value = proxy; + + // Make proxy an indirect object that only exists in memory. + proxy.Reference = this; + } + /// /// Creates a PdfReference from a PdfObject. /// - /// - /// - /// + /// A direct PDF object. + /// The object ID. Can be undefined. + /// The position in the PDF file stream, or -1, if unknown. /// internal static PdfReference CreateFromObject(PdfObject pdfObject, PdfObjectID objectID, SizeType position) - => new(pdfObject, objectID, position); + { +#if TEST_CODE + Debug.Assert(pdfObject.ObjectID.IsEmpty || pdfObject.ObjectID == objectID); // TODO Remove objectID in parameters? +#endif + return new(pdfObject, objectID, position); + } /// /// Creates a PdfReference from a PdfObjectID. @@ -90,7 +103,9 @@ internal static PdfReference CreateFromObject(PdfObject pdfObject, PdfObjectID o /// /// internal static PdfReference CreateForObjectID(PdfObjectID objectID, SizeType position) - => new(objectID, position); + { + return new(objectID, position); + } /// /// Writes the object in PDF iref table format. @@ -99,7 +114,7 @@ internal void WriteXRefEntry(PdfWriter writer) { // PDFsharp does not yet support PDF 1.5 object streams for writing. - // Each line must be exactly 20 bytes long, otherwise Acrobat repairs the file. + // Each line must be exactly 20 bytes long, otherwise Acrobat wants to repair the file. writer.WriteRaw(Invariant($"{_position:0000000000} {_objectID.GenerationNumber:00000} n \n")); } @@ -118,22 +133,8 @@ internal override void WriteObject(PdfWriter writer) public PdfObjectID ObjectID { get => _objectID; - set - { - _objectID = value; -#if true_ - if (Document != null) - { - //PdfXRefTable table = Document.xrefTable; - //table.Remove(this); - //objectID = value; - //table.Add(this); - } -#endif - } + internal set => _objectID = value; } - - // ReSharper disable once InconsistentNaming PdfObjectID _objectID; /// @@ -163,6 +164,9 @@ internal set /// /// Gets or sets the referenced PdfObject. /// + /// + /// The returned value can be null during the reading of a PDF file. + /// public PdfObject Value { get => _value; @@ -170,8 +174,19 @@ internal set { Debug.Assert(value != null, "The value of a PdfReference must never be null."); Debug.Assert(value.Reference == null || ReferenceEquals(value.Reference, this), "The reference of the value must be null or this."); - _value = value; + // value must never be null. + if (value == null!) + throw new ArgumentNullException(nameof(value), "value must not be null."); + + _value = value; +#if DEBUG + if (value.Reference != null) + { + _ = typeof(int); + Debug.Assert(ReferenceEquals(value.Reference, this)); + } +#endif value.Reference = this; } } @@ -183,7 +198,7 @@ internal set internal void ResetObject() => _value = null!; /// - /// Gets or sets the document this object belongs to. + /// Gets the document this object belongs to. /// public PdfDocument Document { @@ -191,13 +206,11 @@ public PdfDocument Document { #if DEBUG if (_document == null) - { PdfSharpLogHost.Logger.LogDebug("Document of object {_objectID} is null.", _objectID); - } #endif return _document!; } - set => _document = value; + internal set => _document = value; } PdfDocument? _document; @@ -206,24 +219,58 @@ public PdfDocument Document /// public override string ToString() => _objectID + " R"; + /// + /// Returns true if the specified item is an indirect object; false otherwise. + /// + public static bool IsIndirect(PdfItem item) // PDFsharp/NT + => item is PdfReference or PdfObject { Reference: not null }; + /// /// Dereferences the specified item. If the item is a PdfReference, the item is set /// to the referenced value. Otherwise, no action is taken. /// - public static void Dereference(ref object item) + public static void Dereference(ref PdfItem item) // Do not change to 'ref PdfItem?'. { if (item is PdfReference reference) item = reference.Value; } /// - /// Dereferences the specified item. If the item is a PdfReference, the item is set - /// to the referenced value. Otherwise, no action is taken. + /// If the specified item is an indirect object it is replaced by its PdfReference. + /// Otherwise, no action is taken. /// - public static void Dereference(ref PdfItem item) + internal static void ToReference(ref PdfItem item) { - if (item is PdfReference reference) - item = reference.Value; + if (item is PdfObject { Reference: not null } obj) + item = obj.RequiredReference; + } + + /// + /// Makes a PDF object an indirect object of the specified document + /// and returns a reference to it. + /// + /// A newly created object. + /// The PDF document the object is added as indirect object. + public static PdfReference MakeIndirect(PdfObject obj, PdfDocument doc) // PDFsharp/NT + { + if (obj == null) + throw new ArgumentNullException(nameof(obj)); + //ArgumentNullException.ThrowIfNull(obj); + //ArgumentNullExceptionEx.ThrowIfNull(obj); + + if (doc == null) + throw new ArgumentNullException(nameof(doc)); + + if (obj.IsIndirect) + throw new InvalidOperationException("Object is already an indirect object."); + + if (obj.Owner != null! && obj.Owner != doc) + throw new InvalidOperationException("Object already has an owner that is not the specified document."); + + doc.Internals.AddObject(obj); + + Debug.Assert(obj.Reference != null); + return obj.Reference; } internal static PdfReferenceComparer Comparer => new(); @@ -238,13 +285,46 @@ public int Compare(PdfReference? l, PdfReference? r) if (l != null) { if (r != null) - return l._objectID.CompareTo(r._objectID); + { + if (l.ObjectNumber == r.ObjectNumber) + return l.GenerationNumber - r.GenerationNumber; + return l.ObjectNumber - r.ObjectNumber; + } return -1; } return r != null ? 1 : 0; } } + internal void SetTemp() => ItemFlags |= ItemFlags.IsTempRef; + + internal void ClearTemp() => ItemFlags &= ~ItemFlags.IsTempRef; + + internal bool IsTemp() => (ItemFlags & ItemFlags.IsTempRef) != 0; + + /// + /// Not yet used. + /// + internal int AddRef() // TODO: Use it for all indirect objects. + { + return Interlocked.Increment(ref _refCount); + } + + /// + /// Not yet used. + /// + internal int Release() + { + return Interlocked.Decrement(ref _refCount); + } + + /// + /// Gets the reference count of this PdfReference. + /// + internal int RefCounter => _refCount; + + int _refCount; + #if UNIQUE_IREF && DEBUG static int s_counter = 0; int _uid; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResourceMap.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResourceMap.cs index ccaebd82..61703184 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResourceMap.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResourceMap.cs @@ -1,14 +1,14 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Collections.Generic; +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Advanced { /// /// Base class for all dictionaries that map resource names to objects. /// - class PdfResourceMap : PdfDictionary + public class PdfResourceMap : PdfDictionary { public PdfResourceMap() { } @@ -21,16 +21,16 @@ protected PdfResourceMap(PdfDictionary dict) : base(dict) { } - // public int Count - // { - // get {return resources.Count;} - // } + // public int Count + // { + // get {return resources.Count;} + // } // - // public PdfObject this[string key] - // { - // get {return resources[key] as PdfObject;} - // set {resources[key] = value;} - // } + // public PdfObject this[string key] + // { + // get {return resources[key] as PdfObject;} + // set {resources[key] = value;} + // } /// /// Adds all imported resource names to the specified hashtable. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResources.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResources.cs index c2a077a7..5f61b66f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResources.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfResources.cs @@ -22,9 +22,20 @@ public sealed class PdfResources : PdfDictionary public PdfResources(PdfDocument document) : base(document) { - Elements[Keys.ProcSet] = new PdfLiteral("[/PDF/Text/ImageB/ImageC/ImageI]"); + //Elements[Keys.ProcSet] = new PdfLiteral("[/PDF/Text/ImageB/ImageC/ImageI]"); + // #US373 begin Must be a PdfArray. + Elements[Keys.ProcSet] = new PdfArray(new PdfName("/PDF"), + new PdfName("/Text"), + new PdfName("/ImageB"), + new PdfName("/ImageC"), + new PdfName("/ImageI")); + // #US373 end } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfResources(PdfDictionary dict) : base(dict) { } @@ -40,7 +51,7 @@ public string AddFont(PdfFont font) _resources[font] = name; if (font.Reference == null) Owner.IrefTable.Add(font); - Fonts.Elements[name] = font.Reference; + Fonts.Elements[name] = font.RequiredReference; } return name; } @@ -57,7 +68,7 @@ public string AddImage(PdfImage image) _resources[image] = name; if (image.Reference == null) Owner.IrefTable.Add(image); - XObjects.Elements[name] = image.Reference; + XObjects.Elements[name] = image.RequiredReference; } return name; } @@ -74,7 +85,7 @@ public string AddForm(PdfFormXObject form) _resources[form] = name; if (form.Reference == null) Owner.IrefTable.Add(form); - XObjects.Elements[name] = form.Reference; + XObjects.Elements[name] = form.RequiredReference; } return name; } @@ -91,7 +102,7 @@ public string AddExtGState(PdfExtGState extGState) _resources[extGState] = name; if (extGState.Reference == null) Owner.IrefTable.Add(extGState); - ExtGStates.Elements[name] = extGState.Reference; + ExtGStates.Elements[name] = extGState.RequiredReference; } return name; } @@ -108,7 +119,7 @@ public string AddPattern(PdfShadingPattern pattern) _resources[pattern] = name; if (pattern.Reference == null) Owner.IrefTable.Add(pattern); - Patterns.Elements[name] = pattern.Reference; + Patterns.Elements[name] = pattern.RequiredReference; } return name; } @@ -125,7 +136,7 @@ public string AddPattern(PdfTilingPattern pattern) _resources[pattern] = name; if (pattern.Reference == null) Owner.IrefTable.Add(pattern); - Patterns.Elements[name] = pattern.Reference; + Patterns.Elements[name] = pattern.RequiredReference; } return name; } @@ -142,7 +153,7 @@ public string AddShading(PdfShading shading) _resources[shading] = name; if (shading.Reference == null) Owner.IrefTable.Add(shading); - Shadings.Elements[name] = shading.Reference; + Shadings.Elements[name] = shading.RequiredReference; } return name; } @@ -293,25 +304,46 @@ internal bool ExistsResourceName(string name) { _importedResourceNames = new(); - if (Elements[Keys.Font] != null) + //if (Elements[Keys.Font] != null) // TODO #US373 Just a null check. + // Fonts.CollectResourceNames(_importedResourceNames); + + //if (Elements[Keys.XObject] != null) // TODO #US373 Just a null check. + // XObjects.CollectResourceNames(_importedResourceNames); + + //if (Elements[Keys.ExtGState] != null) // TODO #US373 Just a null check. + // ExtGStates.CollectResourceNames(_importedResourceNames); + + //if (Elements[Keys.ColorSpace] != null) // TODO #US373 Just a null check. + // ColorSpaces.CollectResourceNames(_importedResourceNames); + + //if (Elements[Keys.Pattern] != null) // TODO #US373 Just a null check. + // Patterns.CollectResourceNames(_importedResourceNames); + + //if (Elements[Keys.Shading] != null) // TODO #US373 Just a null check. + // Shadings.CollectResourceNames(_importedResourceNames); + + //if (Elements[Keys.Properties] != null) // TODO #US373 Just a null check. + // Properties.CollectResourceNames(_importedResourceNames); + + if (Elements.HasValue(Keys.Font)) // #US373 Fonts.CollectResourceNames(_importedResourceNames); - if (Elements[Keys.XObject] != null) + if (Elements.HasValue(Keys.XObject)) // #US373 XObjects.CollectResourceNames(_importedResourceNames); - if (Elements[Keys.ExtGState] != null) + if (Elements.HasValue(Keys.ExtGState)) // #US373 ExtGStates.CollectResourceNames(_importedResourceNames); - if (Elements[Keys.ColorSpace] != null) + if (Elements.HasValue(Keys.ColorSpace)) // #US373 ColorSpaces.CollectResourceNames(_importedResourceNames); - if (Elements[Keys.Pattern] != null) + if (Elements.HasValue(Keys.Pattern)) // #US373 Patterns.CollectResourceNames(_importedResourceNames); - if (Elements[Keys.Shading] != null) + if (Elements.HasValue(Keys.Shading)) // #US373 Shadings.CollectResourceNames(_importedResourceNames); - if (Elements[Keys.Properties] != null) + if (Elements.HasValue(Keys.Properties)) // #US373 Properties.CollectResourceNames(_importedResourceNames); } return _importedResourceNames.ContainsKey(name); @@ -390,7 +422,6 @@ public sealed class Keys : KeysBase /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShading.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShading.cs index 496597c6..94a951dc 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShading.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShading.cs @@ -26,6 +26,14 @@ public PdfShading(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfShading(PdfDictionary dict) + : base(dict) + { } + /// /// Setups the shading from the specified brush. /// @@ -34,7 +42,7 @@ internal void SetupFromBrush(XLinearGradientBrush brush, XGraphicsPdfRenderer re if (brush == null) throw new ArgumentNullException(nameof(brush)); - PdfColorMode colorMode = _document.Options.ColorMode; + PdfColorMode colorMode = Document.Options.ColorMode; XColor color1 = ColorSpaceHelper.EnsureColorMode(colorMode, brush._color1); XColor color2 = ColorSpaceHelper.EnsureColorMode(colorMode, brush._color2); @@ -115,7 +123,7 @@ internal void SetupFromBrush(XLinearGradientBrush brush, XGraphicsPdfRenderer re /// /// Common keys for all streams. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { /// /// (Required) The shading type: diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShadingPattern.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShadingPattern.cs index f45b2264..dd2f85bc 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShadingPattern.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfShadingPattern.cs @@ -29,6 +29,17 @@ public PdfShadingPattern(PdfDocument document) Elements[Keys.PatternType] = new PdfInteger(2); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfShadingPattern(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.Type, "/Pattern"); + Elements[Keys.PatternType] = new PdfInteger(2); + } + /// /// Setups the shading pattern from the specified brush. /// @@ -37,7 +48,8 @@ internal void SetupFromBrush(XLinearGradientBrush brush, XMatrix matrix, XGraphi if (brush == null) throw new ArgumentNullException(nameof(brush)); - PdfShading shading = new PdfShading(_document); + Debug.Assert(ReferenceEquals(_document2, Document)); + PdfShading shading = new PdfShading(Document); shading.SetupFromBrush(brush, renderer); Elements[Keys.Shading] = shading; //Elements[Keys.Matrix] = new PdfLiteral("[" + PdfEncoders.ToString(matrix) + "]"); @@ -89,7 +101,6 @@ internal void SetupFromBrush(XLinearGradientBrush brush, XMatrix matrix, XGraphi /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs index bd5dbe95..913457af 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfSoftMask.cs @@ -26,6 +26,16 @@ public PdfSoftMask(PdfDocument document) Elements.SetName(Keys.Type, "/Mask"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfSoftMask(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.Type, "/Mask"); + } + /// /// Predefined keys of this dictionary. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTilingPattern.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTilingPattern.cs index 2af54d94..6d3dc26b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTilingPattern.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTilingPattern.cs @@ -26,6 +26,17 @@ public PdfTilingPattern(PdfDocument document) Elements[Keys.PatternType] = new PdfInteger(1); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfTilingPattern(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.Type, "/Pattern"); + Elements[Keys.PatternType] = new PdfInteger(1); + } + ///// ///// Setups the shading pattern from the specified brush. ///// @@ -134,7 +145,6 @@ public PdfTilingPattern(PdfDocument document) /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs index 928b384a..bbe2f0f6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs @@ -7,27 +7,39 @@ using PdfSharp.Logging; using PdfSharp.Pdf.Filters; +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + namespace PdfSharp.Pdf.Advanced { /// /// Represents a ToUnicode map for composite font. /// - sealed class PdfToUnicodeMap : PdfDictionary + public sealed class PdfToUnicodeMap : PdfDictionary { - public PdfToUnicodeMap(PdfDocument document) - : base(document) - { } + //public PdfToUnicodeMap(PdfDocument document) + // : base(document) + //{ } - public PdfToUnicodeMap(PdfDocument document, CMapInfo cmapInfo) + internal PdfToUnicodeMap(PdfDocument document, CMapInfo cmapInfo) : base(document) { CMapInfo = cmapInfo; } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfToUnicodeMap(PdfDictionary dict) + : base(dict) + { + CMapInfo = null!; + } + /// /// Gets or sets the CMap info. /// - public CMapInfo CMapInfo { get; set; } = default!; + internal CMapInfo CMapInfo { get; set; } /// /// Creates the ToUnicode map from the CMapInfo. @@ -41,7 +53,7 @@ internal override void PrepareForSave() "/CIDInit /ProcSet findresource begin\n" + "12 dict begin\n" + "begincmap\n" + - "/CIDSystemInfo << /Registry (Adobe)/Ordering (UCS)/Supplement 0>> def\n" + + "/CIDSystemInfo <> def\n" + "/CMapName /Adobe-Identity-UCS def /CMapType 2 def\n"; string suffix = "endcmap CMapName currentdict /CMap defineresource pop end end"; @@ -98,12 +110,13 @@ internal override void PrepareForSave() if (Owner.Options.CompressContentStreams) { - Elements.SetName("/Filter", "/FlateDecode"); - bytes = Filtering.FlateDecode.Encode(bytes, _document.Options.FlateEncodeMode); + Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + Debug.Assert(ReferenceEquals(_document2, Document)); + bytes = Filtering.FlateDecode.Encode(bytes, Document.Options.FlateEncodeMode); } else { - Elements.Remove("/Filter"); + Elements.Remove(PdfStream.Keys.Filter); } if (Stream == null!) @@ -111,7 +124,7 @@ internal override void PrepareForSave() else { Stream.Value = bytes; - Elements.SetInteger("/Length", Stream.Length); + Elements.SetInteger(PdfStream.Keys.Length, Stream.Length); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrailer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrailer.cs index 1c57bada..5bb06fa8 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrailer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrailer.cs @@ -1,35 +1,54 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; +using PdfSharp.Pdf.Internal; using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Security; -using PdfSharp.Pdf.Internal; + +// v7.0.0 Ready + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Advanced { /// - /// Represents a PDF trailer dictionary. Even though trailers are dictionaries they never have a cross - /// reference entry in PdfReferenceTable. + /// Represents a PDF trailer dictionary. Even though trailers are dictionaries they never have a + /// cross-reference entry in PdfReferenceTable. /// // Reference: 3.4.4 File Trailer / Page 96 - class PdfTrailer : PdfDictionary + public class PdfTrailer : PdfDictionary { + // Reference 2.0: 7.5.5 File trailer / Page 58 + // - and - + // Reference 2.0: 12.7.8.2.4 FDF trailer / Page 558 + /// - /// Initializes a new instance of PdfTrailer. + /// Initializes a new instance of the class from a PdfDocument. /// public PdfTrailer(PdfDocument document) : base(document) { - _document = document; + Debug.Assert(ReferenceEquals(_document2, Document)); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfTrailer(PdfDictionary dict) + : base(dict) + { } + /// /// Initializes a new instance of the class from a . /// public PdfTrailer(PdfCrossReferenceStream trailer) - : base(trailer._document) + : base(trailer.Document) { - _document = trailer._document; + // ReSharper disable once VirtualMemberCallInConstructor + Debug.Assert(ReferenceEquals(_document2, trailer.Document)); + Debug.Assert(ReferenceEquals(Document, trailer._document2)); SecurityHandlerInternal = trailer.SecurityHandlerInternal; // /ID [<09F877EBF282E9408ED1882A9A21D9F2><2A4938E896006F499AC1C2EA7BFB08E4>] @@ -47,7 +66,11 @@ public PdfTrailer(PdfCrossReferenceStream trailer) var id = trailer.Elements.GetArray(Keys.ID); if (id != null) + { + // TODO: Check what happened here: Why found we this bug so late? + id = id.Clone(); Elements.SetValue(Keys.ID, id); + } } public int Size @@ -56,35 +79,35 @@ public int Size set => Elements.SetInteger(Keys.Size, value); } - //public int Prev needed when linearized.. - //{ - // get {return Elements.GetInteger(Keys.Prev);} - //} - - public PdfDocumentInformation Info => (PdfDocumentInformation)Elements.GetValue(Keys.Info, VCF.CreateIndirect)!; // Because of CreateIndirect. + public PdfDocumentInformation Info => Elements.GetRequiredValue(Keys.Info, VCF.CreateIndirect); /// /// (Required; must be an indirect reference) /// The catalog dictionary for the PDF document contained in the file. /// - public PdfCatalog Root => (PdfCatalog)Elements.GetValue(PdfTrailer.Keys.Root, VCF.CreateIndirect)!; // Because of CreateIndirect. + public PdfCatalog Root => Elements.GetRequiredValue(PdfTrailer.Keys.Root, VCF.CreateIndirect); /// /// Gets the first or second document identifier. + /// The index must be 0 or 1. /// public string GetDocumentID(int index) { if (index is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be 0 or 1."); - var array = Elements[Keys.ID] as PdfArray; - if (array == null || array.Elements.Count < 2) + //var array = Elements[Keys.ID] as PdfArray; + //if (array == null || array.Elements.Count < 2) + + //if (Elements[Keys.ID] is not PdfArray array || array.Elements.Count < 2) + if (Elements.GetArray(Keys.ID) is not { } array || array.Elements.Count < 2) // #US373 return ""; -#if true - var item = array.Elements[index] as PdfString; - if (item != null) + + //var item = array.Elements[index] as PdfString; + //if (item != null) + if (array.Elements[index] is PdfString item) { - // The DocumentID is just a hex string, never represents unicode content. + // The DocumentID is just a hex string, never represents Unicode content. // Add FEFF if it was truncated from the ID. Unlikely, but can happen. if ((item.Flags & PdfStringFlags.Unicode) != 0) { @@ -93,11 +116,6 @@ public string GetDocumentID(int index) } return item.Value; } -#else - var item = array.Elements[index]; - if (item is PdfString pdfString) - return pdfString.Value; -#endif return ""; } @@ -109,8 +127,11 @@ public void SetDocumentID(int index, string value) if (index is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(index), index, "Index must be 0 or 1."); - var array = Elements[Keys.ID] as PdfArray; - if (array == null || array.Elements.Count < 2) + //var array = Elements[Keys.ID] as PdfArray; + //if (array == null || array.Elements.Count < 2) + + //if (Elements[Keys.ID] is not PdfArray array || array.Elements.Count < 2) + if (Elements.GetArray(Keys.ID) is not { } array || array.Elements.Count < 2) // #US373 array = CreateNewDocumentIDs(); array.Elements[index] = new PdfString(value, PdfStringFlags.HexLiteral); } @@ -120,11 +141,13 @@ public void SetDocumentID(int index, string value) /// internal PdfArray CreateNewDocumentIDs() { - var array = new PdfArray(_document); + Debug.Assert(ReferenceEquals(_document2, Document)); + var array = new PdfArray(Document); byte[] docID = Guid.NewGuid().ToByteArray(); string id = PdfEncoders.RawEncoding.GetString(docID, 0, docID.Length); - array.Elements.Add(new PdfString(id, PdfStringFlags.HexLiteral)); - array.Elements.Add(new PdfString(id, PdfStringFlags.HexLiteral)); + var str = new PdfString(id, PdfStringFlags.HexLiteral); + array.Elements.Add(str); + array.Elements.Add(str); Elements[Keys.ID] = array; return array; } @@ -153,13 +176,14 @@ public PdfStandardSecurityHandler SecurityHandler internal override void WriteObject(PdfWriter writer) { // Delete /XRefStm entry, if any. - // HACK_OLD: - _elements?.Remove(Keys.XRefStm); + // HACK_OLD: TODO When can this happen?? + Elements.Remove(Keys.XRefStm); // Don’t encrypt myself. var effectiveSecurityHandler = writer.EffectiveSecurityHandler; writer.EffectiveSecurityHandler = null; base.WriteObject(writer); + writer.NewLine(); writer.EffectiveSecurityHandler = effectiveSecurityHandler; } @@ -169,79 +193,92 @@ internal override void WriteObject(PdfWriter writer) internal void Finish() { PdfReference? iref; + // /Root - var currentTrailer = _document.Trailer; + var currentTrailer = Document.Trailer; + Debug.Assert(ReferenceEquals(_document2, Document)); do { - iref = currentTrailer.Elements[Keys.Root] as PdfReference; + //iref = currentTrailer.Elements[Keys.Root] as PdfReference; + iref = currentTrailer.Elements.GetReference(Keys.Root); // TODO #US373 //if (iref != null && iref.Value == null) if (iref is { Value: null }) { - iref = _document.IrefTable[iref.ObjectID]; + iref = Document.IrefTable[iref.ObjectID]; Debug.Assert(iref is not null && iref.Value != null); - _document.Trailer.Elements[Keys.Root] = iref; + currentTrailer.Elements[Keys.Root] = iref; } currentTrailer = currentTrailer.PreviousTrailer; } while (currentTrailer != null); // /Info - iref = _document.Trailer.Elements[Keys.Info] as PdfReference; + //iref = Document.Trailer.Elements[Keys.Info] as PdfReference; + iref = Document.Trailer.Elements.GetReference(Keys.Info); // TODO #US373 if (iref is { Value: null }) { - iref = _document.IrefTable[iref.ObjectID]; - Debug.Assert(iref is not null && iref.Value != null); - _document.Trailer.Elements[Keys.Info] = iref; + iref = Document.IrefTable[iref.ObjectID]; + Debug.Assert(iref?.Value != null); + Document.Trailer.Elements[Keys.Info] = iref; } // /Encrypt - iref = _document.Trailer.Elements[Keys.Encrypt] as PdfReference; + //iref = Document.Trailer.Elements[Keys.Encrypt] as PdfReference; + iref = Document.Trailer.Elements.GetReference(Keys.Encrypt); // TODO #US373 if (iref != null) { - iref = _document.IrefTable[iref.ObjectID]; - Debug.Assert(iref?.Value != null); - _document.Trailer.Elements[Keys.Encrypt] = iref; + if (iref.Value == null!) + { + iref = Document.IrefTable[iref.ObjectID]; + Debug.Assert(iref?.Value != null); + Document.Trailer.Elements[Keys.Encrypt] = iref; + } // The encryption dictionary (security handler) was read in before the XRefTable construction // was completed. The next lines fix that state (it took several hours to find these bugs...). - var securityHandler = _document.Trailer.SecurityHandlerInternal!; + var securityHandler = Document.Trailer.SecurityHandlerInternal!; securityHandler.Reference = null; // Reference will be updated new when setting iref.Value. iref.Value = securityHandler; } Elements.Remove(Keys.Prev); - Debug.Assert(_document.IrefTable.IsUnderConstruction == false); - _document.IrefTable.IsUnderConstruction = false; + Debug.Assert(!Document.IrefTable.IsUnderConstruction); + Document.IrefTable.IsUnderConstruction = false; } /// /// Predefined keys of this dictionary. /// - internal class Keys : KeysBase // Reference: TABLE 3.13 Entries in the file trailer dictionary / Page 97 + public class Keys : KeysBase { + // Reference 2.0: Table 15 — Entries in the file trailer dictionary / Page 58 + // - and - + // Reference 2.0: Table 19 — Additional entries in a hybrid-reference file’s trailer dictionary / Page 58 + // - and - + // Reference 2.0: Table 244 — Entry in the FDF trailer dictionary / Page 558 + /// - /// (Required; must not be an indirect reference) The total number of entries in the file’s - /// cross-reference table, as defined by the combination of the original section and all - /// update sections. Equivalently, this value is 1 greater than the highest object number - /// used in the file. - /// Note: Any object in a cross-reference section whose number is greater than this value is - /// ignored and considered missing. + /// (Required; shall not be an indirect reference) The total number of entries in the PDF + /// file’s cross-reference table, as defined by the combination of the original section + /// and all update sections. Equivalently, this value shall be 1 greater than the highest + /// object number defined in the PDF file.
+ /// Any object in a cross-reference section whose number is greater than this value shall + /// be ignored and defined to be missing by a PDF reader. ///
[KeyInfo(KeyType.Integer | KeyType.Required)] public const string Size = "/Size"; /// - /// (Present only if the file has more than one cross-reference section; must not be an indirect - /// reference) The byte offset from the beginning of the file to the beginning of the previous - /// cross-reference section. + ///(Optional, present only if the file has more than one cross-reference section; shall + /// be a direct object) The byte offset from the beginning of the PDF file to the beginning + /// of the previous cross-reference stream. /// [KeyInfo(KeyType.Integer | KeyType.Optional)] public const string Prev = "/Prev"; /// - /// (Required; must be an indirect reference) The catalog dictionary for the PDF document - /// contained in the file. + /// (Required; shall be an indirect reference) The catalog dictionary object for this FDF file. /// [KeyInfo(KeyType.Dictionary | KeyType.Required, typeof(PdfCatalog))] public const string Root = "/Root"; @@ -253,22 +290,41 @@ internal class Keys : KeysBase // Reference: TABLE 3.13 Entries in the file tr public const string Encrypt = "/Encrypt"; /// - /// (Optional; must be an indirect reference) The document’s information dictionary. + /// (Optional; shall be an indirect reference) The PDF file’s information dictionary. + /// As described in "Document information dictionary", this method for specifying document + /// metadata has been deprecated in PDF 2.0 and should therefore only be used to encode + /// information that is stated as required elsewhere in this document.
+ /// NOTE 1
+ /// The ModDate key within the Info dictionary is required if Page-Piece dictionaries(see 14.5, "Page-piece dictionaries") are used. ///
[KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(PdfDocumentInformation))] public const string Info = "/Info"; /// - /// (Optional, but strongly recommended; PDF 1.1) An array of two strings constituting - /// a file identifier for the file. Although this entry is optional, - /// its absence might prevent the file from functioning in some workflows - /// that depend on files being uniquely identified. + /// (Required in PDF 2.0 and later, or if an Encrypt entry is present; optional otherwise; + /// PDF 1.1) An array of two byte-strings constituting a PDF file identifier for the PDF file. + /// Each PDF file identifier byte-string shall have a minimum length of 16 bytes. If there + /// is an Encrypt entry, this array and the two byte-strings shall be direct objects and + /// shall be unencrypted.
+ /// NOTE 2
+ /// Because the ID entries are not encrypted, the ID key can be checked to assure that the + /// correct PDF file is being accessed without decrypting the PDF file. The restrictions + /// that the objects all be direct objects and not be encrypted ensure this.
+ /// NOTE 3
+ /// Although this entry is optional prior to PDF 2.0, its absence can prevent the PDF file + /// from functioning in some workflows that depend on PDF files being uniquely identified.
+ /// NOTE 4
+ /// The values of the ID strings are used as input to the encryption algorithm.If these + /// strings were indirect, or if the ID array were indirect, these strings would be encrypted + /// when written.This would result in a circular condition for a PDF reader: the ID strings + /// need be decrypted in order to use them to decrypt strings, including the ID strings + /// themselves.The preceding restriction prevents this circular condition. ///
[KeyInfo(KeyType.Array | KeyType.Optional)] public const string ID = "/ID"; /// - /// (Optional) The byte offset from the beginning of the file of a cross-reference stream. + /// (Optional) The byte offset in the decoded stream from the beginning of the PDF file of a cross-reference stream. /// [KeyInfo(KeyType.Integer | KeyType.Optional)] public const string XRefStm = "/XRefStm"; @@ -276,7 +332,7 @@ internal class Keys : KeysBase // Reference: TABLE 3.13 Entries in the file tr /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTransparencyGroupAttributes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTransparencyGroupAttributes.cs index 3d0b403d..7249073c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTransparencyGroupAttributes.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTransparencyGroupAttributes.cs @@ -14,6 +14,16 @@ internal PdfTransparencyGroupAttributes(PdfDocument thisDocument) Elements.SetName(Keys.S, "/Transparency"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfTransparencyGroupAttributes(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.S, "/Transparency"); + } + /// /// Predefined keys of this dictionary. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs index 0969ea24..f6314292 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfTrueTypeFont.cs @@ -1,21 +1,32 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal.OpenType; using PdfSharp.Fonts; -using PdfSharp.Fonts.OpenType; using PdfSharp.Drawing; +using PdfSharp.Internal; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Advanced { /// /// Represents a OpenType font that is ANSI encoded in the PDF document. /// - class PdfTrueTypeFont : PdfFont + public class PdfTrueTypeFont : PdfFont { public PdfTrueTypeFont(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfTrueTypeFont(PdfDictionary dict) + : base(dict) + { } + /// /// Initializes a new instance of PdfTrueTypeFont from an XFont. /// @@ -26,17 +37,16 @@ public PdfTrueTypeFont(PdfDocument document, XFont font) Elements.SetName(Keys.Subtype, "/TrueType"); // TrueType with WinAnsiEncoding only. - OpenTypeDescriptor otDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(font); + //var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache; + //OpenTypeDescriptor otDescriptor = (OpenTypeDescriptor)fontDescriptorCache.GetOrCreateDescriptorFor(font); + OpenTypeFontDescriptor otDescriptor = font.GlyphTypeface.OTFontFace.OTDescriptor; + FontDescriptor = document.PdfFontDescriptorCache.GetOrCreatePdfDescriptorFor(otDescriptor, font.GlyphTypeface.GetBaseName()); // When the font subset is created, the cmap table must be added. FontDescriptor.AddCmapTable = true; - //_fontOptions = font.PdfOptions; - //Debug.Assert(_fontOptions != null); - - _cmapInfo = new CMapInfo(otDescriptor); - //FontDescriptor._cmapInfo2 = new(otDescriptor); + CMapInfo = new CMapInfo(otDescriptor.FontFace.GlyphCount); BaseFont = font.GlyphTypeface.GetBaseName(); // Fonts are always embedded. @@ -48,7 +58,7 @@ public PdfTrueTypeFont(PdfDocument document, XFont font) Encoding = "/WinAnsiEncoding"; Owner.IrefTable.TryAdd(FontDescriptor); - Elements[Keys.FontDescriptor] = FontDescriptor.Reference; + Elements[Keys.FontDescriptor] = FontDescriptor.RequiredReference; //FontEncoding = font.PdfOptions.FontEncoding; FontEncoding = PdfFontEncoding.WinAnsi; @@ -61,7 +71,9 @@ public PdfTrueTypeFont(PdfDocument document, XGlyphTypeface glyphTypeface) Elements.SetName(Keys.Subtype, "/TrueType"); // TrueType with WinAnsiEncoding only. - OpenTypeDescriptor otDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(glyphTypeface); + //var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache; + //OpenTypeDescriptor otDescriptor = (OpenTypeDescriptor)fontDescriptorCache.GetOrCreateDescriptorFor(glyphTypeface); + OpenTypeFontDescriptor otDescriptor = glyphTypeface.OTFontFace.OTDescriptor; FontDescriptor = document.PdfFontDescriptorCache.GetOrCreatePdfDescriptorFor(otDescriptor, glyphTypeface.GetBaseName()); // When the font subset is created, the cmap table must be added. @@ -70,7 +82,7 @@ public PdfTrueTypeFont(PdfDocument document, XGlyphTypeface glyphTypeface) //_fontOptions = font.PdfOptions; //Debug.Assert(_fontOptions != null); - _cmapInfo = new CMapInfo(otDescriptor); + CMapInfo = new CMapInfo(otDescriptor.FontFace.GlyphCount); //FontDescriptor._cmapInfo2 = new(otDescriptor); // Fonts are always embedded. @@ -82,7 +94,7 @@ public PdfTrueTypeFont(PdfDocument document, XGlyphTypeface glyphTypeface) Encoding = "/WinAnsiEncoding"; Owner.IrefTable.TryAdd(FontDescriptor); - Elements[Keys.FontDescriptor] = FontDescriptor.Reference; + Elements[Keys.FontDescriptor] = FontDescriptor.RequiredReference; //FontEncoding = font.PdfOptions.FontEncoding; FontEncoding = PdfFontEncoding.WinAnsi; @@ -222,7 +234,8 @@ internal override void PrepareForSave() /// Appendix D) or an encoding dictionary that specifies differences from the font’s /// built-in encoding or from a specified predefined encoding. ///
- [KeyInfo(KeyType.Name | KeyType.Dictionary)] + //[KeyInfo(KeyType.Name | KeyType.Dictionary)] + [KeyInfo(KeyType.NameOrDictionary | KeyType.Optional)] // #US373 public const string Encoding = "/Encoding"; /// @@ -236,7 +249,6 @@ internal override void PrepareForSave() /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType0Font.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType0Font.cs index 1332cf79..cb102e11 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType0Font.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType0Font.cs @@ -2,21 +2,32 @@ // See the LICENSE file in the solution root for more information. using System.Text; +using PdfSharp.Internal.OpenType; using PdfSharp.Fonts; -using PdfSharp.Fonts.OpenType; using PdfSharp.Drawing; +using PdfSharp.Internal; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Advanced { /// /// Represents a composite PDF font. Used for Unicode glyph encoding. /// - sealed class PdfType0Font : PdfFont + public sealed class PdfType0Font : PdfFont { public PdfType0Font(PdfDocument document) : base(document) { } - + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfType0Font(PdfDictionary dict) + : base(dict) + { } + public PdfType0Font(PdfDocument document, XGlyphTypeface glyphTypeface, bool vertical) : base(document) { @@ -24,35 +35,67 @@ public PdfType0Font(PdfDocument document, XGlyphTypeface glyphTypeface, bool ver Elements.SetName(Keys.Subtype, "/Type0"); Elements.SetName(Keys.Encoding, vertical ? "/Identity-V" : "/Identity-H"); - var otDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(glyphTypeface); + var fontDescriptorCache = PsGlobals.Global.Fonts.FontDescriptorCache; + //var otDescriptor = (OpenTypeDescriptor)fontDescriptorCache.GetOrCreateDescriptorFor(glyphTypeface); + var otDescriptor = glyphTypeface.OTFontFace.OTDescriptor; FontDescriptor = document.PdfFontDescriptorCache.GetOrCreatePdfDescriptorFor(otDescriptor, glyphTypeface.GetBaseName()); - //FontOptions = font.PdfOptions; - //Debug.Assert(FontOptions != null); - - _cmapInfo = new CMapInfo(otDescriptor); + CMapInfo = new CMapInfo(otDescriptor.FontFace.GlyphCount); _descendantFont = new PdfCIDFont(document, FontDescriptor /*, font*/) { // Base font uses the same cmap info. - CMapInfo = _cmapInfo + CMapInfo = CMapInfo }; - // Create ToUnicode map - _toUnicodeMap = new PdfToUnicodeMap(document, _cmapInfo); + // Create ToUnicode map. + _toUnicodeMap = new PdfToUnicodeMap(document, CMapInfo); document.Internals.AddObject(_toUnicodeMap); Elements.Add(Keys.ToUnicode, _toUnicodeMap); BaseFont = glyphTypeface.GetBaseName(); - // CID fonts are always embedded + // CID fonts are always embedded. BaseFont = FontDescriptor.FontName; _descendantFont.BaseFont = BaseFont; PdfArray descendantFonts = new PdfArray(document); Owner.IrefTable.Add(_descendantFont); - descendantFonts.Elements.Add(_descendantFont.Reference!); // Reference is set in Add(_descendantFont). + descendantFonts.Elements.Add(_descendantFont.RequiredReference); // Reference is set in Add(_descendantFont). Elements[Keys.DescendantFonts] = descendantFonts; } + //internal PdfType0Font(PdfDocument document, IFontProxy fontProxy, bool vertical) // #PSGFX + // : base(document) + //{ + // Elements.SetName(Keys.Type, "/Font"); + // Elements.SetName(Keys.Subtype, "/Type0"); + // Elements.SetName(Keys.Encoding, vertical ? "/Identity-V" : "/Identity-H"); + + // var otDescriptor = (OpenTypeDescriptor)FontDescriptorCache.GetOrCreateDescriptorFor(fontProxy); + // FontDescriptor = document.PdfFontDescriptorCache.GetOrCreatePdfDescriptorFor(otDescriptor, fontProxy.GetBaseName()); + + // _cmapInfo = new CMapInfo(otDescriptor); + // _descendantFont = new PdfCIDFont(document, FontDescriptor /*, font*/) + // { + // // Base font uses the same cmap info. + // CMapInfo = _cmapInfo + // }; + + // // Create ToUnicode map. + // _toUnicodeMap = new PdfToUnicodeMap(document, _cmapInfo); + // document.Internals.AddObject(_toUnicodeMap); + // Elements.Add(Keys.ToUnicode, _toUnicodeMap); + + // BaseFont = fontProxy.GetBaseName(); + // // CID fonts are always embedded. + // BaseFont = FontDescriptor.FontName; + // _descendantFont.BaseFont = BaseFont; + + // PdfArray descendantFonts = new PdfArray(document); + // Owner.IrefTable.Add(_descendantFont); + // descendantFonts.Elements.Add(_descendantFont.Reference!); // Reference is set in Add(_descendantFont). + // Elements[Keys.DescendantFonts] = descendantFonts; + //} + #if true_ // May be superfluous. public PdfType0Font(PdfDocument document, string idName, byte[] fontData, bool vertical) : base(document) @@ -99,7 +142,7 @@ public PdfType0Font(PdfDocument document, string idName, byte[] fontData, bool v } #endif - XPdfFontOptions FontOptions { get; } = default!; + XPdfFontOptions FontOptions { get; } = null!; public string BaseFont { @@ -107,9 +150,13 @@ public string BaseFont set => Elements.SetName(Keys.BaseFont, value); } - internal PdfCIDFont DescendantFont => _descendantFont; + internal PdfCIDFont DescendantFont + { + get => _descendantFont; + set => _descendantFont = value; + } - readonly PdfCIDFont _descendantFont = default!; + PdfCIDFont _descendantFont = null!; internal override void PrepareForSave() { @@ -118,9 +165,9 @@ internal override void PrepareForSave() // Use GetGlyphIndices to create the widths array. var descriptor = FontDescriptor.Descriptor; var w = new StringBuilder("["); - if (_cmapInfo != null!) + if (CMapInfo != null!) { - ushort[] glyphIndices = _cmapInfo.GetGlyphIndices(); + ushort[] glyphIndices = CMapInfo.GetGlyphIndices(); int count = glyphIndices.Length; int[] glyphWidths = new int[count]; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType1Font.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType1Font.cs index 8012c067..18d92452 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType1Font.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfType1Font.cs @@ -119,7 +119,7 @@ public PdfType1Font(PdfDocument document) /// Appendix D) or an encoding dictionary that specifies differences from the font’s /// built-in encoding or from a specified predefined encoding. ///
- [KeyInfo(KeyType.Name | KeyType.Dictionary)] + [KeyInfo(KeyType.NameOrDictionary | KeyType.Optional)] public const string Encoding = "/Encoding"; /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfXObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfXObject.cs index b850fa07..e788703c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfXObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfXObject.cs @@ -16,6 +16,14 @@ protected PdfXObject(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance from an existing dictionary. + /// Used for object type transformation. + /// + protected PdfXObject(PdfDictionary dict) + : base(dict) + { } + /// /// Predefined keys of this dictionary. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/IAnnotationAppearanceHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/IAnnotationAppearanceHandler.cs index e74aac4a..c613fe76 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/IAnnotationAppearanceHandler.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/IAnnotationAppearanceHandler.cs @@ -3,15 +3,18 @@ using PdfSharp.Drawing; +// v7.0.0 Review + namespace PdfSharp.Pdf.Annotations { /// - /// Draws the visual representation of an AcroForm element. + /// Draws the visual representation of a form element. /// - public interface IAnnotationAppearanceHandler // kann man Annotation generell selber malen? + public interface IAnnotationAppearanceHandler // TODO This is BS + // TODO THHO4STLA Make it better. { /// - /// Draws the visual representation of an AcroForm element. + /// Draws the visual representation of a form element. /// /// /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/Pdf3DAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/Pdf3DAnnotation.cs new file mode 100644 index 00000000..fc82c1fc --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/Pdf3DAnnotation.cs @@ -0,0 +1,120 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF 3D annotation. + /// + public sealed class Pdf3DAnnotation : PdfAnnotation + { + // Reference 2.0: 13.6.2 3D annotations / Page 643 + + /// + /// Initializes a new instance of the class. + /// + public Pdf3DAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal Pdf3DAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.ThreeD); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Required) A 3D stream (see 13.6.3, "3D streams") or 3D reference dictionary + /// (see 13.6.3.3, "3D reference dictionaries") that specifies the 3D artwork + /// to be shown. + /// + [KeyInfo(KeyType.StreamOrDictionary | KeyType.Required)] + public const string _3DD = "/3DD"; + /// + /// (Optional) An object that specifies the default initial view of the 3D artwork that + /// shall be used when the annotation is activated. It may be either a 3D view dictionary + /// (see 13.6.4, "3D views") or one of the following types specifying an element in the + /// VA array in the 3D stream (see "Table 311 — Entries in a 3D stream dictionary"): + /// • An integer specifying an index into the VA array. + /// • A text string matching the IN entry in one of the views in the VA array. + /// • A name that indicates the first(F), last(L), or default (D) entries in the VA array. + /// Default value: the default view in the 3D stream object specified by 3DD. + /// + [KeyInfo(KeyType.Various | KeyType.Optional)] + public const string _3DV = "/3DV"; + + /// + /// (Optional) An activation dictionary (see "Table 310 — Entries in a 3D activation dictionary") + /// that defines the times at which the annotation shall be activated and deactivated and + /// the state of the 3D artwork instance at those times. + /// Default value: an activation dictionary containing default values for all its entries. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string _3DA = "/3DA"; + + /// + /// (Optional) A flag indicating the primary use of the 3D annotation. If true, it is intended + /// to be interactive; if false, it is intended to be manipulated programmatically, as with + /// an ECMAScript animation. Interactive PDF processors may present different user interface + /// controls for interactive 3D annotations (for example, to rotate, pan, or zoom the artwork) + /// than for those managed by a script or other mechanism. + /// Default value: true. + /// + [KeyInfo(KeyType.Boolean | KeyType.Optional)] + public const string _3DI = "/3DI"; + + /// + /// (Optional) The 3D view box, which is the rectangular area in which the 3D artwork shall + /// be drawn. It shall be within the rectangle specified by the annotation’s Rect entry + /// and shall be expressed in the annotation’s target coordinate system (see discussion + /// following this Table). Default value: the annotation’s Rect entry, expressed in the + /// target coordinate system.This value is [-w/2 -h/2 w/2 h/2], where w and h are the width + /// and height, respectively, of Rect. + /// + [KeyInfo(KeyType.Rectangle | KeyType.Optional)] + public const string _3DB = "/3DB"; // TODO Check type. + + /// + /// (Optional; PDF 2.0) A 3D units dictionary that specifies the units definitions for + /// the 3D data associated with this annotation. See "Table 325 — Entries in a 3D units + /// dictionary". + /// + [KeyInfo("2.0", KeyType.Dictionary | KeyType.Optional)] + public const string _3DU = "/3DU"; + + /// + /// (Optional; PDF 2.0) For Geospatial3D requirement type, a geospatial information section + /// may be present as an attribute within a 3D Annotation. There are further conditions + /// placed on the GPTS and LPTS arrays within the geo-reference coordinate tables to include + /// 3D point values. See 12.10.2, "Geospatial measure dictionary". + /// + [KeyInfo("2.0", KeyType.Dictionary | KeyType.Optional)] + public const string GEO = "/GEO"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotation.cs index db6efe7c..cde7ad89 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotation.cs @@ -3,6 +3,11 @@ using PdfSharp.Drawing; using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.PdfDictionaryExtensions; + +// v7.0.0 TODO review + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Annotations { @@ -11,6 +16,8 @@ namespace PdfSharp.Pdf.Annotations /// public abstract class PdfAnnotation : PdfDictionary { + // Reference 2.0: 12.5.2 Annotation dictionaries / Page 467 + /// /// Initializes a new instance of the class. /// @@ -21,15 +28,17 @@ protected PdfAnnotation() /// /// Initializes a new instance of the class. + /// The annotation is created as an indirect object. /// protected PdfAnnotation(PdfDocument document) - : base(document) + : base(document, true) { Initialize(); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// internal PdfAnnotation(PdfDictionary dict) : base(dict) @@ -38,19 +47,19 @@ internal PdfAnnotation(PdfDictionary dict) void Initialize() { Elements.SetName(Keys.Type, "/Annot"); - Elements.SetString(Keys.NM, Guid.NewGuid().ToString("D")); - Elements.SetDateTime(Keys.M, DateTime.Now); + //Elements.SetString(Keys.NM, Guid.NewGuid().ToString("D")); + Elements.SetDateTime(Keys.M, DateTimeOffset.Now); } - /// - /// Removes an annotation from the document - /// - /// - [Obsolete("Use 'Parent.Remove(this)'")] - public void Delete() - { - Parent.Remove(this); - } + ///// + ///// Removes an annotation from the document + ///// + ///// + //[Obsolete("Use 'Parent.Remove(this)'")] + //public void Delete() + //{ + // Parent_.Remove(this); + //} /// /// Gets or sets the annotation flags of this instance. @@ -60,62 +69,92 @@ public PdfAnnotationFlags Flags get => (PdfAnnotationFlags)Elements.GetInteger(Keys.F); set { - Elements.SetInteger(Keys.F, (int)value); - Elements.SetDateTime(Keys.M, DateTime.Now); + Elements.SetIntegerFlag(Keys.F, (int)value); + Elements.SetDateTime(Keys.M, DateTimeOffset.Now); } } + public bool TestFlag(PdfAnnotationFlags flag) + { + var result = (Flags & flag) == flag; + return result; + } + + public void SetFlag(PdfAnnotationFlags flag) + { + Flags |= flag; + } + + public void ClearFlag(PdfAnnotationFlags flag) + { + Flags &= ~flag; + } + /// /// Gets or sets the PdfAnnotations object that this annotation belongs to. /// - public PdfAnnotations Parent + public PdfAnnotations? Parent { get => _parent ?? NRT.ThrowOnNull(); set => _parent = value; } - PdfAnnotations? _parent; /// - /// Gets or sets the annotation rectangle, defining the location of the annotation - /// on the page in default user space units. + /// Gets or sets the page this annotation belongs to. /// - public PdfRectangle Rectangle + public PdfPage? Page { - get => Elements.GetRectangle(Keys.Rect, true); + get => Elements.GetObject(Keys.P); set { - Elements.SetRectangle(Keys.Rect, value); - Elements.SetDateTime(Keys.M, DateTime.Now); + if (value != null) + Elements.SetObject(Keys.P, value); + else + Elements.Remove(Keys.P); } } /// - /// Gets or sets the text label to be displayed in the title bar of the annotation’s - /// pop-up window when open and active. By convention, this entry identifies - /// the user who added the annotation. + /// Gets or sets the annotation rectangle, defining the location of the annotation + /// on the page in default user space units. /// - public string Title + public PdfRectangle Rectangle { - get => Elements.GetString(Keys.T, true); + get => Elements.GetRequiredRectangle(Keys.Rect); set { - Elements.SetString(Keys.T, value); - Elements.SetDateTime(Keys.M, DateTime.Now); + Elements.SetRectangle(Keys.Rect, value); + // TODO: Check if update /M is correct. + //Elements.SetDateTime(Keys.M, DateTimeOffset.Now); } } - /// - /// Gets or sets text representing a short description of the subject being - /// addressed by the annotation. - /// - public string Subject + // TODO Define the space names + public XRect PageRectangle { - get => Elements.GetString(Keys.Subj, true); + get + { + if (Page == null) + throw new InvalidOperationException("The property PageRectangle requires the Page property to be defined."); + + var pdfRect = Elements.GetRectangle(Keys.Rect); + if (pdfRect == null) + return XRect.Empty; // TODO Mist! + + return new(pdfRect.X1, Page.Height.Point - pdfRect.Y1 - pdfRect.Height, pdfRect.Width, pdfRect.Height); + } set { - Elements.SetString(Keys.Subj, value); - Elements.SetDateTime(Keys.M, DateTime.Now); + if (Page == null) + throw new InvalidOperationException("The property PageRectangle requires the Page property to be defined."); + + double y = Page.Height.Point - value.Y - value.Height; + var pdfRect = new PdfRectangle( + value.X, y, + value.X + value.Width, y + value.Height); + Elements.SetRectangle(Keys.Rect, pdfRect); + Elements.SetDateTime(Keys.M, DateTimeOffset.Now); } } @@ -130,7 +169,7 @@ public string Contents set { Elements.SetString(Keys.Contents, value); - Elements.SetDateTime(Keys.M, DateTime.Now); + Elements.SetDateTime(Keys.M, DateTimeOffset.Now); } } @@ -143,9 +182,10 @@ public XColor Color { get { - var item = Elements[Keys.C]; - if (item is PdfReference reference) - item = reference.Value; + var item = Elements.GetValue(Keys.C); // #US373 + //var item = Elements[Keys.C]; + //if (item is PdfReference reference) + // item = reference.Value; if (item is PdfArray array) { if (array.Elements.Count == 3) @@ -164,7 +204,7 @@ public XColor Color var array = new PdfArray(Owner, new PdfReal(value.R / 255.0), new PdfReal(value.G / 255.0), new PdfReal(value.B / 255.0)); Elements[Keys.C] = array; - Elements.SetDateTime(Keys.M, DateTime.Now); + Elements.SetDateTime(Keys.M, DateTimeOffset.Now); } } @@ -187,26 +227,302 @@ public double Opacity if (value is < 0 or > 1) throw new ArgumentOutOfRangeException(nameof(value), value, "Opacity must be a value in the range from 0 to 1."); Elements.SetReal(Keys.CA, value); - Elements.SetDateTime(Keys.M, DateTime.Now); + Elements.SetDateTime(Keys.M, DateTimeOffset.Now); + } + } + + internal static PdfAnnotation CreateAnnotation(PdfDictionary dict, PdfPage? page = null) + { + if (dict is PdfAnnotation annotation) + return annotation; + + var subtype = dict.Elements.GetName(Keys.Subtype); +#if true_ + switch (subtype) + { + case "/" + nameof(PdfAnnotationTypes.Text): + annotation = new PdfTextAnnotation(dict); + break; + + case "/" + nameof(PdfAnnotationTypes.Link): + annotation = new PdfLinkAnnotation(dict); + break; + + case "/" + nameof(PdfAnnotationTypes.FreeText): + + case "/" + nameof(PdfAnnotationTypes.Line): + annotation = new PdfLineAnnotation(dict); + break; + + case "/" + nameof(PdfAnnotationTypes.Square): + annotation = new PdfLineAnnotation(dict); + break; + + case "/" + nameof(PdfAnnotationTypes.Circle): + annotation = new PdfCircleAnnotation(dict); + break; + + case "/" + nameof(PdfAnnotationTypes.Polygon): + case "/" + nameof(PdfAnnotationTypes.PolyLine): + case "/" + nameof(PdfAnnotationTypes.Highlight): + case "/" + nameof(PdfAnnotationTypes.Underline): + case "/" + nameof(PdfAnnotationTypes.Squiggly): + case "/" + nameof(PdfAnnotationTypes.StrikeOut): + case "/" + nameof(PdfAnnotationTypes.Caret): + annotation = new PdfGenericAnnotation(dict); + break; + + Old code, does not compile. + case "/" + nameof(PdfAnnotationTypes.RubberStamp): + annotation = new PdfRubberStampAnnotation(dict); + break; + + case "/" + nameof(PdfAnnotationTypes.Ink): + case "/" + nameof(PdfAnnotationTypes.Popup): + case "/" + nameof(PdfAnnotationTypes.FileAttachment): + case "/" + nameof(PdfAnnotationTypes.Sound): + case "/" + nameof(PdfAnnotationTypes.Movie): + case "/" + nameof(PdfAnnotationTypes.Screen): + annotation = new PdfGenericAnnotation(dict); + break; + + case "/" + nameof(PdfAnnotationTypes.Widget): + annotation = CreateWidget(); + break; + + case "/" + nameof(PdfAnnotationTypes.PrinterMark): + case "/" + nameof(PdfAnnotationTypes.TrapNet): + case "/" + nameof(PdfAnnotationTypes.Watermark): + case "/3D" /*+ nameof(PdfAnnotationTypes.ThreeD)*/: + case "/" + nameof(PdfAnnotationTypes.Redact): + case "/" + nameof(PdfAnnotationTypes.Projection): + case "/" + nameof(PdfAnnotationTypes.RichMedia): + annotation = new PdfGenericAnnotation(dict); + break; + + default: + throw new InvalidOperationException($"Invalid annotation subtype '{subtype}'."); + } +#else + annotation = subtype switch + { + PdfAnnotationTypeNames.Text + => new PdfTextAnnotation(dict), + + PdfAnnotationTypeNames.Link + => new PdfLinkAnnotation(dict), + + PdfAnnotationTypeNames.FreeText + => new PdfFreeTextAnnotation(dict), + + PdfAnnotationTypeNames.Line + => new PdfLineAnnotation(dict), + + PdfAnnotationTypeNames.Square + => new PdfSquareAnnotation(dict), + + PdfAnnotationTypeNames.Circle + => new PdfCircleAnnotation(dict), + + PdfAnnotationTypeNames.Polygon + => new PdfPolygonAnnotation(dict), + + PdfAnnotationTypeNames.PolyLine + => new PdfPolyLineAnnotation(dict), + + PdfAnnotationTypeNames.Highlight + => new PdfHighlightAnnotation(dict), + + PdfAnnotationTypeNames.Underline + => new PdfUnderlineAnnotation(dict), + + PdfAnnotationTypeNames.Squiggly + => new PdfSquigglyAnnotation(dict), + + PdfAnnotationTypeNames.StrikeOut + => new PdfStrikeOutAnnotation(dict), + + PdfAnnotationTypeNames.Caret + => new PdfCaretAnnotation(dict), + + PdfAnnotationTypeNames.Stamp + => new PdfStampAnnotation(dict), + + PdfAnnotationTypeNames.Ink + => new PdfInkAnnotation(dict), + + PdfAnnotationTypeNames.Popup + => new PdfPopupAnnotation(dict), + + PdfAnnotationTypeNames.FileAttachment + => new PdfFileAttachmentAnnotation(dict), + + PdfAnnotationTypeNames.Sound + => new PdfSoundAnnotation(dict), + + PdfAnnotationTypeNames.Movie + => new PdfMovieAnnotation(dict), + + PdfAnnotationTypeNames.Screen + => new PdfScreenAnnotation(dict), + + PdfAnnotationTypeNames.Widget + => new PdfWidgetAnnotation(dict), + + PdfAnnotationTypeNames.PrinterMark + => new PdfPrinterMarkAnnotation(dict), + + PdfAnnotationTypeNames.TrapNet + => new PdfTrapNetAnnotation(dict), + + PdfAnnotationTypeNames.Watermark + => new PdfWatermarkAnnotation(dict), + + PdfAnnotationTypeNames.ThreeD + => new Pdf3DAnnotation(dict), + + PdfAnnotationTypeNames.Redact + => new PdfRedactAnnotation(dict), + + PdfAnnotationTypeNames.Projection + => new PdfProjectionAnnotation(dict), + + PdfAnnotationTypeNames.RichMedia + => new PdfRichMediaAnnotation(dict), + + _ => throw new InvalidOperationException($"Invalid annotation subtype '{subtype}'.") + }; +#endif + if (page != null) + annotation.Page = page; + + return annotation; + + //PdfWidgetAnnotation CreateWidget() + //{ + // if (dict is PdfWidgetAnnotation widget) + // return widget; + // if (dict is PdfFormField field) + // return field.GetAsWidgetAnnotation(); + // return new PdfWidgetAnnotation(dict); + //} + } + + protected internal static void EnsurePageHasDocument(PdfPage page) + { + if (page.Document == null) + { + throw new InvalidOperationException( + "You cannot create a PDF annotation for a page that does not yet belong to a document."); + } + } + + internal string? AppearanceState + { + get + { + if (Elements.TryGetName(Keys.AS, out var result)) + return result; + return null; + } + set + { + if (value != null) + Elements.SetValue(Keys.AS, new PdfName(value)); + else + Elements.Remove(Keys.AS); } } + /// + /// Gets all appearance states for this widget, that are stored in the /N (normal) entry. + /// + internal List GetAppearanceStateNames() + { + var subDict = GetAppearanceStreamSubDictionary(PdfAnnotationAppearance.Keys.N); + return subDict?.Elements.Keys.ToList() ?? []; + } + + /// + /// Gets the appearance stream for the given state of this widget, that is stored in the /N (normal) entry. + /// + /// The name of the appearance state. + internal PdfFormXObject? GetNormalAppearanceStream(string appearanceState) + { + return GetAppearanceStream(PdfAnnotationAppearance.Keys.N, appearanceState); + } + + /// + /// Gets the appearance stream for the given state of this widget, that are stored in the appearanceKey entry. + /// + /// The key of the appearance subdictionary: /N (normal), /R (rollover) or /D (down). + /// The name of the appearance state. + internal PdfFormXObject? GetAppearanceStream(string appearanceKey, string appearanceState) + { + var subDict = GetAppearanceStreamSubDictionary(appearanceKey); + + return subDict?.Elements.GetDictionary(appearanceState); + } + + /// + /// Gets the appearance stream subdictionary for this widget, that is stored in the appearanceKey entry. + /// + /// The key of the appearance subdictionary: /N (normal), /R (rollover) or /D (down). + PdfDictionary? GetAppearanceStreamSubDictionary(string appearanceKey) + { + var appearanceDict = Elements.GetDictionary(Keys.AP); + var appearanceStreamOrSubDict = appearanceDict?.Elements.GetObject(appearanceKey); + + if (appearanceStreamOrSubDict is PdfDictionary subDict) + return subDict; + + return null; + } + + /// + /// Gets the appearance stream for this widget, that is stored directly in the /N (normal) entry. + /// + internal PdfFormXObject? GetNormalAppearanceStream() + { + return GetAppearanceStream(PdfAnnotationAppearance.Keys.N); + } + + /// + /// Gets the appearance stream for this widget, that is stored directly in the appearanceKey entry. + /// + /// The key of the appearance stream: /N (normal), /R (rollover) or /D (down). + internal PdfFormXObject? GetAppearanceStream(string appearanceKey) + { + var appearanceDict = Elements.GetDictionary(Keys.AP); + var appearanceStreamOrSubDict = appearanceDict?.Elements.GetObject(appearanceKey); + + if (appearanceStreamOrSubDict is PdfFormXObject stream) + return stream; + + return null; + } + /// /// Predefined keys of this dictionary. /// public class Keys : KeysBase { + // Updated to: + // Reference 2.0: 12.5.2 Annotation dictionaries / Page 467 + // Reference 2.0: Table 166 — Entries common to all annotation dictionaries / Page 467 + // ReSharper disable InconsistentNaming /// - /// (Optional) The type of PDF object that this dictionary describes; if present, - /// must be Annot for an annotation dictionary. + /// (Optional) The type of PDF object that this dictionary describes; + /// if present, shall be Annot for an annotation dictionary. /// [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Annot")] public const string Type = "/Type"; /// /// (Required) The type of annotation that this dictionary describes. + /// See PdfAnnotationTypes. /// [KeyInfo(KeyType.Name | KeyType.Required)] public const string Subtype = "/Subtype"; @@ -219,16 +535,24 @@ public class Keys : KeysBase public const string Rect = "/Rect"; /// - /// (Optional) Text to be displayed for the annotation or, if this type of annotation - /// does not display text, an alternate description of the annotation’s contents - /// in human-readable form. In either case, this text is useful when - /// extracting the document’s contents in support of accessibility to users with - /// disabilities or for other purposes. + /// (Optional) Text that shall be displayed for the annotation or, if this type of + /// annotation does not display text, an alternative description of the annotation’s + /// contents in human-readable form. In either case, this text is useful when extracting + /// the document’s contents in support of accessibility to users with disabilities or + /// for other purposes. + /// See PdfAnnotation types for more details on the meaning of this entry for each + /// annotation type. /// [KeyInfo(KeyType.TextString | KeyType.Optional)] public const string Contents = "/Contents"; - // P + /// + /// (Optional except as noted below; PDF 1.3; not used in FDF files) An indirect reference + /// to the page object with which this annotation is associated. This entry shall be present + /// in screen annotations associated with rendition actions. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string P = "/P"; /// /// (Optional; PDF 1.4) The annotation name, a text string uniquely identifying it @@ -238,9 +562,8 @@ public class Keys : KeysBase public const string NM = "/NM"; /// - /// (Optional; PDF 1.1) The date and time when the annotation was most recently - /// modified. The preferred format is a date string, but viewer applications should be - /// prepared to accept and display a string in any format. + /// (Optional; PDF 1.1) The date and time when the annotation was most recently modified. + /// The format should be a date string. /// [KeyInfo(KeyType.Date | KeyType.Optional)] public const string M = "/M"; @@ -253,53 +576,64 @@ public class Keys : KeysBase public const string F = "/F"; /// - /// (Optional; PDF 1.2) A border style dictionary specifying the characteristics of - /// the annotation’s border. + /// (Optional; PDF 1.2) An appearance dictionary specifying how the annotation shall be presented + /// visually on the page. A PDF writer shall include an appearance dictionary when writing or + /// updating the PDF file except for the two cases listed below. Every annotation(including those + /// whose Subtype value is Widget, as used for form fields), except for the two cases listed below, + /// shall have at least one appearance dictionary. + /// • Annotations where the value of the Rect key consists of an array where the value at index 1 + /// is equal to the value at index 3 and the value at index 2 is equal to the value at index 4. + /// NOTE(2020) + /// The bullet point above was changed from “or” to “and” in this document to match requirements + /// in other published ISO PDF standards(such as PDF/A). + /// • Annotations whose Subtype value is Popup, Projection or Link. /// - [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)] - public const string BS = "/BS"; - - /// - /// (Optional; PDF 1.2) An appearance dictionary specifying how the annotation - /// is presented visually on the page. Individual annotation handlers may ignore - /// this entry and provide their own appearances. - /// - [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)] + [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)] // type is PdfAnnotationAppearance public const string AP = "/AP"; /// /// (Required if the appearance dictionary AP contains one or more subdictionaries; PDF 1.2) - /// The annotation’s appearance state, which selects the applicable appearance stream from - /// an appearance subdictionary. + /// The annotation’s appearance state, which selects the applicable appearance stream from an + /// appearance subdictionary (see 12.5.5, "Appearance streams"). /// - [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)] + [KeyInfo("1.2", KeyType.Name | KeyType.Optional)] public const string AS = "/AS"; /// - /// (Optional) An array specifying the characteristics of the annotation’s border. - /// The border is specified as a rounded rectangle. - /// In PDF 1.0, the array consists of three numbers defining the horizontal corner - /// radius, vertical corner radius, and border width, all in default user space units. - /// If the corner radii are 0, the border has square (not rounded) corners; if the border - /// width is 0, no border is drawn. - /// In PDF 1.1, the array may have a fourth element, an optional dash array defining a - /// pattern of dashes and gaps to be used in drawing the border. The dash array is - /// specified in the same format as in the line dash pattern parameter of the graphics state. - /// For example, a Border value of [0 0 1 [3 2]] specifies a border 1 unit wide, with - /// square corners, drawn with 3-unit dashes alternating with 2-unit gaps. Note that no - /// dash phase is specified; the phase is assumed to be 0. - /// Note: In PDF 1.2 or later, this entry may be ignored in favor of the BS entry. + /// (Optional) An array specifying the characteristics of the annotation’s border, which + /// shall be drawn as a rounded rectangle. + /// (PDF 1.0) The array consists of three numbers defining the horizontal corner radius, + /// vertical corner radius, and border width, all in default user space units.If the corner + /// radii are 0, the border has square(not rounded) corners; if the border width is 0, no + /// border is drawn. + /// (PDF 1.1) The array may have a fourth element, an optional dash array defining a pattern + /// of dashes and gaps that shall be used in drawing the border.The dash array shall be + /// specified in the same format as in the line dash pattern parameter of the graphics + /// state(see 8.4.3.6, "Line dash pattern"). The dash phase shall not be specified and + /// shall be assumed to be 0. + /// EXAMPLE A Border value of[0 0 1[3 2]] specifies a border 1 unit wide, with square corners, + /// drawn with 3-unit dashes alternating with 2- unit gaps. + /// NOTE + /// (PDF 1.2) The dictionaries for some annotation types (such as free text and polygon + /// annotations) can include the BS entry.That entry specifies a border style dictionary + /// that has more settings than the array specified for the Border entry.If an annotation + /// dictionary includes the BS entry, then the Border entry is ignored. + /// Default value: [0 0 1]. /// [KeyInfo(KeyType.Array | KeyType.Optional)] public const string Border = "/Border"; /// - /// (Optional; PDF 1.1) An array of three numbers in the range 0.0 to 1.0, representing - /// the components of a color in the DeviceRGB color space. This color is used for the - /// following purposes: - /// • The background of the annotation’s icon when closed - /// • The title bar of the annotation’s pop-up window - /// • The border of a link annotation + /// (Optional; PDF 1.1) An array of numbers in the range 0.0 to 1.0, representing a colour + /// used for the following purposes:
+ ///     The background of the annotation’s icon when closed
+ ///     The title bar of the annotation’s popup window
+ /// The border of a link annotation
+ /// The number of array elements determines the colour space in which the colour shall be defined:
+ /// 0 No colour; transparent
+ /// 1 DeviceGray
+ /// 3 DeviceRGB
+ /// 4 DeviceCMYK ///
[KeyInfo("1.1", KeyType.Array | KeyType.Optional)] public const string C = "/C"; @@ -313,19 +647,84 @@ public class Keys : KeysBase public const string StructParent = "/StructParent"; /// - /// (Optional; PDF 1.1) An action to be performed when the annotation is activated. - /// Note: This entry is not permitted in link annotations if a Dest entry is present. - /// Also note that the A entry in movie annotations has a different meaning. + /// (Optional; PDF 1.5) An optional content group or optional content membership dictionary + /// specifying the optional content properties for the annotation. Before the annotation + /// is drawn, its visibility shall be determined based on this entry as well as the annotation + /// flags specified in the F entry. If it is determined to be invisible, the annotation shall + /// not be drawn. /// - [KeyInfo("1.1", KeyType.Dictionary | KeyType.Optional)] - public const string A = "/A"; + [KeyInfo("1.5", KeyType.Dictionary | KeyType.Optional)] + public const string OC = "/OC"; - // AA - // StructParent - // OC + /// + /// (Optional; PDF 2.0) An array of one or more file specification dictionaries which + /// denote the associated files for this annotation. + /// + [KeyInfo("2.0", KeyType.ArrayOfDictionaries | KeyType.Optional)] + public const string AF = "/AF"; + + /// + /// (Optional; PDF 2.0) When regenerating the annotation's appearance stream, + /// this is the opacity value (11.2, "Overview of transparency") that shall be used + /// for all nonstroking operations on all visible elements of the annotation in its + /// closed state (including its background and border) but not the popup window that + /// appears when the annotation is opened. + /// Default value: 1.0 + /// The specified value shall not be used if the annotation has an appearance stream; + /// in that case, the appearance stream shall specify any transparency. + /// If no explicit appearance stream is defined for the annotation, and the processor is not + /// able to regenerate the appearance, the annotation may be painted by implementation-dependent + /// means that do not necessarily conform to the PDF imaging + /// + [KeyInfo("2.0", KeyType.Integer | KeyType.Optional)] + public const string ca = "/ca"; + + /// + /// (Optional; PDF 1.4, PDF 2.0 for non-markup annotations) When regenerating the annotation's + /// appearance stream, this is the opacity value (11.2, "Overview of transparency") that shall + /// be used for stroking all visible elements of the annotation in its closed state, including + /// its background and border, but not the popup window that appears when the annotation is + /// opened. + /// If a ca entry is not present in this dictionary, then the value of this CA entry shall also + /// be used for nonstroking operations as well. + /// Default Value: 1.0 + /// The specified value shall not be used if the annotation has an appearance stream; + /// in that case, the appearance stream shall specify any transparency. + /// If no explicit appearance stream is defined for the annotation, and the processor is not + /// able to regenerate the appearance, the annotation may be painted by implementation-dependent + /// means that do not necessarily conform to the PDF imaging model; in this case, the effect of + /// this entry is implementation-dependent as well. + /// + [KeyInfo("1.4", KeyType.Integer | KeyType.Optional)] + public const string CA = "/CA"; + + /// + /// (Optional; PDF 2.0) The blend mode that shall be used when painting the annotation onto the + /// page. If this key is not present, blending shall take place using the Normal blend mode. The + /// value shall be a name object, designating one of the standard blend modes listed in + /// "Table 134 — Standard separable blend modes" and + /// "Table 135 — Standard non-separable blend modes". + /// + [KeyInfo("2.0", KeyType.Name | KeyType.Optional)] + public const string BM = "/BM"; + + /// + /// (Optional; PDF 2.0) A language identifier overriding the document’s language identifier to + /// specify the natural language for all text in the annotation except where overridden by other + /// explicit language specifications. + /// + [KeyInfo("2.0", KeyType.TextString | KeyType.Optional)] + public const string Lang = "/Lang"; + + /* Excerpt from the specs: + * A PDF reader shall render the appearance dictionary without regard to any other keys and values in + * the annotation dictionary and shall ignore the values of the + * C, IC, Border, BS, BE, BM, CA, ca, H, DA, Q, DS, LE, LL, LLE, and Sy keys. + */ // ----- Excerpt of entries specific to markup annotations ---------------------------------- +#if true_ // TODO Remove this. See PdfMarkupAnnotation. /// /// (Optional; PDF 1.1) The text label to be displayed in the title bar of the annotation’s /// pop-up window when open and active. By convention, this entry identifies @@ -341,20 +740,20 @@ public class Keys : KeysBase [KeyInfo(KeyType.Dictionary | KeyType.Optional)] public const string Popup = "/Popup"; - /// - /// (Optional; PDF 1.4) The constant opacity value to be used in painting the annotation. - /// This value applies to all visible elements of the annotation in its closed state - /// (including its background and border) but not to the popup window that appears when - /// the annotation is opened. - /// The specified value is not used if the annotation has an appearance stream; in that - /// case, the appearance stream must specify any transparency. (However, if the viewer - /// regenerates the annotation’s appearance stream, it may incorporate the CA value - /// into the stream’s content.) - /// The implicit blend mode is Normal. - /// Default value: 1.0. - /// - [KeyInfo(KeyType.Real | KeyType.Optional)] - public const string CA = "/CA"; + ///// + ///// (Optional; PDF 1.4) The constant opacity value to be used in painting the annotation. + ///// This value applies to all visible elements of the annotation in its closed state + ///// (including its background and border) but not to the popup window that appears when + ///// the annotation is opened. + ///// The specified value is not used if the annotation has an appearance stream; in that + ///// case, the appearance stream must specify any transparency. (However, if the viewer + ///// regenerates the annotation’s appearance stream, it may incorporate the CA value + ///// into the stream’s content.) + ///// The implicit blend mode is Normal. + ///// Default value: 1.0. + ///// + //[KeyInfo(KeyType.Real | KeyType.Optional)] + //public const string CA = "/CA"; //RC //CreationDate @@ -369,7 +768,17 @@ public class Keys : KeysBase //RT //IT +#endif // ReSharper restore InconsistentNaming + + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotationAppearance.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotationAppearance.cs new file mode 100644 index 00000000..ab687f17 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotationAppearance.cs @@ -0,0 +1,120 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.PdfDictionaryExtensions; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents the border effect of all annotations. + /// + public sealed class PdfAnnotationAppearance : PdfDictionary + { + // Reference 2.0: 12.5.5 Appearance streams / Page 474 + + /// + /// Initializes a new instance of the class. + /// + public PdfAnnotationAppearance(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfAnnotationAppearance(PdfDictionary dict) + : base(dict) + { } + + public Dictionary? GetNValues() + { + return GetValues("/N"); + } + + public Dictionary? GetRValues() + { + return GetValues("/R"); + } + + public Dictionary? GetDValues() + { + return GetValues("/D"); + } + + Dictionary? GetValues(string type) + { + var dict = Elements.GetDictionary(type); + if (dict == null) + return null; + + Dictionary result = new(); + + if (PdfFormXObject.IsFormXObject(dict)) + { + Transform("/", dict); + } + else + { + var keys = dict.Elements.Keys; + foreach (var key in keys) + { + //if (dict.Elements[key] is PdfDictionary stream && PdfFormXObject.IsFormXObject(stream)) + if (dict.Elements.GetValue(key) is PdfDictionary stream && PdfFormXObject.IsFormXObject(stream)) // #US373 + { + Transform(key, stream); + } + } + } + return result; + + void Transform(string key, PdfDictionary d) + { + var form = (PdfFormXObject)dict.Transform(typeof(PdfFormXObject)); + result.Add(key, form); + } + } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : KeysBase + { + // Reference 2.0: Table 170 — Entries in an appearance dictionary / Page 475 + + // ReSharper disable InconsistentNaming + + /// + /// (Required) The annotation’s normal appearance. + /// + [KeyInfo(KeyType.StreamOrDictionary | KeyType.Required)] + public const string N = "/N"; + + /// + /// (Optional) The annotation’s rollover appearance. Default value: the value of the N entry. + /// + [KeyInfo(KeyType.StreamOrDictionary | KeyType.Optional)] + public const string R = "/R"; + + /// + /// (Optional) The annotation’s down appearance. Default value: the value of the N entry. + /// + [KeyInfo(KeyType.StreamOrDictionary | KeyType.Optional)] + public const string D = "/D"; + + // ReSharper restore InconsistentNaming + + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotations.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotations.cs index 45f1d8dc..0541b391 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotations.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfAnnotations.cs @@ -1,9 +1,12 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using System.Collections; +using PdfSharp.Pdf.Forms; using PdfSharp.Pdf.Advanced; +// v7.0.0 TODO review + namespace PdfSharp.Pdf.Annotations { /// @@ -11,6 +14,8 @@ namespace PdfSharp.Pdf.Annotations /// public sealed class PdfAnnotations : PdfArray { + // Reference 2.0: /Annots key in table 31 — Entries in a page object / Page 105 + internal PdfAnnotations(PdfDocument document) : base(document) { } @@ -26,8 +31,8 @@ internal PdfAnnotations(PdfArray array) public void Add(PdfAnnotation annotation) { annotation.Document = Owner; - Owner.IrefTable.Add(annotation); - Elements.Add(annotation.ReferenceNotNull); + annotation.Page = Page; + Elements.Add(annotation.RequiredReference); } /// @@ -38,8 +43,7 @@ public void Remove(PdfAnnotation annotation) if (annotation.Owner != Owner) throw new InvalidOperationException("The annotation does not belong to this document."); - Owner.Internals.RemoveObject(annotation); - Elements.Remove(annotation.ReferenceNotNull); + Elements.Remove(annotation.RequiredReference); } /// @@ -48,15 +52,10 @@ public void Remove(PdfAnnotation annotation) public void Clear() { for (int idx = Count - 1; idx >= 0; idx--) - Page.Annotations.Remove(Page.Annotations[idx]); + //Page.Annotations.Remove(Page.Annotations[idx]); // ??? + Elements.RemoveAt(idx); } - //public void Insert(int index, PdfAnnotation annotation) - //{ - // annotation.Document = Document; - // annotations.Insert(index, annotation); - //} - /// /// Gets the number of annotations in this collection. /// @@ -85,7 +84,8 @@ public PdfAnnotation this[int index] var annotation = dict as PdfAnnotation; if (annotation == null) { - annotation = new PdfGenericAnnotation(dict); + //annotation = new PdfGenericAnnotation(dict); + annotation = PdfAnnotation.CreateAnnotation(dict, Page); if (iref == null) Elements[index] = annotation; } @@ -93,22 +93,6 @@ public PdfAnnotation this[int index] } } - //public PdfAnnotation this[int index] - //{ - // get - // { - // //DMH 6/7/06 - // //Broke this out to simplify debugging - // //Use a generic annotation to access the metadata - // //Assign this as the parent of the annotation - // PdfReference r = Elements[index] as PdfReference; - // PdfDictionary d = r.Value as PdfDictionary; - // PdfGenericAnnotation a = new PdfGenericAnnotation(d); - // a.Collection = this; - // return a; - // } - //} - /// /// Gets the page the annotations belongs to. /// @@ -119,61 +103,115 @@ internal PdfPage Page } PdfPage? _page; + internal void DeriveInstances() + { + int count = Elements.Count; + for (int idx = 0; idx < count; idx++) + { + var dict = Elements.GetRequiredDictionary(idx); + if (dict is PdfAnnotation) + continue; + dict = PdfAnnotation.CreateAnnotation(dict, _page); + Elements[idx] = dict; + } + } + /// /// Fixes the /P element in imported annotation. /// internal static void FixImportedAnnotation(PdfPage page) { - var annots = page.Elements.GetArray(PdfPage.Keys.Annots); - if (annots != null) + var annotations = page.Elements.GetArray(PdfPage.Keys.Annots); + if (annotations != null) { - int count = annots.Elements.Count; + int count = annotations.Elements.Count; for (int idx = 0; idx < count; idx++) { - var annot = annots.Elements.GetDictionary(idx); - if (annot != null && annot.Elements.ContainsKey("/P")) - annot.Elements["/P"] = page.Reference; + var annot = annotations.Elements.GetDictionary(idx); + if (annot != null && annot.Elements.ContainsKey(PdfAnnotation.Keys.P)) + annot.Elements[PdfAnnotation.Keys.P] = page; } } + // DELETE + //var annots = page.Elements.GetArray(PdfPage.Keys.Annots); + //if (annots != null) + //{ + // int count = annots.Elements.Count; + // for (int idx = 0; idx < count; idx++) + // { + // var annot = annots.Elements.GetDictionary(idx); + // if (annot != null && annot.Elements.ContainsKey("/P")) + // annot.Elements["/P"] = page.RequiredReference; + // } + //} } /// /// Returns an enumerator that iterates through a collection. /// public override IEnumerator GetEnumerator() - { - return (IEnumerator)new AnnotationsIterator(this); - } + => new AnnotationsIterator(this); - class AnnotationsIterator : IEnumerator + class AnnotationsIterator(PdfAnnotations annotations) : IEnumerator { - public AnnotationsIterator(PdfAnnotations annotations) - { - _annotations = annotations; - _index = -1; - } - - public PdfItem/*PdfAnnotation*/ Current => _annotations[_index]; + public PdfItem/*PdfAnnotation*/ Current => annotations[_index]; object IEnumerator.Current => Current; - public bool MoveNext() - { - return ++_index < _annotations.Count; - } + public bool MoveNext() => ++_index < annotations.Count; - public void Reset() - { - _index = -1; - } + public void Reset() => _index = -1; public void Dispose() + { } + + int _index = -1; + } + + internal static class AnnotationPreparer + { + public static void PrepareDocument(PdfDocument doc) { - //throw new NotImplementedException(); + CreateAnnotationObjects(doc); } - readonly PdfAnnotations _annotations; - int _index; + static void CreateAnnotationObjects(PdfDocument doc) + { + var pages = doc.Catalog.Pages; + foreach (var page in pages) + { + var annots = page.Elements.GetArray(PdfPage.Keys.Annots); + if (annots != null) + { + var count = annots.Elements.Count; + for (int idx = 0; idx < count; idx++) + { + var annot = annots.Elements.GetDictionary(idx); + + // Already handled in Acro fields. + if (annot is PdfWidgetAnnotation widget) + continue; + + Debug.Assert(annot is not PdfFormField); + + if (annot != null) + { + Debug.Assert(annot.IsIndirect); + + var type = annot.GetType(); + if (type != typeof(PdfDictionary)) + throw new InvalidOperationException("Not a dictionary???"); + + PdfAnnotation.CreateAnnotation(annot); + } + else + { + throw new InvalidOperationException("Not an annotation???"); + } + } + } + } + } } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderEffect.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderEffect.cs new file mode 100644 index 00000000..f285fb52 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderEffect.cs @@ -0,0 +1,82 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents the border effect of all annotations. + /// + public class PdfBorderEffect : PdfDictionary + { + // Reference 2.0: 12.5.4 Border styles / Page 472 + + /// + /// Initializes a new instance of the class. + /// + protected PdfBorderEffect() + { } + + /// + /// Initializes a new instance of the class. + /// + protected PdfBorderEffect(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfBorderEffect(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + // TODO + //Elements.SetName(Keys.Type, "/Annot"); + //Elements.SetDateTime(Keys.M, DateTimeOffset.Now); + } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : KeysBase + { + // Reference 2.0: Table 168 — Entries in a border style dictionary / Page 473 + + // ReSharper disable InconsistentNaming + + /// + /// (Optional) A name representing the border effect to apply. Values are: + /// C The border should appear "cloudy"; that is, the border should be drawn as a series + /// of convex curved line segments in a manner that simulates the appearance of a cloud. + /// The width and dash array specified by BS shall be honoured. + /// Default value: S. + /// S No effect: the border shall be as described by the annotation dictionary’s BS entry. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string S = "/S"; + + /// + /// (Optional; valid only if the value of S is C) A number describing the intensity of the + /// effect, in the range 0 to 2. Default value: 0. + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string I = "/I"; + + // ReSharper restore InconsistentNaming + + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderStyle.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderStyle.cs new file mode 100644 index 00000000..10b1bba6 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfBorderStyle.cs @@ -0,0 +1,103 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents the border styles of all annotations. + /// + public class PdfBorderStyle : PdfDictionary + { + // Reference 2.0: 12.5.4 Border styles / Page 472 + + /// + /// Initializes a new instance of the class. + /// + protected PdfBorderStyle() + { + Initialize(); + } + + /// + /// Initializes a new instance of the class. + /// + protected PdfBorderStyle(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfBorderStyle(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(Keys.Type, "/Border"); + } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : KeysBase + { + // Reference 2.0: Table 168 — Entries in a border style dictionary / Page 473 + + // ReSharper disable InconsistentNaming + + /// + /// (Optional) The type of PDF object that this dictionary describes; if present, + /// shall be Border for a border style dictionary. + /// + [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Border")] + public const string Type = "/Type"; + + /// + /// (Optional) The border width in points. If this value is 0, no border shall be drawn. + /// Default value: 1. + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string W = "/W"; + + /// + /// (Optional) The border style:
+ /// S (Solid) A solid rectangle surrounding the annotation.Default value.
+ /// D (Dashed) A dashed rectangle surrounding the annotation.The dash pattern may be specified by the D entry.
+ /// B (Beveled) A simulated embossed rectangle that appears to be raised above the surface of the page.
+ /// I (Inset) A simulated engraved rectangle that appears to be recessed below the surface of the page.
+ /// U (Underline) A single line along the bottom of the annotation rectangle.
+ /// An interactive PDF processor shall tolerate other border styles that it does not recognise + /// and shall use the default value (which is S). + ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string S = "/S"; + + /// + /// (Optional) A dash array defining a pattern of dashes and gaps that shall be used in drawing + /// a dashed border (border style D in the S entry). The dash array shall be specified in the + /// same format as in the line dash pattern parameter of the graphics state. The dash phase + /// shall not be specified and shall be assumed to be 0. + /// EXAMPLE + /// A D entry of[3 2] specifies a border drawn with 3-point dashes alternating with 2-point gaps. + /// Default value: [3]. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string D = "/D"; + + // ReSharper restore InconsistentNaming + + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfCaretAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfCaretAnnotation.cs new file mode 100644 index 00000000..d6331ae1 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfCaretAnnotation.cs @@ -0,0 +1,80 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF caret annotation. + /// + public sealed class PdfCaretAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.11 Caret annotations / Page 493 + + /// + /// Initializes a new instance of the class. + /// + public PdfCaretAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfCaretAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Caret); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + // Reference 2.0: Table 183 — Additional entries specific to a caret annotation / Page 493 + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + // ReSharper disable InconsistentNaming + + /// + /// (Optional; PDF 1.5) A set of four numbers that shall describe the numerical differences + /// between two rectangles: the Rect entry of the annotation and the actual boundaries + /// of the underlying caret. Such a difference can occur. When a paragraph symbol specified + /// by Sy is displayed along with the caret. + /// The four numbers shall correspond to the differences in default user space between + /// the left, top, right, and bottom coordinates of Rect and those of the caret, respectively. + /// Each value shall be greater than or equal to 0. The sum of the top and bottom differences + /// shall be less than the height of Rect, and the sum of the left and right differences + /// shall be less than the width of Rect. + /// + //[KeyInfo("1.5", KeyType.Rectangle | KeyType.Optional)] + [KeyInfo("1.5", KeyType.Array | KeyType.Optional)] + public const string RD = "/RD"; // left, top, right, bottom + + /// + /// (Optional) A name specifying a symbol that shall be associated with the caret: + /// P A new paragraph symbol(¶) shall be associated with the caret. + /// None No symbol shall be associated with the caret. + /// Default value: None. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Sy = "/Sy"; + + // ReSharper restore InconsistentNaming + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFileAttachmentAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFileAttachmentAnnotation.cs new file mode 100644 index 00000000..34c6dbd8 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFileAttachmentAnnotation.cs @@ -0,0 +1,66 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF file attachment annotation. + /// + public sealed class PdfFileAttachmentAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.15 File attachment annotations / Page 496 + + /// + /// Initializes a new instance of the class. + /// + public PdfFileAttachmentAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFileAttachmentAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.FileAttachment); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + /// + /// (Required) The file associated with this annotation. + /// + [KeyInfo(KeyType.FileSpecification | KeyType.Required)] + public const string FS = "/FS"; + + /// + /// (Optional) The name of an icon that shall be used in displaying the annotation. + /// PDF writers should include this entry and PDF readers should provide predefined + /// icon appearances for at least the following standard names: + /// Graph, PushPin, Paperclip, Tag + /// Additional names may be supported as well.Default value: PushPin. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Name = "/Name"; + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFreeTextAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFreeTextAnnotation.cs new file mode 100644 index 00000000..7df0d0b9 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfFreeTextAnnotation.cs @@ -0,0 +1,153 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF free text annotation. + /// + public sealed class PdfFreeTextAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.6 Free text annotations / Page 484 + + /// + /// Initializes a new instance of the class. + /// + public PdfFreeTextAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFreeTextAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.FreeText); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + // ReSharper disable InconsistentNaming + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + /// + /// (Required) The default appearance string that shall be used in formatting the text. + /// The annotation dictionary’s AP entry, if present, shall take precedence over the DA + /// entry. + /// + [KeyInfo(KeyType.String | KeyType.Required)] + public const string DA = "/DA"; + + /// + /// (Optional; PDF 1.4) A code specifying the form of quadding (justification) that shall + /// be used in displaying the annotation’s text: + /// 0 Left-justified + /// 1 Centred + /// 2 Right-justified + /// Default value: 0 (left-justified). + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string Q = "/Q"; + + /// + /// (Optional; PDF 1.5) A rich text string that shall be used to generate the appearance + /// of the annotation. + /// NOTE As freetext annotations do not have an open state this cannot apply to the popup + /// window as described for the RC key in "Table 172 — Additional entries in an annotation + /// dictionary specific to markup annotations". + /// + //[KeyInfo(KeyType.TextString | KeyType.Stream | KeyType.Optional)] // @@@@STLA + [KeyInfo(KeyType.TextStringOrStream | KeyType.Optional)] // @@@@STLA // #US373 + public const string RC = "/RC"; + + /// + /// (Optional; PDF 1.5) A default style string, as described in Adobe XML Architecture, + /// XML Forms Architecture (XFA) Specification, version 3.3. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string DS = "/DS"; + + /// + /// (Optional; meaningful only if IT is FreeTextCallout; PDF 1.6) An array of four or six + /// numbers specifying a callout line attached to the free text annotation. Six numbers + /// [x1 y1 x2 y2 x3 y3] represent the starting, knee point, and ending coordinates of the + /// line in default user space. Four numbers [x1 y1 x2 y2] represent the starting and ending + /// coordinates of the line. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string CL = "/CL"; + + /// + /// (Optional; PDF 1.6) A name describing the intent of the free text annotation. The following + /// values shall be valid: + /// FreeText The annotation is intended to function as a plain free-text annotation.A plain + /// free-text annotation is also known as a text box comment. + /// FreeTextCallout The annotation is intended to function as a callout. The callout is + /// associated with an area on the page through the callout line specified in CL. + /// FreeTextTypeWriter The annotation is intended to function as a click-to-type or typewriter + /// object and no callout line is drawn. + /// Default value: FreeText + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string IT = "/IT"; + + /// + /// (Optional; PDF 1.6) A border effect dictionary used in conjunction with the border style + /// dictionary specified by the BS entry. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string BE = "/BE"; + + /// + /// (Optional; PDF 1.6) A set of four numbers describing the numerical differences between + /// two rectangles: the Rect entry of the annotation and a rectangle contained within that + /// rectangle. The inner rectangle is where the annotation’s text should be displayed. + /// Any border styles and/or border effects specified by BS and BE entries, respectively, + /// shall be applied to the border of the inner rectangle. + /// The four numbers correspond to the differences in default user space between the left, + /// top, right, and bottom coordinates of Rect and those of the inner rectangle, respectively. + /// Each value shall be greater than or equal to 0. The sum of the top and bottom differences + /// shall be less than the height of Rect, and the sum of the left and right differences + /// shall be less than the width of Rect. + /// + [KeyInfo(KeyType.Rectangle | KeyType.Optional)] + public const string RD = "/RD"; + + /// + /// (Optional; PDF 1.6) A border style dictionary specifying the line width and dash pattern + /// that shall be used in drawing the annotation’s border. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string BS = "/BS"; + + /// + /// (Optional; meaningful only if CL is present; PDF 1.6) A name specifying the line ending + /// style that shall be used in drawing the callout line specified in CL. The name shall + /// specify the line ending style for the endpoint defined by the pairs of coordinates (x1, y1). + /// Default value: None. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string LE = "/LE"; + + // ReSharper restore InconsistentNaming + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfGenericAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfGenericAnnotation.cs deleted file mode 100644 index a2e5f5c9..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfGenericAnnotation.cs +++ /dev/null @@ -1,33 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -namespace PdfSharp.Pdf.Annotations -{ - /// - /// Represents a generic annotation. Used for annotation dictionaries unknown to PDFsharp. - /// - sealed class PdfGenericAnnotation : PdfAnnotation - { - // DMH 6/7/06 - // Make this public so we can use it in PdfAnnotations to - // get the metadata from existing annotations. - public PdfGenericAnnotation(PdfDictionary dict) - : base(dict) - { } - - /// - /// Predefined keys of this dictionary. - /// - internal new class Keys : PdfAnnotation.Keys - { - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - - static DictionaryMeta? _meta; - } - - /// - /// Gets the KeysMeta of this dictionary type. - /// - internal override DictionaryMeta Meta => Keys.Meta; - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfInkAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfInkAnnotation.cs new file mode 100644 index 00000000..368bf82a --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfInkAnnotation.cs @@ -0,0 +1,82 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF ink annotation. + /// + public sealed class PdfInkAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.13 Ink annotations / Page 494 + + /// + /// Initializes a new instance of the class. + /// + public PdfInkAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfInkAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Ink); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Required) An array of n arrays, each representing a stroked path. Each array shall + /// be a series of alternating horizontal and vertical coordinates in default user space, + /// specifying points along the path. When drawn, the points shall be connected by straight + /// lines or curves in an implementation-dependent way. + /// + [KeyInfo(KeyType.Array | KeyType.Required)] + public const string InkList = "/InkList"; + + /// + /// (Optional) A border style dictionary (see "Table 168 — Entries in a border style dictionary") + /// specifying the line width and dash pattern that shall be used in drawing the paths. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string BS = "/BS"; + + /// + /// (Optional; PDF 2.0) An array of n arrays, each supplying the operands for a path building + /// operator (m, l or c). Each of the n arrays shall contain pairs of values specifying + /// the points(x and y values) for a path drawing operation. The first array shall be of + /// length 2 and specifies the operand of a moveto operator which establishes a current point. + /// Subsequent arrays of length 2 specify the operands of lineto operators. Arrays of length + /// 6 specify the operands for curveto operators. Each array is processed in sequence to + /// construct the path. The current graphics state shall control the path width, + /// dash pattern, etc. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string Path = "/Path"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLineAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLineAnnotation.cs new file mode 100644 index 00000000..52dbf1d8 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLineAnnotation.cs @@ -0,0 +1,166 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF Line annotation. + /// + public sealed class PdfLineAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.7 Line annotations / Page 486 + + /// + /// Initializes a new instance of the class. + /// + public PdfLineAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfLineAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Line); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + // ReSharper disable InconsistentNaming + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + /// + /// (Required) An array of four numbers, [x1 y1 x2 y2], specifying the starting and ending + /// coordinates of the line in default user space. + /// If the LL entry is present, this value shall represent the endpoints of the leader lines + /// rather than the endpoints of the line itself. + /// + [KeyInfo(KeyType.Array | KeyType.Required)] + public const string L = "/L"; + + /// + /// (Optional) A border style dictionary specifying the width and dash pattern that shall + /// be used in drawing the line. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string BS = "/BS"; + + /// + /// (Optional; PDF 1.4) An array of two names specifying the line ending styles that shall + /// be used in drawing the line. The first and second elements of the array shall specify + /// the line ending styles for the endpoints defined, respectively, by the first and second + /// pairs of coordinates, (x1, y1 ) and (x2, y2 ), in the L array. + /// "Table 179 — Line ending styles" shows the permitted values. Default value: [ /None /None ]. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string LE = "/LE"; + + /// + /// (Optional; PDF 1.4) An array of numbers in the range 0.0 to 1.0 specifying the interior + /// colour that shall be used to fill the annotation’s line endings. The number of array + /// elements shall determine the colour space in which the colour is defined: + /// 0 No colour; transparent + /// 1 DeviceGray + /// 3 DeviceRGB + /// 4 DeviceCMYK + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string IC = "/IC"; + + /// + /// (Required if LLE is present, otherwise optional; PDF 1.6) The length of leader lines + /// in default user space that extend from each endpoint of the line perpendicular to the + /// line itself. A positive value shall mean that the leader lines appear in the direction + /// that is clockwise when traversing the line from its starting point to its ending point + /// (as specified by L); a negative value shall indicate the opposite direction. + /// Default value: 0 (no leader lines). + /// + [KeyInfo(KeyType.Real | KeyType.Required)] + public const string LL = "/LL"; + + /// + /// (Optional; PDF 1.6) A non-negative number that shall represent the length of leader + /// line extensions that extend from the line proper 180 degrees from the leader lines. + /// Default value: 0 (no leader line extensions). + /// + [KeyInfo(KeyType.Real | KeyType.Optional)] + public const string LLE = "/LLE"; + + /// + /// (Optional; PDF 1.6) If true, the text specified by the Contents or RC entries shall + /// be replicated as a caption in the appearance of the line. The text shall be rendered + /// in a manner appropriate to the content, taking into account factors such as writing + /// direction. + /// Default value: false. + /// + [KeyInfo(KeyType.Boolean | KeyType.Optional)] + public const string Cap = "/Cap"; + + /// + /// (Optional; PDF 1.6) A name describing the intent of the line annotation. Valid values + /// shall be LineArrow, which means that the annotation is intended to function as an arrow, + /// and LineDimension, which means that the annotation is intended to function as a dimension + /// line. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string IT = "/IT"; + + /// + /// (Optional; PDF 1.7) A non-negative number that shall represent the length of the leader + /// line offset, which is the amount of empty space between the endpoints of the annotation + /// and the beginning of the leader lines. + /// + [KeyInfo(KeyType.Real | KeyType.Optional)] + public const string LLO = "/LLO"; + + /// + /// (Optional; meaningful only if Cap is true; PDF 1.7) A name describing the annotation’s + /// caption positioning. Valid values are Inline, meaning the caption shall be centred inside + /// the line, and Top, meaning the caption shall be on top of the line. + /// Default value: Inline + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string CP = "/CP"; + + /// + /// (Optional; PDF 1.7) A measure dictionary that shall specify the scale and units that + /// apply to the line annotation. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string Measure = "/Measure"; + + /// + /// (Optional; meaningful only if Cap is true; PDF 1.7) An array of two numbers that shall + /// specify the offset of the caption text from its normal position. The first value shall + /// be the horizontal offset along the annotation line from its midpoint, with a positive + /// value indicating offset to the right and a negative value indicating offset to the left. + /// The second value shall be the vertical offset perpendicular to the annotation line, + /// with a positive value indicating a shift up and a negative value indicating a shift down. + /// Default value: [0, 0] (no offset from normal positioning) + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string CO = "/CO"; + + // ReSharper restore InconsistentNaming + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLinkAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLinkAnnotation.cs index 38ec5c04..40621011 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLinkAnnotation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfLinkAnnotation.cs @@ -6,102 +6,158 @@ using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Internal; +// v7.0.0 TODO review creator functions + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Annotations +{ + // Just a hack to make MigraDoc work with this code. + enum PdfLinkAnnotationTypes + { + None, + Document, + NamedDestination, + Web, + File // TODO: un-nest + } +} + namespace PdfSharp.Pdf.Annotations { /// - /// Represents a link annotation. + /// Represents a PDF link annotation. /// public sealed class PdfLinkAnnotation : PdfAnnotation { - // Just a hack to make MigraDoc work with this code. - enum LinkType - { - None, Document, NamedDestination, Web, File - } + // Reference 2.0: 12.5.6.5 Link annotations / Page 482 + + [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")] + public PdfLinkAnnotation() + => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter."); /// /// Initializes a new instance of the class. /// - public PdfLinkAnnotation() + public PdfLinkAnnotation(PdfDocument document) + : base(document) { - _linkType = LinkType.None; - Elements.SetName(PdfAnnotation.Keys.Subtype, "/Link"); + Initialize(); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// - public PdfLinkAnnotation(PdfDocument document) - : base(document) + internal PdfLinkAnnotation(PdfDictionary dict) + : base(dict) + { + Initialize(); + } + + void Initialize() { - _linkType = LinkType.None; - Elements.SetName(PdfAnnotation.Keys.Subtype, "/Link"); + _linkType = PdfLinkAnnotationTypes.None; + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Link); } + const string ObsoleteMessage = "PDFsharp 6.4: Parameter requires a PDF page as first parameter."; + + [Obsolete(ObsoleteMessage)] + public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, int destinationPage, XPoint? point = null) + => throw new NotImplementedException(ObsoleteMessage); + /// /// Creates a link within the current document. /// + /// The page the annotation belong to. /// The link area in default page coordinates. /// The one-based destination page number. /// The position in the destination page. - public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, int destinationPage, XPoint? point = null) + public static PdfLinkAnnotation CreateDocumentLink(PdfPage page, PdfRectangle rect, int destinationPage, XPoint? point = null) { + EnsurePageHasDocument(page); + if (destinationPage < 1) throw new ArgumentException("Invalid destination page in call to CreateDocumentLink: page number is one-based and must be 1 or higher.", nameof(destinationPage)); - PdfLinkAnnotation link = new PdfLinkAnnotation(); - link._linkType = LinkType.Document; - link.Rectangle = rect; - link._destPage = destinationPage; - link._point = point; + var link = new PdfLinkAnnotation(page.Document) + { + _linkType = PdfLinkAnnotationTypes.Document, + Rectangle = rect, + _destPage = destinationPage, + _point = point + }; + page.Annotations.Add(link); return link; } int _destPage; - LinkType _linkType; + PdfLinkAnnotationTypes _linkType; string _url = ""; - private XPoint? _point = null; + XPoint? _point = null; + + [Obsolete(ObsoleteMessage)] + public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string destinationName) + => throw new NotImplementedException(ObsoleteMessage); /// /// Creates a link within the current document using a named destination. /// + /// The page the annotation belong to. /// The link area in default page coordinates. /// The named destination’s name. - public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string destinationName) + public static PdfLinkAnnotation CreateDocumentLink(PdfPage page, PdfRectangle rect, string destinationName) { - var link = new PdfLinkAnnotation + EnsurePageHasDocument(page); + + var link = new PdfLinkAnnotation(page.Document) { - _linkType = LinkType.NamedDestination, + _linkType = PdfLinkAnnotationTypes.NamedDestination, Rectangle = rect, _action = PdfGoToAction.CreateGoToAction(destinationName) }; + page.Annotations.Add(link); return link; } - PdfAction _action = default!; + PdfAction _action = null!; + + [Obsolete(ObsoleteMessage)] + public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string documentPath, string destinationName, bool? newWindow = null) + => throw new NotImplementedException(ObsoleteMessage); /// /// Creates a link to an external PDF document using a named destination. /// + /// The page the annotation belong to. /// The link area in default page coordinates. /// The path to the target document. /// The named destination’s name in the target document. /// True, if the destination document shall be opened in a new window. /// If not set, the viewer application should behave in accordance with the current user preference. - public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string documentPath, string destinationName, bool? newWindow = null) + public static PdfLinkAnnotation CreateDocumentLink(PdfPage page, PdfRectangle rect, string documentPath, string destinationName, bool? newWindow = null) { - var link = new PdfLinkAnnotation + EnsurePageHasDocument(page); + + var link = new PdfLinkAnnotation(page.Document) { - _linkType = LinkType.NamedDestination, + _linkType = PdfLinkAnnotationTypes.NamedDestination, Rectangle = rect, _action = PdfRemoteGoToAction.CreateRemoteGoToAction(documentPath, destinationName, newWindow) }; + page.Annotations.Add(link); return link; } + [Obsolete(ObsoleteMessage)] + public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, string destinationPath, bool? newWindow = null) + => throw new NotImplementedException(ObsoleteMessage); + /// /// Creates a link to an embedded document. /// + /// The page the annotation belong to. /// The link area in default page coordinates. /// The path to the named destination through the embedded documents. /// The path is separated by '\' and the last segment is the name of the named destination. @@ -109,18 +165,27 @@ public static PdfLinkAnnotation CreateDocumentLink(PdfRectangle rect, string doc /// ".." references to the parent, other strings refer to a child with this name in the EmbeddedFiles name dictionary. /// True, if the destination document shall be opened in a new window. /// If not set, the viewer application should behave in accordance with the current user preference. - public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, string destinationPath, bool? newWindow = null) + public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfPage page, PdfRectangle rect, string destinationPath, bool? newWindow = null) { - PdfLinkAnnotation link = new PdfLinkAnnotation(); - link._linkType = LinkType.NamedDestination; + EnsurePageHasDocument(page); + + //PdfLinkAnnotation link = new PdfLinkAnnotation(page); // TODO: Dangerous bug. Page binds to PdfDictionary and creates a type transformation. + PdfLinkAnnotation link = new PdfLinkAnnotation(page.Document); + link._linkType = PdfLinkAnnotationTypes.NamedDestination; link.Rectangle = rect; link._action = PdfEmbeddedGoToAction.CreatePdfEmbeddedGoToAction(destinationPath, newWindow); + page.Annotations.Add(link); return link; } + [Obsolete(ObsoleteMessage)] + public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, string documentPath, string destinationPath, bool? newWindow = null) + => throw new NotImplementedException(ObsoleteMessage); + /// /// Creates a link to an embedded document in another document. /// + /// The page the annotation belong to. /// The link area in default page coordinates. /// The path to the target document. /// The path to the named destination through the embedded documents in the target document. @@ -129,41 +194,64 @@ public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, st /// Each segment name refers to a child with this name in the EmbeddedFiles name dictionary. /// True, if the destination document shall be opened in a new window. /// If not set, the viewer application should behave in accordance with the current user preference. - public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfRectangle rect, string documentPath, string destinationPath, bool? newWindow = null) + public static PdfLinkAnnotation CreateEmbeddedDocumentLink(PdfPage page, PdfRectangle rect, string documentPath, string destinationPath, bool? newWindow = null) { - PdfLinkAnnotation link = new PdfLinkAnnotation(); - link._linkType = LinkType.NamedDestination; + EnsurePageHasDocument(page); + + PdfLinkAnnotation link = new PdfLinkAnnotation(page.Document); + link._linkType = PdfLinkAnnotationTypes.NamedDestination; link.Rectangle = rect; link._action = PdfEmbeddedGoToAction.CreatePdfEmbeddedGoToAction(documentPath, destinationPath, newWindow); + page.Annotations.Add(link); return link; } + [Obsolete(ObsoleteMessage)] + public static PdfLinkAnnotation CreateWebLink(PdfRectangle rect, string url) + => throw new NotImplementedException(ObsoleteMessage); + /// /// Creates a link to the web. /// - public static PdfLinkAnnotation CreateWebLink(PdfRectangle rect, string url) + /// The page the annotation belong to. + /// The rectangle using World coordinates. + /// The destination URL. + public static PdfLinkAnnotation CreateWebLink(PdfPage page, PdfRectangle rect, string url) { - var link = new PdfLinkAnnotation + EnsurePageHasDocument(page); + + var link = new PdfLinkAnnotation(page.Document) { - _linkType = PdfLinkAnnotation.LinkType.Web, + _linkType = PdfLinkAnnotationTypes.Web, Rectangle = rect, _url = url }; + page.Annotations.Add(link); return link; } + [Obsolete("PDFsharp 6.4: Requires a PDF page as first parameter.")] + public static PdfLinkAnnotation CreateFileLink(PdfRectangle rect, string fileName) + => throw new NotImplementedException(""); + /// /// Creates a link to a file. /// - public static PdfLinkAnnotation CreateFileLink(PdfRectangle rect, string fileName) + /// The page the annotation belong to. + /// The rectangle using World coordinates. + /// The destination file. + public static PdfLinkAnnotation CreateFileLink(PdfPage page, PdfRectangle rect, string fileName) { - var link = new PdfLinkAnnotation + EnsurePageHasDocument(page); + + var link = new PdfLinkAnnotation(page.Document) { - _linkType = LinkType.File, + _linkType = PdfLinkAnnotationTypes.File, // TODO_OLD: Adjust bleed box here (if possible) Rectangle = rect, _url = fileName }; + page.Annotations.Add(link); return link; } @@ -177,55 +265,52 @@ internal override void WriteObject(PdfWriter writer) // Older Adobe Reader versions uses a border width of 0 as default value if neither Border nor BS are present. // But the PDF Reference specifies: - // "If neither the Border nor the BS entry is present, the border is drawn as a solid line with a width of 1 point." + // “If neither the Border nor the BS entry is present, the border is drawn as a solid line with a width of 1 point.” // After this issue was fixed in newer Reader versions older PDFsharp created documents show an ugly solid border. // The following hack fixes this by specifying a 0 width border. - if (Elements[PdfAnnotation.Keys.BS] == null) - Elements[PdfAnnotation.Keys.BS] = new PdfLiteral("<>"); + //if (Elements[Keys.BS] == null) // TODO #US373 Just a null check. + if (!Elements.HasValue(Keys.BS)) // #US373 + Elements[Keys.BS] = new PdfLiteral("<>"); // May be superfluous. See comment above. - if (Elements[PdfAnnotation.Keys.Border] == null) + //if (Elements[PdfAnnotation.Keys.Border] == null) // TODO #US373 Just a null check. + if (!Elements.HasValue(PdfAnnotation.Keys.Border)) // #US373 Elements[PdfAnnotation.Keys.Border] = new PdfLiteral("[0 0 0]"); switch (_linkType) { - case LinkType.None: + case PdfLinkAnnotationTypes.None: break; - case LinkType.Document: + case PdfLinkAnnotationTypes.Document: // destIndex > Owner.PageCount can happen when rendering pages using PDFsharp directly. int destIndex = _destPage; if (destIndex > Owner.PageCount) destIndex = Owner.PageCount; destIndex--; dest = Owner.Pages[destIndex]; - ////pdf.AppendFormat("/Dest[{0} 0 R/XYZ null null 0]\n", dest.ObjectID); - //Elements[Keys.Dest] = new PdfLiteral("[{0} 0 R/XYZ null null 0]", dest.ObjectNumber); + // TODO: Use format strings for double. if (_point.HasValue) { - Elements[Keys.Dest] = new PdfLiteral(Invariant($"[{dest.ObjectNumber} 0 R /XYZ {_point.Value.X} {_point.Value.Y} 0]") /*, dest.ObjectNumber, _point.Value.X, _point.Value.Y*/); + Elements[Keys.Dest] = new PdfLiteral(Invariant($"[{dest.ObjectNumber} 0 R /XYZ {_point.Value.X} {_point.Value.Y} 0]")); } else { - Elements[Keys.Dest] = new PdfLiteral(Invariant($"[{dest.ObjectNumber} 0 R /XYZ null null 0]") /*, dest.ObjectNumber*/); + Elements[Keys.Dest] = new PdfLiteral(Invariant($"[{dest.ObjectNumber} 0 R /XYZ null null 0]")); } break; - case LinkType.NamedDestination: - Elements[PdfAnnotation.Keys.A] = _action; + case PdfLinkAnnotationTypes.NamedDestination: + Elements[Keys.A] = _action; break; - case LinkType.Web: - //pdf.AppendFormat("/A<>\n", PdfEncoders.EncodeAsLiteral(url)); - Elements[PdfAnnotation.Keys.A] = new PdfLiteral("<>", //PdfEncoders.EncodeAsLiteral(url)); + case PdfLinkAnnotationTypes.Web: + Elements[Keys.A] = new PdfLiteral("<>", PdfEncoders.ToStringLiteral(_url, PdfStringEncoding.WinAnsiEncoding, writer.EffectiveSecurityHandler)); break; - case LinkType.File: - //pdf.AppendFormat("/A<> >>\n", - // PdfEncoders.EncodeAsLiteral(url)); - Elements[PdfAnnotation.Keys.A] = new PdfLiteral("<> >>", - //PdfEncoders.EncodeAsLiteral(url)); + case PdfLinkAnnotationTypes.File: + Elements[Keys.A] = new PdfLiteral("<>>>", PdfEncoders.ToStringLiteral(_url, PdfStringEncoding.WinAnsiEncoding, writer.EffectiveSecurityHandler)); break; } @@ -235,49 +320,77 @@ internal override void WriteObject(PdfWriter writer) /// /// Predefined keys of this dictionary. /// - internal new class Keys : PdfAnnotation.Keys + public new class Keys : PdfAnnotation.Keys { - // /// - // /// (Required) The type of annotation that this dictionary describes; - // /// must be Link for a link annotation. - // /// - // inherited from base class + // Reference 2.0: Table 176 — Additional entries specific to a link annotation / Page 483 + + // ReSharper disable InconsistentNaming + + /// + /// (Optional; PDF 1.1) An action that shall be performed when the link annotation is activated. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Required)] + public const string A = "/A"; /// - /// (Optional; not permitted if an A entry is present) A destination to be displayed - /// when the annotation is activated. + /// (Optional; not permitted if an A entry is present) A destination that shall be displayed + /// when the annotation is activated (12.3.2, "Destinations"). /// [KeyInfo(KeyType.ArrayOrNameOrString | KeyType.Optional)] public const string Dest = "/Dest"; /// - /// (Optional; PDF 1.2) The annotation’s highlighting mode, the visual effect to be - /// used when the mouse button is pressed or held down inside its active area: - /// N (None) No highlighting. - /// I (Invert) Invert the contents of the annotation rectangle. - /// O (Outline) Invert the annotation’s border. - /// P (Push) Display the annotation as if it were being pushed below the surface of the page. + /// (Optional; PDF 1.2) The annotation’s highlighting mode, the visual effect that shall be + /// used when the mouse button is pressed or held down inside its active area:
+ /// N (None) No highlighting.
+ /// I (Invert) Invert the contents of the annotation rectangle.
+ /// O (Outline) Invert the annotation’s border.
+ /// P (Push) Display the annotation as if it were being pushed below the surface of the page.
/// Default value: I. - /// Note: In PDF 1.1, highlighting is always done by inverting colors inside the annotation rectangle. ///
[KeyInfo("1.2", KeyType.Name | KeyType.Optional)] public const string H = "/H"; /// - /// (Optional; PDF 1.3) A URI action formerly associated with this annotation. When Web - /// Capture changes and annotation from a URI to a go-to action, it uses this entry to save - /// the data from the original URI action so that it can be changed back in case the target page for - /// the go-to action is subsequently deleted. + /// (Optional; PDF 1.3) A URI action (see 12.6.4.8, "URI actions") formerly associated with this + /// annotation. When a PDF processor changes an annotation from a URI to a go-to action, it may + /// use this entry to save the data from the original URI action so that it can be changed back + /// in case the target page for the go-to action is subsequently deleted. /// [KeyInfo("1.3", KeyType.Dictionary | KeyType.Optional)] public const string PA = "/PA"; - // QuadPoints + /// + /// (Optional; PDF 1.6) An array of 8×𝑛 numbers specifying the coordinates of n quadrilaterals + /// in default user space that comprise the region in which the link should be activated. The + /// coordinates for each quadrilateral are given in the order:
+ /// 𝑥1 𝑦1 𝑥2 𝑦2 𝑥3 𝑦3 𝑥4 𝑦4
+ /// specifying the four vertices of the quadrilateral in counterclockwise order.For orientation + /// purposes, such as when applying an underline border style, the bottom of a quadrilateral is + /// the line formed by (x1, y1) and (x2, y2).
+ /// If this entry is not present, or the PDF processor does not recognise it, or if any + /// coordinates in the QuadPoints array lie outside the region specified by Rect then the + /// activation region for the link annotation shall be defined by its Rect entry.
+ /// NOTE
+ /// The last paragraph above was clarified in this document (2020). + ///
+ [KeyInfo("1.6", KeyType.Array | KeyType.Optional)] + public const string QuadPoints = "/QuadPoints"; + + + /// + /// (Optional; PDF 1.6) A border style dictionary specifying the line width and dash pattern + /// that shall be used in drawing the annotation’s border. + /// + [KeyInfo("1.6", KeyType.Dictionary | KeyType.Optional)] + public const string BS = "/BS"; + + // ReSharper restore InconsistentNaming /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMarkupAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMarkupAnnotation.cs new file mode 100644 index 00000000..ad7d11e9 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMarkupAnnotation.cs @@ -0,0 +1,166 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Base class of all Markup annotations. + /// + public abstract class PdfMarkupAnnotation : PdfAnnotation + { + // Reference 2.0: 12.5.6.2 Markup annotations / Page 477 + [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")] + protected PdfMarkupAnnotation() + => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter."); + + /// + /// Initializes a new instance of the class. + /// + protected PdfMarkupAnnotation(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of the class. + /// + protected PdfMarkupAnnotation(PdfDictionary dict) + : base(dict) + { } + + /// + /// Gets or sets the text label to be displayed in the title bar of the annotation’s + /// pop-up window when open and active. By convention, this entry identifies + /// the user who added the annotation. + /// + public string Title + { + get => Elements.GetString(Keys.T, true); + set + { + Elements.SetString(Keys.T, value); + Elements.SetDateTime(PdfAnnotation.Keys.M, DateTimeOffset.Now); + } + } + + /// + /// Gets or sets text representing a short description of the subject being + /// addressed by the annotation. + /// + public string Subject + { + get => Elements.GetString(Keys.Subj, true); + set + { + Elements.SetString(Keys.Subj, value); + Elements.SetDateTime(PdfAnnotation.Keys.M, DateTimeOffset.Now); + } + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + // Reference 2.0: Table 172 — Additional entries in an annotation dictionary specific to markup annotations / Page 479 + + // ReSharper disable InconsistentNaming + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + /// + /// (Optional; PDF 1.1) The text label that shall be displayed in the title bar of the + /// annotation’s popup window when open and active. This entry shall identify the user + /// who added the annotation. + /// + [KeyInfo("1.1", KeyType.TextString | KeyType.Optional)] + public const string T = "/T"; + + /// + /// (Optional; PDF 1.3) An indirect reference to a pop-up annotation for entering or + /// editing the text associated with this annotation. + /// + [KeyInfo("1.3", KeyType.Dictionary | KeyType.Optional)] + public const string Popup = "/Popup"; + + /// + /// (Optional; PDF 1.5) A rich text string (see Adobe XML Architecture, XML Forms + /// Architecture (XFA) Specification, version 3.3) that shall be displayed in the + /// popup window when the annotation is opened. + /// + [KeyInfo("1.5", KeyType.TextStringOrTextStream | KeyType.Optional)] + public const string RC = "/RC"; + + /// + /// (Optional; PDF 1.5) The date and time (7.9.4, "Dates") when the annotation + /// was created. + /// + [KeyInfo("1.5", KeyType.Date | KeyType.Optional)] + public const string CreationDate = "/CreationDate"; + + /// + /// (Required if an RT entry is present, otherwise optional; PDF 1.5) A reference to + /// the annotation that this annotation is "in reply to." Both annotations shall be on + /// the same page of the document. The relationship between the two annotations shall + /// be specified by the RT entry. + /// If this entry is present in an FDF file (see 12.7.8, "Forms data format"), its type + /// shall not be a dictionary but a text string containing the contents of the NM entry + /// of the annotation being replied to, to allow for a situation where the annotation + /// being replied to is not in the same FDF file. + /// + [KeyInfo("1.5", KeyType.Dictionary | KeyType.Optional)] + public const string IRT = "/IRT"; + + /// + /// (Optional; PDF 1.5) Text representing a short description of the subject being + /// addressed by the annotation. + /// + [KeyInfo("1.5", KeyType.TextString | KeyType.Optional)] + public const string Subj = "/Subj"; + + /// + /// (Optional; meaningful only if IRT is present; PDF 1.6) A name specifying the + /// relationship (the "reply type") between this annotation and one specified + /// by IRT. + /// Valid values are: + /// R The annotation is considered a reply to the annotation specified by IRT. + /// Interactive PDF processors shall not display replies to an annotation + /// individually but together in the form of threaded comments. + /// Group The annotation shall be grouped with the annotation specified by IRT; + /// see the discussion following this Table. + /// Default value: R. + /// + [KeyInfo("1.6", KeyType.Name | KeyType.Optional)] + public const string RT = "/RT"; + + /// + /// (Optional; PDF 1.6) A name describing the intent of the markup annotation. + /// Intents allow interactive PDF processors to distinguish between different + /// uses and behaviours of a single markup annotation type. If this entry is + /// not present or its value is the same as the annotation type, the annotation + /// shall have no explicit intent and should behave in a generic manner in an + /// interactive PDF processor. Free text annotations ("Table 177 — Additional + /// entries specific to a free text annotation"), line annotations + /// ("Table 178 — Additional entries specific to a line annotation"), + /// polygon annotations ("Table 181 — Additional entries specific to a polygon + /// or polyline annotation"), (PDF 1.7) polyline annotations + /// ("Table 181 — Additional entries specific to a polygon or polyline + /// annotation") and stamp annotations (“Table 184 — Additional entries + /// specific to a rubber stamp annotation") have defined intents, whose values + /// are enumerated in the corresponding tables. + /// + [KeyInfo("1.6", KeyType.Name | KeyType.Optional)] + public const string IT = "/IT"; + + // ReSharper restore InconsistentNaming + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMovieAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMovieAnnotation.cs new file mode 100644 index 00000000..eabf2104 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfMovieAnnotation.cs @@ -0,0 +1,76 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF movie annotation. + /// + public sealed class PdfMovieAnnotation : PdfAnnotation + { + // Reference 2.0: 12.5.6.17 Movie annotations / Page 497 + + /// + /// Initializes a new instance of the class. + /// + public PdfMovieAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfMovieAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Movie); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Optional) The title of the movie annotation. Movie actions (12.6.4.10, "Movie actions") + /// may use this title to reference the movie annotation. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string T = "/T"; + + /// + /// (Required) A movie dictionary that shall describe the movie’s static characteristics + /// (see 13.4, "Movies"). + /// + [KeyInfo(KeyType.Dictionary | KeyType.Required)] + public const string Movie = "/Movie"; + + /// + /// (Optional) A flag or dictionary specifying whether and how to play the movie when the + /// annotation is activated. If this value is a dictionary, it shall be a movie activation + /// dictionary (see 13.4, "Movies") specifying how to play the movie. If the value is the + /// boolean true, the movie shall be played using default activation parameters. If the + /// value is false, the movie shall not be played. Default value: true. + /// + [KeyInfo(KeyType.BooleanOrDictionary | KeyType.Optional)] + public const string A = "/A"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPolyAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPolyAnnotation.cs new file mode 100644 index 00000000..4005fbf6 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPolyAnnotation.cs @@ -0,0 +1,188 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Base class of PdfPolygonAnnotation and PdfPolyLineAnnotation. + /// + public abstract class PdfPolyAnnotation : PdfMarkupAnnotation + { + /// + /// Initializes a new instance of the class. + /// + protected PdfPolyAnnotation(PdfDocument document) + + : base(document) + { } + + /// + /// Initializes a new instance of the class. + /// + protected PdfPolyAnnotation(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + // Reference 2.0: Table 181 — Additional entries specific to a polygon or polyline annotation / Page 491 + + // ReSharper disable InconsistentNaming + + /// + /// (Required unless a Path key is present, in which case it shall be ignored) An array + /// of numbers specifying the alternating horizontal and vertical coordinates, respectively, + /// of each vertex, in default user space. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string Vertices = "/Vertices"; + + /// + /// (Optional; meaningful only for polyline annotations) An array of two names that shall + /// specify the line ending styles. The first and second elements of the array shall specify + /// the line ending styles for the endpoints defined, respectively, by the first and last + /// pairs of coordinates in the Vertices array. "Table 179 — Line ending styles" shows the + /// allowed values. Default value: [/None /None]. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string LE = "/LE"; + + /// + /// (Optional) A border style dictionary (see "Table 168 — Entries in a border style dictionary") + /// specifying the width and dash pattern that shall be used in drawing the line. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string BS = "/BS"; + + /// + /// (Optional) An array of numbers that shall be in the range 0.0 to 1.0 and shall specify + /// the interior color with which to fill the annotation’s line endings + /// (see "Table 179 — Line ending styles"). The number of array elements determines the + /// colour space in which the colour shall be defined: + /// 0 No colour; transparent + /// 1 DeviceGray + /// 3 DeviceRGB + /// 4 DeviceCMYK + /// For Polyline annotations, the value of the IC key is used to fill only the line ending. + /// However, for Polygon annotations, the value of the IC key is used to fill the entire + /// shape, much as the F operator would fill a shape in a content stream. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string IC = "/IC"; + + /// + /// (Optional; meaningful only for polygon annotations) A border effect dictionary that + /// shall describe an effect applied to the border described by the BS entry + /// (see "Table 169 — Entries in a border effect dictionary"). + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string BE = "/BE"; + + /// + /// (Optional; PDF 1.6) A name that shall describe the intent of the polygon or polyline + /// annotation (see also "Table 172 — Additional entries in an annotation dictionary + /// specific to markup annotations"). + /// The following values shall be valid: + /// PolygonCloud The annotation is intended to function as a cloud object. + /// PolyLineDimension (PDF 1.7) The polyline annotation is intended to function as a dimension. + /// PolygonDimension (PDF 1.7) The polygon annotation is intended to function as a dimension. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string IT = "/IT"; + + /// + /// (Optional; PDF 1.7) A measure dictionary (see "Table 266 — Entries in a measure dictionary") + /// that shall specify the scale and units that apply to the annotation. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string Measure = "/Measure"; + + /// + /// (Optional; PDF 2.0) An array of n arrays, each supplying the operands for a path building + /// operator (m, l or c). If this key is present the Vertices key shall not be present. + /// Each of the n arrays shall contain pairs of values specifying the points (x and y values) + /// for a path drawing operation. The first array shall be of length 2 and specifies the + /// operand of a moveto operator which establishes a current point. Subsequent arrays of + /// length 2 specify the operands of lineto operators. Arrays of length 6 specify the operands + /// for curveto operators.Each array is processed in sequence to construct the path. The + /// current graphics state shall control the path width, dash pattern, etc. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string Path = "/Path"; + + // ReSharper restore InconsistentNaming + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + /// + /// Represents a PDF polygon annotation. + /// + public sealed class PdfPolygonAnnotation : PdfPolyAnnotation + { + // Reference 2.0: 12.5.6.9 Polygon and polyline annotations / Page 491 + + /// + /// Initializes a new instance of the class. + /// + public PdfPolygonAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfPolygonAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Polygon); + } + } + + /// + /// Represents a PDF polyline annotation. + /// + public sealed class PdfPolyLineAnnotation : PdfPolyAnnotation + { + // Reference 2.0: 12.5.6.9 Polygon and polyline annotations / Page 491 + + /// + /// Initializes a new instance of the class. + /// + public PdfPolyLineAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfPolyLineAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.PolyLine); + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPopupAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPopupAnnotation.cs new file mode 100644 index 00000000..2e1f8338 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPopupAnnotation.cs @@ -0,0 +1,69 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF popup annotation. + /// + public sealed class PdfPopupAnnotation : PdfAnnotation + { + // Reference 2.0: 12.5.6.14 Popup annotations / Page 495 + + /// + /// Initializes a new instance of the class. + /// + public PdfPopupAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfPopupAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Popup); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Optional; shall be an indirect reference) The parent annotation with which this popup + /// annotation shall be associated. If this entry is present, the parent annotation’s Contents, + /// M, C, and T entries (see "Table 170 — Entries in an appearance dictionary") shall override + /// those of the popup annotation itself. NOTE See also the Popup entry in "Table 172 — Additional + /// entries in an annotation dictionary specific to markup annotations". + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string Parent = "/Parent"; + + /// + /// (Optional) A flag specifying whether the popup annotation shall initially be displayed open. + /// Default value: false (closed). + /// + [KeyInfo(KeyType.Boolean | KeyType.Optional)] + public const string Open = "/Open"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPrinterMarkAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPrinterMarkAnnotation.cs new file mode 100644 index 00000000..5d1c4939 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfPrinterMarkAnnotation.cs @@ -0,0 +1,59 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF printer’s mark annotation. + /// + public sealed class PdfPrinterMarkAnnotation : PdfAnnotation + { + // Reference 2.0: 12.5.6.20 Printer’s mark annotations / Page 501 + + /// + /// Initializes a new instance of the class. + /// + public PdfPrinterMarkAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfPrinterMarkAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.PrinterMark); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Optional) An arbitrary name identifying the type of printer’s mark, such as ColorBar + /// or RegistrationTarget. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string MN = "/MN"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfProjectionAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfProjectionAnnotation.cs new file mode 100644 index 00000000..3222983f --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfProjectionAnnotation.cs @@ -0,0 +1,37 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF projection annotation. + /// + public sealed class PdfProjectionAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.24 Projection annotations / Page 505 + + /// + /// Initializes a new instance of the class. + /// + public PdfProjectionAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfProjectionAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Projection); + } + + // This annotation has no special keys of its own. + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRedactAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRedactAnnotation.cs new file mode 100644 index 00000000..29627d70 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRedactAnnotation.cs @@ -0,0 +1,119 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF redact annotation. + /// + public sealed class PdfRedactAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.23 Redaction annotations / Page 504 + + /// + /// Initializes a new instance of the class. + /// + public PdfRedactAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfRedactAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Redact); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Optional) An array of 8 x n numbers specifying the coordinates of n quadrilaterals + /// in default user space, as described in "Table 182 — Additional entries specific to + /// text markup annotations" for text markup annotations. If present, these quadrilaterals + /// denote the content region that is intended to be removed. If this entry is not present, + /// the Rect entry denotes the content region that is intended to be removed. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string QuadPoints = "/QuadPoints"; + + /// + /// (Optional) An array of three numbers in the range 0.0 to 1.0 specifying the components, + /// in the DeviceRGB colour space, of the interior colour with which to fill the redacted + /// region after the affected content has been removed. If this entry is absent, the interior + /// of the redaction region is left transparent. + /// This entry is ignored if the RO entry is present. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string IC = "/IC"; + + /// + /// (Optional) A form XObject specifying the overlay appearance for this redaction annotation. + /// After this redaction is applied and the affected content has been removed, the overlay + /// appearance should be drawn such that its origin lines up with the lower-left corner + /// of the annotation rectangle. This form XObject is not necessarily related to other + /// annotation appearances, and may or may not be present in the AP dictionary. This entry + /// takes precedence over the IC, OverlayText, DA, and Q entries. + /// + [KeyInfo(KeyType.Stream | KeyType.Optional)] + public const string RO = "/RO"; + + /// + /// (Optional) A text string specifying the overlay text that should be drawn over the redacted + /// region after the affected content has been removed. This entry is ignored if the RO entry + /// is present. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string OverlayText = "/OverlayText"; + + /// + /// (Optional) If true, then the text specified by OverlayText should be repeated to fill + /// the redacted region after the affected content has been removed. This entry is ignored + /// if the RO entry is present. Default value: false. + /// + [KeyInfo(KeyType.Boolean | KeyType.Optional)] + public const string Repeat = "/Repeat"; + + /// + /// (Required if OverlayText is present, ignored otherwise) The appearance string that shall + /// be used in formatting the overlay text when it is drawn after the affected content has + /// been removed (see 12.7.4.3, "Variable text"). This entry is ignored if the RO entry + /// is present. + /// + [KeyInfo(KeyType.ByteString | KeyType.Optional)] + public const string DA = "/DA"; + + /// + /// (Optional) A code specifying the form of quadding (justification) that shall be used + /// in laying out the overlay text: + /// 0 Left-justified + /// 1 Centred + /// 2 Right-justified This entry is ignored if the RO entry is present. + /// Default value: 0 (left-justified). + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string Q = "/Q"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRichMediaAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRichMediaAnnotation.cs new file mode 100644 index 00000000..c4fa7d85 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRichMediaAnnotation.cs @@ -0,0 +1,71 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF RichMedia annotation. + /// + public sealed class PdfRichMediaAnnotation : PdfAnnotation + { + // Reference 2.0: 13.7.2 RichMedia annotations / Page 700 + + /// + /// Initializes a new instance of the class. + /// + public PdfRichMediaAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfRichMediaAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.RichMedia); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Required; PDF 2.0) A RichMediaContent dictionary that stores the rich media artwork + /// and information as to how it should be configured and viewed. See "Table 341 — Entries + /// in a RichMediaContent dictionary". + /// + [KeyInfo("2.0", KeyType.Dictionary | KeyType.Required)] + public const string RichMediaContent = "/RichMediaContent"; + + /// + /// (Optional; PDF 2.0) A RichMediaSettings dictionary that stores conditions and responses + /// that determine when the annotation should be activated and deactivated by an interactive + /// PDF processor and the initial state of artwork in those states. See "Table 334 — Entries + /// in a RichMediaSettings dictionary". Default value: If no RichMediaSettings dictionary + /// is present, the first configuration is loaded. + /// + [KeyInfo("2.0", KeyType.Dictionary | KeyType.Required)] + public const string RichMediaSettings = "/RichMediaSettings"; + + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRubberStampAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRubberStampAnnotation.cs deleted file mode 100644 index 9550f9e7..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfRubberStampAnnotation.cs +++ /dev/null @@ -1,102 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using System; -using PdfSharp.Drawing; - -namespace PdfSharp.Pdf.Annotations -{ - /// - /// Represents a rubber stamp annotation. - /// - public sealed class PdfRubberStampAnnotation : PdfAnnotation - { - /// - /// Initializes a new instance of the class. - /// - public PdfRubberStampAnnotation() - { - Initialize(); - } - - /// - /// Initializes a new instance of the class. - /// - /// The document. - public PdfRubberStampAnnotation(PdfDocument document) - : base(document) - { - Initialize(); - } - - void Initialize() - { - Elements.SetName(Keys.Subtype, "/Stamp"); - Color = XColors.Yellow; - } - - /// - /// Gets or sets an icon to be used in displaying the annotation. - /// - public PdfRubberStampAnnotationIcon Icon - { - get - { - string value = Elements.GetName(Keys.Name); - if (value == "") - return PdfRubberStampAnnotationIcon.NoIcon; - value = value.Substring(1); - if (!Enum.IsDefined(typeof(PdfRubberStampAnnotationIcon), value)) - return PdfRubberStampAnnotationIcon.NoIcon; - return (PdfRubberStampAnnotationIcon)Enum.Parse(typeof(PdfRubberStampAnnotationIcon), value, false); - } - set - { - if (Enum.IsDefined(typeof(PdfRubberStampAnnotationIcon), value) && - PdfRubberStampAnnotationIcon.NoIcon != value) - { - Elements.SetName(Keys.Name, "/" + value.ToString()); - } - else - Elements.Remove(Keys.Name); - } - } - - /// - /// Predefined keys of this dictionary. - /// - internal new class Keys : PdfAnnotation.Keys - { - /// - /// (Optional) The name of an icon to be used in displaying the annotation. Viewer - /// applications should provide predefined icon appearances for at least the following - /// standard names: - /// Approved - /// AsIs - /// Confidential - /// Departmental - /// Draft - /// Experimental - /// Expired - /// Final - /// ForComment - /// ForPublicRelease - /// NotApproved - /// NotForPublicRelease - /// Sold - /// TopSecret - /// - [KeyInfo(KeyType.Name | KeyType.Optional)] - public const string Name = "/Name"; - - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - - static DictionaryMeta? _meta; - } - - /// - /// Gets the KeysMeta of this dictionary type. - /// - internal override DictionaryMeta Meta => Keys.Meta; - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfScreenAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfScreenAnnotation.cs new file mode 100644 index 00000000..fd134331 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfScreenAnnotation.cs @@ -0,0 +1,80 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF screen annotation. + /// + public sealed class PdfScreenAnnotation : PdfAnnotation + { + // Reference 2.0: 12.5.6.18 Screen annotations / Page 497 + + /// + /// Initializes a new instance of the class. + /// + public PdfScreenAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfScreenAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Screen); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Optional) The title of the screen annotation. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string T = "/T"; + + /// + /// (Optional) An appearance characteristics dictionary (see "Table 192 — Entries in an + /// appearance characteristics dictionary"). The I entry of this dictionary provides the + /// icon used in generating the appearance referred to by the screen annotation’s AP entry. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string MK = "/MK"; + + /// + /// (Optional; PDF 1.1) An action that shall be performed when the annotation is activated + /// (see 12.6, "Actions"). + /// + [KeyInfo("1.1", KeyType.Dictionary | KeyType.Optional)] + public const string A = "/A"; + + /// + /// (Optional; PDF 1.2) An additional-actions dictionary defining the screen annotation’s + /// behaviour in response to various trigger events (see 12.6.3, "Trigger events"). + /// + [KeyInfo("1.2", KeyType.Dictionary | KeyType.Optional)] + public const string AA = "/AA"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfShapeAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfShapeAnnotation.cs new file mode 100644 index 00000000..48a8f143 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfShapeAnnotation.cs @@ -0,0 +1,193 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Base class of PdfSquareAnnotation and PdfCircleAnnotation. + /// + public abstract class PdfShapeAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.8 Square and circle annotations / Page 489 + + /// + /// Initializes a new instance of the class. + /// + protected PdfShapeAnnotation(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of the class. + /// + protected PdfShapeAnnotation(PdfDictionary dict) + : base(dict) + { } + + //public XColor FillColor + //{ + // get + // { + // return XColor.Empty; + // } + // set + // { + // } + //} + + internal static PdfShapeAnnotation Initialize(PdfShapeAnnotation annot, PdfRectangle rect, XColor color, XColor fillColor) + { + annot.Page!.Annotations.Add(annot); + + annot.PageRectangle = new XRect(rect.Location, rect.Size); + if (!color.IsEmpty) + annot.Color = color; + + // Interior color. + if (!fillColor.IsEmpty) + { + // IMPROVE: Write helper for this. + var array = new PdfArray(annot.Page.Document); + array.Elements.Add(new PdfReal(fillColor.R / 255.0)); + array.Elements.Add(new PdfReal(fillColor.G / 255.0)); + array.Elements.Add(new PdfReal(fillColor.B / 255.0)); + annot.Elements[PdfShapeAnnotation.Keys.IC] = array; + } + + return annot; + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + // Reference 2.0: Table 180 — Additional entries specific to a square or circle annotation / Page 490 + + // ReSharper disable InconsistentNaming + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + /// + /// (Optional) A border style dictionary specifying the line width and dash pattern that + /// shall be used in drawing the rectangle or ellipse. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string BS = "/BS"; + + /// + /// (Optional; PDF 1.4) An array of numbers that shall be in the range 0.0 to 1.0 and shall + /// specify the interior colour with which to fill the annotation’s rectangle or ellipse. + /// The number of array elements determines the colour space in which the colour shall be + /// defined: + /// 0 No colour; transparent + /// 1 DeviceGray + /// 3 DeviceRGB + /// 4 DeviceCMYK + /// + [KeyInfo("1.4", KeyType.Array | KeyType.Optional)] + public const string IC = "/IC"; + + /// + /// (Optional; PDF 1.5) A border effect dictionary describing an effect applied to the + /// border described by the BS entry. + /// + [KeyInfo("1.5", KeyType.Dictionary | KeyType.Optional)] + public const string BE = "/BE"; + + /// + /// (Optional; PDF 1.5) A set of four numbers that shall describe the numerical differences + /// between two rectangles: the Rect entry of the annotation and the actual boundaries of + /// the underlying square or circle. Such a difference may occur in situations where a border + /// effect (described by BE) causes the size of the Rect to increase beyond that of the + /// square or circle. + /// The four numbers shall correspond to the differences in default user space between the + /// left, top, right, and bottom coordinates of Rect and those of the square or circle, + /// respectively. Each value shall be greater than or equal to 0. The sum of the top and + /// bottom differences shall be less than the height of Rect, and the sum of the left and + /// right differences shall be less than the width of Rect. + /// + [KeyInfo("1.5", KeyType.Rectangle | KeyType.Optional)] + public const string RD = "/RD"; + + // ReSharper restore InconsistentNaming + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + /// + /// Represents a PDF square annotation. + /// + public sealed class PdfSquareAnnotation : PdfShapeAnnotation + { + // Reference 2.0: 12.5.6.8 Square and circle annotations / Page 489 + + /// + /// Initializes a new instance of the class. + /// + public PdfSquareAnnotation(PdfDocument document) + : base(document) + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Square); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfSquareAnnotation(PdfDictionary dict) + : base(dict) + { } + + public static PdfSquareAnnotation Create(PdfPage page, PdfRectangle rect, XColor color, XColor fillColor) + { + var annot = new PdfSquareAnnotation(page.Document); + page.Annotations.Add(annot); + Initialize(annot, rect, color, fillColor); + return annot; + } + } + + /// + /// Represents a PDF circle annotation. + /// + public sealed class PdfCircleAnnotation : PdfShapeAnnotation + { + // Reference 2.0: 12.5.6.8 Square and circle annotations / Page 489 + + /// + /// Initializes a new instance of the class. + /// + public PdfCircleAnnotation(PdfDocument document) + : base(document) + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Circle); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfCircleAnnotation(PdfDictionary dict) + : base(dict) + { } + + public static PdfCircleAnnotation Create(PdfPage page, PdfRectangle rect, XColor color, XColor fillColor) + { + var annot = new PdfCircleAnnotation(page.Document); + page.Annotations.Add(annot); + Initialize(annot, rect, color, fillColor); + return annot; + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfSoundAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfSoundAnnotation.cs new file mode 100644 index 00000000..d63c8a2f --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfSoundAnnotation.cs @@ -0,0 +1,68 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF sound annotation. + /// + public sealed class PdfSoundAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.16 Sound annotations / Page 496 + + /// + /// Initializes a new instance of the class. + /// + public PdfSoundAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfSoundAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Sound); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Required) A sound object defining the sound that shall be played when the annotation + /// is activated (see 13.3, "Sounds"). + /// + [KeyInfo(KeyType.Stream | KeyType.Required)] + public const string Sound = "/Sound"; + + /// + /// (Optional) The name of an icon that shall be used in displaying the annotation. + /// PDF writers should include this entry and PDF readers should provide predefined icon + /// appearances for at least the standard names Speaker and Mic. Additional names may be + /// supported as well. Default value: Speaker. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Name = "/Name"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfStampAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfStampAnnotation.cs new file mode 100644 index 00000000..52ccc200 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfStampAnnotation.cs @@ -0,0 +1,127 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; + +// v7.0.0 TODO review creator functions + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF rubber stamp annotation. + /// + public sealed class PdfStampAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.12 Rubber stamp annotations / Page 494 + + [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")] + public PdfStampAnnotation() + => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter."); + + /// + /// Initializes a new instance of the class. + /// + public PdfStampAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfStampAnnotation(PdfDictionary dict) + : base(dict) + { + Initialize(); + } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, "/Stamp"); + Color = XColors.Yellow; + } + + /// + /// Gets or sets an icon to be used in displaying the annotation. + /// + public PdfStampAnnotationIcons Icon + { + get + { + string value = Elements.GetName(Keys.Name); + if (value == "") + return PdfStampAnnotationIcons.NoIcon; + value = value.Substring(1); + if (!Enum.IsDefined(typeof(PdfStampAnnotationIcons), value)) + return PdfStampAnnotationIcons.NoIcon; + return (PdfStampAnnotationIcons)Enum.Parse(typeof(PdfStampAnnotationIcons), value, false); + } + set + { + if (Enum.IsDefined(typeof(PdfStampAnnotationIcons), value) && + PdfStampAnnotationIcons.NoIcon != value) + { + Elements.SetName(Keys.Name, "/" + value.ToString()); + } + else + Elements.Remove(Keys.Name); + } + } + + /// + /// Predefined keys of this dictionary. + /// + internal new class Keys : PdfAnnotation.Keys + { + // Reference 2.0: Table 184 — Additional entries specific to a rubber stamp annotation / Page 494 + + // ReSharper disable InconsistentNaming + + /// + /// (Optional) The name of an icon that shall be used in displaying the annotation. + /// PDF writers should include this entry and PDF readers should provide predefined icon + /// appearances for at least the following standard names: + /// Approved, Experimental, NotApproved, AsIs, Expired, NotForPublicRelease, Confidential, + /// Final, Sold, Departmental, ForComment, TopSecret, Draft, ForPublicRelease + /// Additional names may be supported as well. + /// Default value: Draft. + /// If the IT key is present and its value is not Stamp, this Name key shall not be present. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Name = "/Name"; + + /// + /// (Optional; PDF 2.0) A name that shall describe the intent of the stamp. The following + /// values shall be valid:
+ /// StampSnapshot The appearance of this annotation has been taken from preexisting PDF content.
+ /// StampImage The appearance of this annotation is an Image.
+ /// Stamp The appearance of this annotation is a rubber stamp.
+ /// Default value: Stamp + ///
+ [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string IT = "/IT"; + + // ReSharper restore InconsistentNaming + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + /// + /// Use PdfStampAnnotation. + /// + [Obsolete("Use PdfStampAnnotation.")] + public class PdfRubberStampAnnotation + { } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextAnnotation.cs index bb11d8d2..9ad370d6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextAnnotation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextAnnotation.cs @@ -1,28 +1,38 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; +// v7.0.0 TODO review + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Annotations { /// - /// Represents a text annotation. + /// Represents a PDF text annotation. /// - public sealed class PdfTextAnnotation : PdfAnnotation + public sealed class PdfTextAnnotation : PdfMarkupAnnotation { + // Reference 2.0: 12.5.6.4 Text annotations / Page 482 + + [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")] + public PdfTextAnnotation() + => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter."); + /// /// Initializes a new instance of the class. /// - public PdfTextAnnotation() + public PdfTextAnnotation(PdfDocument document) + : base(document) { Initialize(); } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// - public PdfTextAnnotation(PdfDocument document) - : base(document) + internal PdfTextAnnotation(PdfDictionary dict) + : base(dict) { Initialize(); } @@ -31,7 +41,7 @@ void Initialize() { Elements.SetName(Keys.Subtype, "/Text"); // By default make a yellow comment. - Icon = PdfTextAnnotationIcon.Comment; + Icon = PdfTextAnnotationIcons.Comment; //Color = XColors.Yellow; } @@ -56,22 +66,22 @@ public bool Open /// /// Gets or sets an icon to be used in displaying the annotation. /// - public PdfTextAnnotationIcon Icon + public PdfTextAnnotationIcons Icon { get { string value = Elements.GetName(Keys.Name); - if (value == "") - return PdfTextAnnotationIcon.NoIcon; - value = value.Substring(1); - if (!Enum.IsDefined(typeof(PdfTextAnnotationIcon), value)) - return PdfTextAnnotationIcon.NoIcon; - return (PdfTextAnnotationIcon)Enum.Parse(typeof(PdfTextAnnotationIcon), value, false); + if (value == "" || value == "/") + return PdfTextAnnotationIcons.NoIcon; + value = value[1..]; + if (!Enum.IsDefined(typeof(PdfTextAnnotationIcons), value)) + return PdfTextAnnotationIcons.NoIcon; + return (PdfTextAnnotationIcons)Enum.Parse(typeof(PdfTextAnnotationIcons), value, false); } set { - if (Enum.IsDefined(typeof(PdfTextAnnotationIcon), value) && - PdfTextAnnotationIcon.NoIcon != value) + if (Enum.IsDefined(typeof(PdfTextAnnotationIcons), value) && + PdfTextAnnotationIcons.NoIcon != value) { Elements.SetName(Keys.Name, "/" + value.ToString()); } @@ -85,32 +95,41 @@ public PdfTextAnnotationIcon Icon ///
internal new class Keys : PdfAnnotation.Keys { + // Reference 2.0: Table 175 — Additional entries specific to a text annotation / Page 482 + /// - /// (Optional) A flag specifying whether the annotation should initially be displayed open. + /// (Optional) A flag specifying whether the annotation shall initially be displayed open. /// Default value: false (closed). /// [KeyInfo(KeyType.Boolean | KeyType.Optional)] public const string Open = "/Open"; /// - /// (Optional) The name of an icon to be used in displaying the annotation. Viewer - /// applications should provide predefined icon appearances for at least the following - /// standard names: - /// Comment - /// Help - /// Insert - /// Key - /// NewParagraph - /// Note - /// Paragraph + /// (Optional) The name of an icon that shall be used in displaying the annotation. + /// Interactive PDF processors shall provide predefined icon appearances for at least + /// the following standard names: + /// Comment, Key, Note, Help, NewParagraph, Paragraph, Insert + /// Additional names may be supported as well. + /// Default value: Note. /// [KeyInfo(KeyType.Name | KeyType.Optional)] public const string Name = "/Name"; - //State - //StateModel + /// + /// (Optional; PDF 1.5) The state to which the original annotation shall be set. + /// Default: Unmarked if StateModel is Marked; None if StateModel is Review. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string State = "/State"; + + /// + /// (Required if State is present, otherwise optional; PDF 1.5) + /// The state model corresponding to State. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string StateModel = "/StateModel"; - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextMarkupAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextMarkupAnnotation.cs new file mode 100644 index 00000000..32a6d9cd --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTextMarkupAnnotation.cs @@ -0,0 +1,192 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Base class of all Text Markup annotations. + /// + public abstract class PdfTextMarkupAnnotation : PdfMarkupAnnotation + { + // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492 + [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")] + protected PdfTextMarkupAnnotation() + => throw new NotImplementedException("PDFsharp 6.4: Use a constructor with a PDF document parameter."); + + /// + /// Initializes a new instance of the class. + /// + protected PdfTextMarkupAnnotation(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of the class. + /// + protected PdfTextMarkupAnnotation(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfMarkupAnnotation.Keys + { + // Reference 2.0: Table 182 — Additional entries specific to text markup annotations / Page 492 + + // ReSharper disable InconsistentNaming + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + /// + /// (Required) An array of 8×𝑛 numbers specifying the coordinates of n quadrilaterals in + /// default user space. Each quadrilateral shall encompasses a word or group of contiguous + /// words in the text underlying the annotation. The coordinates for each quadrilateral + /// shall be given in the order: + /// 𝑥1 𝑦1 𝑥2 𝑦2 𝑥3 𝑦3 𝑥4 𝑦4 + /// specifying the quadrilateral’s four vertices in counterclockwise order + /// (see "Figure 84 — QuadPoints specification"). The text shall be oriented with respect + /// to the edge connecting points(x1, y1) and(x2, y2). + /// + [KeyInfo(KeyType.Array | KeyType.Required)] + public const string QuadPoints = "/QuadPoints"; + + + // ReSharper restore InconsistentNaming + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + /// + /// Represents a PDF highlight annotation. + /// + public sealed class PdfHighlightAnnotation : PdfTextMarkupAnnotation + { + // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492 + + /// + /// Initializes a new instance of the class. + /// + public PdfHighlightAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfHighlightAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Highlight); + } + } + + /// + /// Represents a PDF underline annotation. + /// + public sealed class PdfUnderlineAnnotation : PdfTextMarkupAnnotation + { + // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492 + + /// + /// Initializes a new instance of the class. + /// + public PdfUnderlineAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfUnderlineAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Underline); + } + } + + /// + /// Represents a PDF squiggly annotation. + /// + public sealed class PdfSquigglyAnnotation : PdfTextMarkupAnnotation + { + // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492 + + /// + /// Initializes a new instance of the class. + /// + public PdfSquigglyAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfSquigglyAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Squiggly); + } + } + + /// + /// Represents a PDF strikeout annotation. + /// + public sealed class PdfStrikeOutAnnotation : PdfTextMarkupAnnotation + { + // Reference 2.0: 12.5.6.10 Text markup annotations / Page 492 + + /// + /// Initializes a new instance of the class. + /// + public PdfStrikeOutAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfStrikeOutAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.StrikeOut); + } + + ///// + ///// Gets the KeysMeta of this dictionary type. + ///// + //internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTrapNetAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTrapNetAnnotation.cs new file mode 100644 index 00000000..e58e2897 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfTrapNetAnnotation.cs @@ -0,0 +1,95 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF trap network annotation. + /// + public sealed class PdfTrapNetAnnotation : PdfAnnotation + { + // Reference 2.0: 12.5.6.21 Trap network annotations / Page 501 + + /// + /// Initializes a new instance of the class. + /// + public PdfTrapNetAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfTrapNetAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.TrapNet); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfAnnotation.Keys + { + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + // ReSharper disable InconsistentNaming + + /// + /// (Required if Version and AnnotStates are absent; shall be absent if Version and AnnotStates + /// are present; PDF 1.4) The date and time (see 7.9.4, "Dates") when the trap network was + /// most recently modified. + /// + [KeyInfo(KeyType.Date | KeyType.Required)] + public const string LastModified = "/LastModified"; + + /// + /// (Required if AnnotStates is present; shall be absent if LastModified is present) An unordered + /// array of all objects present in the page description at the time the trap networks were + /// generated and that, if changed, could affect the appearance of the page. If present, + /// the array shall include the following objects: + /// • All content streams identified in the page object’s Contents entry(see 7.7.3.3, + /// "Page objects") + /// • All resource objects(other than procedure sets) in the page’s resource dictionary + /// (see 7.8.3, "Resource dictionaries") + /// • All resource objects(other than procedure sets) in the resource dictionaries of any + /// form XObjects on the page(see 8.10, "Form XObjects") + /// • All OPI dictionaries associated with XObjects on the page(see 14.11.7, "Open prepress + /// interface (OPI)"). This entry is deprecated in PDF 2.0. + /// + [KeyInfo(KeyType.Array | KeyType.Required)] + public const string Version = "/Version"; + + /// + /// (Required if Version is present; shall be absent if LastModified is present) An array + /// of name objects representing the appearance states (value of the AS entry) for annotations + /// associated with the page. The appearance states shall be listed in the same order as + /// the annotations in the page’s Annots array (see 7.7.3.3, "Page objects"). For an annotation + /// with no AS entry, the corresponding array element should be null. No appearance state + /// shall be included for the trap network annotation itself. + /// + [KeyInfo(KeyType.Array | KeyType.Required)] + public const string AnnotStates = "/AnnotStates"; + + /// + /// (Optional) An array of font dictionaries representing fonts that were fauxed (replaced + /// by substitute fonts) during the generation of trap networks for the page. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string FontFauxing = "/FontFauxing"; + + // ReSharper restore InconsistentNaming + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWatermarkAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWatermarkAnnotation.cs new file mode 100644 index 00000000..f3731884 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWatermarkAnnotation.cs @@ -0,0 +1,131 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Represents a PDF watermark annotation. + /// + public sealed class PdfWatermarkAnnotation : PdfAnnotation + { + // Reference 2.0: 12.5.6.22 Watermark annotations / Page 501 + + /// + /// Initializes a new instance of the class. + /// + public PdfWatermarkAnnotation(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfWatermarkAnnotation(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Watermark); + } + + /// + /// Predefined keys of this dictionary. + /// + new class Keys : PdfAnnotation.Keys + { + // Reference 2.0: Table 193 — Additional entries specific to a watermark annotation / Page 502 + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(PdfWatermarkAnnotationFixedPrint))] + public const string FixedPrint = "/FixedPrint"; + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + /// + /// Represents a fixed print dictionary for a PDF watermark annotation. + /// + public sealed class PdfWatermarkAnnotationFixedPrint : PdfDictionary + { + // Reference 2.0: 12.5.6.22 Watermark annotations / Page 501 + + /// + /// Initializes a new instance of the class. + /// + public PdfWatermarkAnnotationFixedPrint(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfWatermarkAnnotationFixedPrint(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfAnnotation.Keys.Subtype, "/FixedPrint"); + } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : PdfAnnotation.Keys + { + // Reference 2.0: Table 194 — Entries in a fixed print dictionary / Page 502 + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + /// + /// (Optional) The matrix used to transform the annotation’s rectangle before rendering. + /// Default value: the identity matrix[1 0 0 1 0 0]. + /// When positioning content near the edge of the media, this entry should be used to provide + /// a reasonable offset to allow for unprintable margins. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string Matrix = "/Matrix"; + + /// + /// (Optional) The amount to translate the associated content horizontally, as a percentage + /// of the width of the target media (or if unknown, the width of the page’s MediaBox). + /// 1.0 represents 100% and 0.0 represents 0%. Negative values should not be used, since + /// they may cause content to be drawn off the media. + /// Default value: 0. + /// + [KeyInfo(KeyType.Real | KeyType.Optional)] + public const string H = "/H"; + + /// + /// (Optional) The amount to translate the associated content vertically, as a percentage + /// of the height of the target media (or if unknown, the height of the page’s MediaBox). + /// 1.0 represents 100% and 0.0 represents 0%. Negative values should not be used, since + /// they may cause content to be drawn off the media. + /// Default value: 0. + /// + [KeyInfo(KeyType.Real | KeyType.Optional)] + public const string V = "/V"; + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWidgetAnnotation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWidgetAnnotation.cs index 24d26f45..ad136c83 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWidgetAnnotation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/PdfWidgetAnnotation.cs @@ -1,59 +1,405 @@ // 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.Forms; +using PdfSharp.Pdf.Advanced; + +// v7.0.0 TODO review + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + namespace PdfSharp.Pdf.Annotations { /// - /// Represents a text annotation. + /// Represents a PDF widget annotation. /// - sealed class PdfWidgetAnnotation : PdfAnnotation + public sealed class PdfWidgetAnnotation : PdfAnnotation { + // Reference 2.0: 12.5.6.19 Widget annotations / Page 498 + + [Obsolete("PDFsharp 6.4: Use a constructor with a PDF document parameter.")] public PdfWidgetAnnotation() + => throw new InvalidOperationException("PDFsharp 6.4: Use a constructor with a PDF document parameter."); + + public PdfWidgetAnnotation(PdfDocument document) + : base(document) { Initialize(); } - public PdfWidgetAnnotation(PdfDocument document) - : base(document) + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfWidgetAnnotation(PdfDictionary dictionary) + : base(dictionary) { Initialize(); } + internal PdfWidgetAnnotation(PdfFormField acroField) + { + Document = acroField.Owner; + + Debug.Assert(acroField.IsIndirect); + + var oldElementsFromBaseClasses = Elements; + Elements = null!; + Elements = acroField.Elements; + foreach (var item in oldElementsFromBaseClasses) + Elements.Add(item.Key, item.Value); + + // Create new reference. Same ObjectID, but different value. + _actualIRef = acroField.Reference; + var iref = new PdfReference(acroField, this); + + Initialize(); + } + + internal static PdfWidgetAnnotation CreateWidgetAnnotationAndField(PdfDocument document) + { + var field = new PdfFormFieldWidget(document); + return new PdfWidgetAnnotation(field); + } + void Initialize() { - Elements.SetName(Keys.Subtype, "/Widget"); + Elements.SetName(PdfAnnotation.Keys.Subtype, PdfAnnotationTypeNames.Widget); + } + + internal override PdfReference? ActualReference + { + get => _actualIRef ?? base.ActualReference; + } + readonly PdfReference? _actualIRef = null; + + internal static bool IsWidgetAnnotation(PdfFormField field) + { + return field.Elements.GetName(PdfAnnotation.Keys.Subtype) == PdfAnnotationTypeNames.Widget; + } + + internal bool IsField() + { + return _actualIRef?.Value is PdfFormField; + } + + internal PdfFormField? GetAsField() + { + if (_field != null) + return _field; + + return _field ??= _actualIRef?.Value as PdfFormField; + } + PdfFormField? _field; + + /// + /// Returns the shared field object or the parent field. + /// The returned field may be a field, that shall not be considered a field but simply a widget. + /// However, those fields still may have the /V entry set. Therefore, return that field instead of the bottom-most fully qualified field. + /// Remember to get the bottom-most fully qualified field to access all the fields widgets, for example. + /// + public PdfFormField? GetField() + { + var field = GetAsField(); + if (field != null) + { + // This widget is also a field: return the referred field object. + return field; + } + + field = GetParent(); + + if (field == null) + PdfSharpLogHost.Logger.LogWarning($"No parent field could be found for the widget annotation with object ID '{Reference?.ObjectID}'."); + + return field; + } + + + PdfFormField? GetParent() + { + var parentFromKey = Elements.GetDictionary(Keys.Parent); + + var parentFromSearch = FindParent(); + Debug.Assert(parentFromKey == null || parentFromSearch == parentFromKey, "Check for the correct parent."); + + return parentFromSearch; // By now, we always return the parent from search. + } + + PdfFormField? FindParent() + { + static IEnumerable GetAllFields(IEnumerable fields) + { + foreach (var field in fields) + { + yield return field; + + foreach (var kid in GetAllFields(field.GetKids())) + yield return kid; + } + } + + var acroForm = Document.Catalog.GetAcroForm(); + if (acroForm == null) + return null; + + var allFields = GetAllFields(acroForm.Fields); + + + return allFields.SingleOrDefault(f => f.GetKids().Any(field => + { + // Currently, each kid is a field - for a pure widget a PdfFormFieldWidget is added. + var widget = field.GetAsWidgetAnnotation(); + return widget == this; + })); } /// /// Predefined keys of this dictionary. /// - internal new class Keys : PdfAnnotation.Keys + public new class Keys : PdfAnnotation.Keys { + // Reference 2.0: Table 191 — Additional entries specific to a widget annotation / Page 499 + + // ReSharper disable InconsistentNaming + /// /// (Optional) The annotation’s highlighting mode, the visual effect to be used when - /// the mouse button is pressed or held down inside its active area: - /// N (None) No highlighting. - /// I (Invert) Invert the contents of the annotation rectangle. - /// O (Outline) Invert the annotation’s border. + /// the mouse button is pressed or held down inside its active area:
+ /// N (None) No highlighting.
+ /// I (Invert) Invert the contents of the annotation rectangle.
+ /// O (Outline) Invert the annotation’s border.
/// P (Push) Display the annotation’s down appearance, if any. If no down appearance is defined, /// offset the contents of the annotation rectangle to appear as if it were being pushed below - /// the surface of the page. - /// T (Toggle) Same as P (which is preferred). - /// A highlighting mode other than P overrides any down appearance defined for the annotation. + /// the surface of the page.
+ /// T (Toggle) Same as P (which is preferred).
+ /// A highlighting mode other than P overrides any down appearance defined for the annotation.
/// Default value: I. ///
[KeyInfo(KeyType.Name | KeyType.Optional)] public const string H = "/H"; /// - /// (Optional) An appearance characteristics dictionary to be used in constructing a dynamic - /// appearance stream specifying the annotation’s visual presentation on the page. + /// (Optional) An appearance characteristics dictionary that shall be used in constructing a + /// dynamic appearance stream specifying the annotation’s visual presentation on the page. /// The name MK for this entry is of historical significance only and has no direct meaning. /// [KeyInfo(KeyType.Dictionary | KeyType.Optional)] public const string MK = "/MK"; - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + /// + /// (Optional; PDF 1.1) An action that shall be performed when the annotation is activated. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string A = "/A"; + + /// + /// (Optional; PDF 1.2) An additional-actions dictionary defining the annotation’s behaviour + /// in response to various trigger events. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string AA = "/AA"; + + /// + /// (Optional; PDF 1.2) A border style dictionary specifying the width and dash pattern that + /// shall be used in drawing the annotation’s border. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string BS = "/BS"; + + /// + /// (Required if this widget annotation is one of multiple children in a field; optional otherwise) + /// An indirect reference to the widget annotation’s parent field. A widget annotation may have + /// at most one parent; that is, it can be included in the Kids array of at most one field. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string Parent = "/Parent"; + + // ReSharper restore InconsistentNaming + + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + internal static string[] WidgetKeys = + [ + // Keys specific to all annotation. + // "/Type", comes from AcroFiled + "/Subtype", // Can be /Widget + "/Rect", + "/Contents", + "/P", + "/NM", + "/M", + "/F", + "/AP", + "/AS", + "/Border", + "/C", + "/StructParent", + "/OC", + + // Keys specific to a widget annotation. + "/H", + "/MK", + "/A", + "/AA", + "/BS", + "/Parent" + ]; + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + + //readonly PdfFormField? _acroField; + } +} + +namespace PdfSharp.Pdf.Annotations // #FILE PdfWidgetAnnotationAppearanceCharacteristics.cs +{ + /// + /// Represents a text annotation. + /// + public sealed class PdfWidgetAnnotationAppearanceCharacteristics : PdfDictionary + { + public PdfWidgetAnnotationAppearanceCharacteristics() + { } + + public PdfWidgetAnnotationAppearanceCharacteristics(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfWidgetAnnotationAppearanceCharacteristics(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : KeysBase + { + // Reference 2.0: 12.5.6.19 Widget annotations / Page 498 + // Reference 2.0: Table 192 — Entries in an appearance characteristics dictionary / Page 500 + + // ReSharper disable InconsistentNaming + + /// + /// (Optional) The number of degrees by which the widget annotation shall be rotated + /// counterclockwise relative to the page. The value shall be a multiple of 90. + /// Default value: 0. + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string R = "/R"; + + /// + /// (Optional) An array of numbers that shall be in the range 0.0 to 1.0 specifying the colour + /// of the widget annotation’s border. The number of array elements determines the colour space + /// in which the colour shall be defined: + /// 0 No colour; transparent + /// 1 DeviceGray + /// 3 DeviceRGB + /// 4 DeviceCMYK + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string BC = "/BC"; + + /// + /// (Optional) An array of numbers that shall be in the range 0.0 to 1.0 specifying the colour + /// of the widget annotation’s background. The number of array elements shall determine the + /// colour space, as described for BC. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string BG = "/BG"; + + /// + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string XXX = "/XXX"; + + /// + /// (Optional; button fields only) The widget annotation’s normal caption, which shall be + /// displayed when it is not interacting with the user. + /// Unlike the remaining entries listed in this Table, which apply only to widget annotations + /// associated with push-button fields, the CA entry may be used with any type of button field, + /// including check boxes and radio buttons. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string CA = "/CA"; + + /// + /// (Optional; push-button fields only) The widget annotation’s rollover caption, which shall + /// be displayed when the user rolls the cursor into its active area without pressing the + /// mouse button. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string RC = "/RC"; + + /// + /// (Optional; push-button fields only) The widget annotation’s alternate (down) caption, + /// which shall be displayed when the mouse button is pressed within its active area. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string AC = "/AC"; + + /// + /// (Optional; push-button fields only; shall be an indirect reference) A form XObject + /// defining the widget annotation’s normal icon, which shall be displayed when it is not + /// interacting with the user. + /// + [KeyInfo(KeyType.Stream | KeyType.Optional)] + public const string I = "/I"; + + /// + /// (Optional; push-button fields only; shall be an indirect reference) A form XObject defining + /// the widget annotation’s rollover icon, which shall be displayed when the user rolls the + /// cursor into its active area without pressing the mouse button. + /// + [KeyInfo(KeyType.Stream | KeyType.Optional)] + public const string RI = "/RI"; + + /// + /// (Optional; push-button fields only; shall be an indirect reference) A form XObject defining + /// the widget annotation’s alternate (down) icon, which shall be displayed when the mouse button + /// is pressed within its active area. + /// + [KeyInfo(KeyType.Stream | KeyType.Optional)] + public const string IX = "/IX"; + + /// + /// (Optional; push-button fields only) An icon fit dictionary specifying how the widget + /// annotation’s icon shall be displayed within its annotation rectangle. If present, the icon + /// fit dictionary shall apply to all of the annotation’s icons (normal, rollover, and alternate). + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string IF = "/IF"; + + /// + /// (Optional; push-button fields only) A code indicating where to position the text of the widget + /// annotation’s caption relative to its icon:
+ /// 0 No icon; caption only
+ /// 1 No caption; icon only
+ /// 2 Caption below the icon
+ /// 3 Caption above the icon
+ /// 4 Caption to the right of the icon
+ /// 5 Caption to the left of the icon
+ /// 6 Caption overlaid directly on the icon
+ /// Default value: 0. + ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string TP = "/TP"; + + // ReSharper restore InconsistentNaming + + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationFlags.cs index a5ba1bf0..a1d01b85 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationFlags.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationFlags.cs @@ -1,6 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +// v7.0.0 Ready + namespace PdfSharp.Pdf.Annotations { /// @@ -9,78 +11,90 @@ namespace PdfSharp.Pdf.Annotations [Flags] public enum PdfAnnotationFlags { + // Reference 2.0: 12.5.3 Annotation flags / Page 470 + /// - /// If set, do not display the annotation if it does not belong to one of the standard - /// annotation types and no annotation handler is available. If clear, display such an - /// unknown annotation using an appearance stream specified by its appearancedictionary, - /// if any. + /// Applies only to annotations which do not belong to one of the standard annotation + /// types and for which no annotation handler is available. If set, do not render the + /// unknown annotation and do not print it even if the Print flag is set. If clear, + /// render such an unknown annotation using an appearance stream specified by its + /// appearance dictionary, if any. /// Invisible = 1 << (1 - 1), /// - /// (PDF 1.2) If set, do not display or print the annotation or allow it to interact - /// with the user, regardless of its annotation type or whether an annotation - /// handler is available. In cases where screen space is limited, the ability to hide - /// and show annotations selectively can be used in combination with appearance - /// streams to display auxiliary pop-up information similar in function to online - /// help systems. + /// (PDF 1.2) If set, do not render the annotation or allow it to interact with the user, + /// regardless of its annotation type or whether an annotation handler is available. + /// NOTE 1 + /// In cases where screen space is limited, the ability to hide and show annotations + /// selectively can be used in combination with appearance streams to render auxiliary + /// popup information similar in function to online help systems. /// Hidden = 1 << (2 - 1), /// - /// (PDF 1.2) If set, print the annotation when the page is printed. If clear, never - /// print the annotation, regardless of whether it is displayed on the screen. This - /// can be useful, for example, for annotations representing interactive pushbuttons, - /// which would serve no meaningful purpose on the printed page. + /// (PDF 1.2) If set, print the annotation when the page is printed unless the Hidden + /// flag is also set. If clear, never print the annotation, regardless of whether it + /// is rendered on the screen. If the annotation does not contain any appearance streams + /// this flag shall be ignored. + /// NOTE 2 + /// This can be useful for annotations representing interactive push-buttons, which + /// would serve no meaningful purpose on the printed page. /// Print = 1 << (3 - 1), /// /// (PDF 1.3) If set, do not scale the annotation’s appearance to match the magnification - /// of the page. The location of the annotation on the page (defined by the - /// upper-left corner of its annotation rectangle) remains fixed, regardless of the - /// page magnification. See below for further discussion. + /// of the page. The location of the annotation on the page (defined by the upper-left + /// corner of its annotation rectangle) shall remain fixed, regardless of the page + /// magnification. See further discussion following this table. /// NoZoom = 1 << (4 - 1), /// - /// (PDF 1.3) If set, do not rotate the annotation’s appearance to match the rotation - /// of the page. The upper-left corner of the annotation rectangle remains in a fixed - /// location on the page, regardless of the page rotation. See below for further discussion. + /// (PDF 1.3) If set, do not rotate the annotation’s appearance to match the rotation of + /// the page. The upper-left corner of the annotation rectangle shall remain in a fixed + /// location on the page, regardless of the page rotation. See further discussion + /// following this table. /// NoRotate = 1 << (5 - 1), /// - /// (PDF 1.3) If set, do not display the annotation on the screen or allow it to - /// interact with the user. The annotation may be printed (depending on the setting - /// of the Print flag) but should be considered hidden for purposes of on-screen - /// display and user interaction. + /// (PDF 1.3) If set, do not render the annotation on the screen or allow it to interact + /// with the user. The annotation may be printed (depending on the setting of the Print flag) + /// but should be considered hidden for purposes of on-screen display and user interaction. /// NoView = 1 << (6 - 1), /// - /// (PDF 1.3) If set, do not allow the annotation to interact with the user. The - /// annotation may be displayed or printed (depending on the settings of the - /// NoView and Print flags) but should not respond to mouse clicks or change its - /// appearance in response to mouse motions. - /// Note: This flag is ignored for widget annotations; its function is subsumed by - /// the ReadOnly flag of the associated form field. + /// (PDF 1.3) If set, do not allow the annotation to interact with the user. The annotation + /// may be rendered or printed (depending on the settings of the NoView and Print flags) + /// but should not respond to mouse clicks or change its appearance in response to mouse + /// motions. + /// This flag shall be ignored for widget annotations; its function is subsumed by the + /// ReadOnly flag of the associated form field. /// ReadOnly = 1 << (7 - 1), /// /// (PDF 1.4) If set, do not allow the annotation to be deleted or its properties - /// (including position and size) to be modified by the user. However, this flag does - /// not restrict changes to the annotation’s contents, such as the value of a form - /// field. + /// (including position and size) to be modified by the user. However, this flag does not + /// restrict changes to the annotation’s contents, such as the value of a form field. /// Locked = 1 << (8 - 1), /// - /// (PDF 1.5) If set, invert the interpretation of the NoView flag for certain events. - /// A typical use is to have an annotation that appears only when a mouse cursor is - /// held over it. + /// (PDF 1.5) If set, invert the interpretation of the NoView flag for annotation selection + /// and mouse hovering, causing the annotation to be visible when the mouse pointer hovers + /// over the annotation or when the annotation is selected. /// ToggleNoView = 1 << (9 - 1), + + /// + /// (PDF 1.7) If set, do not allow the contents of the annotation to be modified by the user. This + /// flag does not restrict deletion of the annotation or changes to other annotation properties, + /// such as position and size. + /// + LockedContents = 1 << (10 - 1), } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationStates.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationStates.cs new file mode 100644 index 00000000..284db5b4 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationStates.cs @@ -0,0 +1,56 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 TODO + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Specifies the annotation types. + /// + [Flags] + public enum PdfAnnotationStates + { + // Reference 2.0: Table 174 — Annotation states / Page 481 + + /// + /// The user has indicated nothing about the change (the default). + /// + None, + + // ----- States if the state model is Marked -------------------------------------------------------- + + /// + /// The annotation has been marked by the user. + /// Markup: Yes + /// + Marked, + + /// + /// The annotation has not been marked by the user (the default). + /// + Unmarked, + + // ----- States if the state model is Review -------------------------------------------------------- + + /// + /// The user agrees with the change. + /// + Accepted, + + /// + /// The user disagrees with the change. + /// + Rejected, + + /// + /// The change has been cancelled. + /// + Cancelled, + + /// + /// The change has been completed. + /// + Completed + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationTypes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationTypes.cs new file mode 100644 index 00000000..423b88ac --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfAnnotationTypes.cs @@ -0,0 +1,373 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 Ready + +namespace PdfSharp.Pdf.Annotations +{ + /// + /// Specifies the annotation types. + /// + public enum PdfAnnotationTypes + { + // Reference 2.0: 12.5.6 Annotation types / Page 476 + // Reference 2.0: Table 171 — Annotation types / Page 476 + + /// + /// Text annotation. + /// Markup: Yes + /// + Text = 1, + + /// + /// Link annotation. + /// Markup: No + /// + Link, + + /// + /// (PDF 1.3) Free text annotation. + /// Markup: Yes + /// + FreeText, + + /// + /// (PDF 1.3) Line annotation. + /// Markup: Yes + /// + Line, + + /// + /// (PDF 1.3) Square annotation. + /// Markup: Yes + /// + Square, + + /// + /// (PDF 1.3) Circle annotation. + /// Markup: Yes + /// + Circle, + + /// + /// (PDF 1.5) Polygon annotation. + /// Markup: Yes + /// + Polygon, + + /// + /// (PDF 1.5) Polyline annotation. + /// Markup: Yes + /// + PolyLine, + + /// + /// (PDF 1.3) Highlight annotation. + /// Markup: Yes + /// + Highlight, + + /// + /// (PDF 1.3) Underline annotation. + /// Markup: Yes + /// + Underline, + + /// + /// (PDF 1.4) Squiggly-underline annotation. + /// Markup: Yes + /// + Squiggly, + + /// + /// (PDF 1.3) Strikeout annotation.. + /// Markup: Yes + /// + StrikeOut, + + /// + /// (PDF 1.5) Caret annotation. + /// Markup: Yes + /// + Caret, + + /// + /// (PDF 1.3) Rubber stamp annotation. + /// Markup: Yes + /// + [Obsolete("Use Stamp instead.")] + RubberStamp, + + /// + /// (PDF 1.3) Rubber stamp annotation. + /// Markup: Yes + /// + Stamp, + + /// + /// (PDF 1.3) Ink annotation. + /// Markup: Yes + /// + Ink, + + /// + /// (PDF 1.3) Popup annotation. + /// Markup: No + /// + Popup, + + /// + /// (PDF 1.3) File attachment annotation. + /// Markup: Yes + /// + FileAttachment, + + /// + /// (PDF 1.2; deprecated in PDF 2.0) Sound annotation. + /// Markup: Yes + /// + Sound, + + /// + /// (PDF 1.2; deprecated in PDF 2.0) Movie annotation. + /// Markup: No + /// + Movie, + + /// + /// (PDF 1.5) Screen annotation. + /// Markup: No + /// + Screen, + + /// + /// (PDF 1.2) Widget annotation. + /// Markup: No + /// + Widget, + + /// + /// (PDF 1.4) Printer’s mark annotation. + /// Markup: No + /// + PrinterMark, + + /// + /// (PDF 1.3; deprecated in PDF 2.0) Trap network annotation. + /// Markup: No + /// + TrapNet, + + /// + /// (PDF 1.6) Watermark annotation. + /// Markup: No + /// + Watermark, + + /// + /// (PDF 1.6) 3D annotation. + /// Markup: No + /// + ThreeD, // Use '3D', not 'nameof(ThreeD)'. + + /// + /// (PDF 1.7) Redact annotation. + /// Markup: Yes + /// + Redact, + + /// + /// (PDF 2.0) Projection annotation. + /// Markup: Yes + /// + Projection, + + /// + /// (PDF 2.0) RichMedia annotation. + /// Markup: No + /// + RichMedia + } +} + +namespace PdfSharp.Pdf.Annotations +{ +#pragma warning disable CS0414 // Field is assigned but its value is never used // DELETE + + /// + /// Specifies the annotation types. + /// + public static class PdfAnnotationTypeNames + { + // Reference 2.0: 12.5.6 Annotation types / Page 476 + // Reference 2.0: Table 171 — Annotation types / Page 476 + + /// + /// Text annotation. + /// Markup: Yes + /// + public const string Text = "/" + nameof(PdfAnnotationTypes.Text); + + /// + /// Link annotation. + /// Markup: No + /// + public const string Link = "/" + nameof(PdfAnnotationTypes.Link); + + /// + /// (PDF 1.3) Free text annotation. + /// Markup: Yes + /// + public const string FreeText = "/" + nameof(PdfAnnotationTypes.FreeText); + + /// + /// (PDF 1.3) Line annotation. + /// Markup: Yes + /// + public const string Line = "/" + nameof(PdfAnnotationTypes.Line); + + /// + /// (PDF 1.3) Square annotation. + /// Markup: Yes + /// + public const string Square = "/" + nameof(PdfAnnotationTypes.Square); + + /// + /// (PDF 1.3) Circle annotation. + /// Markup: Yes + /// + public const string Circle = "/" + nameof(PdfAnnotationTypes.Circle); + + /// + /// (PDF 1.5) Polygon annotation. + /// Markup: Yes + /// + public const string Polygon = "/" + nameof(PdfAnnotationTypes.Polygon); + + /// + /// (PDF 1.5) Polyline annotation. + /// Markup: Yes + /// + public const string PolyLine = "/" + nameof(PdfAnnotationTypes.PolyLine); + + /// + /// (PDF 1.3) Highlight annotation. + /// Markup: Yes + /// + public const string Highlight = "/" + nameof(PdfAnnotationTypes.Highlight); + + /// + /// (PDF 1.3) Underline annotation. + /// Markup: Yes + /// + public const string Underline = "/" + nameof(PdfAnnotationTypes.Underline); + + /// + /// (PDF 1.4) Squiggly-underline annotation. + /// Markup: Yes + /// + public const string Squiggly = "/" + nameof(PdfAnnotationTypes.Squiggly); + + /// + /// (PDF 1.3) Strikeout annotation.. + /// Markup: Yes + /// + public const string StrikeOut = "/" + nameof(PdfAnnotationTypes.StrikeOut); + + /// + /// (PDF 1.5) Caret annotation. + /// Markup: Yes + /// + public const string Caret = "/" + nameof(PdfAnnotationTypes.Caret); + + /// + /// (PDF 1.3) Rubber stamp annotation. + /// Markup: Yes + /// + public const string Stamp = "/" + nameof(PdfAnnotationTypes.Stamp); + + /// + /// (PDF 1.3) Ink annotation. + /// Markup: Yes + /// + public const string Ink = "/" + nameof(PdfAnnotationTypes.Ink); + + /// + /// (PDF 1.3) Popup annotation. + /// Markup: No + /// + public const string Popup = "/" + nameof(PdfAnnotationTypes.Popup); + + /// + /// (PDF 1.3) File attachment annotation. + /// Markup: Yes + /// + public const string FileAttachment = "/" + nameof(PdfAnnotationTypes.FileAttachment); + + /// + /// (PDF 1.2; deprecated in PDF 2.0) Sound annotation. + /// Markup: Yes + /// + public const string Sound = "/" + nameof(PdfAnnotationTypes.Sound); + + /// + /// (PDF 1.2; deprecated in PDF 2.0) Movie annotation. + /// Markup: No + /// + public const string Movie = "/" + nameof(PdfAnnotationTypes.Movie); + + /// + /// (PDF 1.5) Screen annotation. + /// Markup: No + /// + public const string Screen = "/" + nameof(PdfAnnotationTypes.Screen); + + /// + /// (PDF 1.2) Widget annotation. + /// Markup: No + /// + public const string Widget = "/" + nameof(PdfAnnotationTypes.Widget); + + /// + /// (PDF 1.4) Printer’s mark annotation. + /// Markup: No + /// + public const string PrinterMark = "/" + nameof(PdfAnnotationTypes.PrinterMark); + + /// + /// (PDF 1.3; deprecated in PDF 2.0) Trap network annotation. + /// Markup: No + /// + public const string TrapNet = "/" + nameof(PdfAnnotationTypes.TrapNet); + + /// + /// (PDF 1.6) Watermark annotation. + /// Markup: No + /// + public const string Watermark = "/" + nameof(PdfAnnotationTypes.Watermark); + + /// + /// (PDF 1.6) 3D annotation. + /// Markup: No + /// + public const string ThreeD = "/3D"; + + /// + /// (PDF 1.7) Redact annotation. + /// Markup: Yes + /// + public const string Redact = "/" + nameof(PdfAnnotationTypes.Redact); + + /// + /// (PDF 2.0) Projection annotation. + /// Markup: Yes + /// + public const string Projection = "/" + nameof(PdfAnnotationTypes.Projection); + + /// + /// (PDF 2.0) RichMedia annotation. + /// Markup: No + /// + public const string RichMedia = "/" + nameof(PdfAnnotationTypes.RichMedia); + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfRubberStampAnnotationIcon.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfStampAnnotationIcons.cs similarity index 89% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfRubberStampAnnotationIcon.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfStampAnnotationIcons.cs index fae16dd9..0855aef4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfRubberStampAnnotationIcon.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfStampAnnotationIcons.cs @@ -1,13 +1,17 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. namespace PdfSharp.Pdf.Annotations { + // v7.0.0 Ready + /// /// Specifies the predefined icon names of rubber stamp annotations. /// - public enum PdfRubberStampAnnotationIcon + public enum PdfStampAnnotationIcons { + // Reference 2.0: Table 184 — Additional entries specific to a rubber stamp annotation / Page 494 + /// /// A pre-defined rubber stamp annotation icon. /// @@ -21,32 +25,32 @@ public enum PdfRubberStampAnnotationIcon /// /// A pre-defined rubber stamp annotation icon. /// - AsIs, + Experimental, /// /// A pre-defined rubber stamp annotation icon. /// - Confidential, + NotApproved, /// /// A pre-defined rubber stamp annotation icon. /// - Departmental, + AsIs, /// /// A pre-defined rubber stamp annotation icon. /// - Draft, + Expired, /// /// A pre-defined rubber stamp annotation icon. /// - Experimental, + NotForPublicRelease, /// /// A pre-defined rubber stamp annotation icon. /// - Expired, + Confidential, /// /// A pre-defined rubber stamp annotation icon. @@ -56,31 +60,31 @@ public enum PdfRubberStampAnnotationIcon /// /// A pre-defined rubber stamp annotation icon. /// - ForComment, + Sold, /// /// A pre-defined rubber stamp annotation icon. /// - ForPublicRelease, + Departmental, /// /// A pre-defined rubber stamp annotation icon. /// - NotApproved, + ForComment, /// /// A pre-defined rubber stamp annotation icon. /// - NotForPublicRelease, + TopSecret, /// /// A pre-defined rubber stamp annotation icon. /// - Sold, + Draft, /// /// A pre-defined rubber stamp annotation icon. /// - TopSecret, + ForPublicRelease } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcon.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcons.cs similarity index 54% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcon.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcons.cs index 715fa844..07db8acb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcon.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Annotations/enums/PdfTextAnnotationIcons.cs @@ -1,51 +1,55 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +// v7.0.0 Ready + namespace PdfSharp.Pdf.Annotations { /// /// Specifies the pre-defined icon names of text annotations. /// - public enum PdfTextAnnotationIcon + public enum PdfTextAnnotationIcons { + // Reference 2.0: Table 175 — Values for Name key / Page 482 + /// - /// A pre-defined annotation icon. + /// A pre-defined text annotation icon. /// NoIcon, /// - /// A pre-defined annotation icon. + /// A pre-defined text annotation icon. /// Comment, /// - /// A pre-defined annotation icon. + /// A pre-defined text annotation icon. /// - Help, + Key, /// - /// A pre-defined annotation icon. + /// A pre-defined text annotation icon. /// - Insert, + Note, /// - /// A pre-defined annotation icon. + /// A pre-defined text annotation icon. /// - Key, + Help, /// - /// A pre-defined annotation icon. + /// A pre-defined text annotation icon. /// NewParagraph, /// - /// A pre-defined annotation icon. + /// A pre-defined text annotation icon. /// - Note, + Paragraph, /// - /// A pre-defined annotation icon. + /// A pre-defined text annotation icon. /// - Paragraph, + Insert } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFileInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFileInfo.cs new file mode 100644 index 00000000..19abe3cb --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFileInfo.cs @@ -0,0 +1,53 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Attachments +{ + /// + /// Sums up all relevant information of an embedded file in a PDF file. + /// + public class EmbeddedFileInfo + { + /// + /// The key of the PdfFileSpecification in the /Names array of the /EmbeddedFiles name tree. + /// + public string NamesKey { get; set; } = ""; + + /// + /// Gets or sets the name of the file. + /// + public string FileName { get; set; } = ""; + + /// + /// Gets or sets the file mime type. + /// + public string FileType { get; set; } = ""; + + /// + /// Gets or sets an optional description of the file. + /// + public string Description { get; set; } = ""; + + /// + /// Gets or sets the optional creation time of the file. + /// + public DateTimeOffset? CreationTime { get; set; } + + /// + /// Gets or sets the optional modification time of the file. + /// + public DateTimeOffset? ModificationTime { get; set; } + + /// + /// Gets or sets the bytes of the file. + /// + public byte[] Data { get; set; } = []; + + /// + /// Gets or sets the PdfAFRelationship of the file. + /// See PDF specification for further details. + /// + // ReSharper disable once InconsistentNaming + public string AFRelationship { get; set; } = PdfAFRelationship.Unspecified; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFilesManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFilesManager.cs new file mode 100644 index 00000000..76914bec --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/EmbeddedFilesManager.cs @@ -0,0 +1,140 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Internal; + +namespace PdfSharp.Pdf.Attachments +{ + /// + /// Provides functionality to add and retrieve embedded files from a PDF document. + /// + public class EmbeddedFilesManager : ManagerBase + { + EmbeddedFilesManager(PdfDocument document) : base(document) + { + _document = document; + + switch (_document.State) + { + case DocumentState.Created: + InitializeNewDocument(); + break; + + case DocumentState.Imported: + InitializeImportedDocument(); + break; + + case DocumentState.Disposed: + case DocumentState.Saved: + default: + throw new InvalidOperationException($"Document is in state '{document.State}' and cannot be modified anymore."); + } + } + + void InitializeNewDocument() + { } + + void InitializeImportedDocument() + { + var catalog = _document.Catalog; + var x = catalog.Names; + } + + /// + /// Gets the number of embedded files of this document. + /// + public int FileCount => EmbeddedFiles?.FileCount ?? 0; // ChatGPT suggests FileCount over FilesCount. + + /// + /// Gets the keys of all embedded files from the /Names array. + /// + public string[] NamesKeys // Here we use NamesKey because of the /Names array. + { + get + { + var embeddedFiles = _document.Catalog.Names.GetEmbeddedFiles(true); + return embeddedFiles.Names?.NamesKeys ?? []; + } + } + + /// + /// Embeds the specified file in the PDF document. + /// + /// + /// + public void AddFile(EmbeddedFileInfo fileInfo, bool compressStream = true) + { + //// TODO: Should return non-nullable object. + //var embeddedFiles = _document.Catalog.Names.GetEmbeddedFiles(true) + // ?? throw new InvalidOperationException(SyMsgs.UnexpectedNullValueRetrieved( + // $"Function: {nameof(EmbeddedFilesManager)}.{nameof(AddFile)}.").Message); + + var embeddedFiles = _document.Catalog.Names.GetEmbeddedFiles(true); + + //var embeddedFileStream = new PdfEmbeddedFileStream(_document, fileInfo.Data, fileInfo.FileType, + // fileInfo.ModificationTime); // TODO compress + //var fileSpecification = new PdfFileSpecification(_document, embeddedFileStream, fileInfo.FileName); + + var fileSpecification = new PdfFileSpecification(_document, fileInfo); + embeddedFiles.AddFileSpecification(fileInfo.NamesKey, fileSpecification); + } + + /// + /// Gets an EmbeddedFileInfo for the embedded file with the specified index. + /// + /// Index of the embedded file in range [0..FileCount]. + public EmbeddedFileInfo GetEmbeddedFileInfo(int index) + { + var ef = EmbeddedFiles; + if (ef == null) + throw new InvalidOperationException("Document has no embedded files."); + + return ef.GetFileSpecification(index).GetFileInfo(); + } + + /// + /// Gets an EmbeddedFileInfo for the embedded file with the specified /Names key. + /// + /// The key of the embedded file in the /Names array. + /// + public EmbeddedFileInfo? GetEmbeddedFileInfo(string namesKey) + { + var ef = EmbeddedFiles; + if (ef == null) + return null; + + return ef.GetFileSpecification(namesKey)?.GetFileInfo() ?? null; + } + + /// + /// Get the /EmbeddedFiles name tree dictionary from the catalogs /Names entry, or null, + /// if no such entry exists. + /// Use this property to get direct access to the PDF objects that describes the embedded files. + /// + PdfEmbeddedFiles? EmbeddedFiles + { + get + { + var catalog = _document.Catalog; + if (catalog.HasNames) + { + var names = catalog.Names; + if (names.HasEmbeddedFiles) + { + var embeddedFiles = names.GetEmbeddedFiles(); + return embeddedFiles; + } + } + return null; + } + } + + /// + /// Gets or creates the EmbeddedFilesManager for the specified document. + /// + public static EmbeddedFilesManager ForDocument(PdfDocument document) + => document.EmbeddedFilesManager ??= new(document); + + readonly PdfDocument _document; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfAFRelationship.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfAFRelationship.cs new file mode 100644 index 00000000..9bf17473 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfAFRelationship.cs @@ -0,0 +1,58 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Attachments +{ + /// + /// Specifies the associated files relationship. + /// (Optional; PDF 2.0) A name value that represents the relationship between the component of this PDF document + /// that refers to this file specification and the associated file denoted by this file specification dictionary. + /// These values represent the following relationships: + /// + // ReSharper disable once InconsistentNaming + public static class PdfAFRelationship // TODO: clean up #US322 + { + /// + /// shall be used when the relationship is not known or cannot be described using one of the other values. + /// Unspecified is to be used only when no other value correctly reflects the relationship. + /// NOTE 3 The value of AFRelationship does not explicitly provide any processing instructions for a PDF processor.It is provided for information and semantic purposes for those processors that are able to use such additional information. + /// + public const string Unspecified = nameof(Unspecified); + + /// + /// shall be used if this file specification is the original source material for the associated content. + /// + public const string Source = nameof(Source); + + /// + /// shall be used if this file specification represents information used to derive a visual presentation – such as for a table or a graph. + /// + public const string Data = nameof(Data); + + /// + /// shall be used if this file specification is an alternative representation of content, for example audio. + /// + public const string Alternative = nameof(Alternative); + + /// + /// shall be used if this file specification represents a supplemental representation of the original source or data that may be more easily consumable (e.g., A MathML version of an equation). + /// + public const string Supplement = nameof(Supplement); + + /// + /// shall be used if this file specification is an encrypted payload document that should be displayed to the user if the PDF processor has the cryptographic filter needed to decrypt the document. + /// + public const string EncryptedPayload = nameof(EncryptedPayload); + + /// + /// shall be used if this file specification is the data associated with the AcroForm (see 12.7.3, "Interactive form dictionary") of this PDF. + /// + public const string FormData = nameof(FormData); + + /// + /// shall be used if this file specification is a schema definition for the associated object (e.g. an XML schema associated with a metadata stream). + /// + public const string Schema = nameof(Schema); + } +} + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfEmbeddedFiles.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfEmbeddedFiles.cs new file mode 100644 index 00000000..663f0d6c --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfEmbeddedFiles.cs @@ -0,0 +1,132 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; + +namespace PdfSharp.Pdf.Attachments +{ + /// + /// Represents the name tree /EmbeddedFiles of the document’s catalog /Names dictionary. + /// + public class PdfEmbeddedFiles : PdfNameTreeNode + { + // Reference 2.0: 7.7.4 Name dictionary: Table 32 — Entries in the name dictionary / Page 110 + + /// + /// Initialize a new instance of this class. + /// + public PdfEmbeddedFiles() + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfEmbeddedFiles(PdfDictionary dict) + : base(dict) + { + InitializeExisting(); + } + + void InitializeExisting() + { + // Transform /Names values to PdfFileSpecification. + var names = Names; + if (names != null) + { + for (int idx = 0; idx < names.Count; idx++) + { + var abc = names[0]; + var item = abc.Value; + if (abc.Value is PdfFileSpecification) + continue; + + //var fc = PdfObjectsHelper.TransformItem(item); + //var fc = PdfObjectsHelper.TransformArrayItem(names, idx * 2 + 1); +#if true + // #warning TODO Check transformation in ArrayElements.GetItemInternal(...) + var fc = names.Elements.GetValue(idx * 2 + 1); +#else + var fc = PdfObjectsHelper.TransformArrayItem(names, idx * 2 + 1); +#endif + // /*v*/ar fc + + + // HACK: + var test = names.Elements[1]; + } + } + } + + /// + /// Gets the number of embedded files. + /// + public int FileCount => Names?.Count ?? 0; + + /// + /// Gets the PDF file specification of the embedded file with the specified index. + /// + /// The 0-based index. + public PdfFileSpecification GetFileSpecification(int index) + { + var entry = Names?[index]; + if (entry == null) + throw new InvalidOperationException("Name tree has no /Names entry."); // #MSG + + var item = entry.Value; + PdfReference.Dereference(ref item); + if (item is PdfFileSpecification fs) + { + fs.NamesKey = entry.Key.Value; + return fs; + } + + // TODO Should not come here. + return (PdfFileSpecification)item; + } + + /// + /// Gets the PDF file specification of the embedded file with the specified key, + /// or null, if no file with the key exists.. + /// + /// The name of the file. + public PdfFileSpecification? GetFileSpecification(string key) + { + var item = Names?[key]; + if (item == null) + return null; + PdfReference.Dereference(ref item); + if (item is PdfFileSpecification fs) + return fs; + + Debugger.Break(); // TODO + return null; + } + + /// + /// Adds a new embedded file defined by a PDF file specification to the document + /// + /// The name of the file. + /// A PDF file specification. + public void AddFileSpecification(string key, PdfFileSpecification fileSpec) + { + PdfDocument owner = OwningDocument; + + if (fileSpec.Reference is null) + { + // Make object indirect. + owner.Internals.AddObject(fileSpec); + } + + // Add it to the name tree. + if (String.IsNullOrEmpty(key)) + key = fileSpec.Name; + AddName(key, fileSpec); + + // Add it to the /AF catalog entry. + // It’s a PDF 2.0 feature, but some producer apps use it in PDF 1.7 documents. + var af = owner.Catalog.Elements.GetRequiredArray(PdfCatalog.Keys.AF, VCF.Create); + af.AddDictionary(fileSpec); + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFileSpecification.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfFileSpecification.cs similarity index 55% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFileSpecification.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfFileSpecification.cs index 09da5b41..d724f2fe 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfFileSpecification.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Attachments/PdfFileSpecification.cs @@ -1,42 +1,172 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.Advanced +using PdfSharp.Internal; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.PdfItemExtensions; + +namespace PdfSharp.Pdf.Attachments { /// - /// Represents a file specification dictionary. + /// Represents a PDF file specification dictionary. /// public class PdfFileSpecification : PdfDictionary { + // TODO: move file from PdfSharp.Pdf.Advanced to PdfSharp.Pdf.Attachments. + + // Reference 2.0: 7.11.3 File specification dictionaries / Page 134 + /// - /// Initializes a new instance of PdfFileSpecification referring an embedded file stream. + /// Initializes a new instance of PdfFileSpecification referring an embedded file info. /// - public PdfFileSpecification(PdfDocument document, PdfEmbeddedFileStream embeddedFileStream, string name) : base(document) + public PdfFileSpecification(PdfDocument document, EmbeddedFileInfo embeddedFileInfo) + : base(document) { - _embeddedFileStream = embeddedFileStream; - _name = name; + // ReSharper disable once StringLiteralTypo + Elements.SetName(Keys.Type, "/Filespec"); + + Elements.SetString(Keys.F, embeddedFileInfo.FileName); + Elements.SetString(Keys.UF, embeddedFileInfo.FileName); + + Elements.SetName(Keys.AFRelationship, embeddedFileInfo.AFRelationship); - Initialize(); + if (!String.IsNullOrEmpty(embeddedFileInfo.Description)) + Elements.SetString(Keys.Desc, embeddedFileInfo.Description); + + var embeddedFileStream = new PdfEmbeddedFileStream(document, embeddedFileInfo.Data, + embeddedFileInfo.FileType, + embeddedFileInfo.ModificationTime); // TODO compress + document.Internals.AddObject(embeddedFileStream); + + var embeddedFileDictionary = new PdfDictionary(document); + embeddedFileDictionary.Elements.SetReference(Keys.F, + embeddedFileStream.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull()); + embeddedFileDictionary.Elements.SetReference(Keys.UF, embeddedFileStream.Reference); + + Elements.SetObject(Keys.EF, embeddedFileDictionary); } - void Initialize() + /// + /// Initializes a new instance of PdfFileSpecification referring an embedded file stream. + /// + [Obsolete("Use PdfFileSpecification constructor with EmbeddedFileInfo parameter.")] + public PdfFileSpecification(PdfDocument document, PdfEmbeddedFileStream embeddedFileStream, string name) + : base(document) { + // ReSharper disable once StringLiteralTypo Elements.SetName(Keys.Type, "/Filespec"); - Elements.SetString(Keys.F, _name); - Elements.SetString(Keys.UF, _name); + Elements.SetString(Keys.F, name); + Elements.SetString(Keys.UF, name); var embeddedFileDictionary = new PdfDictionary(Owner); - Owner.Internals.AddObject(_embeddedFileStream); - embeddedFileDictionary.Elements.SetReference(Keys.F, _embeddedFileStream.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull()); - embeddedFileDictionary.Elements.SetReference(Keys.UF, _embeddedFileStream.Reference); + Owner.Internals.AddObject(embeddedFileStream); + embeddedFileDictionary.Elements.SetReference(Keys.F, + embeddedFileStream.Reference ?? throw TH.InvalidOperationException_ReferenceMustNotBeNull()); + embeddedFileDictionary.Elements.SetReference(Keys.UF, embeddedFileStream.Reference); Elements.SetObject(Keys.EF, embeddedFileDictionary); } - readonly PdfEmbeddedFileStream _embeddedFileStream; - readonly string _name; + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFileSpecification(PdfDictionary dict) + : base(dict) + { + // TODO: Nothing??? + } + + /// + /// Gets the name of the file, or the empty string, + /// if no name exists. + /// + public string Name + { + get + { +#if true + if (Elements.TryGetString(Keys.UF, out var name)) + return name; + if (Elements.TryGetString(Keys.F, out name)) + return name; + return ""; +#else + var name = Elements.GetString(Keys.UF); + if (String.IsNullOrEmpty(name)) + name = Elements.GetString(Keys.F); + return name; +#endif + } + } + + internal string NamesKey { get; set; } = ""; + + /// + /// Gets a summary of all relevant information of this file specification and + /// the associated embedded file stream. + /// + public EmbeddedFileInfo GetFileInfo() + { + var ef = Elements.GetDictionary(Keys.EF); + if (ef == null) + throw new InvalidOperationException("Key not found: /EF"); // #MSG + + var f = ef.Elements.GetDictionary("/F") // This is not Keys.F. + ?? ef.Elements.GetDictionary("/UF"); // This is not Keys.UF. + if (f == null) + throw new InvalidOperationException( + "The PdfFileSpecification’s /EF dictionary does neither contain an /F nor an /AF key. " + + "No PdfEmbeddedFileStream found."); // #msg + + var efs = f; + + // HACK + if (efs.GetType() != typeof(PdfEmbeddedFileStream)) + { + // Should not come here. + Debugger.Break(); + if (!efs.IsIndirect) + throw new InvalidOperationException("File stream must be indirect object."); // #MSG + + efs = new PdfEmbeddedFileStream(efs); + } + + var info = new EmbeddedFileInfo + { + NamesKey = NamesKey + }; + + var name = Elements.GetString(Keys.F); + if (!String.IsNullOrEmpty(name)) + info.FileName = name; + + var type = efs.Elements.GetString(PdfEmbeddedFileStream.Keys.Subtype); + if (!String.IsNullOrEmpty(type)) + info.FileType = type; + + var desc = Elements.GetString(Keys.Desc); + if (!String.IsNullOrEmpty(desc)) + info.Description = desc; + + //var @params = efs.Elements[PdfEmbeddedFileStream.Keys.Params].AsDictionary(); + var @params = efs.Elements.GetDictionary(PdfEmbeddedFileStream.Keys.Params); // TODO #US373 + if (@params != null) // TODO: StL: params is never null. + { + if (@params.Elements.TryGetDateTime(PdfEmbeddedFileParameters.Keys.CreationDate, out var creationDate)) + info.CreationTime = creationDate; + + if (@params.Elements.TryGetDateTime(PdfEmbeddedFileParameters.Keys.ModDate, out var modificationTime)) + info.ModificationTime = modificationTime; + } + + if (efs.Stream != null) + info.Data = efs.Stream.UnfilteredValue; + + return info; + } /// /// Predefined keys of this dictionary. @@ -156,12 +286,12 @@ public class Keys : KeysBase //[KeyInfo(KeyType.Dictionary | KeyType.Optional)] //public const string RF = "/RF"; - ///// - ///// (Optional; PDF 1.6) Descriptive text associated with the file specification. It is used for - ///// files in the EmbeddedFiles name tree (see Section 3.6.3, “Name Dictionary”). - ///// - //[KeyInfo(KeyType.TextString | KeyType.Optional)] - //public const string Desc = "/Desc"; + /// + /// (Optional; PDF 1.6) Descriptive text associated with the file specification. It is used for + /// files in the EmbeddedFiles name tree (see Section 3.6.3, “Name Dictionary”). + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string Desc = "/Desc"; ///// ///// (Optional; must be indirect reference; PDF 1.7) A collection item dictionary, which is used to @@ -170,6 +300,13 @@ public class Keys : KeysBase //[KeyInfo(KeyType.Dictionary | KeyType.Optional)] //public const string CI = "/CI"; + /// + /// (Optional; PDF 2.0) A name value that represents the relationship between the component of this PDF document + /// that refers to this file specification and the associated file denoted by this file specification dictionary. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string AFRelationship = "/AFRelationship"; + // ReSharper restore InconsistentNaming } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs index 068d8fc5..98d2eaf6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/CObjects.cs @@ -5,12 +5,16 @@ using System.Text; using PdfSharp.Internal; -namespace PdfSharp.Pdf.Content.Objects // TODO_OLD: split into single files +// v7.0.0 TODO + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Content.Objects // TODO: split into single files { /// /// Base class for all PDF content stream objects. /// - public abstract class CObject : ICloneable + public abstract class CObject : ICloneable // #FILE: CObject.cs { /// /// Initializes a new instance of the class. @@ -34,11 +38,14 @@ protected CObject() protected virtual CObject Copy() => (CObject)MemberwiseClone(); /// - /// + /// Must be overridden in a derived class and writes the object to a content writer. /// internal abstract void WriteObject(ContentWriter writer); } +} +namespace PdfSharp.Pdf.Content.Objects // TODO: split into single files +{ /// /// Represents a comment in a PDF content stream. /// @@ -63,6 +70,7 @@ public string Text get => _text ?? ""; set => _text = value; } + string? _text; /// @@ -72,7 +80,10 @@ public string Text internal override void WriteObject(ContentWriter writer) => writer.WriteLineRaw(ToString()); } +} +namespace PdfSharp.Pdf.Content.Objects +{ /// /// Represents a sequence of objects in a PDF content stream. /// @@ -90,7 +101,7 @@ public class CSequence : CObject, IList protected override CObject Copy() { var clone = (CSequence)base.Copy(); - clone._items = []; + clone._items.Clear(); for (int idx = 0; idx < _items.Count; idx++) clone._items.Add(_items[idx].Clone()); return clone; @@ -102,12 +113,6 @@ protected override CObject Copy() /// The sequence. public void Add(CSequence sequence) { - if (sequence is CArray array) - { - _items.Add(array); - return; - } - int count = sequence.Count; for (int idx = 0; idx < count; idx++) _items.Add(sequence[idx]); @@ -187,10 +192,28 @@ public CObject this[int index] /// /// Converts the sequence to a PDF content stream. /// - public byte[] ToContent() + public string ToContent(ContentWriterOptions? options = null) { +#if true + options ??= new(); + //Stream stream = new MemoryStream(); + ContentWriter writer = new(options); + WriteObject(writer); + //writer.Close(false); + var result = writer.ToString(); + return result; + + //stream.Position = 0; + //int count = (int)stream.Length; + //byte[] bytes = new byte[count]; + //var readBytes = stream.Read(bytes, 0, count); + //Debug.Assert(readBytes == count); + //stream.Close(); + //return bytes; +#else + options ??= new(); Stream stream = new MemoryStream(); - ContentWriter writer = new(stream); + ContentWriter writer = new(stream, options); WriteObject(writer); writer.Close(false); @@ -201,6 +224,7 @@ public byte[] ToContent() Debug.Assert(readBytes == count); stream.Close(); return bytes; +#endif } /// @@ -213,11 +237,10 @@ public override string ToString() for (int idx = 0; idx < _items.Count; idx++) { // Add spaces except for first item. - if (s.Length > 0) + if (idx > 0) s.Append(' '); s.Append(_items[idx]); } - return s.ToString(); } @@ -264,8 +287,8 @@ void ICollection.CopyTo(CObject[] array, int arrayIndex) if (_items.Count > array.Length - arrayIndex) throw new ArgumentException("The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array."); - for (int i = arrayIndex; i < _items.Count; i++) - array[i] = _items[i]; + for (int idx = arrayIndex; idx < _items.Count; idx++) + array[idx] = _items[idx]; } int ICollection.Count => _items.Count; @@ -282,9 +305,12 @@ void ICollection.CopyTo(CObject[] array, int arrayIndex) #endregion - List _items = []; + readonly List _items = []; } +} +namespace PdfSharp.Pdf.Content.Objects +{ /// /// Represents the base class for numerical objects in a PDF content stream. /// @@ -299,14 +325,23 @@ public abstract class CNumber : CObject ///// Implements the copy mechanism of this class. ///// //protected override CObject Copy() => base.Copy(); + + public double DoubleValue => GetDoubleValue(); + + protected abstract double GetDoubleValue(); } +} +namespace PdfSharp.Pdf.Content.Objects +{ /// /// Represents an integer value in a PDF content stream. /// [DebuggerDisplay("({" + nameof(Value) + "})")] public class CInteger : CNumber { + public CInteger(int value) => Value = value; + /// /// Creates a new object that is a copy of the current instance. /// @@ -327,6 +362,11 @@ public int Value } int _value; + protected override Double GetDoubleValue() + { + return Value; + } + /// /// Returns a string that represents the current value. /// @@ -334,15 +374,21 @@ public override string ToString() => _value.ToString(CultureInfo.InvariantCulture); internal override void WriteObject(ContentWriter writer) - => writer.WriteRaw(ToString() + " "); + //=> writer.WriteRaw(ToString() + " "); + => writer.Write(this); } +} +namespace PdfSharp.Pdf.Content.Objects +{ /// /// Represents a real value in a PDF content stream. /// [DebuggerDisplay("({" + nameof(Value) + "})")] public class CReal : CNumber { + public CReal(double value) => Value = value; + /// /// Creates a new object that is a copy of the current instance. /// @@ -358,6 +404,11 @@ public class CReal : CNumber /// public double Value { get; set; } + protected override Double GetDoubleValue() + { + return Value; + } + /// /// Returns a string that represents the current value. /// @@ -368,9 +419,13 @@ public override string ToString() } internal override void WriteObject(ContentWriter writer) - => writer.WriteRaw(ToString() + " "); + //=> writer.WriteRaw(ToString() + " "); + => writer.Write(this); } +} +namespace PdfSharp.Pdf.Content.Objects // #FOLDER Pdf.Content.Objects/enum +{ /// /// Type of the parsed string. /// @@ -386,105 +441,96 @@ public enum CStringType /// HexString, - /// - /// The string... TODO_OLD. - /// - UnicodeString, - - /// - /// The string... TODO_OLD. - /// - UnicodeHexString, - /// /// The string is the content of a dictionary. /// Currently, there is no parser for dictionaries in content streams. /// Dictionary, } +} +namespace PdfSharp.Pdf.Content.Objects +{ /// /// Represents a string value in a PDF content stream. /// [DebuggerDisplay("({" + nameof(Value) + "})")] - public class CString : CObject + public class CString(string value, CStringType stringType = CStringType.String) : CObject { /// /// Creates a new object that is a copy of the current instance. /// public new CString Clone() => (CString)Copy(); - ///// - ///// Implements the copy mechanism of this class. - ///// - //protected override CObject Copy() => base.Copy(); - /// - /// Gets or sets the value. + /// Gets the value as a raw string. /// public string Value { - get => _value ?? ""; - set => _value = value; - } - string? _value; + get => field ?? ""; + set => field = value ?? throw new ArgumentNullException(nameof(value)); + } = value; /// /// Gets or sets the type of the content string. /// public CStringType CStringType { - get => _cStringType ?? NRT.ThrowOnNull(); - set => _cStringType = value; - } - CStringType? _cStringType; + get; // => _cStringType ?? NRT.ThrowOnNull(); + set; // => _cStringType = value; + } = stringType; + //CStringType? _cStringType; /// - /// Returns a string that represents the current value. + /// Returns a string that represents the current value in a form that can be used in a + /// PDF content stream. /// public override string ToString() { - var s = new StringBuilder(); + var sb = new StringBuilder(); + int length = Value.Length; + switch (CStringType) { case CStringType.String: - s.Append("("); - int length = _value!.Length; // NRT - for (int ich = 0; ich < length; ich++) + sb.Append("("); + for (int idx = 0; idx < length; idx++) { - char ch = _value[ich]; + char ch = Value[idx]; switch (ch) { + // Reference 2.0: Table 3 — Escape sequences in literal strings / Page 25 + case Chars.LF: - s.Append(@"\n"); + sb.Append(@"\n"); break; case Chars.CR: - s.Append(@"\r"); + sb.Append(@"\r"); break; case Chars.HT: - s.Append(@"\t"); + sb.Append(@"\t"); break; case Chars.BS: - s.Append(@"\b"); + sb.Append(@"\b"); break; case Chars.FF: - s.Append(@"\f"); + sb.Append(@"\f"); break; case Chars.ParenLeft: - s.Append(@"\("); + sb.Append(@"\("); break; case Chars.ParenRight: - s.Append(@"\)"); + sb.Append(@"\)"); break; case Chars.BackSlash: - s.Append(@"\\"); + sb.Append(@"\\"); break; default: @@ -499,37 +545,47 @@ public override string ToString() } else #endif - s.Append(ch); + sb.Append(ch); break; } } - s.Append(')'); + sb.Append(')'); break; case CStringType.HexString: - throw new NotImplementedException(); - - case CStringType.UnicodeString: - throw new NotImplementedException(); + sb.Append("<"); + for (int ich = 0; ich < length; ich++) + { + char ch = Value[ich]; + byte hi = (byte)((ch >> 4) + '0'); + byte lo = (byte)((ch & 0xF) + '0'); + sb.Append((char)(hi < ':' ? hi : hi + ('A' - ':'))); + sb.Append((char)(lo < ':' ? lo : lo + ('A' - ':'))); + } + sb.Append('>'); + break; - case CStringType.UnicodeHexString: - throw new NotImplementedException(); case CStringType.Dictionary: - s.Append(_value); + sb.Append(Value); break; default: throw new ArgumentOutOfRangeException(); } - return s.ToString(); + return sb.ToString(); } internal override void WriteObject(ContentWriter writer) { - writer.WriteRaw(ToString()); + //writer.WriteRaw(ToString()); + writer.Write(ToString()); } } +} + +namespace PdfSharp.Pdf.Content.Objects +{ /// /// Represents a name in a PDF content stream. @@ -558,12 +614,14 @@ public class CName : CObject ///// //protected override CObject Copy() => base.Copy(); + public Name Value => _name ?? "/"; + /// /// Gets or sets the name. Names must start with a slash. /// public string Name { - get => _name ?? NRT.ThrowOnNull(); + get => _name ?? "/"; set { if (String.IsNullOrEmpty(value)) @@ -581,35 +639,247 @@ public string Name public override string? ToString() => _name; internal override void WriteObject(ContentWriter writer) - => writer.WriteRaw(ToString() + " "); + => writer.Write(this); } +} +namespace PdfSharp.Pdf.Content.Objects +{ /// /// Represents an array of objects in a PDF content stream. /// [DebuggerDisplay("(count={" + nameof(Count) + "})")] - public class CArray : CSequence + public class CArray : CObject, IList // Not derived from CSequence. { /// /// Creates a new object that is a copy of the current instance. /// public new CArray Clone() => (CArray)Copy(); - ///// - ///// Implements the copy mechanism of this class. - ///// - //protected override CObject Copy() => base.Copy(); + /// + /// Implements the copy mechanism of this class. + /// + protected override CObject Copy() + { + var clone = (CArray)base.Copy(); + clone._items.Clear(); + for (int idx = 0; idx < _items.Count; idx++) + clone._items.Add(_items[idx].Clone()); + return clone; + } /// - /// Returns a string that represents the current value. + /// Returns a string that represents the current array. + /// + public override string ToString() + { + var sb = new StringBuilder(); + sb.Append('['); + bool delimiter = true; + for (int idx = 0; idx < _items.Count; idx++) + { +#if true + var value = _items[idx].ToString() ?? throw new InvalidOperationException("Sequence element must not be null."); + if (!delimiter && !CLexer.IsDelimiter(value[0])) + sb.Append(' '); + delimiter = CLexer.IsDelimiter(value[^1]); +#else + // Add spaces except for first item. + if (idx > 0) + sb.Append(' '); +#endif + sb.Append(value); + } + sb.Append(']'); + + return sb.ToString(); + } + + public void Add(CSequence sequence) + { + foreach (var value in sequence) + Add(value); + } + + #region IList Members + + /// + /// Adds the specified value add the end of the sequence. + /// + public void Add(CObject value) => _items.Add(value); + + /// + /// Removes all elements from the sequence. + /// + public void Clear() => _items.Clear(); + + /// + /// Determines whether the specified value is in the sequence. + /// + public bool Contains(CObject value) => _items.Contains(value); + + /// + /// Returns the index of the specified value in the sequence or -1, if no such value is in the sequence. + /// + public int IndexOf(CObject value) => _items.IndexOf(value); + + /// + /// Inserts the specified value in the sequence. + /// + public void Insert(int index, CObject value) => _items.Insert(index, value); + + /// + /// Removes the specified value from the sequence. + /// + public bool Remove(CObject value) => _items.Remove(value); + + /// + /// Removes the value at the specified index from the sequence. + /// + public void RemoveAt(int index) => _items.RemoveAt(index); + + /// + /// Gets or sets a CObject at the specified index. + /// + /// + public CObject this[int index] + { + get => _items[index]; + set => _items[index] = value; + } + #endregion + + #region ICollection Members + + /// + /// Copies the elements of the sequence to the specified array. + /// + public void CopyTo(CObject[] array, int index) => _items.CopyTo(array, index); + + /// + /// Gets the number of elements contained in the sequence. + /// + public int Count => _items.Count; + + #endregion + + #region IEnumerable Members + + /// + /// Returns an enumerator that iterates through the sequence. + /// + public IEnumerator GetEnumerator() => _items.GetEnumerator(); + + #endregion + + + internal override void WriteObject(ContentWriter writer) + //=> writer.WriteRaw(ToString()); + => writer.Write(this); + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + + #region IList Members + + int IList.IndexOf(CObject item) => _items.IndexOf(item); + + void IList.Insert(int index, CObject item) => _items.Insert(index, item); + + void IList.RemoveAt(int index) => _items.RemoveAt(index); + + CObject IList.this[int index] + { + get => _items[index]; + set => _items[index] = value; + } + + #endregion + + #region ICollection Members + + void ICollection.Add(CObject item) => Add(item); + + void ICollection.Clear() => Clear(); + + bool ICollection.Contains(CObject item) => Contains(item); + + void ICollection.CopyTo(CObject[] array, int arrayIndex) + { + if (array == null!) + throw new ArgumentNullException(nameof(array)); + + if (arrayIndex < 0) + throw new ArgumentOutOfRangeException(nameof(array)); + + if (_items.Count > array.Length - arrayIndex) + throw new ArgumentException("The number of elements in the source collection is greater than the available space from arrayIndex to the end of the destination array."); + + for (int idx = arrayIndex; idx < _items.Count; idx++) + array[idx] = _items[idx]; + } + + int ICollection.Count => _items.Count; + + bool ICollection.IsReadOnly => false; + + bool ICollection.Remove(CObject item) => _items.Remove(item); + + #endregion + + #region IEnumerable Members + + IEnumerator IEnumerable.GetEnumerator() => _items.GetEnumerator(); + + #endregion + + readonly List _items = []; + } +} + +namespace PdfSharp.Pdf.Content.Objects +{ + /// + /// Represents a literal part a PDF content stream. + /// Use for the BI and ID operators. + /// They are parsed literally. + /// + //[DebuggerDisplay("({Name}, operands={Operands.Count})")] + //[DebuggerDisplay($"{{{nameof(DebuggerDisplay)},nq}}")] + //[DebuggerTypeProxy(typeof(COperatorDebuggerDisplay))] + public class CLiteral : CObject + { + /// + /// Initializes a new instance of the class. + /// + public CLiteral(string literal) => Value = literal; + + /// + /// Creates a new object that is a copy of the current instance. + /// + public new COperator Clone() => (COperator)Copy(); + + /// + /// Gets or sets the byte string. + /// + public string Value { get; set; } + + /// + /// Returns a string that represents the current operator. /// public override string ToString() - => "[" + base.ToString() + "]"; + { + return Value; + } internal override void WriteObject(ContentWriter writer) - => writer.WriteRaw(ToString()); + { + writer.WriteRaw(Value); + } } +} +namespace PdfSharp.Pdf.Content.Objects +{ /// /// Represents an operator a PDF content stream. /// @@ -638,8 +908,7 @@ public class COperator : CObject /// /// Gets or sets the name of the operator. /// - /// The name. - public virtual string Name => _opCode.Name; + public string Name => _opCode.Name; /// /// Gets or sets the operands. @@ -683,17 +952,52 @@ public override string ToString() internal override void WriteObject(ContentWriter writer) { - if (_sequence != null) + if ((OpCode.Flags & OpCodeFlags.InlineImage) == 0) { - int count = _sequence.Count; - for (int idx = 0; idx < count; idx++) + if (_sequence != null) { - _sequence[idx].WriteObject(writer); + int count = _sequence.Count; + for (int idx = 0; idx < count; idx++) + { + _sequence[idx].WriteObject(writer); + } } - } - writer.WriteLineRaw(_opCode.OpCodeName == OpCodeName.Dictionary + writer.WriteLineRaw(_opCode.OpCodeName == OpCodeName.Dictionary ? " " : Name); + } + else + { + // Hack for inline images. + // Operands of BI and ID are parsed as CLiteral. + + switch (OpCode.OpCodeName) + { + case OpCodeName.BI: + { + Debug.Assert(_sequence?.Count == 1); + var operand = (CLiteral)_sequence[0]; + writer.WriteLineRaw($"BI\n{operand.Value}"); + } + break; + + case OpCodeName.ID: + { + Debug.Assert(_sequence?.Count == 1); + var operand = (CLiteral)_sequence[0]; + writer.WriteLineRaw($"ID {operand.Value}"); + } + break; + + case OpCodeName.EI: + writer.WriteLineRaw("EI"); + break; + + default: + Debug.Assert(false); + break; + } + } } #region Printing/Debugger display diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/Operators.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/Operators.cs index 20a39504..ad358361 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/Operators.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/Operators.cs @@ -1,7 +1,7 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Reflection.Emit; +// v7.0.0 TODO namespace PdfSharp.Pdf.Content.Objects { @@ -98,7 +98,6 @@ public static COperator OperatorFromName(string name) /// static OpCodes() { - for (int idx = 0; idx < ops.Length; idx++) { var op = ops[idx]; @@ -106,11 +105,13 @@ static OpCodes() } } - private static readonly Dictionary StringToOpCode = []; + static readonly Dictionary StringToOpCode = []; // ReSharper disable InconsistentNaming // ReSharper disable StringLiteralTypo + // TODO: Make all these OpCodes internal/public? Or don’t use them in UpdateTextFieldAppearanceStream(). + static readonly OpCode Dictionary = new("Dictionary", OpCodeName.Dictionary, -1, "name, dictionary", OpCodeFlags.None, "E.g.: /Name << ... >>"); @@ -129,13 +130,13 @@ static OpCodes() static readonly OpCode BDC = new("BDC", OpCodeName.BDC, -1, null, OpCodeFlags.None, "(PDF 1.2) Begin marked-content sequence with property list"); - static readonly OpCode BI = new("BI", OpCodeName.BI, 0, null, OpCodeFlags.None, + static readonly OpCode BI = new("BI", OpCodeName.BI, 1, null, OpCodeFlags.InlineImage, "Begin inline image object"); - static readonly OpCode BMC = new("BMC", OpCodeName.BMC, 1, null, OpCodeFlags.None, + internal static readonly OpCode BMC = new("BMC", OpCodeName.BMC, 1, null, OpCodeFlags.None, "(PDF 1.2) Begin marked-content sequence"); - static readonly OpCode BT = new("BT", OpCodeName.BT, 0, null, OpCodeFlags.None, + internal static readonly OpCode BT = new("BT", OpCodeName.BT, 0, null, OpCodeFlags.None, "Begin text object"); static readonly OpCode BX = new("BX", OpCodeName.BX, 0, null, OpCodeFlags.None, @@ -168,13 +169,13 @@ static OpCodes() static readonly OpCode DP = new("DP", OpCodeName.DP, 2, null, OpCodeFlags.None, "(PDF 1.2) Define marked-content point with property list"); - static readonly OpCode EI = new("EI", OpCodeName.EI, 0, null, OpCodeFlags.None, + static readonly OpCode EI = new("EI", OpCodeName.EI, 0, null, OpCodeFlags.InlineImage, "End inline image object"); - static readonly OpCode EMC = new("EMC", OpCodeName.EMC, 0, null, OpCodeFlags.None, + internal static readonly OpCode EMC = new("EMC", OpCodeName.EMC, 0, null, OpCodeFlags.None, "(PDF 1.2) End marked-content sequence"); - static readonly OpCode ET = new("ET", OpCodeName.ET, 0, null, OpCodeFlags.None, + internal static readonly OpCode ET = new("ET", OpCodeName.ET, 0, null, OpCodeFlags.None, "End text object"); static readonly OpCode EX = new("EX", OpCodeName.EX, 0, null, OpCodeFlags.None, @@ -204,7 +205,7 @@ static OpCodes() static readonly OpCode i = new("i", OpCodeName.i, 1, "setflat", OpCodeFlags.None, "Set flatness tolerance"); - static readonly OpCode ID = new("ID", OpCodeName.ID, 0, null, OpCodeFlags.None, + static readonly OpCode ID = new("ID", OpCodeName.ID, 1, null, OpCodeFlags.InlineImage, "Begin inline image data"); static readonly OpCode j = new("j", OpCodeName.j, 1, "setlinejoin", OpCodeFlags.None, @@ -234,10 +235,10 @@ static OpCodes() static readonly OpCode n = new("n", OpCodeName.n, 0, null, OpCodeFlags.None, "End path without filling or stroking"); - static readonly OpCode q = new("q", OpCodeName.q, 0, "gsave", OpCodeFlags.None, + internal static readonly OpCode q = new("q", OpCodeName.q, 0, "gsave", OpCodeFlags.None, "Save graphics state"); - static readonly OpCode Q = new("Q", OpCodeName.Q, 0, "grestore", OpCodeFlags.None, + internal static readonly OpCode Q = new("Q", OpCodeName.Q, 0, "grestore", OpCodeFlags.None, "Restore graphics state"); static readonly OpCode re = new("re", OpCodeName.re, 4, null, OpCodeFlags.None, @@ -270,34 +271,34 @@ static OpCodes() static readonly OpCode scn = new("scn", OpCodeName.scn, -1, "setcolor", OpCodeFlags.None, "(PDF 1.2) Set color for non-stroking operations (ICCBased and special color spaces)"); - static readonly OpCode sh = new("sh", OpCodeName.sh, 1, "shfill", OpCodeFlags.None, + static readonly OpCode sh = new("sh", OpCodeName.Sh, 1, "shfill", OpCodeFlags.None, "(PDF 1.3) Paint area defined by shading pattern"); - static readonly OpCode Tx = new("T*", OpCodeName.Tx, 0, null, OpCodeFlags.None, + internal static readonly OpCode Tx = new("T*", OpCodeName.Tx, 0, null, OpCodeFlags.None, "Move to start of next text line"); static readonly OpCode Tc = new("Tc", OpCodeName.Tc, 1, null, OpCodeFlags.None, "Set character spacing"); - static readonly OpCode Td = new("Td", OpCodeName.Td, 2, null, OpCodeFlags.None, + internal static readonly OpCode Td = new("Td", OpCodeName.Td, 2, null, OpCodeFlags.None, "Move text position"); - static readonly OpCode TD = new("TD", OpCodeName.TD, 2, null, OpCodeFlags.None, + internal static readonly OpCode TD = new("TD", OpCodeName.TD, 2, null, OpCodeFlags.None, "Move text position and set leading"); - static readonly OpCode Tf = new("Tf", OpCodeName.Tf, 2, "selectfont", OpCodeFlags.None, + internal static readonly OpCode Tf = new("Tf", OpCodeName.Tf, 2, "selectfont", OpCodeFlags.None, "Set text font and size"); - static readonly OpCode Tj = new("Tj", OpCodeName.Tj, 1, "show", OpCodeFlags.TextOut, + internal static readonly OpCode Tj = new("Tj", OpCodeName.Tj, 1, "show", OpCodeFlags.TextOut, "Show text"); - static readonly OpCode TJ = new("TJ", OpCodeName.TJ, 1, null, OpCodeFlags.TextOut, + internal static readonly OpCode TJ = new("TJ", OpCodeName.TJ, 1, null, OpCodeFlags.TextOut, "Show text, allowing individual glyph positioning"); static readonly OpCode TL = new("TL", OpCodeName.TL, 1, null, OpCodeFlags.None, "Set text leading"); - static readonly OpCode Tm = new("Tm", OpCodeName.Tm, 6, null, OpCodeFlags.None, + internal static readonly OpCode Tm = new("Tm", OpCodeName.Tm, 6, null, OpCodeFlags.None, "Set text matrix and text line matrix"); static readonly OpCode Tr = new("Tr", OpCodeName.Tr, 1, null, OpCodeFlags.None, @@ -327,10 +328,10 @@ static OpCodes() static readonly OpCode y = new("y", OpCodeName.y, 4, "curveto", OpCodeFlags.None, "Append curved segment to path (final point replicated)"); - static readonly OpCode QuoteSingle = new("'", OpCodeName.QuoteSingle, 1, null, OpCodeFlags.TextOut, + internal static readonly OpCode QuoteSingle = new("'", OpCodeName.QuoteSingle, 1, null, OpCodeFlags.TextOut, "Move to next line and show text"); - static readonly OpCode QuoteDouble = new("\"", OpCodeName.QuoteDouble, 3, null, OpCodeFlags.TextOut, + internal static readonly OpCode QuoteDouble = new("\"", OpCodeName.QuoteDouble, 3, null, OpCodeFlags.TextOut, "Set word and character spacing, move to next line, and show text"); /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeFlags.cs index 60bced01..01867696 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeFlags.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeFlags.cs @@ -1,7 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; +// v7.0.0 TODO namespace PdfSharp.Pdf.Content.Objects { @@ -20,6 +20,12 @@ public enum OpCodeFlags /// /// TextOut = 0x0001, + + /// + /// BI, ID, or EI. + /// + InlineImage = 0x0002, + //Color, Pattern, Images,... } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeName.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeName.cs index 333a2a03..053512c9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeName.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content.Objects/enum/OpCodeName.cs @@ -1,21 +1,34 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -// ReSharper disable InconsistentNaming +// v7.0.0 TODO namespace PdfSharp.Pdf.Content.Objects { + /* + Zero/non-zero winding and alternate/winding modes both refer to polygon filling rules, + with "non-zero" and "winding" being the same rule, while "zero" and "alternate" refer to + the same "even-odd" rule. The core difference is that non-zero winding (also called winding mode) + counts the direction of path segments, filling any region where the winding number is not zero, + whereas alternate filling (also called even-odd) simply counts the number of crossings and fills every other region. + non-zero = winding + even-odd = alternate + + */ + /// /// The names of the op-codes. /// - public enum OpCodeName + public enum OpCodeName // TODO sync names with PDFsharp PDF Graphics. { + // ReSharper disable InconsistentNaming + /// /// Pseudo op-code for the name of a dictionary. /// Dictionary, // Name followed by dictionary. - // I know that this is not useable in VB or other languages with no case sensitivity. + // I know that this is not usable in VB or other languages with no case sensitivity. // Reference: TABLE A.1 PDF content stream operators / Page 985 // Reference 2.0: Table A.1 — PDF content stream operators / Page 844 @@ -23,22 +36,24 @@ public enum OpCodeName /// /// Close, fill, and stroke path using non-zero winding number rule. /// - b, + b, // CloseFillStrokePathWinding /// /// Fill and stroke path using non-zero winding number rule. /// - B, + B, // FillStrokePathAlternate /// /// Close, fill, and stroke path using even-odd rule. /// - bx, // actually b* + bx, // CloseFillStrokePathWinding + // actually b* /// /// Fill and stroke path using even-odd rule. /// - Bx, // actually B* + Bx, // FillStrokeEvenOdd_BStar + // actually B* /// /// (PDF 1.2) Begin marked-content sequence with property list @@ -283,7 +298,7 @@ public enum OpCodeName /// /// (PDF 1.3) Paint area defined by shading pattern. /// - sh, + Sh, /// /// Move to start of next text line. @@ -354,11 +369,19 @@ public enum OpCodeName /// Append curved segment to path (initial point replicated). /// v, + /// + /// Append curved segment to path (initial point replicated). + /// + AppendCurvedSegmentToPath = v, /// /// Set line width. /// w, + /// + /// Set line width. + /// + SetLineWidth = w, /// /// Set clipping path using non-zero winding number rule. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CLexer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CLexer.cs index 232e5f12..85102697 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CLexer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CLexer.cs @@ -1,9 +1,13 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Text; using Microsoft.Extensions.Logging; +using PdfSharp.Internal; using PdfSharp.Logging; +using PdfSharp.Pdf.Internal; +using System.Text; + +// v7.0.0 REVIEW namespace PdfSharp.Pdf.Content { @@ -39,7 +43,6 @@ public CLexer(MemoryStream content) _content = content.ToArray(); ContLength = _content.Length; } - _charIndex = 0; } @@ -103,7 +106,8 @@ public CSymbol ScanNextToken() } /// - /// Scans a comment line. (Not yet used, comments are skipped by lexer.) + /// Scans a comment line. + /// Not used, comments are skipped by lexer. /// public CSymbol ScanComment() { @@ -116,36 +120,47 @@ public CSymbol ScanComment() } /// - /// Scans the bytes of an inline image. - /// NYI: Just scans over it. + /// Scans the dictionary of BI a CLiteral. /// - public CSymbol ScanInlineImage() + public CSymbol ScanBeginImage() { - // TODO_OLD: Implement inline images. - // Skip this: - // BI - // … Key-value pairs … - // ID - // … Image data … - // EI + int biIndex = _charIndex; + AdjustBackwards(ref biIndex, 'B'); + biIndex += 3; bool ascii85 = false; do { ScanNextToken(); - // HACK_OLD: Is image ASCII85 decoded? + if (Symbol == CSymbol.Eof) + break; + if (!ascii85 && Symbol == CSymbol.Name && Token is "/ASCII85Decode" or "/A85") ascii85 = true; } while (Symbol != CSymbol.Operator || Token != "ID"); - if (ascii85) - { - // Look for '~>' because 'EI' may be part of the encoded image. - while (_currChar != Chars.EOF && (_currChar != '~' || _nextChar != '>')) - ScanNextChar(); - if (_currChar == Chars.EOF) - ContentReaderDiagnostics.HandleUnexpectedCharacter(_currChar); - } + Debug.Assert(Token == "ID"); + AdjustBackwards(ref _charIndex, 'I'); + int idIndex = _charIndex - 1; + SetPosition(_charIndex); + + var biBytes = _content[biIndex..idIndex]; + var bi = PdfEncoders.RawEncoding.GetString(biBytes); + + ClearToken(); + _token.Append(bi); + + return CSymbol.Operator; + } + + /// + /// Scans the data of ID a CLiteral. + /// + public CSymbol ScanImageData() + { + int idIndex = _charIndex; + AdjustBackwards(ref idIndex, 'I'); + idIndex += 3; // Look for 'EI', as 'EI' may be part of the binary image data here too. while (_currChar != Chars.EOF) @@ -163,8 +178,17 @@ public CSymbol ScanInlineImage() if (_currChar == Chars.EOF) ContentReaderDiagnostics.HandleUnexpectedCharacter(_currChar); - // We currently do nothing with inline images. - return CSymbol.None; + AdjustBackwards(ref _charIndex, 'E'); + int endIndex = _charIndex - 1; + SetPosition(_charIndex); + + var idBytes = _content[idIndex..endIndex]; + var id = PdfEncoders.RawEncoding.GetString(idBytes); + + ClearToken(); + _token.Append(id); + + return CSymbol.Operator; } /// @@ -203,7 +227,7 @@ public CSymbol ScanName() static char LogError(char ch) { - PdfSharpLogHost.Logger.LogError("Illegal character {char} in hex string.", ch); + PdfSharpLogHost.Logger.LogError("Illegal character '{char}' in hex string.", ch); return '\0'; } } @@ -304,7 +328,7 @@ protected CSymbol ScanDictionary() /// public CSymbol ScanNumber() { - // Note: This is a copy of Lexer.ScanNumber with minimal changes. Keep both versions in sync as far as possible. + // Note that this is a copy of Lexer.ScanNumber with minimal changes. Keep both versions in sync as far as possible. // Parsing Strategy: // Most real life numbers in PDF files have less than 19 digits. So we try to parse all digits as 64-bit integers @@ -486,7 +510,10 @@ public CSymbol ScanOperator() // Scan token. while (IsOperatorChar(ch)) ch = AppendAndScanNextChar(); - +#if DEBUG + if (_token.ToString() == "BI") + _ = typeof(int); +#endif return Symbol = CSymbol.Operator; } @@ -579,7 +606,7 @@ public CSymbol ScanLiteralString() default: if (Char.IsDigit(ch)) { - // Octal character code + // Octal character code. int n = ch - '0'; if (Char.IsDigit(_nextChar)) { @@ -721,7 +748,6 @@ public CSymbol ScanLiteralString() public CSymbol ScanHexadecimalString() { Debug.Assert(_currChar == Chars.Less); - ClearToken(); ScanNextChar(); while (true) @@ -814,6 +840,22 @@ char ScanNextChar() return _currChar; } + void SetPosition(int charIndex) + { + _charIndex = charIndex; + _currChar = (char)_content[_charIndex++]; + _nextChar = (char)_content[_charIndex++]; + } + + /// + /// Find index of BI, ID, or EI. + /// + void AdjustBackwards(ref int index, char stop) + { + while ((char)_content[index] != stop) + index--; + } + /// /// Resets the current token to the empty string. /// @@ -836,9 +878,9 @@ char AppendAndScanNextChar() } /// - /// If the current character is not a white space, the function immediately returns it. - /// Otherwise, the PDF cursor is moved forward to the first non-white space or EOF. - /// White spaces are NUL, HT, LF, FF, CR, and SP. + /// If the current character is not a white-space, the function immediately returns it. + /// Otherwise, the PDF cursor is moved forward to the first non-white-space or EOF. + /// White-spaces are NUL, HT, LF, FF, CR, and SP. /// public char MoveToNonWhiteSpace() { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CParser.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CParser.cs index fda05cde..245eaccf 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CParser.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/CParser.cs @@ -5,6 +5,8 @@ using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Content.Objects; +// v7.0.0 REVIEW + namespace PdfSharp.Pdf.Content { /// @@ -62,6 +64,16 @@ public CSequence ReadContent() return sequence; } + /// + /// Parses whatever comes until the end of the array is reached. + /// + void ParseArray(CArray array) + { + var sequence = new CSequence(); + ParseObject(sequence, CSymbol.EndArray); + array.Add(sequence); + } + /// /// Parses whatever comes until the specified stop symbol is reached. /// @@ -82,42 +94,29 @@ void ParseObject(CSequence sequence, CSymbol stop) break; case CSymbol.Integer: - CInteger n = new() - { - Value = _lexer.TokenToInteger - }; + CInteger n = new(_lexer.TokenToInteger); _operands.Add(n); break; case CSymbol.Real: - CReal r = new() - { - Value = _lexer.TokenToReal - }; + CReal r = new(_lexer.TokenToReal); _operands.Add(r); break; case CSymbol.String: + s = new(_lexer.Token); + _operands.Add(s); + break; + case CSymbol.HexString: - case CSymbol.UnicodeString: - case CSymbol.UnicodeHexString: - s = new() - { - Value = _lexer.Token, - CStringType = CStringType.String // Must set string type. So far, only CStringType.String is supported in CString.ToString(). - }; + s = new(_lexer.Token, CStringType.HexString); _operands.Add(s); break; case CSymbol.Dictionary: - s = new() - { - Value = _lexer.Token, - CStringType = CStringType.Dictionary - }; + s = new(_lexer.Token, CStringType.Dictionary); _operands.Add(s); op = CreateOperator(OpCodeName.Dictionary); - //_operands.Clear(); sequence.Add(op); break; @@ -131,7 +130,6 @@ void ParseObject(CSequence sequence, CSymbol stop) case CSymbol.Operator: op = CreateOperator(); - //_operands.Clear(); sequence.Add(op); break; @@ -140,10 +138,10 @@ void ParseObject(CSequence sequence, CSymbol stop) if (_operands.Count != 0) ContentReaderDiagnostics.ThrowContentReaderException("Array within array..."); - ParseObject(array, CSymbol.EndArray); + ParseArray(array); array.Add(_operands); _operands.Clear(); - _operands.Add((CObject)array); + _operands.Add(array); break; case CSymbol.EndArray: @@ -174,9 +172,32 @@ COperator CreateOperator(OpCodeName nameop) COperator CreateOperator(COperator op) { - if (op.OpCode.OpCodeName == OpCodeName.BI) + // Special handling for inline images. + if ((op.OpCode.Flags & OpCodeFlags.InlineImage) != 0) { - _lexer.ScanInlineImage(); + string literal; + switch (op.OpCode.OpCodeName) + { + case OpCodeName.BI: + _lexer.ScanBeginImage(); + literal = _lexer.Token; + _operands.Add(new CLiteral(literal)); + break; + + case OpCodeName.ID: + _lexer.ScanImageData(); + literal = _lexer.Token; + _operands.Add(new CLiteral(literal)); + break; + + case OpCodeName.EI: + // Has no operands. + break; + + default: + Debug.Assert(false); + break; + } } #if DEBUG if (op.OpCode.Operands != -1 && op.OpCode.Operands != _operands.Count) @@ -188,8 +209,7 @@ COperator CreateOperator(COperator op) } } #endif - //if (_operands.Count != 0) // Do not create empty Operands sequence. - op.Operands.Add(_operands); + op.Operands.Add(_operands); _operands.Clear(); return op; } @@ -217,8 +237,8 @@ CSymbol ReadSymbol(CSymbol symbol) return current; } - readonly CSequence _operands = new CSequence(); - PdfPage _page = default!; + readonly CSequence _operands = new(); + PdfPage _page = null!; readonly CLexer _lexer; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/Chars.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/Chars.cs index 4c95045a..0e7bcaa5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/Chars.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/Chars.cs @@ -1,7 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -// ReSharper disable InconsistentNaming +// v7.0 namespace PdfSharp.Pdf.Content { @@ -10,6 +10,8 @@ namespace PdfSharp.Pdf.Content /// static class Chars { + // ReSharper disable InconsistentNaming + public const char EOF = '\uFFFF'; // EOF public const char NUL = '\0'; // NUL public const char CR = '\x0D'; // Carriage return, ignored by lexer. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReader.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReader.cs index 91f6efa8..8399a862 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReader.cs @@ -3,8 +3,14 @@ using PdfSharp.Pdf.Content.Objects; +// v7.0.0 TODO + namespace PdfSharp.Pdf.Content { + // TODO: FormsCleanUp: Remove Content and Content.Objects and replace with Content2 and Content2.Objects: + // Attention: Content(.Objects) classes ContentReader, ContentReaderException, ContentWriter, OpCodeName, CSequence, CName, CArray, COperator, OpCodes are still in use. + // Change the namespace in the using classes and test it before removal. + /// /// Represents the functionality for reading PDF content streams. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReaderException.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReaderException.cs index ad0069a3..824f4c25 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReaderException.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentReaderException.cs @@ -1,6 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +// v7.0.0 TODO + namespace PdfSharp.Pdf.Content { /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs index a8821dae..171cb53c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs @@ -1,64 +1,84 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using PdfSharp.Pdf.Internal; +using System.Text; +using PdfSharp.Pdf.Content.Objects; + +// v7.0.0 REVIEW + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf.Content { /// /// Represents a writer for generation of PDF streams. /// - class ContentWriter + public class ContentWriter { - public ContentWriter(Stream contentStream) + public ContentWriter(ContentWriterOptions options) { - _stream = contentStream; -#if DEBUG - //layout = PdfWriterLayout.Verbose; -#endif + //_stream = contentStream; + _options = options; } + StringBuilder _sb = new StringBuilder(); public void Close(bool closeUnderlyingStream) { - if (_stream != null && closeUnderlyingStream) - { - _stream.Close(); - _stream = null; - } + //if (_stream != null && closeUnderlyingStream) + //{ + // _stream.Close(); + // _stream = null; + //} } public void Close() => Close(true); - public int Position => (int)(_stream?.Position ?? 0); + public void Clear() + { + _sb.Clear(); + } + + public override String ToString() + { + return _sb.ToString(); + } - //public PdfWriterLayout Layout - //{ - // get { return layout; } - // set { layout = value; } - //} - //PdfWriterLayout layout; + public void Write(CNumber number) + { + var s = number.ToString(); + _sb.Append(s); + _sb.Append(' '); // HACK + } - //public PdfWriterOptions Options - //{ - // get { return options; } - // set { options = value; } - //} - //PdfWriterOptions options; + public void Write(string s) + { + _sb.Append(s); + } - // ----------------------------------------------------------- + public void Write(CName name) + { + _sb.Append(name); + _sb.Append(' '); // HACK + } - /// - /// Writes the specified value to the PDF stream. - /// - public void Write(bool value) + public void Write(CArray array) { - //WriteSeparator(CharCat.Character); - //WriteRaw(value ? bool.TrueString : bool.FalseString); - //lastCat = CharCat.Character; + _sb.Append(array.ToString()); // HACK } public void WriteRaw(string rawString) { +#if true + if (String.IsNullOrEmpty(rawString)) + return; + _sb.Append(rawString); + + //AppendBlank(rawString[0]); + //byte[] bytes = PdfEncoders.RawEncoding.GetBytes(rawString); + //_stream?.Write(bytes, 0, bytes.Length); + // ReSharper disable once UseIndexFromEndExpression + _lastCat = GetCategory(rawString[rawString.Length - 1]); +#else if (String.IsNullOrEmpty(rawString)) return; //AppendBlank(rawString[0]); @@ -66,10 +86,23 @@ public void WriteRaw(string rawString) _stream?.Write(bytes, 0, bytes.Length); // ReSharper disable once UseIndexFromEndExpression _lastCat = GetCategory((char)bytes[bytes.Length - 1]); +#endif } public void WriteLineRaw(string rawString) { +#if true + if (String.IsNullOrEmpty(rawString)) + return; + _sb.Append(rawString); + _sb.Append('\n'); + //AppendBlank(rawString[0]); + //byte[] bytes = PdfEncoders.RawEncoding.GetBytes(rawString); + //_stream!.Write(bytes, 0, bytes.Length); + //_stream!.Write([(byte)'\n'], 0, 1); + //_lastCat = GetCategory((char)bytes[bytes.Length - 1]); + _lastCat = GetCategory(rawString[^1]); +#else if (String.IsNullOrEmpty(rawString)) return; //AppendBlank(rawString[0]); @@ -77,13 +110,21 @@ public void WriteLineRaw(string rawString) _stream!.Write(bytes, 0, bytes.Length); _stream!.Write([(byte)'\n'], 0, 1); _lastCat = GetCategory((char)bytes[bytes.Length - 1]); +#endif } public void WriteRaw(char ch) { +#if true + Debug.Assert(ch < 256, "Raw character greater than 255 detected."); + _sb.Append(ch); + //_stream?.WriteByte((byte)ch); + _lastCat = GetCategory(ch); +#else Debug.Assert(ch < 256, "Raw character greater than 255 detected."); _stream?.WriteByte((byte)ch); _lastCat = GetCategory(ch); +#endif } /// @@ -121,7 +162,7 @@ void WriteSeparator(CharCat cat, char ch) switch (_lastCat) { //case CharCat.NewLine: - // if (this.layout == PdfWriterLayout.Verbose) + // if (this.layout == PdfWriterLayout.Ver/bose) // WriteIndent(); // break; @@ -129,7 +170,7 @@ void WriteSeparator(CharCat cat, char ch) break; //case CharCat.Character: - // if (this.layout == PdfWriterLayout.Verbose) + // if (this.layout == PdfWriterLayout.Ver/bose) // { // //if (cat == CharCat.Character || ch == '/') // this.stream.WriteByte((byte)' '); @@ -168,14 +209,57 @@ enum CharCat NewLine, Character, Delimiter, + IRef } CharCat _lastCat; + ///// + ///// Gets the underlying stream. + ///// + //internal Stream Stream => _stream ?? NRT.ThrowOnNull(); + + //Stream? _stream = null!; + ContentWriterOptions _options; + } +} + +namespace PdfSharp.Pdf.Content +{ + public class ContentWriterOptions + { + public ContentWriterLayout Layout { get; set; } = +#if DEBUG + ContentWriterLayout.LineFeed; +#else + ContentWriterLayout.SingleLine; +#endif + } +} + +namespace PdfSharp.Pdf.Content // #FOLDER Pdf.Content.enums +{ + /// + /// Determines how the PDF output stream is formatted. Even all formats create valid PDF files, + /// only Compact or Standard should be used for production purposes. + /// + public enum ContentWriterLayout + { + // TODO + + /// + /// The content stream is written in one single line. + /// This is default in release build. + /// + SingleLine, + /// - /// Gets the underlying stream. + /// The content stream is written with a line feed after each operator. + /// This is default in debug build. /// - internal Stream Stream => _stream ?? NRT.ThrowOnNull(); + LineFeed, - Stream? _stream = null!; + ///// + ///// + //Verbose, } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs index 4ebbcd10..fe708fc4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/enums/Symbol.cs @@ -1,6 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +// v7.0.0 TODO + namespace PdfSharp.Pdf.Content { /// @@ -16,8 +18,8 @@ public enum CSymbol /*Boolean?,*/ String, HexString, - UnicodeString, - UnicodeHexString, + //UnicodeString, + //UnicodeHexString, Name, Operator, BeginArray, diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filter.cs index 69d72034..dc2f15c3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filter.cs @@ -78,7 +78,7 @@ public string DecodeToString(byte[] data) } /// - /// Removes all white spaces from the data. The function assumes that the bytes are characters. + /// Removes all white-spaces from the data. The function assumes that the bytes are characters. /// protected byte[] RemoveWhiteSpace(byte[] data) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs index 1f48f8f8..a32c64ac 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/Filtering.cs @@ -168,9 +168,11 @@ public static Filter GetFilter(string filterName) // Crypt = new Crypt(); // return Crypt; - + case PdfFilterNames.RunLengthDecode: + case PdfFilterNames.RunLengthDecodeAbbreviation: case PdfFilterNames.CcittFaxDecode: + case PdfFilterNames.CcittFaxDecodeAbbreviation: case PdfFilterNames.Jbig2Decode: case PdfFilterNames.JpxDecode: case PdfFilterNames.Crypt: @@ -266,18 +268,21 @@ public static byte[] Decode(byte[] data, PdfItem filterItem, PdfItem? decodeParm { byte[]? result = null; -#if true - PdfReference.Dereference(ref filterItem); -#else - if (filterItem is PdfReference iref) - { - Debug.Assert(iref.Value != null, "Indirect /Filter value is null"); - filterItem = iref.Value; - } -#endif - - if (decodeParms is not null) - PdfReference.Dereference(ref decodeParms); + // #US373. +// // TODO Make sure Decode is never called with a reference. +//#if true +// PdfReference.Dereference(ref filterItem); +//#else +// if (filterItem is PdfReference iref) +// { +// Debug.Assert(iref.Value != null, "Indirect /Filter value is null"); +// filterItem = iref.Value; +// } +//#endif + +// // TODO Make sure Decode is never called with a reference. +// if (decodeParms is not null) +// PdfReference.Dereference(ref decodeParms); if (filterItem is PdfName && decodeParms is null or PdfDictionary) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/FlateDecode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/FlateDecode.cs index fa2f113f..fa314b14 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/FlateDecode.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Filters/FlateDecode.cs @@ -2,6 +2,8 @@ // See the LICENSE file in the solution root for more information. using System.IO.Compression; +using Microsoft.Extensions.Logging; +using PdfSharp.Logging; namespace PdfSharp.Pdf.Filters { @@ -127,16 +129,30 @@ public override byte[] Decode(byte[] data, FilterParms? parms) { using var msInput = new MemoryStream(data); using var msOutput = new MemoryStream(); +#if true + // CMF (Compression Method Flags) must be deflated. + byte byte1 = (byte)msInput.ReadByte(); + if ((byte1 & 0xF) != 0x8) + PdfSharpLogHost.Logger.LogWarning("Cannot decode stream. Compression method must be deflate."); + + // Flags. + byte byte2 = (byte)msInput.ReadByte(); + if ((byte2 & 0x20) != 0) + PdfSharpLogHost.Logger.LogWarning("Cannot decode stream. DeflateStream does not support Adler32."); +#else // ReSharper disable once RedundantAssignment var header = new byte[] { (byte)msInput.ReadByte(), // CMF (Compression Method and flags) (byte)msInput.ReadByte() // Flags }; -#if true - Debug.Assert((header[0] & 0xF) == 0x8); // Compression method must be deflate. - Debug.Assert((header[1] & 0x20) == 0); // DeflateStream does not support Adler32. + + if ((header[0] & 0xF) != 0x8) + PdfSharpLogHost.Logger.LogWarning("Can’t decode stream: Wrong header: Compression method must be deflate."); + + if ((header[1] & 0x20) != 0) + PdfSharpLogHost.Logger.LogWarning("Can’t decode stream: Wrong header: DeflateStream does not support Adler32."); #endif using var zip = new DeflateStream(msInput, CompressionMode.Decompress, true); @@ -149,7 +165,6 @@ public override byte[] Decode(byte[] data, FilterParms? parms) if (parms?.DecodeParms != null) return StreamDecoder.Decode(msOutput.GetBuffer(), parms.DecodeParms); } - return msOutput.GetBuffer(); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/!Notes.md b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/!Notes.md new file mode 100644 index 00000000..1a495d38 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/!Notes.md @@ -0,0 +1,35 @@ + + + + +## Classes + +* Table 224 — Entries in the interactive form dictionary + ** Table 225 — Signature flags +* Table 226 — Entries common to all field dictionaries + ** Table 227 — Field flags common to all field types + **PdfAcroFieldFlags** + **FileName** als eigene Struct implementieren, analog Name +* Table 228 — Additional entries common to all fields containing variable text +* Table 230 — Additional entry specific to check box and radio button fields +* Table 232 — Additional entry specific to a text field +* Table 234 — Additional entries specific to a choice field +* Table 235 — Additional entries specific to a signature field +* Table 236 — Entries in a signature field lock dictionary +* Table 237 — Entries in a signature field seed value dictionary +* Table 238 — Entries in a certificate seed value dictionary + +* Table 239 — Additional entries specific to a submit-form action +* Table 240 — Flags for submit-form actions +* Table 243 — Additional entries specific to an import-data action + +* FDF files??? + + +## Fild types + +Interactive forms support the following field types: +• Button fields represent interactive controls on the screen that the user can manipulate with the mouse. They include push-buttons, check boxes, and radio buttons. +• Text fields are boxes or spaces in which the user can enter text from the keyboard. +• Choice fields contain several text items, at most one of which may be selected as the field value. They include scrollable list boxes and combo boxes. +• Signature fields represent digital signatures and optional data for authenticating the name of the signer and the document’s contents. \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/Design.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/Design.cs new file mode 100644 index 00000000..b5ef944a --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/Design.cs @@ -0,0 +1,1352 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.IO; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +// v7.0.0 TODO review, Fields OTT + +namespace PdfSharp.Pdf.Forms +{ + /// + /// Base class for all interactive form (AcroForm) fields. + /// Fields are always created as indirect objects. + /// + public abstract class PdfFormField : PdfDictionary // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.4 Field dictionaries / Page 530 + + internal PdfFormField(PdfDocument document) + : base(document, true) + { } + + /// + /// Initializes a new instance of the class. + /// + protected PdfFormField(PdfDictionary dict) + : base(dict) + { } + + /// + /// Gets or sets the name of this field. + /// + /// + /// Returns the value of the /T key. + /// + public string Name + => Elements.GetString(Keys.T); + + public string FullName + { + get + { + var fullName = Parent?.FullName; + var name = Name; + + // The /T entry is required. Only a pure widget at a leaf doesn’t have one. It is recognized as a + // PdfFormFieldWidget, as the widget may also contain field keys. If it doesn’t have a name, simply + // return its parent’s FullName. + if (String.IsNullOrEmpty(name)) + return fullName ?? throw new NotImplementedException("Should not occur: fullName is null."); + + fullName = String.IsNullOrEmpty(fullName) + ? name + : fullName + "." + name; + + return fullName; + } + } + + public PdfFormField? Parent + => Elements.GetDictionary(Keys.Parent); + + /// + /// Gets the actual type of the field with inheritance considered. + /// + public virtual Type FieldType => GetType(); + + /// + /// Gets or sets the field flags of this instance. + /// + /// + /// Returns the value of the /Ff key. + /// + internal PdfFormFieldFlags Flags + { + get + { + if (Elements.TryGetEnum(Keys.Ff, out var result)) + return result; + + var parent = Parent; + return parent?.Flags ?? 0; + } + set => Elements.SetIntegerFlag(Keys.Ff, (int)value); + } + + /// + /// Gets or sets the value of the field, considering inheritance. + /// + public PdfItem? Value + { + get => GetElementsValue(Keys.V); + set + { + if (IsReadOnly) + throw new InvalidOperationException("The field is read only."); + + if (value == null) + { + Elements.Remove(Keys.V); + Parent?.Value = null; + return; + } + + if (value is PdfString or PdfName) + Elements[Keys.V] = value; + else + throw new NotImplementedException("Values other than string or name cannot be set."); + } + } + + /// + /// Returns false, if the field shall not be considered a field but simply a widget annotation. + /// + public bool IsFullyQualifiedField() + { + // According to Reference 2.0: 12.7.4.2 Field names / Page 532, + // a field may have no partial field name (/T). This shall than not be considered a field but simply a widget annotation. + // Such a field shall only differ in properties specifying the visual appearance and is only one of possibly more representations of the same underlying field. + return !String.IsNullOrEmpty(Name); + } + + public bool TestFlag(PdfFormFieldFlags flag) + { + var result = (Flags & flag) == flag; + return result; + } + + public void SetFlag(PdfFormFieldFlags flag, bool value) + { + if (value) + SetFlag(flag); + else + ClearFlag(flag); + } + + public void SetFlag(PdfFormFieldFlags flag) => Flags |= flag; + + public void ClearFlag(PdfFormFieldFlags flag) => Flags &= ~flag; + + /// + /// Gets or sets a value indicating whether the field flag ReadOnly is set. + /// + public bool IsReadOnly + { + get => TestFlag(PdfFormFieldFlags.ReadOnly); + set + { + if (value) + SetFlag(PdfFormFieldFlags.ReadOnly); + else + ClearFlag(PdfFormFieldFlags.ReadOnly); + } + } + + /// + /// Gets or sets a value indicating whether the field flag NoExport is set. + /// If set to true, the field shall not be exported. + /// + public bool IsNoExport + { + get => TestFlag(PdfFormFieldFlags.NoExport); + set + { + if (value) + SetFlag(PdfFormFieldFlags.NoExport); + else + ClearFlag(PdfFormFieldFlags.NoExport); + } + } + + /// + /// Gets or sets a value indicating whether the field flag Required is set. + /// If set to true, the field is required. + /// + public bool IsRequired + { + get => TestFlag(PdfFormFieldFlags.Required); + set + { + if (value) + SetFlag(PdfFormFieldFlags.Required); + else + ClearFlag(PdfFormFieldFlags.Required); + } + } + + /// + /// Gets or sets a value indicating whether the field flag MultiSelect is set. + /// If set to true, a choice field allows selection of multiple items. + /// + public bool IsMultiSelect + { + get => TestFlag(PdfFormFieldFlags.MultiSelect); + set + { + if (value) + SetFlag(PdfFormFieldFlags.MultiSelect); + else + ClearFlag(PdfFormFieldFlags.MultiSelect); + } + } + + /// + /// Gets or sets a value indicating whether the field flag Multiline is set. + /// If set to true, a text field may have multiple lines. + /// + public bool IsMultiLine + { + get => TestFlag(PdfFormFieldFlags.Multiline); + set + { + if (value) + SetFlag(PdfFormFieldFlags.Multiline); + else + ClearFlag(PdfFormFieldFlags.Multiline); + } + } + + /// + /// Gets or sets a value indicating whether the field flag Password is set. + /// If set to true, a text field doesn’t show its content as clear text. + /// + public bool IsPassword + { + get => TestFlag(PdfFormFieldFlags.Password); + set + { + if (value) + SetFlag(PdfFormFieldFlags.Password); + else + ClearFlag(PdfFormFieldFlags.Password); + } + } + + /// + /// Indicates whether the field has children in the /Kids array. + /// + public bool HasKids + { + get + { + var fields = Elements.GetArray(Keys.Kids); + return fields?.Elements.Count > 0; + } + } + + /// + /// Gets all fields within this field. + /// + /// Returns only fields that shall not be considered as widget annotations only. + public IEnumerable GetKids(bool onlyFullyQualifiedFields = false) + { + if (!HasKids) + yield break; + + foreach (var kidField in Kids) + { + if (onlyFullyQualifiedFields && !kidField.IsFullyQualifiedField()) + continue; + + yield return kidField; + } + } + + /// + /// Gets all fields within this field recursively. + /// + /// Returns only fields that shall not be considered as widget annotations only. + public IEnumerable GetDescendants(bool onlyFullyQualifiedFields = false) + { + foreach (var kidField in GetKids(onlyFullyQualifiedFields)) + { + yield return kidField; + + foreach (var descendant in kidField.GetDescendants(onlyFullyQualifiedFields)) + yield return descendant; + } + } + + /// + /// Gets the collection of fields within this field. + /// + public PdfFormFields Kids => Elements.GetRequiredArray(Keys.Kids, VCF.Create); + + /// + /// Gets a value of a field entry, considering inheritance. + /// + public T? GetElementsValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key) where T : PdfItem + { + return GetElementsValue(key, out _); + } + + /// + /// Gets a value of a field entry, considering inheritance. + /// + public T? GetElementsValue<[DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, out PdfFormField? fieldHoldingValue) where T : PdfItem + { + // Due to inheritance, the value of an entry can be set at a field, that is not of the type that defines the key. + // In some cases it is also important to know, at which field in the hierarchy the value is set. + + var value = Elements.GetValue(key); + // Option 1: Value is set at this field. + if (value != null) + fieldHoldingValue = this; + // Option 2: Value may be set at parent field: Recursion. + else if (Parent != null) + value = Parent.GetElementsValue(key, out fieldHoldingValue); + // Option 3: Value is not set. + else + fieldHoldingValue = null; + + return value; + } + + /// + /// Sets the value of a field entry, checking the type with the given meta. + /// + internal void SetElementsValue(DictionaryMeta meta, string key, T value) where T : PdfItem + { + // Due to inheritance, the value of an entry can be set at a field, that is not of the type that defines the key. + + var descriptor = meta[key]; + var expectedType = descriptor?.GetValueType(); + var actualType = typeof(T); + + if (expectedType != null && expectedType.IsAssignableFrom(actualType)) + throw new Exception($"Value of {key} must be of type {expectedType}. The value of type {actualType} can’t be assigned"); + + Elements[key] = value; + } + + /// + /// Gets a value indicating whether Acro field contains keys of a PdfWidgetAnnotation. + /// + public bool ContainsWidgetPart // TODO: FormsCleanUp: Remove. Use GetAsWidgetAnnotation() or TryGetAsWidgetAnnotation() instead. + { + get + { + // TODO: Test may be too simple. + if (Elements.TryGetName("/Subtype", out var value)) + { + return value == "/Widget"; + } + return false; + } + } + + internal bool TryGetAsWidgetAnnotation([MaybeNullWhen(false)] out PdfWidgetAnnotation result) + { + if (!PdfWidgetAnnotation.IsWidgetAnnotation(this)) + { + result = _widget = null; + return false; + } + + result = _widget ??= new(this); + return true; + } + + internal PdfWidgetAnnotation GetAsWidgetAnnotation() + { + if (!TryGetAsWidgetAnnotation(out var result)) + { + throw new InvalidOperationException( + $"This Acro field is of subtype '{Elements.GetName(PdfAnnotation.Keys.Subtype, false, "/???")}', but it should be of subtype '/Widget'."); + } + return result; + } + internal PdfWidgetAnnotation? _widget; // If the object ID of the field changes the widget must be adjusted too. + + internal override void PrepareForSave() + { + //base.PrepareForSave(); + if (Elements.TryGetValue(Keys.Ff, out var item)) + item.IsFlag = true; + + foreach (var field in GetKids()) + { + field.PrepareForSave(); + } + } + + internal override void WriteObject(PdfWriter writer) + { +#if DEBUG + if (writer.IsVerboseLayout) + { + if (PdfWidgetAnnotation.IsWidgetAnnotation(this)) + writer.WriteComment("This field is also a widget"); + } +#endif + base.WriteObject(writer); + } + + /// + /// Gets the derived type of PdfFormField. + /// Returns field type and a flag that indicates if the field is also a widget. + /// + internal static (Type Type, bool IsWidget) GetAcroFieldType(PdfDictionary dict) + { + bool isWidget = dict.Elements.GetName(PdfAnnotation.Keys.Subtype) == "/Widget"; + string ft = dict.Elements.GetName(Keys.FT); + PdfFormFieldFlags flags = (PdfFormFieldFlags)dict.Elements.GetInteger(Keys.Ff); + switch (ft) + { + case PdfFormFieldType.ButtonLiteral: + if ((PdfFormFieldFlags.Pushbutton & flags) != 0) + return (typeof(PdfFormPushButtonField), isWidget); + + if ((PdfFormFieldFlags.Radio & flags) != 0) + return (typeof(PdfFormRadioButtonField), isWidget); + + return (typeof(PdfFormCheckBoxField), isWidget); + + case PdfFormFieldType.TextLiteral: + return (typeof(PdfFormTextField), isWidget); + + case PdfFormFieldType.ChoiceLiteral: + return (PdfFormFieldFlags.Combo & flags) == 0 + ? (typeof(PdfFormListBoxField), isWidget) + : (typeof(PdfFormComboBoxField), isWidget); + + case PdfFormFieldType.SignatureLiteral: + return (typeof(PdfFormSignatureField), isWidget); + + default: + // The field has no field type. + if (isWidget) + { + // A field without field type but widget aspect is a PdfFormFieldWidget. + return (typeof(PdfFormFieldWidget), true); + } + + // A field without field type and no widget aspect is a PdfFormFieldNode. + return (typeof(PdfFormFieldNode), false); + } + } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : KeysBase + { + // Reference 2.0: Table 226 — Entries common to all field dictionaries / Page 531 + + // ReSharper disable InconsistentNaming + + /// + /// (Required for terminal fields; inheritable) The type of field that this dictionary describes:
+ /// Btn Button
+ /// Tx Text
+ /// Ch Choice
+ /// Sig (PDF 1.3) Signature
+ /// This entry may be present in a non-terminal field(one whose descendants are fields) to + /// provide an inheritable FT value.However, a non-terminal field does not logically have a + /// type of its own; it is merely a container for inheritable attributes that are intended + /// for descendant terminal fields of any type. + ///
+ [KeyInfo(KeyType.Name | KeyType.Required)] + public const string FT = "/FT"; + + /// + /// (Required if this field is the child of another in the field hierarchy; absent otherwise) + /// The field that is the immediate parent of this one (the field, if any, whose Kids array + /// includes this field). A field can have at most one parent; that is, it can be included in + /// the Kids array of at most one other field. + /// + [KeyInfo(KeyType.Dictionary)] + public const string Parent = "/Parent"; + + /// + /// (Sometimes required, as described below) An array of indirect references to the immediate + /// children of this field. + /// In a non-terminal field, the Kids array shall refer to field dictionaries that are immediate + /// descendants of this field.In a terminal field, the Kids array ordinarily shall refer to one + /// or more separate widget annotations that are associated with this field. However, if there is + /// only one associated widget annotation, and its contents have been merged into the field + /// dictionary, Kids shall be omitted. + /// + [KeyInfo(KeyType.Array | KeyType.Optional, typeof(PdfFormFields))] + public const string Kids = "/Kids"; + + /// + /// (Required) The partial field name. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string T = "/T"; + + /// + /// (Optional; PDF 1.3) An alternative field name that shall be used in place of the actual + /// field name wherever the field shall be identified in the user interface (such as in error + /// or status messages referring to the field). This text is also useful when extracting the + /// document’s contents in support of accessibility to users with disabilities or for other + /// purposes. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string TU = "/TU"; + + /// + /// (Optional; PDF 1.3) The mapping name that shall be used when exporting interactive form + /// field data from the document. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string TM = "/TM"; + + /// + /// (Optional; inheritable) A set of flags specifying various characteristics of the field. + /// Default value: 0. + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string Ff = "/Ff"; + + /// + /// (Optional; inheritable) The field’s value, whose format varies depending on the field type. + /// See the descriptions of individual field types for further information. + /// + [KeyInfo(KeyType.Various | KeyType.Optional)] + public const string V = "/V"; + + /// + /// (Optional; inheritable) The default value to which the field reverts when a reset-form + /// action is executed. The format of this value is the same as that of V. + /// + [KeyInfo(KeyType.Various | KeyType.Optional)] + public const string DV = "/DV"; + + /// + /// (Optional; PDF 1.2) An additional-actions dictionary defining the field’s behaviour in + /// response to various trigger events. This entry has exactly the same meaning as the AA + /// entry in an annotation dictionary. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string AA = "/AA"; + + // ReSharper restore InconsistentNaming + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} + +namespace PdfSharp.Pdf.Forms +{ + /// + /// TODO MaOs4StLa What does that mean: Node interactive form (AcroForm) fields. + /// + public class PdfFormFieldWidget : PdfFormField // TODO: FormsCleanUp: Move to own file. + { + // // Reference 2.0: 12.7.4 Field dictionaries / Page 530 + + internal PdfFormFieldWidget(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of the class. + /// + protected PdfFormFieldWidget(PdfDictionary dict) + : base(dict) + { } + + /// + /// Gets the actual type of the field with inheritance considered. + /// + public override Type FieldType + { + get + { + // PdfFormFieldWidget doesn’t define the field type, so we ask the parent. + var parent = Parent; + if (parent == null) + throw new Exception("PdfFormFieldWidget must have a parent, which defines the type of the field."); + return parent.FieldType; + } + } + } +} + +namespace PdfSharp.Pdf.Forms +{ + public abstract class PdfFormTextFieldBase : PdfFormField // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.4.3 Variable text / Page 533 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormTextFieldBase(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormTextFieldBase(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfFormField.Keys + { + // Reference 2.0: Table 228 — Additional entries common to all fields containing variable text / Page 533 + + // ReSharper disable InconsistentNaming + + ///// + ///// (Required; inheritable) A resource dictionary containing default resources + ///// (such as fonts, patterns, or color spaces) to be used by the appearance stream. + ///// At a minimum, this dictionary must contain a Font entry specifying the resource + ///// name and font dictionary of the default font for displaying the field’s text. + ///// + //[KeyInfo(KeyType.Dictionary | KeyType.Required)] + //public const string DR = "/DR"; + + /// + /// (Required; inheritable) The default appearance string containing a sequence of valid + /// page-content graphics or text state operators that define such properties as the field’s + /// text size and colour. + /// + [KeyInfo(KeyType.String | KeyType.Required)] + public const string DA = "/DA"; + + /// + /// (Optional; inheritable) A code specifying the form of quadding (justification) that shall + /// be used in displaying the text:
+ /// 0 Left-justified
+ /// 1 Centred
+ /// 2 Right-justified
+ /// Default value: 0 (left-justified). + ///
+ [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string Q = "/Q"; + + /// + /// (Optional; PDF 1.5) A default style string, as described in Adobe XML Architecture, + /// XML Forms Architecture (XFA) Specification, version 3.3. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string DS = "/DS"; + + /// + /// (Optional; PDF 1.5) A rich text string, as described in Adobe XML Architecture, + /// XML Forms Architecture (XFA) Specification, version 3.3. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string RV = "/RV"; + + // ReSharper restore InconsistentNaming + + /// + /// Gets the KeysMeta for these keys. + /// + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} + +namespace PdfSharp.Pdf.Forms +{ + internal static class PdfFormFieldType // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5 Field types / Page 535 + public const string ButtonLiteral = "/Btn"; + public const string TextLiteral = "/Tx"; + public const string ChoiceLiteral = "/Ch"; + public const string SignatureLiteral = "/Sig"; + + public static readonly Name Button = new(ButtonLiteral); + public static readonly Name Text = new(TextLiteral); + public static readonly Name Choice = new(ChoiceLiteral); + public static readonly Name Signature = new(SignatureLiteral); + } +} + +namespace PdfSharp.Pdf.Forms +{ + public abstract class PdfFormButtonField : PdfFormField // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.2 Button fields / Page 535 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormButtonField(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormButtonField(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfFormField.Keys.FT, PdfFormFieldType.Button.Value); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfFormField.Keys + { + // Buttons have no additional entries. + } + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormPushButtonField : PdfFormButtonField // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.2.2 Push-buttons / Page 536 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormPushButtonField(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormPushButtonField(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + SetFlag(PdfFormFieldFlags.Pushbutton); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfFormField.Keys + { + // Push-buttons have no additional entries. + } + } +} + +namespace PdfSharp.Pdf.Forms +{ + public abstract class PdfFormCheckBoxOrRadioButtonField : PdfFormButtonField // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.2.3 Check boxes / Page 536 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormCheckBoxOrRadioButtonField(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormCheckBoxOrRadioButtonField(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfFormButtonField.Keys + { + // Reference 2.0: Table 230 — Additional entry specific to check box and radio button fields / Page 537 + + /// + /// (Optional; inheritable; PDF 1.4) An array containing one entry for each widget annotation + /// in the Kids array of the radio button or check box field. Each entry shall be a text string + /// representing the on state of the corresponding widget annotation. + /// When this entry is present, the names used to represent the on state in the AP dictionary of + /// each annotation may use numerical position (starting with 0) of the annotation in the Kids + /// array, encoded as a name object (for example: /0, /1). This allows distinguishing between + /// the annotations even if two or more of them have the same value in the Opt array. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string Opt = "/Opt"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormCheckBoxField : PdfFormCheckBoxOrRadioButtonField // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.2.3 Check boxes / Page 536 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormCheckBoxField(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormCheckBoxField(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + // -- Reference 2.0: Table 229 — Field flags specific to button fields + // No flag to set here. + } + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormRadioButtonField : PdfFormCheckBoxOrRadioButtonField // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.2.4 Radio buttons / Page 538 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormRadioButtonField(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormRadioButtonField(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + // NoToggleToOff should be by default. When leaving it clear, clicking the selected radiobutton leaves no radiobutton of the group selected. + SetFlag(PdfFormFieldFlags.Radio | PdfFormFieldFlags.NoToggleToOff); + } + + // Check boxes have no additional key entries. + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormTextField : PdfFormTextFieldBase // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.3 Text fields / Page 539 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormTextField(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormTextField(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfFormField.Keys.FT, PdfFormFieldType.Text.Value); + + // -- Reference 2.0: Table 231 — Field flags specific to text fields + // No flag to set here. + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfFormField.Keys + { + // Reference 2.0: Table 232 — Additional entry specific to a text field / Page 541 + + /// + /// (Optional; inheritable) The maximum length of the field’s text, in characters. + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string MaxLen = "/MaxLen"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} + +namespace PdfSharp.Pdf.Forms +{ + public abstract class PdfFormChoiceField : PdfFormTextFieldBase // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.4 Choice fields / Page 541 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormChoiceField(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormChoiceField(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + Elements.SetName(PdfFormField.Keys.FT, PdfFormFieldType.Choice.Value); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfFormTextFieldBase.Keys + { + // Reference 2.0: Table 234 — Additional entries specific to a choice field / Page 542 + + // ReSharper disable InconsistentNaming + + /// + /// (Optional) An array of options that shall be presented to the user. Each element of the + /// array is either a text string representing one of the available options or an array + /// consisting of two text strings: the option’s export value and the text that shall be + /// displayed as the name of the option. + /// If this entry is not present, no choices should be presented to the user. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string Opt = "/Opt"; + + /// + /// (Optional) For scrollable list boxes, the top index (the index in the Opt array of the + /// first option visible in the list). + /// Default value: 0. + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string TI = "/TI"; + + /// + /// (Sometimes required, otherwise optional; PDF 1.4) For choice fields that allow multiple + /// selection (MultiSelect flag set), an array of integers, sorted in ascending order, + /// representing the zero-based indices in the Opt array of the currently selected option + /// items. This entry shall be used when two or more elements in the Opt array have different + /// names but the same export value or when the value of the choice field is an array. If the + /// items identified by this entry differ from those in the V entry of the field dictionary + /// (see discussion following this Table), the V entry shall be used. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string I = "/I"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormListBoxField : PdfFormChoiceField // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.4 Choice fields / Page 541 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormListBoxField(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormListBoxField(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + // -- Reference 2.0: Table 233 — Field flags specific to choice fields + // No flag to set here. + } + + // List boxes have no additional key entries. + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormComboBoxField : PdfFormChoiceField // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.4 Choice fields / Page 541 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormComboBoxField(PdfDocument document) + : base(document) + { + Initialize(); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormComboBoxField(PdfDictionary dict) + : base(dict) + { } + + void Initialize() + { + //// -- Reference 2.0: Table 233 — Field flags specific to choice fields + //AddFieldFlags(PdfFormFieldFlags.Combo); + SetFlag(PdfFormFieldFlags.Combo); + } + + // Combo boxes have no additional key entries. + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormSignatureField : PdfFormTextFieldBase // TODO: FormsCleanUp: Move to own file. + { + // Reference 2.0: 12.7.5.5 Signature fields / Page 543 + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormSignatureField(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormSignatureField(PdfDictionary dict) + : base(dict) + { } + + // New code: Renders appearance when adding the field. + internal void RenderAppearance() + { + if (CustomAppearanceHandler != null!) + RenderCustomAppearance(); + } + + /// + /// Handler that creates the visual representation of the digital signature in PDF. + /// + public IAnnotationAppearanceHandler? CustomAppearanceHandler { get; internal set; } + + /// + /// Creates the custom appearance form X object for the annotation that represents + /// this acro form text field. + /// + void RenderCustomAppearance() + { + var rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect); + if (rect == null) + return; + + var visible = rect.X1 + rect.X2 + rect.Y1 + rect.Y2 != 0; + if (!visible) + return; + + if (CustomAppearanceHandler == null) + throw new Exception("AppearanceHandler is not set."); + + var form = new XForm(Document, rect.Size); + var gfx = XGraphics.FromForm(form); + + CustomAppearanceHandler.DrawAppearance(gfx, rect.ToXRect()); + + form.DrawingFinished(); + + // Get existing or create new appearance dictionary + if (!Elements.TryGetValue(PdfAnnotation.Keys.AP, out var ap)) + { + ap = new PdfDictionary(Document); + Elements[PdfAnnotation.Keys.AP] = ap; + } + + // Set XRef to normal state + ap.Elements["/N"] = form.PdfForm.RequiredReference; + + // PdfRenderer can be null. + // ReSharper disable once ConditionalAccessQualifierIsNonNullableAccordingToAPIContract + form.PdfRenderer?.Close(); + } + + /// + /// Predefined keys of this dictionary. + /// + public new class Keys : PdfFormTextFieldBase.Keys + { + // Reference 2.0: Table 235 — Additional entries specific to a signature field / Page 544 + + /// + /// (Optional; shall be an indirect reference; PDF 1.5) A signature field lock dictionary + /// that specifies a set of form fields that shall be locked when this signature field is + /// signed. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string Lock = "/Lock"; + + /// + /// (Optional; shall be an indirect reference; PDF 1.5) A seed value dictionary containing + /// information that constrains the properties of a signature that is applied to this field + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string SV = "/SV"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormSignatureFieldLock : PdfDictionary // TODO: FormsCleanUp: NOT USED BY NOW! Move to own file. + { + // Reference 2.0: TODO + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormSignatureFieldLock(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormSignatureFieldLock(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : KeysBase // TODO + { + // TODO Reference 2.0: Table 237 — Entries in a signature field seed value dictionary / Page 545 + + /// + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Type = "/Type"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormSignatureFieldSeedValue : PdfDictionary // TODO: FormsCleanUp: NOT USED BY NOW! Move to own file. + { + // Reference 2.0: TODO + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormSignatureFieldSeedValue(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormSignatureFieldSeedValue(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : KeysBase // TODO + { + // TODO Reference 2.0: Table 236 — Entries in a signature field lock dictionary / Page 544 + + /// + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Type = "/Type"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} + +namespace PdfSharp.Pdf.Forms +{ + public class PdfFormCertificateSeedValue : PdfDictionary // TODO: FormsCleanUp: NOT USED BY NOW! Move to own file. + { + // Reference 2.0: TODO + + /// + /// Initializes a new instance of the class. + /// + internal PdfFormCertificateSeedValue(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormCertificateSeedValue(PdfDictionary dict) + : base(dict) + { } + + /// + /// Predefined keys of this dictionary. + /// + public class Keys : KeysBase // TODO + { + // TODO Reference 2.0: Table 236 — Entries in a signature field lock dictionary / Page 544 + + /// + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Type = "/Type"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/FieldName.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/FieldName.cs new file mode 100644 index 00000000..55c8e0cc --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/FieldName.cs @@ -0,0 +1,113 @@ +//// PDFsharp - A .NET library for processing PDF +//// See the LICENSE file in the solution root for more information. + +//// v7.0.0 TODO review + +// #pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member +//namespace PdfSharp.Pdf.Forms +//{ +// public struct FieldName // TODO: FormsCleanUp: Remove. +// { +// // Reference 2.0: 12.7.4.2 Field names / Page 532 + +// /// +// /// Initializes an empty name "/". +// /// +// public FieldName() +// { } + +// public FieldName(string canonicName) +// { +// Name = new(canonicName); +// } + +// public FieldName(Name name) +// { +// Name = name; +// } + +// public Name Name { get; set; } + +// public string Value => Name.Value; + +// // TODO: Split etc. + +// public FieldName Append(FieldName name) +// { +// return Append(name.Value); +// } + +// public FieldName Append(string? name) +// { +// if (String.IsNullOrEmpty(name)) +// return this; +// return new(Value + "/" + name[1..]); +// } + +// ///// +// ///// Ensures that the name is formally correct. +// ///// It must be a non-empty string starting with a '/'. +// ///// +// //public static void EnsureName(string name) +// //{ +// // if (String.IsNullOrEmpty(name)) +// // throw new ArgumentNullException(nameof(name)); + +// // if (name[0] != '/') +// // throw new ArgumentException($"Name '{name}' must start with a slash ('/')."); +// //} + +// ///// +// ///// Converts a string into an atomic name by adding a '/' +// ///// as prefix to the string. +// ///// If the specified string already starts with a '/' +// ///// no action is taken. +// ///// +// //public static string MakeName(string name) +// //{ +// // if (String.IsNullOrEmpty(name)) +// // return "/"; +// // if (name[0] != '/') +// // return String.Concat('/', name); +// // return name; +// //} + +// public bool IsEmpty => Name.IsEmpty; + +// public bool Equals(FieldName other) +// => Name.Comparer.Compare(Name, other.Name) == 0; + +// public override bool Equals(object? obj) +// { +// if (obj is FieldName fieldName) +// return Name.Comparer.Compare(this.Name, fieldName.Name) == 0; +// return true; +// } + +// public override int GetHashCode() => Name.GetHashCode(); + +// public override String ToString() +// { +// return Value; +// } + +// /// +// /// Compares two names. +// /// +// public int Compare(FieldName l, FieldName r) +// => Name.Comparer.Compare(l.Name, r.Name); + +// /// +// /// Determines whether the two field names are equal. +// /// +// public static bool operator ==(FieldName l, FieldName r) => +// Name.Comparer.Compare(l.Name, r.Name) == 0; + +// /// +// /// Determines whether the two field names are not equal. +// /// +// public static bool operator !=(FieldName l, FieldName r) => !(l == r); + +// public static readonly FieldName Empty = new(); +// } +//} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfForm.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfForm.cs new file mode 100644 index 00000000..90243e48 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfForm.cs @@ -0,0 +1,130 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 TODO review, Fields OTT + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Forms +{ + public sealed class PdfForm : PdfDictionary + { + // Reference 2.0: 12.7.3 Interactive form dictionary / Page 529 + + /// + /// Initializes a new instance of the class. + /// + internal PdfForm(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfForm(PdfDictionary dictionary) + : base(dictionary) + { } + + /// + /// Gets the fields collection of this form. + /// It is created if it does not exist. + /// + public PdfFormFields Fields + { + get + { + var fields = Elements.GetRequiredValue(Keys.Fields, VCF.CreateIndirect); + return fields; + } + } + + /// + /// Predefined keys of this dictionary. + /// + internal class Keys : KeysBase + { + // Reference 2.0: Table 224 — Entries in the interactive form dictionary / Page 529 + + // ReSharper disable InconsistentNaming + + /// + /// (Required) An array of references to the document’s root fields (those with no ancestors + /// in the field hierarchy). + /// + [KeyInfo(KeyType.Array | KeyType.Required, typeof(PdfFormFields))] + public const string Fields = "/Fields"; + + /// + /// (Optional; deprecated in PDF 2.0) A flag specifying whether to construct appearance + /// streams and appearance dictionaries for all widget annotations in the document. + /// Default value: false. + /// A PDF writer shall include this key, with a value of true, if it has not provided + /// appearance streams for all visible widget annotations present in the document. + /// NOTE + /// Appearance streams are required in PDF 2.0 and later. + /// + [KeyInfo(KeyType.Boolean | KeyType.Optional)] + public const string NeedAppearances = "/NeedAppearances"; + + /// + /// (Optional; PDF 1.3) A set of flags specifying various document-level characteristics + /// related to signature fields. + /// Default value: 0. + /// + [KeyInfo("1.3", KeyType.Integer | KeyType.Optional)] + public const string SigFlags = "/SigFlags"; + + /// + /// (Required if any fields in the document have additional-actions dictionaries containing + /// a C entry; PDF 1.3) An array of indirect references to field dictionaries with calculation + /// actions, defining the calculation order in which their values will be recalculated when + /// the value of any field changes. + /// + [KeyInfo(KeyType.Array)] + public const string CO = "/CO"; + + /// + /// (Optional) A resource dictionary containing default resources (such as fonts, patterns, + /// or colour spaces) that shall be used by form field appearance streams. At a minimum, + /// this dictionary shall contain a Font entry specifying the resource name and font + /// dictionary of the default font for displaying text. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Optional)] + public const string DR = "/DR"; + + /// + /// (Optional) A document-wide default value for the DA attribute of variable text fields. + /// + [KeyInfo(KeyType.String | KeyType.Optional)] + public const string DA = "/DA"; // E.g. "/DA (/Helv 0 Tf 0 g)" + + /// + /// (Optional) A document-wide default value for the Q attribute of variable text fields. + /// + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string Q = "/Q"; + + /// + /// (Optional; deprecated in PDF 2.0) A stream or array containing an XFA resource, + /// whose format shall conform to the Data Package (XDP) Specification. + /// + [KeyInfo(KeyType.StreamOrArray | KeyType.Optional)] + public const string XFA = "/XFA"; + + // ReSharper restore InconsistentNaming + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormButtonField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormButtonField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCertificateSeedValue.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCertificateSeedValue.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCertificateSeedValue.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxOrRadioButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxOrRadioButtonField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormCheckBoxOrRadioButtonField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormChoiceField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormChoiceField.cs new file mode 100644 index 00000000..87955eb9 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormChoiceField.cs @@ -0,0 +1,12 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormComboBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormComboBoxField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormComboBoxField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldNode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldNode.cs new file mode 100644 index 00000000..abd0e45c --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldNode.cs @@ -0,0 +1,46 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + /// + /// TODO: Needed? + /// Represents an Acro field that has no type entry (/FT) nor has a widget annotation part. + /// + public class PdfFormFieldNode : PdfFormField + { + // Reference 2.0: 12.7.4 Field dictionaries / Page 530 + + internal PdfFormFieldNode(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of the class. + /// + protected PdfFormFieldNode(PdfDictionary dict) + : base(dict) + { } + + /// + /// Gets the actual type of the field with inheritance considered. + /// + public override Type FieldType + { + get + { + // PdfFormFieldNode doesn’t define the field type, so we ask the parent. + var parent = Parent; + if (parent == null) + throw new Exception("PdfFormFieldNode must have a parent, which defines the type of the field, or it should not be asked, as a child of the field defines the type."); + return parent.FieldType; + } + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldType.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldType.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFieldType.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFields.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFields.cs new file mode 100644 index 00000000..860d6b8a --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormFields.cs @@ -0,0 +1,283 @@ +// 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.Advanced; +using PdfSharp.Pdf.Annotations; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + /// + /// Holds a collection of interactive fields (Acro fields). + /// + public sealed class PdfFormFields : PdfArray, IEnumerable + { + internal PdfFormFields(PdfDocument document) + : base(document) + { } + + internal PdfFormFields(PdfArray array) + : base(array) + { } + + //internal void GetDescendantNames(ref List names, string? partialName) // TODO: FormsCleanUp: Remove. Use FullName for each field instead. + //{ + // int count = Elements.Count; + // for (int idx = 0; idx < count; idx++) + // { + // var field = this[idx]; + // if (field != null!) + // field.GetDescendantNames(ref names, partialName); + // } + //} + + /// + /// Gets a field from the collection. For your convenience an instance of a derived class like + /// PdfTextField or PdfCheckBox is returned if PDFsharp can guess the actual type of the dictionary. + /// If the actual type cannot be guessed by PDFsharp the function returns an instance + /// of PdfGenericField. + /// + public PdfFormField this[int index] + { + get + { +#if true + var item = Elements.GetRequiredValue(index); + return item; +#else + PdfItem item = Elements[index]; + Debug.Assert(item is PdfReference); + PdfDictionary? dict = ((PdfReference)item).Value as PdfDictionary; + Debug.Assert(dict != null); + PdfFormField? field = dict as PdfFormField; + if (field == null && dict != null!) + { + // Do type transformation + field = null; // CreateAcroField(dict); MAKE CODE COMPILE + //Elements[index] = field.XRef; + } + + return field!; // NRT +#endif + } + } + + /// + /// Returns an enumerator that iterates through an array of Acro fields. + /// + public new IEnumerator GetEnumerator() + { + foreach (var item in Elements) + { + var field = item; + PdfReference.Dereference(ref field); + yield return (PdfFormField)field; + } + } + + /// + /// Gets all fields recursively. + /// + /// Returns only fields that shall not be considered as widget annotations only. + public IEnumerable GetAllFields(bool onlyFullyQualifiedFields = true) + { + foreach (var field in this) + { + if (onlyFullyQualifiedFields && !field.IsFullyQualifiedField()) + continue; + + yield return field; + + foreach (var descendant in field.GetDescendants(onlyFullyQualifiedFields)) + yield return descendant; + } + } + + internal static class AcroFieldPreparer + { + public static void PrepareDocument(PdfDocument doc) + { + CreateAcroFieldObjects(doc); + CreateAnnotationObjects(doc); + } + + /// + /// Creates all acro field objects after reading a PDF document. + /// + /// The document. + static void CreateAcroFieldObjects(PdfDocument doc) + { + var catalog = doc.Catalog; + var acroForm = catalog.GetAcroForm(); + + var fields = acroForm?.Elements.GetArray(PdfForm.Keys.Fields); + if (fields != null) + { + var fieldCount = fields.Elements.Count; + for (int idx = 0; idx < fieldCount; idx++) + { + var field = fields.Elements.GetRequiredDictionary(idx); + CreateDerivedField(fields, idx); + + var acroField = fields.Elements.GetRequiredDictionary(idx); +#if DEBUG + if (!ReferenceEquals(field, acroField)) + { + if (field.IsIndirect) + { + Debug.Assert(ReferenceEquals(field.RequiredReference, acroField.RequiredReference)); + } + } +#endif + HandleAP(acroField); + HandleKids(acroField); + } + } + } + + static void CreateAnnotationObjects(PdfDocument doc) + { + var pages = doc.Catalog.Pages; + foreach (var page in pages) + { + var annots = page.Elements.GetArray(PdfPage.Keys.Annots); + if (annots != null) + { + for (int idx = 0; idx < annots.Elements.Count; idx++) + { + var annot = annots.Elements.GetDictionary(idx); + if (annot is PdfWidgetAnnotation widget) + { + throw new InvalidOperationException("WTF happened???"); + } + else if (annot is PdfFormField field) + { + if (field.TryGetAsWidgetAnnotation(out var proxyWidget)) + annots.Elements[idx] = proxyWidget; + else + { + PdfSharpLogHost.Logger.LogWarning("A PdfFormField was found in the /Annots array instead of a PdfWidgetAnnotation. The field is not valid here and will be removed to avoid exceptions."); + annots.Elements.RemoveAt(idx--); + } + } + else + { + + } + } + } + } + } + + static void CreateDerivedField(PdfArray fields, int index) + { + var field = fields.Elements.GetRequiredDictionary(index); + + var (type, isWidget) = PdfFormField.GetAcroFieldType(field); + var newField = (PdfFormField)fields.Elements.GetRequiredDictionary(index, VCF.None, type); + if (isWidget) + newField.GetAsWidgetAnnotation(); + } + + // ReSharper disable once InconsistentNaming + static void HandleAP(PdfFormField field) + { + var ap = field.Elements.GetDictionary(PdfAnnotation.Keys.AP); + if (ap != null) + { + var valN = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.N); + HandleAPEntry(valN); + + var valR = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.R); + HandleAPEntry(valR); + + var valD = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.D); + HandleAPEntry(valD); + } + + // ReSharper disable once InconsistentNaming + void HandleAPEntry(PdfDictionary? streamOrDict) + { + if (streamOrDict == null) + return; + + var stream = streamOrDict.Stream; + if (stream is null) + { + var keys = streamOrDict.Elements.Keys; + foreach (var key in keys) + { + var xo = streamOrDict.Elements.GetDictionary(key); + if (xo is PdfFormXObject) + continue; + + if (xo != null) + { + Debug.Assert(xo.IsIndirect); +#if true + var xo2 = streamOrDict.Elements.GetDictionary(key, VCF.None, typeof(PdfFormXObject)); +#else + TransformToXObject(xo); +#endif + Debug.Assert(xo.IsDead); + } + else + { + Debug.Assert(false); + } + } + } + else + { + Debug.Assert(streamOrDict.IsIndirect); + TransformToXObject(streamOrDict); + } + + void TransformToXObject(PdfDictionary dict) + { + if (dict is PdfFormXObject alreadyFormXObject) + { + _ = typeof(int); + } + else + { + dict.Elements.CreateContainer(typeof(PdfFormXObject), dict, true); + Debug.Assert(dict.IsDead); + } + } + } + } + + static void HandleKids(PdfFormField field) + { + // Don't use GetKids() here, as elements are pure dictionaries by now. + var kids = field.Elements.GetArray(PdfFormField.Keys.Kids); + var hasKids = field.HasKids; + Debug.Assert(kids == null || (hasKids && kids.Elements.Count > 0) || (!hasKids && kids.Elements.Count == 0)); + if (kids != null) + { + // Transform kids to Acro fields. + var count = kids.Elements.Count; + for (int idx = 0; idx < count; idx++) + { + CreateDerivedField(kids, idx); + } + + for (int idx = 0; idx < count; idx++) + { + var acroField2 = kids.Elements.GetDictionary(idx); + if (acroField2 is not PdfFormField) + _ = typeof(int); + + var acroField = kids.Elements.GetRequiredDictionary(idx); + HandleAP(acroField); + HandleKids(acroField); + } + } + } + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormListBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormListBoxField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormListBoxField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormPushButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormPushButtonField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormPushButtonField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormRadioButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormRadioButtonField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormRadioButtonField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldLock.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldLock.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldLock.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldSeedValue.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldSeedValue.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormSignatureFieldSeedValue.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextField.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextField.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextFieldBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextFieldBase.cs new file mode 100644 index 00000000..57cc6da5 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/PdfFormTextFieldBase.cs @@ -0,0 +1,13 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Internal; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Forms +{ + +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormFieldFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormFieldFlags.cs new file mode 100644 index 00000000..85b931eb --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormFieldFlags.cs @@ -0,0 +1,166 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 Ready + +namespace PdfSharp.Pdf.Forms +{ + /// + /// Specifies the flags of interactive form (AcroForm) fields. + /// + [Flags] + public enum PdfFormFieldFlags + { + // Reference 2.0: Table 227 — Field flags common to all field types / Page 532 + + // ----- Common to all fields ----------------------------------------------------------------------- + + /// + /// If set, an interactive PDF processor shall not allow a user to change the value of the + /// field. Additionally, any associated widget annotations should not interact with the user; + /// that is, they should not respond to mouse clicks nor change their appearance in response + /// to mouse motions. + /// NOTE: This flag is useful for fields whose values are computed or imported from a database. + /// + ReadOnly = 1 << (1 - 1), + + /// + /// If set, the field shall have a value at the time it is exported by a submit-form action. + /// + Required = 1 << (2 - 1), + + /// + /// If set, the field shall not be exported by a submit-form action. + /// + NoExport = 1 << (3 - 1), + + // ----- Specific to button fields ------------------------------------------------------------------ + + // Reference 2.0: Table 229 — Field flags specific to button fields / Page 535 + + /// + /// (Radio buttons only) If set, exactly one radio button shall be selected at all times; + /// selecting the currently selected button has no effect. If clear, clicking the selected + /// button deselects it, leaving no button selected. + /// + NoToggleToOff = 1 << (15 - 1), + + /// + /// If set, the field is a set of radio buttons; if clear, the field is a check box. + /// This flag may be set only if the Pushbutton flag is clear. + /// + Radio = 1 << (16 - 1), + + /// + /// If set, the field is a push-button that does not retain a permanent value. + /// + Pushbutton = 1 << (17 - 1), + + /// + /// (PDF 1.5) If set, a group of radio buttons within a radio button field that use the same value + /// for the on state will turn on and off in unison; that is if one is checked, they are all checked. + /// If clear, the buttons are mutually exclusive (the same behaviour as HTML radio buttons). + /// + RadiosInUnison = 1 << (26 - 1), + + //// ----- Specific to text fields ------------------------------------------------------------------ + + // Reference 2.0: Table 231 — Field flags specific to text fields / Page 539 + + /// + /// If set, the field may contain multiple lines of text; if clear, the field’s text shall be + /// restricted to a single line. + /// + Multiline = 1 << (13 - 1), + + /// + /// If set, the field is intended for entering a secure password that should not be echoed + /// visibly to the screen. Characters typed from the keyboard shall instead be echoed in some + /// unreadable form, such as asterisks or bullet characters. + /// NOTE + /// To protect password confidentiality, it is imperative that PDF processors never store + /// the value of the text field in the PDF file if this flag is set. + /// + Password = 1 << (14 - 1), + + /// + /// (PDF 1.4) If set, the text entered in the field represents the pathname of a file whose + /// contents shall be submitted as the value of the field. + /// + FileSelect = 1 << (21 - 1), + + /// + /// (PDF 1.4) If set, text entered in the field shall not be spell-checked. + /// + DoNotSpellCheckTextField = 1 << (23 - 1), + + /// + /// (PDF 1.4) If set, the field shall not scroll (horizontally for single-line fields, vertically + /// for multiple-line fields) to accommodate more text than fits within its annotation rectangle. + /// Once the field is full, no further text shall be accepted for interactive form filling; + /// for non-interactive form filling, the filler should take care not to add more character than + /// will visibly fit in the defined area. + /// + DoNotScroll = 1 << (24 - 1), + + /// + /// (PDF 1.5) May be set only if the MaxLen entry is present in the text field dictionary and if the + /// Multiline, Password, and FileSelect flags are clear. If set, the field shall be automatically + /// divided into as many equally spaced positions, or combs, as the value of MaxLen, and the text + /// is laid out into those combs. + /// + CombTextField = 1 << (25 - 1), + + /// + /// (PDF 1.5) If set, the value of this field shall be a rich text string. If the field has a value, + /// the RV entry of the field dictionary shall specify the rich text string. + /// + RichTextTextField = 1 << (26 - 1), + + // ----- Specific to choice fields ------------------------------------------------------------------ + + // Reference 2.0: Table 233 — Field flags specific to choice fields / Page 542 + + /// + /// If set, the field is a combo box; if clear, the field is a list box. + /// + Combo = 1 << (18 - 1), + + /// + /// If set, the combo box shall include an editable text box as well as a drop-down list; if clear, + /// it shall include only a drop-down list. This flag shall be used only if the Combo flag is set. + /// + Edit = 1 << (19 - 1), + + /// + /// If set, the field’s option items shall be sorted alphabetically. This flag is intended for use + /// by PDF writers, not by PDF readers. PDF readers shall display the options in the order in which + /// they occur in the Opt array. + /// + Sort = 1 << (20 - 1), + + /// + /// (PDF 1.4) If set, more than one of the field’s option items may be selected simultaneously; + /// if clear, at most one item shall be selected. + /// + MultiSelect = 1 << (22 - 1), + + /// + /// (PDF 1.4) If set, text entered in the field shall not be spell-checked. This flag shall not be + /// used unless the Combo and Edit flags are both set. + /// + DoNotSpellCheckChoiceField = 1 << (23 - 1), + + /// + /// (PDF 1.5) If set, the new value shall be committed as soon as a selection is made (commonly with + /// the pointing device). In this case, supplying a value for a field involves three actions: + /// selecting the field for fill-in, selecting a choice for the fill-in value, and leaving that field, + /// which finalizes or "commits" the data choice and triggers any actions associated with the entry + /// or changing of this data. If this flag is on, then processing does not wait for leaving the field + /// action to occur, but immediately proceeds to the third step. + /// This option enables applications to perform an action once a selection is made, without requiring + /// the user to exit the field. If clear, the new value is not committed until the user exits the field. + /// + CommitOnSelChangeChoiceField = 1 << (27 - 1), + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormSignatureFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormSignatureFlags.cs new file mode 100644 index 00000000..b8098fe9 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Forms/enums/PdfFormSignatureFlags.cs @@ -0,0 +1,34 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 Ready + +namespace PdfSharp.Pdf.Forms +{ + /// + /// Specifies the flags of interactive form (AcroForm) fields. + /// + [Flags] + public enum PdfFormSignatureFlags + { + // Reference 2.0: Table 225 — Signature flags / Page 530 + + /// + /// If set, the document contains at least one signature field. This flag allows an interactive + /// PDF processor to enable user interface items (such as menu items or push-buttons) related to + /// signature processing without having to scan the entire document for the presence of signature + /// fields. + /// + SignaturesExist = 1 << (1 - 1), + + /// + /// If set, the document contains signatures that may be invalidated if the PDF file is saved + /// (written) in a way that alters its previous contents, as opposed to an incremental update. + /// Merely updating the PDF file by appending new information to the end of the previous version + /// is safe. Interactive PDF processors may use this flag to inform a user requesting a full save + /// that signatures will be invalidated and require explicit confirmation before continuing with + /// the operation. + /// + AppendOnly = 1 << (2 - 1), + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs index bd76a0e2..6675ea25 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Chars.cs @@ -49,6 +49,7 @@ public static class Chars /// The horizontal tab character. ///
public const char HT = '\t'; // Horizontal tab + /// /// The vertical tab character. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs index 1822b675..738b49dd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs @@ -4,6 +4,7 @@ using System.Text; using Microsoft.Extensions.Logging; using PdfSharp.Logging; +using PdfSharp.Internal; using PdfSharp.Pdf.Internal; namespace PdfSharp.Pdf.IO @@ -44,7 +45,16 @@ public SizeType Position { get { - Debug.Assert(_pdfStream.Position == _charIndex + 2); +#if DEBUG + // The following assertion may not be true in some rare cases, + // e.g. if the PDF document is invalid and the lexer reached + // unexpectedly EOF. + // See PDFsharp.Tests file "Unexpected_Token_EmptyChar(PageCount).pdf". + // This file ends with "startxref‹LF›" not followed by an offset. + // In this case _charIndex is identical with stream position. + if (_pdfLength > _pdfStream.Position) + Debug.Assert(_pdfStream.Position == _charIndex + 2); +#endif return _charIndex; } set @@ -134,21 +144,19 @@ public Symbol ScanNextToken(bool testForObjectReference) case >= 'a' and <= 'z': return ScanKeyword(); -#if DEBUG - case 'R': - Debug.Assert(false, "'R' should not be parsed anymore."); - // Note: "case 'R':" is not scanned, because it is only used in an object reference. + case 'R': // Came here only in invalid PDF files. + // Note that "case 'R':" is not scanned, because it is only used in an object reference. // And object references are now parsed the 'compound symbol' ObjRef. + // However, invalid PDF files may have entries in the xref table that points randomly + // at a position in the file (e.g. timing.pdf in our test files on our NAS). + // In this case we still scan it and crash later. ScanNextChar(true); - // The next line only exists for the 'UseOldCode' case in PdfReader. return Symbol = Symbol.R; -#endif case Chars.EOF: return Symbol = Symbol.Eof; default: - Debug.Assert(!Char.IsLetter(ch), "PDFsharp did something wrong. See code below."); ParserDiagnostics.HandleUnexpectedCharacter(ch, DumpNeighborhoodOfPosition()); return Symbol = Symbol.None; } @@ -199,12 +207,20 @@ public Symbol ScanName() ClearToken(); while (true) { +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(_currChar); +#endif var ch = AppendAndScanNextChar(); if (IsWhiteSpace(ch) || IsDelimiter(ch) || ch == Chars.EOF) break; if (ch == '#') { +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('#'); + _parsedValue.Append(_currChar); + _parsedValue.Append(_nextChar); +#endif ScanNextChar(true); var newChar = (_currChar switch { @@ -272,7 +288,7 @@ internal Symbol ScanNumber(bool testForObjectReference) // // So we introduced a LongInteger. - // Note: This is a copy of CLexer.ScanNumber with minimal changes. Keep both versions in sync as far as possible. + // Note that this is a copy of CLexer.ScanNumber with minimal changes. Keep both versions in sync as far as possible. // Update StL: Function is revised for object reference look ahead. // Parsing Strategy: @@ -593,6 +609,9 @@ public Symbol ScanStringLiteral() Debug.Assert(_currChar == Chars.ParenLeft); ClearToken(); +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('('); +#endif int parenLevel = 0; //RetryAfterSkipIllegalCharacter: char ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n. @@ -603,10 +622,16 @@ public Symbol ScanStringLiteral() switch (ch) { case '(': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('('); +#endif parenLevel++; break; case ')': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(')'); +#endif if (parenLevel == 0) { ScanNextChar(false); // The string ended, so ignore \r, \n and \r\n again. @@ -617,49 +642,82 @@ public Symbol ScanStringLiteral() case '\\': { +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('\\'); +#endif ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n. switch (ch) { case 'n': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('n'); +#endif ch = Chars.LF; break; case 'r': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('r'); +#endif ch = Chars.CR; break; case 't': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('t'); +#endif ch = Chars.HT; break; case 'b': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('b'); +#endif ch = Chars.BS; break; case 'f': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('f'); +#endif ch = Chars.FF; break; case '(': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('('); +#endif ch = Chars.ParenLeft; break; case ')': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(')'); +#endif ch = Chars.ParenRight; break; case '\\': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('\\'); +#endif ch = Chars.BackSlash; break; // AutoCAD PDFs may contain such strings: (\ ) case ' ': +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(' '); +#endif // #PRD Notify about a string with an escaped blank. ch = ' '; break; case Chars.CR: case Chars.LF: +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(ch); +#endif ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n. continue; @@ -669,6 +727,9 @@ public Symbol ScanStringLiteral() //if (Char.IsDigit(ch) && ch is not '8' and not '9') // First octal character. if (ch is >= '0' and <= '7') // First octal character. { +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(ch); +#endif //// Octal character code. //if (ch >= '8') // ParserDiagnostics.HandleUnexpectedCharacter(ch); @@ -677,6 +738,9 @@ public Symbol ScanStringLiteral() //if (Char.IsDigit(_nextChar) && _nextChar is not '8' and not '9') // Second octal character. if (_nextChar is >= '0' and <= '7') // Second octal character. { +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(_nextChar); +#endif ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n. //if (ch >= '8') // ParserDiagnostics.HandleUnexpectedCharacter(ch); @@ -685,6 +749,9 @@ public Symbol ScanStringLiteral() //if (Char.IsDigit(_nextChar) && _nextChar is not '8' and not '9') // Third octal character. if (_nextChar is >= '0' and <= '7') // Third octal character. { +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(_nextChar); +#endif ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n. //if (ch >= '8') // ParserDiagnostics.HandleUnexpectedCharacter(ch); @@ -701,8 +768,10 @@ public Symbol ScanStringLiteral() } else { +#if PRESERVE_PARSED_VALUES + _parsedValue.Append(ch); +#endif // PDF 32000: "If the character following the REVERSE SOLIDUS is not one of those shown in Table 3, the REVERSE SOLIDUS shall be ignored." - // fyi: REVERSE SOLIDUS is a backslash // What does that mean: "abc\qxyz" is "abcxyz" oder "abcqxyz"? // Adobe Reader ignores '\', but keeps 'q'. We do the same. // #PRD Notify about unknown escape character. @@ -718,11 +787,11 @@ public Symbol ScanStringLiteral() _token.Append(ch); ch = ScanNextChar(true); // Inside of a string \r, \n and \r\n without preceding \\ shall be treated as \n. } + // (dummy comment to suppress incorrect warning about label indentation) End: return Symbol = Symbol.String; } -#if true /// /// Scans a hex encoded literal byte string, contained between "<" and ">". /// @@ -733,22 +802,28 @@ public Symbol ScanHexadecimalString() // 25-09-16/StL // Now we can handle Panose style codes that may look like this: // < 0 0 2 b 6 6 3 8 4 2 2 4> - // This is technically an illegal format, but found by a user. + // This is technically an illegal format, but found by a user in a real PDF file. ClearToken(); +#if PRESERVE_PARSED_VALUES + _parsedValue.Append('<'); +#endif ScanNextChar(true); bool tryUsePanoseHack = false; while (true) { MoveToNonWhiteSpace(); - +#if DEBUG + if (_currChar == '4' && _nextChar == '1') + _ = typeof(int); +#endif if (_currChar == '>') { ScanNextChar(true); break; } - // TODO Handle EOF correctly. Check if other methods must also handle EOF. + // IMPROVE Handle EOF correctly. Check if other methods must also handle EOF. var hex = _currChar switch { @@ -776,7 +851,7 @@ public Symbol ScanHexadecimalString() goto ScanNextChar; } } - // Second char is assumed to be '0' if not exists according to the PDF specs. + // Second char is assumed to be '0' if it does not exist according to the PDF specs. _token.Append((char)(hex << 4)); ScanNextChar: @@ -800,7 +875,7 @@ public Symbol ScanHexadecimalString() // Some fool may add a line-break between the hex digits. MoveToNonWhiteSpace(); - // TODO Handle EOF correctly. + // IMPROVE Handle EOF correctly. hex = (hex << 4) + _currChar switch { @@ -821,62 +896,6 @@ char LogError(char ch) return '\0'; } } -#else - /// - /// Scans a hex encoded literal string, contained between "<" and ">". - /// - public Symbol ScanHexadecimalString() - { - Debug.Assert(_currChar == Chars.Less); - - ClearToken(); - ScanNextChar(true); - while (true) - { - MoveToNonWhiteSpace(); - if (_currChar == '>') - { - ScanNextChar(true); - break; - } - - var hex = _currChar switch - { - >= '0' and <= '9' => _currChar - '0', - >= 'A' and <= 'F' => _currChar - ('A' - 10), // Not optimized in IL without parenthesis. - >= 'a' and <= 'f' => _currChar - ('a' - 10), - _ => LogError(_currChar) - }; - - ScanNextChar(true); - if (_currChar == '>') - { - // Second char is optional in PDF spec. - _token.Append((char)(hex << 4)); - ScanNextChar(true); - break; - } - - hex = (hex << 4) + _currChar switch - { - >= '0' and <= '9' => _currChar - '0', - >= 'A' and <= 'F' => _currChar - ('A' - 10), - >= 'a' and <= 'f' => _currChar - ('a' - 10), - _ => LogError(_currChar) - }; - _token.Append((char)hex); - ScanNextChar(true); - } - - return Symbol = Symbol.HexString; - - static char LogError(char ch) - { - PdfSharpLogHost.Logger.LogError("Illegal character {char} in hex string.", ch); - return '\0'; - } - } -#endif /// /// Tries to scan the specified literal from the current stream position. @@ -990,7 +1009,7 @@ public byte[] ScanStream(SizeType position, int length, out int bytesRead) return bytes; } - // Note: Position += length cannot be used here. + // Note that Position += length cannot be used here. Position = position + length; return bytes; } @@ -1072,13 +1091,13 @@ public int DetermineStreamLength(SizeType start, int searchLength, SuppressExcep var rawString = RandomReadRawString(start, searchLength); // When we come here, we have either an invalid or no \Length entry. - // Best we can do is to consider all byte before 'endstream' are part of the stream content. + // Best we can do is to consider all bytes before 'endstream' are part of the stream content. // In case the stream is zipped, this is no problem. In case the stream is encrypted // it would be a serious problem. But we wait if this really happens. int idxEndStream = rawString.LastIndexOf("endstream", StringComparison.Ordinal); if (idxEndStream == -1) { - SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength()); + SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength(start)); return -1; } @@ -1155,7 +1174,13 @@ internal char ScanNextChar(bool handleCRLF) /// /// Resets the current token to the empty string. /// - void ClearToken() => _token.Clear(); + void ClearToken() + { + _token.Clear(); +#if PRESERVE_PARSED_VALUES + _parsedValue.Clear(); +#endif + } /// /// Appends current character to the token and @@ -1171,9 +1196,9 @@ char AppendAndScanNextChar() } /// - /// If the current character is not a white space, the function immediately returns it. - /// Otherwise, the PDF cursor is moved forward to the first non-white space or EOF. - /// White spaces are NUL, HT, LF, FF, CR, and SP. + /// If the current character is not a white-space, the function immediately returns it. + /// Otherwise, the PDF cursor is moved forward to the first non-white-space or EOF. + /// White-spaces are NUL, HT, LF, FF, CR, and SP. /// public char MoveToNonWhiteSpace() { @@ -1215,7 +1240,7 @@ public string DumpNeighborhoodOfPosition(SizeType position = -1, bool hex = fals //_pdfStream.Position = 5; //_pdfStream.Position = _pdfLength - 5; - // Note: The _pdfStream Position is mostly two bytes/chars behind the Lexer Position, + // Note that the _pdfStream Position is mostly two bytes/chars behind the Lexer Position, // because the stream already has read the current and the next character. if (position < 0) position = Position; @@ -1314,6 +1339,7 @@ public bool TokenToBoolean } } + /// /// Interprets current token as integer literal. /// @@ -1426,10 +1452,14 @@ internal static bool IsDelimiter(char ch) /// public SizeType PdfLength => _pdfLength; readonly SizeType _pdfLength; + SizeType _charIndex; char _currChar; char _nextChar; readonly StringBuilder _token = new(); +#if PRESERVE_PARSED_VALUES + readonly StringBuilder _parsedValue = new(); +#endif long _tokenAsLong; double _tokenAsReal; (int, int) _tokenAsObjectID; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs index b14f93c0..64fe1c92 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Parser.cs @@ -1,23 +1,23 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using Microsoft.Extensions.Logging; using PdfSharp.Internal; using PdfSharp.Logging; using PdfSharp.Pdf.Advanced; -using Microsoft.Extensions.Logging; namespace PdfSharp.Pdf.IO { /* - Direct and indirect objects + Direct and indirect objects: - * If a simple object (boolean, integer, number, date, string, rectangle etc.) is referenced indirectly, + * If a primitive object (boolean, integer, number, date, string, rectangle etc.) is referenced indirectly, the parser reads this object immediately and consumes the indirection. - * If a composite object (dictionary, array etc.) is referenced indirectly, a PdfReference object + * If a compound object (dictionary, array etc.) is referenced indirectly, a PdfReference object is returned. - * If a composite object is a direct object, no PdfReference is created and the object is + * If a compound object is a direct object, no PdfReference is created and the object is parsed immediately. * A reference to a non-existing object is specified as legal, therefore null is returned. @@ -243,13 +243,8 @@ PdfObject ReadObjectInternal(PdfObject? pdfObject, PdfObjectID objectID, bool in } break; - // Acrobat 6 Professional proudly presents: The Null object! - // Even with a one-digit object number an indirect reference «x 0 R» to this object is - // one character larger than the direct use of «null». Probable this is the reason why - // it is true that Acrobat Web Capture 6.0 creates this object, but obviously never - // creates a reference to it! case Symbol.Null: - pdfObject = new PdfNullObject(_document); + pdfObject = new PdfNullObject(_document, false); pdfObject.SetObjectID(objectNumber, generationNumber); if (!fromObjectStream) ReadSymbol(Symbol.EndObj); @@ -257,48 +252,54 @@ PdfObject ReadObjectInternal(PdfObject? pdfObject, PdfObjectID objectID, bool in // Empty object. Invalid PDF, but we need to handle it. Treat as null object. case Symbol.EndObj: // #INVALID_PDF - pdfObject = new PdfNullObject(_document); + pdfObject = new PdfNullObject(_document, false); pdfObject.SetObjectID(objectNumber, generationNumber); return pdfObject; case Symbol.Boolean: - pdfObject = new PdfBooleanObject(_document, String.Compare(_lexer.Token, Boolean.TrueString, StringComparison.OrdinalIgnoreCase) == 0); + pdfObject = new PdfBooleanObject(_document, String.Compare(_lexer.Token, Boolean.TrueString, StringComparison.OrdinalIgnoreCase) == 0, false); pdfObject.SetObjectID(objectNumber, generationNumber); if (!fromObjectStream) ReadSymbol(Symbol.EndObj); return pdfObject; case Symbol.Integer: - pdfObject = new PdfIntegerObject(_document, _lexer.TokenToInteger); + pdfObject = new PdfIntegerObject(_document, _lexer.TokenToInteger, false); pdfObject.SetObjectID(objectNumber, generationNumber); if (!fromObjectStream) ReadSymbol(Symbol.EndObj); return pdfObject; case Symbol.LongInteger: - pdfObject = new PdfLongIntegerObject(_document, _lexer.TokenToLongInteger); + pdfObject = new PdfLongIntegerObject(_document, _lexer.TokenToLongInteger, false); pdfObject.SetObjectID(objectNumber, generationNumber); if (!fromObjectStream) ReadSymbol(Symbol.EndObj); return pdfObject; case Symbol.Real: - pdfObject = new PdfRealObject(_document, _lexer.TokenToReal); + pdfObject = new PdfRealObject(_document, _lexer.TokenToReal, false); pdfObject.SetObjectID(objectNumber, generationNumber); if (!fromObjectStream) ReadSymbol(Symbol.EndObj); return pdfObject; case Symbol.String: - case Symbol.HexString: - pdfObject = new PdfStringObject(_document, _lexer.Token); + pdfObject = new PdfStringObject(_document, _lexer.Token, false); + pdfObject.SetObjectID(objectNumber, generationNumber); + if (!fromObjectStream) + ReadSymbol(Symbol.EndObj); + return pdfObject; + + case Symbol.HexString: // #HEX_STRING_FIX DELETE + pdfObject = new PdfStringObject(_document, _lexer.Token, false) { HexLiteral = true }; pdfObject.SetObjectID(objectNumber, generationNumber); if (!fromObjectStream) ReadSymbol(Symbol.EndObj); return pdfObject; case Symbol.Name: - pdfObject = new PdfNameObject(_document, _lexer.Token); + pdfObject = new PdfNameObject(_document, _lexer.Token, false); pdfObject.SetObjectID(objectNumber, generationNumber); if (!fromObjectStream) ReadSymbol(Symbol.EndObj); @@ -362,8 +363,7 @@ void ReadDictionaryStream(PdfDictionary dict, SuppressExceptions? suppressObject int streamLength = GetStreamLength(dict, suppressObjectOrderExceptions); if (SuppressExceptions.HasError(suppressObjectOrderExceptions)) return; - //#warning THHO4STLA What to do if startPosition + streamLength is larger than length of stream? => Better not show "Please send us your PDF file" but another error message. - // TODO_OLD THHO4STLA What to do if startPosition + streamLength is larger than length of stream? => Better not show "Please send us your PDF file" but another error message. + // TODO_OLD What to do if startPosition + streamLength is larger than length of stream? => Better not show "Please send us your PDF file" but another error message. int retryCount = 0; RetryReadStream: // Step 3: We try to read the stream content. @@ -421,7 +421,8 @@ void ReadDictionaryStream(PdfDictionary dict, SuppressExceptions? suppressObject /// int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderExceptions) { - if (dict.Elements["/F"] != null) + //if (dict.Elements["/F"] != null) // TODO #US373 Just a null check. + if (dict.Elements.HasValue("/F")) // #US373 throw new NotImplementedException("File streams are not yet implemented."); #if TEST_CODE_ // By uncommenting this and the label below, @@ -434,7 +435,7 @@ int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderE // the length of a xref stream. // Creating object streams requires a sophisticated producer apps. For such apps it is very // unlikely that they produce ill formatted stream objects. - // Note: When the stream length is determined by the position of 'endstream' all trailing + // When the stream length is determined by the position of 'endstream' all trailing // CR and LF characters are considered to be part of the stream. In case the stream is // encrypted decryption will fail. if (dict is not PdfCrossReferenceStream && dict.Owner.SecuritySettings.IsEncrypted is false) @@ -442,7 +443,7 @@ int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderE Debug.Assert(dict.Elements["/Type"]?.ToString() == "/XRef"); #endif // Most common case first: Length is a direct integer. - var lengthItem = dict.Elements["/Length"]; + var lengthItem = dict.Elements["/Length"]; // #US373 References handled below. Should we try GetValue here? if (lengthItem is PdfInteger pdfInteger) { Debug.Assert(Convert.ToInt32(lengthItem) == pdfInteger.Value); @@ -458,7 +459,7 @@ int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderE // If somebody came here, please send us your PDF file so that we can fix it (issues (at) pdfsharp.net). if (reference.Value is not PdfIntegerObject pdfIntegerObject) { - SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength()); + SuppressExceptions.HandleError(suppressObjectOrderExceptions, () => throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength(_lexer.Position)); return -1; } @@ -479,7 +480,7 @@ int GetStreamLength(PdfDictionary dict, SuppressExceptions? suppressObjectOrderE catch (Exception ex) { // If somebody came here, please send us your PDF file so that we can fix it (issues (at) pdfsharp.net). - throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength(ex); + throw TH.ObjectNotAvailableException_CannotRetrieveStreamLength(_lexer.Position, ex); } RestoreState(state); } @@ -584,7 +585,7 @@ public PdfArray ReadArray(PdfArray array, bool includeReferences) Debug.Assert(Symbol == Symbol.BeginArray); if (array == null!) - array = new PdfArray(_document); + array = new(_document); var items = ParseObject(Symbol.EndArray); var count = items.Count; @@ -628,6 +629,11 @@ internal PdfDictionary ReadDictionary(PdfDictionary? dict, bool includeReference var items = ParseObject(Symbol.EndDictionary); int count = items.Count; + if (count % 2 != 0) + ParserDiagnostics.ThrowParserException( + "A PDF dictionary contains an odd number of PDF objects. " + + "PDF expects pairs of names and values."); + for (int idx = 0; idx < count; idx += 2) { var val = items[idx]; @@ -635,6 +641,7 @@ internal PdfDictionary ReadDictionary(PdfDictionary? dict, bool includeReference ParserDiagnostics.ThrowParserException("Name expected."); // TODO_OLD L10N using PsMsgs string key = val.ToString() ?? NRT.ThrowOnNull(); + val = items[idx + 1]; if (includeReferences && val is PdfReference reference) { @@ -700,7 +707,7 @@ List ParseObject(Symbol stopSymbol) break; case Symbol.HexString: - items.Add(new PdfString(_lexer.Token, PdfStringFlags.HexLiteral)); + items.Add(new PdfString(_lexer.Token, true)); // #HEX_STRING_FIX DELETE break; case Symbol.Name: @@ -830,8 +837,7 @@ void ReadObjectID(PdfObject? obj) int objectNumber = ReadInteger(); int generationNumber = ReadInteger(); ReadSymbol(Symbol.Obj); - if (obj != null) - obj.SetObjectID(objectNumber, generationNumber); + obj?.SetObjectID(objectNumber, generationNumber); } PdfItem ReadReference(PdfReference iref, bool includeReferences) @@ -1157,7 +1163,7 @@ internal void ReadAllObjectStreamsAndTheirReferences() } // Create the parser for the object stream. - var objectStreamParser = new Parser(_document, new MemoryStream(objectStream.Stream.UnfilteredValue), _documentParser); + var objectStreamParser = new Parser(_document, new MemoryStream(objectStream.Stream!.UnfilteredValue), _documentParser); // Get all ObjectIDs and offsets of objects residing in the object stream. var objectIDsWithOffset = objectStream.ReadObjectIDsWithOffsets(); @@ -1462,8 +1468,9 @@ internal PdfTrailer ReadTrailer() idToUse = idChecked; //ParserDiagnostics.ThrowParserException("Invalid entry in XRef table, ID=" + id + ", Generation=" + generation + ", Position=" + position + ", ID of referenced object=" + idChecked + ", Generation of referenced object=" + generationChecked); // TODO_OLD L10N using PsMsgs } - var message = Invariant( - $"Object ID mismatch: Object at position {position} has ID '{id}' according to xref table and ID '{idChecked}' at its position of file."); + var message = id == idChecked ? + Invariant($"Object ID/generation mismatch: Object at position {position} has ID '{id} {generation}' according to xref table and ID '{idChecked} {generationChecked}' at its position of file.") : + Invariant($"Object ID mismatch: Object at position {position} has ID '{id}' according to xref table and ID '{idChecked}' at its position of file."); PdfSharpLogHost.Logger.LogError(message); } #endif @@ -1514,6 +1521,11 @@ internal PdfTrailer ReadTrailer() /// The generation found in the PDF file. bool CheckXRefTableEntry(SizeType position, int id, int generation, out int idChecked, out int generationChecked) { + // We found a lot of PDF files with wrong position in xref table: + // …〈LF〉42 0 obj〈LF〉… + // ^ + // The offsets points behind the numbers. + // Should we handle this wrong case? SizeType origin = _lexer.Position; idChecked = -1; generationChecked = -1; @@ -1555,7 +1567,6 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable) // Read cross-reference stream. //Debug.Assert(_lexer.Symbol == Symbol.Integer); - // NEEDED??? var xrefStart = _lexer.Position - _lexer.Token.Length; int number = _lexer.TokenToInteger; @@ -1565,7 +1576,9 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable) if (generation != 0) { // Considered to be an error, but without consequences. - PdfSharpLogHost.Logger.LogError($"Generation number of object '{number} {generation}' which is cross-reference stream shall not be other than zero."); + PdfSharpLogHost.Logger.LogError( + $"Generation number of object '{number} {generation}' " + + "which is cross-reference stream shall not be other than zero."); } // Reference 2.0: 7.5.7 Object streams / Page 61 @@ -1617,7 +1630,14 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable) //_ = typeof(int); byte[] bytes = xrefStream.Stream.UnfilteredValue; - int size = xrefStream.Elements.GetInteger(PdfCrossReferenceStream.Keys.Size); + // We found PDF files with invalid size entry. + int size = xrefStream.Elements.GetInteger(PdfCrossReferenceStream.Keys.Size, false, Int32.MaxValue); + if (size == Int32.MaxValue) + { + var value = xrefStream.Elements.GetValue(PdfCrossReferenceStream.Keys.Size, VCF.NoTransform)?.ToString() ?? "(no value)"; + throw new InvalidOperationException( + $"The /Size entry in a cross-reference stream has the invalid value '{value}'."); + } var index = xrefStream.Elements.GetValue(PdfCrossReferenceStream.Keys.Index) as PdfArray; int prev = xrefStream.Elements.GetInteger(PdfCrossReferenceStream.Keys.Prev); var w = (PdfArray?)xrefStream.Elements.GetValue(PdfCrossReferenceStream.Keys.W); @@ -1626,7 +1646,7 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable) // Setup subsections. int subsectionCount; - int[][] subsections = default!; + int[][] subsections = null!; int subsectionEntryCount = 0; if (index == null) { @@ -1725,7 +1745,13 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable) if (objectID.ObjectNumber == 1074) _ = typeof(int); #endif - Debug.Assert(objectID.GenerationNumber == item.Field3); + // TODO Sometimes fails. + //Debug.Assert(objectID.GenerationNumber == item.Field3); + +#if DEBUG + if (objectID.ObjectNumber == 96049) + _ = typeof(int); +#endif // Ignore the latter one. if (!xrefTable.Contains(objectID)) @@ -1748,72 +1774,153 @@ PdfTrailer ReadXRefStream(PdfCrossReferenceTable xrefTable) } } } + + // If set, replace the temporary PdfReferences in xrefStream.Elements, which were set when reading the XrefStream’s dictionary. + // They have no position set, so reading them would assign the wrong objects to their PdfReference duplicates. + // The actual references with the correct positions were read later to the xrefTable when reading the XrefStream’s stream. + // These are the ones to replace the temporary ones. + ReplaceTemporaryReferences(xrefStream, xrefTable); + return xrefStream; } + void ReplaceTemporaryReferences(PdfArray array, PdfCrossReferenceTable xrefTable) + { + for (var i = 0; i < array.Elements.Count; i++) + { + switch (array.Elements[i]) + { + case PdfReference reference: + { + var refID = reference.ObjectID; + var actualReference = xrefTable[refID]; + + if (reference != actualReference && actualReference != null) + array.Elements[i] = actualReference; + break; + } + + case PdfDictionary dic: + ReplaceTemporaryReferences(dic, xrefTable); + break; + + case PdfArray arr: + ReplaceTemporaryReferences(arr, xrefTable); + break; + } + } + } + + void ReplaceTemporaryReferences(PdfDictionary dictionary, PdfCrossReferenceTable xrefTable) + { + // Dictionary elements are modified inside the loop. Avoid "Collection was modified; enumeration operation may not execute" error occuring in net 4.6.2. + // There is no way to access KeyValuePairs via index natively to use a for loop with. + // Instead, enumerate Keys and get value via Elements[key], which should be O(1). + foreach (var key in dictionary.Elements.Keys) + { + var value = dictionary.Elements[key]; // #US373 References handled below. + + switch (value) + { + case PdfReference reference: + { + var refID = reference.ObjectID; + var actualReference = xrefTable[refID]; + + if (reference != actualReference && actualReference != null) + dictionary.Elements[key] = actualReference; + break; + } + + case PdfDictionary dict: + ReplaceTemporaryReferences(dict, xrefTable); + break; + + case PdfArray arr: + ReplaceTemporaryReferences(arr, xrefTable); + break; + } + } + } + /// /// Parses a PDF date string. /// - internal static DateTime ParseDateTime(string date, DateTime errorValue) // TODO_OLD: TryParseDateTime + internal static bool TryParseDate(string pdfDate, out DateTimeOffset? dateTime) { - DateTime datetime = errorValue; + dateTime = null; + bool success = false; try { - if (date.StartsWith("D:", StringComparison.Ordinal)) + TryAgain: + if (pdfDate.StartsWith("D:", StringComparison.Ordinal)) { // Format is // D:YYYYMMDDHHmmSSOHH'mm' // ^2 ^10 ^16 ^20 - int length = date.Length; + // with O is '+', '-', or 'Z'. + int length = pdfDate.Length; int year = 0, month = 0, day = 0, hour = 0, minute = 0, second = 0, hh = 0, mm = 0; char o = 'Z'; - if (length >= 10) + if (length >= 10) // Can read date? { - year = Int32.Parse(date.Substring(2, 4)); - month = Int32.Parse(date.Substring(6, 2)); - day = Int32.Parse(date.Substring(8, 2)); - if (length >= 16) + year = Int32.Parse(pdfDate.Substring(2, 4)); + month = Int32.Parse(pdfDate.Substring(6, 2)); + day = Int32.Parse(pdfDate.Substring(8, 2)); + if (length >= 16) // Can read time? { - hour = Int32.Parse(date.Substring(10, 2)); - minute = Int32.Parse(date.Substring(12, 2)); - second = Int32.Parse(date.Substring(14, 2)); - if (length >= 23) + hour = Int32.Parse(pdfDate.Substring(10, 2)); + minute = Int32.Parse(pdfDate.Substring(12, 2)); + second = Int32.Parse(pdfDate.Substring(14, 2)); + if (length >= 22) // Can read offset? Do not care about trailing ‘'’. { - if ((o = date[16]) != 'Z') + if ((o = pdfDate[16]) != 'Z') { - hh = Int32.Parse(date.Substring(17, 2)); - mm = Int32.Parse(date.Substring(20, 2)); + hh = Int32.Parse(pdfDate.Substring(17, 2)); + mm = Int32.Parse(pdfDate.Substring(20, 2)); } } } } - // There are miserable PDF tools around the world. - month = Math.Min(Math.Max(month, 1), 12); - datetime = new DateTime(year, month, day, hour, minute, second); + + var offset = TimeSpan.Zero; if (o != 'Z') { - TimeSpan ts = new TimeSpan(hh, mm, 0); + offset = new TimeSpan(hh, mm, 0); if (o == '-') - datetime = datetime.Add(ts); - else - datetime = datetime.Subtract(ts); + offset = offset.Negate(); } - // Now that we converted datetime to UTC, mark it as UTC. - datetime = DateTime.SpecifyKind(datetime, DateTimeKind.Utc); + + // There are miserable PDF tools around the world. + month = Math.Min(Math.Max(month, 1), 12); + + dateTime = new(year, month, day, hour, minute, second, offset); + success = true; } else { - // Some libraries use plain English format. - datetime = DateTime.Parse(date, CultureInfo.InvariantCulture); + // Some dumb PDF creator tools use plain English date format. + if (DateTimeOffset.TryParse(pdfDate, CultureInfo.InvariantCulture, DateTimeStyles.None, out var result)) + { + dateTime = result; + success = true; + } + else + { + // Try again with starting “D:”. The specs say it is optional. + pdfDate = "D:" + pdfDate; + goto TryAgain; + } } } - // ReSharper disable once EmptyGeneralCatchClause - catch (Exception ex) + catch (Exception) { // If we cannot parse datetime, just eat it, but give a hint in DEBUG build. - Debug.Assert(false, ex.Message); + //Debug.Assert(false, ex.Message); + PdfSharpLogHost.PdfReadingLogger.LogError("Date string '{Date}' could not be parsed.", pdfDate); + success = false; } - return datetime; + return success; } /// @@ -1855,7 +1962,7 @@ struct ParserState readonly Dictionary _objectStreamsWithParsers = new(); #endif readonly Parser _documentParser; - private int _endStreamNotFoundCounter = 0; + int _endStreamNotFoundCounter = 0; readonly ILogger _logger; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs index f9b9f6fc..e872c4c4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs @@ -1,10 +1,13 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.ComponentModel; using Microsoft.Extensions.Logging; using PdfSharp.Internal; using PdfSharp.Logging; +using PdfSharp.Pdf.Forms; using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; using PdfSharp.Pdf.Internal; namespace PdfSharp.Pdf.IO @@ -114,7 +117,6 @@ public static int TestPdfFile(Stream stream) { } } - return 0; } @@ -208,6 +210,12 @@ public static PdfDocument Open(string path, string password, PdfReaderOptions? o public static PdfDocument Open(Stream stream) => Open(stream, PdfDocumentOpenMode.Modify); + /// + /// Opens an existing PDF document. + /// + public static PdfDocument Open(Stream stream, string password, PdfReaderOptions? options = null) + => Open(stream, password, PdfDocumentOpenMode.Modify, null, options); + /// /// Opens an existing PDF document. /// @@ -258,7 +266,7 @@ PdfDocument OpenFromFile(string path, string? password, PdfDocumentOpenMode open } catch (Exception ex) { - PdfSharpLogHost.Logger.LogError(ex, "Open a PDF document failed."); + PdfSharpLogHost.Logger.LogError(ex, "Opening a PDF document failed."); throw; } return document!; @@ -268,7 +276,7 @@ PdfDocument OpenFromFile(string path, string? password, PdfDocumentOpenMode open /// Opens a PDF document from a stream. /// PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode openMode, - PdfPasswordProvider? passwordProvider, PdfReaderOptions? options = null) + PdfPasswordProvider? passwordProvider) // MaOs4StLa Review: Removed options parameter. The parameter was not used and there is already an _options field. { try { @@ -279,8 +287,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode var lexer = new Lexer(stream, _logger); _document = new PdfDocument(lexer); - _document._state |= DocumentState.Imported; - _document._openMode = openMode; + _document.State |= DocumentState.Imported; + _document.OpenMode = openMode; try { @@ -301,8 +309,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode byte[] header = new byte[1024]; stream.Position = 0; var _ = stream.Read(header, 0, 1024); - _document._version = GetPdfFileVersion(header); - if (_document._version == 0) + _document.SetVersion(GetPdfFileVersion(header)); + if (_document.Version == 0) throw new InvalidOperationException(PsMsgs.InvalidPdf); // Set IsUnderConstruction for IrefTable to true. This allows Parser.ParseObject() to insert placeholder references for objects not yet known. @@ -310,19 +318,27 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode // After reading all objects, all documents placeholder references get replaced by references knowing their objects in FinishReferences(), // which finally sets IsUnderConstruction to false. _document.IrefTable.IsUnderConstruction = true; - var parser = new Parser(_document, options ?? new PdfReaderOptions(), _logger); + var parser = new Parser(_document, _options, _logger); // 1. Read all trailers or cross-reference streams, but no objects. _document.Trailer = parser.ReadTrailer(); if (_document.Trailer == null!) ParserDiagnostics.ThrowParserException( "Invalid PDF file: no trailer found."); // TODO_OLD L10N using PsMsgs - // References available by now: All references to file-level objects. - // Reference.Values available by now: All trailers and cross-reference streams (which are not encrypted by definition). + // References available by now: All references to file-level objects. + // Reference.Values available by now: All trailers and cross-reference streams (which are not encrypted by definition). // 2. Read the encryption dictionary, if existing. - if (_document.Trailer!.Elements[PdfTrailer.Keys.Encrypt] is PdfReference xrefEncrypt) + // #US373: Should we expect references here? + if (_document.Trailer!.Elements[PdfTrailer.Keys.Encrypt] is PdfReference xrefEncrypt) // #US373 Expect a reference here. { +#if DEBUG_ + if (xrefEncrypt.ObjectNumber == 96049) + { + bool contains = _document.IrefTable.Contains(xrefEncrypt.ObjectID); + var xrefExisting = _document.IrefTable[xrefEncrypt.ObjectID]; + } +#endif var encrypt = parser.ReadIndirectObject(xrefEncrypt, null, true); encrypt.Reference = xrefEncrypt; xrefEncrypt.Value = encrypt; @@ -336,6 +352,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode var effectiveSecurityHandler = _document.EffectiveSecurityHandler; if (effectiveSecurityHandler != null) { + effectiveSecurityHandler.DoNotResetEncryption = _options.DoNotResetEncryption; + TryAgain: // ... after the password provider provides a valid password. // ReSharper disable RedundantIfElseBlock to keep code more readable. PasswordValidity validity = effectiveSecurityHandler.ValidatePassword(password); @@ -370,7 +388,15 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode goto TryAgain; } else - throw new PdfReaderException(PsMsgs.OwnerPasswordRequired); + { +#if PDFSHARP_DEBUG + // Needed for testing and debugging of encrypted files. + if (PdfSharpDebug.Instance.AllowOpenWithUserPasswordOnly) + goto ContinueWithoutOwnerPassword; +#endif + if (!_options.AllowModifyWithoutOwnerPassword) + throw new PdfReaderException(PsMsgs.OwnerPasswordRequired); + } } // ReSharper restore RedundantIfElseBlock } @@ -383,6 +409,10 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode } } +#if PDFSHARP_DEBUG + ContinueWithoutOwnerPassword: +#endif + // 4. Read all Objects streams and the references to the objects saved in them. parser.ReadAllObjectStreamsAndTheirReferences(); // References available by now: All references (to file-level objects and to objects residing in object streams). @@ -394,7 +424,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode // Reference.Values available by now: All objects. // 6. Reset encryption so that it must be redefined to save the document encrypted. - effectiveSecurityHandler?.SetEncryptionToNoneAndResetPasswords(); + if (!_options.DoNotResetEncryption) + effectiveSecurityHandler?.SetEncryptionToNoneAndResetPasswords(); // 7. Replace all document’s placeholder references by references knowing their objects. // Placeholder references are used, when reading indirect objects referring objects stored in object streams before reading and decoding them. @@ -422,7 +453,7 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode } // Change modification date. - _document.Info.ModificationDate = DateTime.Now; + _document.Info.ModificationDate = DateTimeOffset.Now; // Remove all unreachable objects. int removed = _document.IrefTable.Compact(); @@ -433,13 +464,29 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode } // Force flattening of page tree. - var pages = _document.Pages; - Debug.Assert(pages != null); + _document.Pages.FlattenPageTree(); _document.IrefTable.CheckConsistence(); _document.IrefTable.Renumber(); _document.IrefTable.CheckConsistence(); } + else if (openMode == PdfDocumentOpenMode.Import) + { + // Keep the page tree and generate a flat array of all pages for simple access. + _document.Pages.PreservePageTree(); + } + else + { + throw new InvalidOperationException($"Open mode {openMode.ToString()} does not exist; use Import instead."); + } + + // Create metadata. + PdfMetadata.MetadataPreparer.PrepareDocument(_document); + // Create Acro fields and widgets. + PdfFormFields.AcroFieldPreparer.PrepareDocument(_document); + // Create annotations. + PdfAnnotations.AnnotationPreparer.PrepareDocument(_document); + } catch (Exception ex) { @@ -456,6 +503,8 @@ void FinalizeReferences() { Debug.Assert(_document.IrefTable.IsUnderConstruction); + // TODO: Can a direct container in a top-level object also contain references to be updated? + foreach (var iref in _document.IrefTable.AllReferences) { var pdfObject = iref.Value; @@ -463,6 +512,9 @@ void FinalizeReferences() Debug.Assert(pdfObject != null, "All references saved in IrefTable should have been created when their referred PdfObject has been accessible."); + if (pdfObject.IsDead) + throw new InvalidOperationException("TODO: REPORT A BUG"); // TODO + // Update all references to PdfDictionary’s and PdfArray’s child objects. switch (pdfObject) { @@ -472,13 +524,14 @@ void FinalizeReferences() // Instead, enumerate Keys and get value via Elements[key], which should be O(1). foreach (var key in dictionary.Elements.Keys) { - var item = dictionary.Elements[key]; + var item = dictionary.Elements[key]; // #US373 ??? Do not use GetValue - see below. // Replace each reference with its final item, if necessary. if (item is PdfReference currentReference && ShouldUpdateReference(currentReference, out var finalItem)) dictionary.Elements[key] = finalItem; } break; + case PdfArray array: var elements = array.Elements; for (var i = 0; i < elements.Count; i++) @@ -516,7 +569,7 @@ bool ShouldUpdateReference(PdfReference currentReference, out PdfItem finalItem) { // Read the reference for the ObjectID of the placeholder reference from IrefTable, which should contain the value. var newIref = _document.IrefTable[currentReference.ObjectID]; - reference = newIref; + reference = newIref; // New value can be null if no object with the specified ID exists. isChanged = true; // reference may be null. Don’t return yet. } @@ -574,7 +627,7 @@ static void RereadUnicodeStrings(PdfItem pdfItem) } } - PdfReaderOptions _options; + readonly PdfReaderOptions _options; PdfDocument _document = default!; readonly ILogger _logger; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs index ed530d51..a15c5014 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. #pragma warning disable CS1591 // Missing XML comment for publicly visible type or member because it is for internal use only. @@ -99,8 +99,16 @@ public class PdfReaderOptions public ReaderProblemDelegate? ReaderProblemCallback { get; set; } - // Testing only + /// + /// Allow opening owner password secured PDF files for modification without specifying the owner password. + /// The default behavior is to throw an exception and deny the modification to respect the author’s intention. + /// + public bool AllowModifyWithoutOwnerPassword { get; set; } - //public bool UseOldCode { get; set; } = false; + /// + /// Do not reset the encryption of an opened document and reuse these settings when saving. + /// The default behaviour is to reset the encryption, so that it’s on the user to set an encryption before saving. + /// + public bool DoNotResetEncryption { get; set; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs index cd70d0e6..18346cd5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs @@ -2,42 +2,34 @@ // See the LICENSE file in the solution root for more information. using System.Text; +using PdfSharp.Internal; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Security; using PdfSharp.Pdf.Internal; -using PdfSharp.Pdf.Signatures; + +// v7.0.0 Review namespace PdfSharp.Pdf.IO { /// /// Represents a writer for generation of PDF streams. /// - class PdfWriter + class PdfWriter(Stream pdfStream, PdfDocument document, PdfStandardSecurityHandler? effectiveSecurityHandler) { - public PdfWriter(Stream pdfStream, PdfDocument document, PdfStandardSecurityHandler? effectiveSecurityHandler) - { - _stream = pdfStream ?? throw new ArgumentNullException(nameof(pdfStream)); - _document = document ?? throw new ArgumentNullException(nameof(document)); - EffectiveSecurityHandler = effectiveSecurityHandler; - - Layout = document.Options.Layout; - } - - public void Close(bool closeUnderlyingStream) + public void Close(bool closeUnderlyingStream = true) { if (closeUnderlyingStream) _stream.Close(); _stream = null!; } - public void Close() => Close(true); - + // ReSharper disable once RedundantCast because SizeType can be a 32- or 64-bit integer. public SizeType Position => (SizeType)_stream.Position; /// /// Gets or sets the kind of layout. /// - public PdfWriterLayout Layout { get; set; } + public PdfWriterLayout Layout { get; set; } = document.Options.Layout; internal bool IsCompactLayout => Layout == PdfWriterLayout.Compact; @@ -52,105 +44,101 @@ public void Close(bool closeUnderlyingStream) // ----------------------------------------------------------- /// - /// Writes the specified value to the PDF stream. + /// Writes a PDF comment to the PDF stream. /// - public void Write(bool value) + public void WriteComment(string text) { WriteSeparator(CharCat.Character); - // Wrong: Writes "True" or "False" where it should be "true" or "false": WriteRaw(value ? bool.TrueString : bool.FalseString); - WriteRaw(value ? "true" : "false"); - _lastCat = CharCat.Character; + WriteRaw("% " + text + '\n'); + } + /// + /// Writes ‘mull’ to the PDF stream. + /// + public void Write(PdfNull _) + { + WriteSeparator(CharCat.Character); + WriteRaw("null"); } /// - /// Writes the specified value to the PDF stream. + /// Writes ‘true’ or ‘false’ to the PDF stream. /// - public void Write(PdfBoolean value) + public void Write(bool value) { WriteSeparator(CharCat.Character); - WriteRaw(value.Value ? "true" : "false"); - _lastCat = CharCat.Character; + WriteRaw(value ? "true" : "false"); } /// - /// Writes the specified value to the PDF stream. + /// Writes ‘true’ or ‘false’ to the PDF stream. /// - public void Write(int value) + public void Write(PdfBoolean value) => Write(value.Value); + + /// + /// Writes the specified integer value to the PDF stream. + /// + public void Write(int value, bool isFlag) { WriteSeparator(CharCat.Character); - WriteRaw(value.ToString(CultureInfo.InvariantCulture)); - _lastCat = CharCat.Character; + if (isFlag) + { + // Maybe an unsigned value instead of a negative one causes + // problems with some PDF readers. + //WriteRaw(((uint)value).ToString(CultureInfo.InvariantCulture)); + WriteRaw(value.ToString(CultureInfo.InvariantCulture)); + if (IsVerboseLayout) + { + WriteRaw($" % 0x{(uint)value >> 4:X4}_{(uint)value & 0xFFFF:X4}\n"); + } + } + else + WriteRaw(value.ToString(CultureInfo.InvariantCulture)); } + /// + /// Writes the specified long integer value to the PDF stream. + /// public void Write(long value) { WriteSeparator(CharCat.Character); WriteRaw(value.ToString(CultureInfo.InvariantCulture)); - _lastCat = CharCat.Character; } /// - /// Writes the specified value to the PDF stream. + /// Writes the specified unsigned integer value to the PDF stream. /// public void Write(uint value) { WriteSeparator(CharCat.Character); WriteRaw(value.ToString(CultureInfo.InvariantCulture)); - _lastCat = CharCat.Character; } /// - /// Writes the specified value to the PDF stream. + /// Writes the specified integer value to the PDF stream. /// - public void Write(PdfInteger value) - { - WriteSeparator(CharCat.Character); - _lastCat = CharCat.Character; - WriteRaw(value.Value.ToString(CultureInfo.InvariantCulture)); - } - - // DELETE - //// /// - //// /// Writes the specified value to the PDF stream. - //// /// - ////#pragma warning disable CS0618 // Type or member is obsolete - //// public void Write(PdfUInteger value) - ////#pragma warning restore CS0618 // Type or member is obsolete - //// { - //// WriteSeparator(CharCat.Character); - //// _lastCat = CharCat.Character; - //// WriteRaw(value.Value.ToString(CultureInfo.InvariantCulture)); - //// } + public void Write(PdfInteger value) => Write(value.Value, value.IsFlag); /// - /// Writes the specified value to the PDF stream. + /// Writes the specified long integer value to the PDF stream. /// - public void Write(PdfLongInteger value) - { - WriteSeparator(CharCat.Character); - _lastCat = CharCat.Character; - WriteRaw(value.Value.ToString(CultureInfo.InvariantCulture)); - } + public void Write(PdfLongInteger value) => Write(value.Value); /// - /// Writes the specified value to the PDF stream. + /// Writes the specified real value to the PDF stream. /// public void Write(double value) { + // See unit test Test_Single_Write_and_Read to understand why double values now + // are written as singles values. + float f = (float)value; WriteSeparator(CharCat.Character); - WriteRaw(value.ToString(Config.SignificantDecimalPlaces7, CultureInfo.InvariantCulture)); - _lastCat = CharCat.Character; + WriteRaw(f.ToString(Config.SignificantDecimalPlaces7, CultureInfo.InvariantCulture)); } /// - /// Writes the specified value to the PDF stream. + /// Writes the specified real value to the PDF stream. /// - public void Write(PdfReal value) - { - WriteSeparator(CharCat.Character); - WriteRaw(value.Value.ToString(Config.SignificantDecimalPlaces7, CultureInfo.InvariantCulture)); - _lastCat = CharCat.Character; - } + public void Write(PdfReal value) => Write(value.Value); /// /// Writes the specified value to the PDF stream. @@ -158,62 +146,50 @@ public void Write(PdfReal value) public void Write(PdfString value) { WriteSeparator(CharCat.Delimiter); -#if true - PdfStringEncoding encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask); + var encoding = (PdfStringEncoding)(value.Flags & PdfStringFlags.EncodingMask); string pdf = (value.Flags & PdfStringFlags.HexLiteral) == 0 ? PdfEncoders.ToStringLiteral(value.Value, encoding, EffectiveSecurityHandler) : PdfEncoders.ToHexStringLiteral(value.Value, encoding, EffectiveSecurityHandler); WriteRaw(pdf); -#else - switch (value.Flags & PdfStringFlags.EncodingMask) - { - case PdfStringFlags.Undefined: - case PdfStringFlags.PDFDocEncoding: - if ((value.Flags & PdfStringFlags.HexLiteral) == 0) - WriteRaw(PdfEncoders.DocEncode(value.Value, false)); - else - WriteRaw(PdfEncoders.DocEncodeHex(value.Value, false)); - break; - - case PdfStringFlags.WinAnsiEncoding: - throw new NotImplementedException("Unexpected encoding: WinAnsiEncoding"); - - case PdfStringFlags.Unicode: - if ((value.Flags & PdfStringFlags.HexLiteral) == 0) - WriteRaw(PdfEncoders.DocEncode(value.Value, true)); - else - WriteRaw(PdfEncoders.DocEncodeHex(value.Value, true)); - break; - - case PdfStringFlags.StandardEncoding: - case PdfStringFlags.MacRomanEncoding: - case PdfStringFlags.MacExpertEncoding: - default: - throw new NotImplementedException("Unexpected encoding"); - } -#endif - _lastCat = CharCat.Delimiter; + //_lastCat = CharCat.Delimiter; } /// /// Writes a signature placeholder hexadecimal string to the PDF stream. /// - public void Write(PdfSignaturePlaceholderItem item, out SizeType startPosition, out SizeType endPosition) + public void Write(PdfPlaceholder item, out SizeType startPosition, out SizeType endPosition) { WriteSeparator(CharCat.Delimiter); + // ReSharper disable once RedundantCast + startPosition = (SizeType)Position; + + // We have to write effectively filler bytes to easily update the stream postion. + WriteRaw(item.Value); + + // ReSharper disable once RedundantCast + endPosition = (SizeType)Position; + } + +#if false + /// // DELETE + /// Writes a signature placeholder hexadecimal string to the PDF stream. + /// + public void Write(PdfSignaturePlaceholderItem_ item, out SizeType startPosition, out SizeType endPosition) // TODO: Use PdfPlaceholder and DELETE + { + WriteSeparator(CharCat.Delimiter); // ReSharper disable once RedundantCast startPosition = (SizeType)Position; // A PDF hex string with question marks '' WriteRaw(item.ToString()); // ReSharper disable once RedundantCast endPosition = (SizeType)Position; - - _lastCat = CharCat.Delimiter; + //_lastCat = CharCat.Delimiter; } +#endif /// - /// Writes the specified value to the PDF stream. + /// Writes the specified PDF name to the PDF stream. /// public void Write(PdfName value) { @@ -229,140 +205,101 @@ public void Write(PdfName value) // In such situations, it is recommended that the sequence of bytes (after expansion // of # sequences, if any) be interpreted according to UTF-8, a variable-length byte-encoded // representation of Unicode in which the printable ASCII characters have - // the same representations as in ASCII.This enables a name object to represent text + // the same representations as in ASCII. This enables a name object to represent text // in any natural language, subject to the implementation limit on the length of a // name. WriteSeparator(CharCat.Delimiter); - string name = value.Value; - Debug.Assert(name[0] == '/'); - - // Encode to raw UTF-8 if any char is larger than 126. - // 127 [DEL] is not a valid value and is also encoded. - for (int idx = 1; idx < name.Length; idx++) - { - char ch = name[idx]; - if (ch > 126) - { - // Non-ASCII character found, convert whole string to raw UTF-8. - var bytes = Encoding.UTF8.GetBytes(name); - var nameBuilder = new StringBuilder(); - foreach (var ch2 in bytes) - nameBuilder.Append((char)ch2); - name = nameBuilder.ToString(); - break; - } - } - - // Here all high bytes are zero. - var pdf = new StringBuilder("/"); - for (int idx = 1; idx < name.Length; idx++) - { - char ch = name[idx]; - Debug.Assert(ch < 256); - if (ch > ' ') - { - switch (ch) - { - case '%': - case '/': - case '<': - case '>': - case '(': - case ')': - case '[': - case ']': - case '{': - case '}': - case '#': - break; - - default: - if (ch <= 126) - { - // See recommendation above. - pdf.Append(ch); - continue; - } - break; - } - } - pdf.AppendFormat("#{0:X2}", (int)ch); - } - WriteRaw(pdf.ToString()); + var literalName = value.Name.LiteralValue; + WriteRaw(literalName); + // In the rare case someone used an empty name the last category must + // nevertheless set to Character. Otherwise, a subsequent number would be parsed as + // part of the name if the writer mode is Compact. + _lastChar = '0'; // 0 as a symbolic character. _lastCat = CharCat.Character; } + /// + /// Writes the specified PDF literal value to the PDF stream. + /// public void Write(PdfLiteral value) { - var rawString = value.Value; - var first = rawString[0]; - var last = rawString[^1]; - WriteSeparator(GetCategory(first)); - WriteRaw(rawString); - _lastCat = GetCategory(last); + if (String.IsNullOrEmpty(value.Value)) + return; + + WriteSeparator(GetCategory(value.Value[0])); + WriteRaw(value.Value); } + /// + /// Writes the specified PDF rectangle value to the PDF stream. + /// public void Write(PdfRectangle rect) { const string format = Config.SignificantDecimalPlaces3; - WriteSeparator(CharCat.Delimiter/*, '/'*/); + WriteSeparator(CharCat.Delimiter); WriteRaw(PdfEncoders.Format("[{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "}]", rect.X1, rect.Y1, rect.X2, rect.Y2)); - _lastCat = CharCat.Delimiter; } + /// + /// Writes the specified PDF reference value to the PDF stream. + /// public void Write(PdfReference iref) { +#if DEBUG_ + if (iref.ObjectID.ObjectNumber == 6) + _ = typeof(int); +#endif WriteSeparator(CharCat.Character); WriteRaw(iref.ToString()); - _lastCat = CharCat.Character; } public void WriteDocString(string text, bool unicode) { WriteSeparator(CharCat.Delimiter); - //WriteRaw(PdfEncoders.DocEncode(text, unicode)); - byte[] bytes; - if (!unicode) - bytes = PdfEncoders.DocEncoding.GetBytes(text); - else - bytes = PdfEncoders.UnicodeEncoding.GetBytes(text); + var bytes = unicode + ? PdfEncoders.UnicodeEncoding.GetBytes(text) + : PdfEncoders.DocEncoding.GetBytes(text); bytes = PdfEncoders.FormatStringLiteral(bytes, unicode, true, false, EffectiveSecurityHandler); Write(bytes); - _lastCat = CharCat.Delimiter; } + /// + /// Writes the specified PDF DOC encoded string value to the PDF stream. + /// public void WriteDocString(string text) { WriteSeparator(CharCat.Delimiter); - //WriteRaw(PdfEncoders.DocEncode(text, false)); byte[] bytes = PdfEncoders.DocEncoding.GetBytes(text); bytes = PdfEncoders.FormatStringLiteral(bytes, false, false, false, EffectiveSecurityHandler); Write(bytes); - _lastCat = CharCat.Delimiter; } + /// + /// Writes the specified PDF DOC encoded string value to the PDF stream. + /// public void WriteDocStringHex(string text) { WriteSeparator(CharCat.Delimiter); - //WriteRaw(PdfEncoders.DocEncodeHex(text)); byte[] bytes = PdfEncoders.DocEncoding.GetBytes(text); bytes = PdfEncoders.FormatStringLiteral(bytes, false, false, true, EffectiveSecurityHandler); - _stream.Write(bytes, 0, bytes.Length); - _lastCat = CharCat.Delimiter; + Write(bytes); } /// /// Begins a direct or indirect dictionary or array. /// - public void WriteBeginObject(PdfObject obj) + public void WriteBeginObject(PdfObject obj, int childItemCount = 0) { +#if DEBUG + if (obj.ObjectID.ObjectNumber == 10) + _ = typeof(int); +#endif bool isIndirect = obj.IsIndirect; if (isIndirect) - { WriteObjectAddress(obj); - } - _stack.Add(new StackItem(obj)); + + _stack.Add(new StackItem(obj, childItemCount)); string? suffix = null; if (IsVerboseLayout && _stack.Count > 1) @@ -379,22 +316,19 @@ public void WriteBeginObject(PdfObject obj) else { if (suffix != null) - WriteRaw("[" + suffix); + WriteRaw("[ " + suffix); else - WriteRaw("[\n"); - + WriteRaw("["); } } else if (obj is PdfDictionary) { if (IsCompactLayout) - { WriteRaw("<<"); - } else { if (suffix != null) - WriteRaw("<<" + suffix); + WriteRaw("<< " + suffix); else WriteRaw("<<\n"); } @@ -403,69 +337,46 @@ public void WriteBeginObject(PdfObject obj) { // Case: PdfIntegerObject or PdfNullObject - //Debug.Assert(false, "Should not come here."); Debug.Assert(obj is not null, "Should not come here."); + Debug.Assert(obj is PdfPrimitiveObject, "Should be primitive object."); } - _lastCat = CharCat.NewLine; } - else + else // !isIndirect { if (obj is PdfArray) { -#if true_ - // Same as PdfDictionary - NewLine(); - WriteSeparator(CharCat.Delimiter); - WriteRaw("[\n"); - _lastCat = CharCat.NewLine; -#else if (IsCompactLayout) - { WriteRaw('['); - } else { - //NewLine(); - //WriteSeparator(CharCat.Delimiter); if (suffix != null) - { - WriteRaw("[ " + GetTypeAndComment(obj)); - _lastCat = CharCat.NewLine; - } + WriteRaw(" [ " + suffix); else - { - WriteRaw('['); - _lastCat = CharCat.Delimiter; - } + WriteRaw(" ["); } -#endif } else if (obj is PdfDictionary) { if (IsCompactLayout) - { WriteRaw("<<"); - } else { NewLine(); WriteSeparator(CharCat.Delimiter); if (suffix != null) - WriteRaw("<< " + GetTypeAndComment(obj)); + WriteRaw("<< " + suffix); else WriteRaw("<<\n"); - _lastCat = CharCat.NewLine; } } else { // Case: PdfIntegerObject or PdfNullObject - //Debug.Assert(false, "Should not come here."); Debug.Assert(obj is not null, "Should not come here."); } } - if (IsVerboseLayout) + if (IsIndentedLayout) IncreaseIndent(); } @@ -474,18 +385,16 @@ public void WriteBeginObject(PdfObject obj) /// public void WriteEndObject() { - bool noLayout = Layout == PdfWriterLayout.Compact; - int count = _stack.Count; Debug.Assert(count > 0, "PdfWriter stack underflow."); - StackItem stackItem = _stack[count - 1]; + var stackItem = _stack[count - 1]; _stack.RemoveAt(count - 1); PdfObject value = stackItem.Object; var indirect = value.IsIndirect; - if (IsVerboseLayout) + if (IsIndentedLayout) DecreaseIndent(); if (value is PdfArray) @@ -493,29 +402,23 @@ public void WriteEndObject() if (indirect) { if (IsCompactLayout) - { WriteRaw("]\n"); - _lastCat = CharCat.NewLine; - } else - { - - WriteRaw("\n]\n"); - _lastCat = CharCat.Delimiter; - } + WriteRaw("]"); } else { + // DELETE TODO if (IsCompactLayout) { WriteRaw("]"); - _lastCat = CharCat.Delimiter; } else { - //WriteSeparator(CharCat.NewLine); + // Indent after new line. + if (_lastCat == CharCat.NewLine && IsIndentedLayout) + WriteIndent(); WriteRaw("]"); - _lastCat = CharCat.Delimiter; } } } @@ -527,13 +430,11 @@ public void WriteEndObject() { if (!stackItem.HasStream) WriteRaw(">>\n"); - _lastCat = CharCat.NewLine; } else { if (!stackItem.HasStream) WriteRaw(">>\n"); - _lastCat = CharCat.NewLine; } } else @@ -543,21 +444,15 @@ public void WriteEndObject() { WriteSeparator(CharCat.NewLine); WriteRaw(">>"); - _lastCat = CharCat.Delimiter; } else { WriteSeparator(CharCat.NewLine); + //WriteRaw(">>\n"); if (IsVerboseLayout) - { WriteRaw(">>\n"); - _lastCat = CharCat.NewLine; - } else - { WriteRaw(">>"); - _lastCat = CharCat.Delimiter; - } } } } @@ -591,7 +486,7 @@ public void WriteStream(PdfDictionary dict, bool omitStream) var bytes = dict.Stream!.Value; if (IsCompactLayout) { - WriteRaw(">>\nstream\n"); + WriteRaw(">>stream\n"); // Earlier versions of PDFsharp skipped the '\n' before 'endstream' if the last byte of // the stream is a linefeed. This was wrong and is now fixed. @@ -601,10 +496,8 @@ public void WriteStream(PdfDictionary dict, bool omitStream) } else { - //WriteRaw(_lastCat == CharCat.NewLine ? ">>\nstream\n" : " >>\nstream\n"); - if (IsVerboseLayout) - WriteRaw(Invariant($">>\n% Length: {bytes.Length}\nstream\n")); + WriteRaw(Invariant($">>\n% Real length: {bytes.Length}\nstream\n")); else WriteRaw(">>\nstream\n"); @@ -627,7 +520,8 @@ public void WriteRaw(string rawString) byte[] bytes = PdfEncoders.RawEncoding.GetBytes(rawString); _stream.Write(bytes, 0, bytes.Length); - _lastCat = GetCategory((char)bytes[^1]); + _lastChar = (char)bytes[^1]; + _lastCat = GetCategory(_lastChar); } public void WriteRaw(char ch) @@ -635,6 +529,7 @@ public void WriteRaw(char ch) Debug.Assert(ch < 256, "Raw character greater than 255 detected."); _stream.WriteByte((byte)ch); + _lastChar = ch; _lastCat = GetCategory(ch); } @@ -644,60 +539,62 @@ public void Write(byte[] bytes) return; _stream.Write(bytes, 0, bytes.Length); - _lastCat = GetCategory((char)bytes[^1]); + _lastChar = (char)bytes[^1]; + _lastCat = GetCategory(_lastChar); } void WriteObjectAddress(PdfObject value) { - if (Layout == PdfWriterLayout.Verbose) + var objID = value.ObjectID; + if (IsVerboseLayout) { - string comment = value.Comment; - if (!String.IsNullOrEmpty(comment)) - comment = $" -- {value.Comment}"; - -#if DEBUG_ - if (_document is null) - _ = typeof(int); -#endif // #PDF-A if (_document.IsPdfA) { // Write full type name and comment in a separate line to be PDF-A conform. - WriteRaw(Invariant($"% {value.GetType().FullName}{comment}\n")); - WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj\n")); + WriteRaw(GetTypeAndComment(value)!); + WriteRaw(Invariant($"{objID.ObjectNumber} {objID.GenerationNumber} obj\n")); } else { // Write object number and full type name and comment in one line. - WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj % {value.GetType().FullName}{comment}\n")); + WriteRaw(Invariant($"{objID.ObjectNumber} {objID.GenerationNumber} obj {GetTypeAndComment(value, true)}")); } } else { // Write object number only. - WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj\n")); + WriteRaw(Invariant($"{objID.ObjectNumber} {objID.GenerationNumber} obj\n")); } } public void WriteFileHeader(PdfDocument document) { var header = new StringBuilder("%PDF-"); - int version = document._version; + int version = document.Version; //header.Append((version / 10).ToString(CultureInfo.InvariantCulture) + "." + // (version % 10).ToString(CultureInfo.InvariantCulture) + "\n%\xD3\xF4\xCC\xE1\n"); header.Append(Invariant($"{version / 10}.{version % 10}\n%\xD3\xF4\xCC\xE1\n")); WriteRaw(header.ToString()); - if (Layout == PdfWriterLayout.Verbose) + if (IsVerboseLayout) { - WriteRaw($"% PDFsharp Version {PdfSharpProductVersionInformation.Version} (verbose mode)\n"); + WriteRaw($"% PDFsharp {PdfSharpProductVersionInformation.Version} (verbose layout)\n"); + WriteRaw($"% Version {PdfSharpProductVersionInformation.SemanticVersion} ({Capabilities.Build.BuildName}) under .NET {Capabilities.Build.Framework}\n"); // Keep some space for later fix-up. _commentPosition = (int)_stream.Position + 2; - WriteRaw("% \n"); // Creation date placeholder - WriteRaw("% \n"); // Creation time placeholder - WriteRaw("% \n"); // File size placeholder - WriteRaw("% \n"); // Pages placeholder - WriteRaw("% \n"); // Objects placeholder + WriteRaw("% \n"); // Title placeholder + WriteRaw("% \n"); // Creation date placeholder + WriteRaw("% \n"); // Creation time placeholder + WriteRaw("% \n"); // File size placeholder + WriteRaw("% \n"); // Pages placeholder + WriteRaw("% \n"); // Objects placeholder + WriteRaw("% \n"); // PDF/A placeholder + WriteRaw("% \n"); // Embedded files placeholder + WriteRaw("% \n"); // Signature type placeholder + WriteRaw("% \n"); // Encryption type placeholder + WriteRaw("% \n"); // User password placeholder + WriteRaw("% \n"); // Owner password placeholder #if DEBUG WriteRaw("% This document is created from a DEBUG build. Do not use a DEBUG build of PDFsharp for production.\n"); #endif @@ -705,30 +602,82 @@ public void WriteFileHeader(PdfDocument document) } } + // ReSharper disable once IdentifierTypo public void WriteEof(PdfDocument document, SizeType startxref) { WriteRaw($"% Created with PDFsharp {PdfSharpProductVersionInformation.SemanticVersion} ({Capabilities.Build.BuildName}) under .NET {Capabilities.Build.Framework}\n"); +#if DEBUG + WriteRaw($"% Branch {SemVersionInformation.BranchName} ({SemVersionInformation.Sha}) {SemVersionInformation.CommitDate}\n"); +#endif WriteRaw("startxref\n"); WriteRaw(startxref.ToString(CultureInfo.InvariantCulture)); WriteRaw("\n%%EOF\n"); SizeType fileSize = (SizeType)_stream.Position; - if (Layout == PdfWriterLayout.Verbose) + if (IsVerboseLayout) { - TimeSpan duration = DateTime.Now - document._creation; + // Note + // Without InvariantCulture parameter the following code fails if the current culture is e.g. + // a Far East culture, because the date string may contain non-ASCII characters. + // So never never never never use ToString without a culture info. + + var now = DateTimeOffset.Now; + TimeSpan duration = now - document.CreationDate; + + const int offset = 60; + // Set stream to document comment section. _stream.Position = _commentPosition; - // Without InvariantCulture parameter the following line fails if the current culture is e.g. - // a Far East culture, because the date string contains non-ASCII characters. - // So never never never never use ToString without a culture info. - WriteRaw(Invariant($"Creation date: {document._creation:G}")); - _stream.Position = _commentPosition + 50; - WriteRaw(Invariant($"Creation time: {duration.TotalSeconds:0.000} seconds")); - _stream.Position = _commentPosition + 100; - WriteRaw(Invariant($"File size: {fileSize:#,###} bytes")); - _stream.Position = _commentPosition + 150; - WriteRaw(Invariant($"Pages: {document.Pages.Count:#}")); // No thousands separator here. - _stream.Position = _commentPosition + 200; - WriteRaw(Invariant($"Objects: {document.IrefTable.Count:#,###}")); + + var title = _document.Info.Title; + title = (title.Length switch + { + > 46 => "'" + title[..43] + "...'", + > 0 => "'" + title + "'", + _ => "«not set»" + new String(' ', 39) + } + " ")[..48]; + WriteRaw(Invariant($"Title {title}")); + + // Always write the real creation date here, i.e. the time on the producing operating system. + _stream.Position = _commentPosition + offset; + WriteRaw(Invariant($"Creation date: ·· {now:yyyy-MM-dd HH:mm:sszzz}")); + + _stream.Position = _commentPosition + 2 * offset; + WriteRaw(Invariant($"Creation time: ·· {duration.TotalSeconds:0.0##} seconds")); + + _stream.Position = _commentPosition + 3 * offset; + WriteRaw(Invariant($"File size ······· {fileSize:#,###} bytes")); + + _stream.Position = _commentPosition + 4 * offset; + WriteRaw(Invariant($"Pages ··········· {document.Pages.Count:#}")); // No thousands separator here. + + _stream.Position = _commentPosition + 5 * offset; + WriteRaw(Invariant($"Ind. objects ···· {document.IrefTable.Count:#,###}")); + + _stream.Position = _commentPosition + 6 * offset; + WriteRaw(Invariant($"PDF/A ··········· {document.GetPdfAManager().Format.Name}")); + + _stream.Position = _commentPosition + 7 * offset; + WriteRaw(Invariant($"Embedded files ·· {document.GetEmbeddedFilesManager().FileCount}")); + + _stream.Position = _commentPosition + 8 * offset; + WriteRaw(Invariant($"Signature type ·· {document.GetSigningManager().SignatureType}")); + + var sm = document.GetSecurityManager(); + + _stream.Position = _commentPosition + 9 * offset; + WriteRaw(Invariant($"Encryption type · {sm.EncryptionType}")); + + _stream.Position = _commentPosition + 10 * offset; + WriteRaw(Invariant($"User password ··· {sm.UserPassword}")); + + _stream.Position = _commentPosition + 11 * offset; + WriteRaw(Invariant($"Owner password ·· {sm.OwnerPassword}")); + + //“” „“ ’ ‘’ ‚‘ »« ›‹ – + //· × ² ³ ½ € † … + //✔ ✘ ↯ ± − × ÷ ⋅ √ ≠ ≤ ≥ ≡ + //® © ← ↑ → ↓ ↔ ↕ ∅ + //✔⇒ } } @@ -743,10 +692,22 @@ public void WriteEof(PdfDocument document, SizeType startxref) if (showType) { - if (!String.IsNullOrEmpty(comment)) - result = Invariant($"% {value.GetType().Name} ({value.GetType().FullName}) -- {comment}\n"); + if (type == typeof(PdfArray) || type == typeof(PdfDictionary)) + { + if (!String.IsNullOrEmpty(comment)) + result = Invariant($"% {value.GetType().Name} -- {comment}\n"); + else + result = $"% {value.GetType().Name}\n"; + } else - result = $"% {value.GetType().Name} ({value.GetType().FullName})\n"; + { + var name = type.Name; + var fullName = type.FullName; + if (!String.IsNullOrEmpty(comment)) + result = Invariant($"% {value.GetType().Name} ({value.GetType().FullName}) -- {comment}\n"); + else + result = $"% {value.GetType().Name} ({value.GetType().FullName})\n"; + } } else { @@ -773,52 +734,70 @@ internal int Indent /// /// Increases indent level. /// - void IncreaseIndent() - { - _writeIndent += _indent; - } + void IncreaseIndent() => _writeIndent += _indent; /// /// Decreases indent level. /// - void DecreaseIndent() - { - _writeIndent -= _indent; - } + void DecreaseIndent() => _writeIndent -= _indent; /// /// Gets an indent string of current indent. /// string IndentBlanks => new(' ', _writeIndent); - void WriteIndent() - { - WriteRaw(IndentBlanks); - } + void WriteIndent() => WriteRaw(IndentBlanks); - void WriteSeparator(CharCat cat /*, char ch = '\0'*/) + /// + /// Writes a space depending on the category of the last character. + /// + /// + void WriteSeparator(CharCat cat) { - switch (_lastCat) + if (IsCompactLayout) { - case CharCat.NewLine: - if (Layout == PdfWriterLayout.Verbose) - WriteIndent(); - break; - - case CharCat.Delimiter: - break; + // Write a blank only if two characters are neighbors. + switch (_lastCat) + { + case CharCat.NewLine: + case CharCat.Delimiter: + break; - case CharCat.Character: - if (Layout == PdfWriterLayout.Verbose) - { - _stream.WriteByte((byte)' '); - } - else - { + case CharCat.Character: if (cat == CharCat.Character) - _stream.WriteByte((byte)' '); - } - break; + { + //_stream.WriteByte((byte)' '); + //_lastCat = CharCat.Delimiter; + WriteRaw(' '); + } + break; + } + } + else + { + switch (_lastCat) + { + case CharCat.NewLine: + if (IsIndentedLayout) + WriteIndent(); + break; + + case CharCat.Delimiter: + // Case e.g. “[0 […]0]” should be “[0 […] 0]”. And yes, that’s very nerdy to fix such details. + if (_lastChar is ']' or '>' or ')' or '}') // '}' may occur in PostScript functions. + WriteRaw(' '); + break; + + case CharCat.Character: + if (IsStandardLayout) + WriteRaw(' '); + else + { + if (cat == CharCat.Character) + WriteRaw(' '); + } + break; + } } } @@ -839,25 +818,48 @@ static CharCat GetCategory(char ch) enum CharCat { + /// + /// A LF character. + /// NewLine, + + /// + /// A regular character as used in keywords ('true', 'false', 'null', or 'R') or a number used + /// in an indirect reference or literals. + /// Character, + + /// + /// A PDF delimiter character ('(', ',')', '<', '>', '[', ']', '{', '}', '/') + /// Delimiter, } + + /// + /// Character Category of the last character written. + /// CharCat _lastCat; + /// + /// Last character of the last character written. + /// Needed for optimal verbose layout. + /// + char _lastChar; + /// /// Gets the underlying stream. /// internal Stream Stream => _stream; - Stream _stream; - readonly PdfDocument _document; + Stream _stream = pdfStream ?? throw new ArgumentNullException(nameof(pdfStream)); + readonly PdfDocument _document = document ?? throw new ArgumentNullException(nameof(document)); - internal PdfStandardSecurityHandler? EffectiveSecurityHandler { get; set; } + internal PdfStandardSecurityHandler? EffectiveSecurityHandler { get; set; } = effectiveSecurityHandler; - class StackItem(PdfObject value) + class StackItem(PdfObject value, int childItemCount) { public readonly PdfObject Object = value; + public readonly int ChildItemCount = childItemCount; public bool HasStream; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfDocumentOpenMode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfDocumentOpenMode.cs index 03f4cd94..d4149e57 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfDocumentOpenMode.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfDocumentOpenMode.cs @@ -17,7 +17,7 @@ public enum PdfDocumentOpenMode /// /// The PDF stream is opened for importing pages from it. A document opened in this mode cannot - /// be modified. + /// be modified, but you can extract pages from it. /// Import, diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs index ba0b69c3..77cc6a6c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterLayout.cs @@ -27,8 +27,8 @@ public enum PdfWriterLayout /// /// The PDF stream is indented to reflect the nesting levels of the objects and contains additional - /// information about the PDFsharp objects. Furthermore, content streams are not deflated. This - /// is useful for debugging purposes only and increases the size of the file significantly. + /// information about the type of PDFsharp objects. Furthermore, content streams are not deflated. + /// This is useful for debugging purposes only and increases the size of the file significantly. /// Verbose, } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterOptions.cs index 312e2fbd..aa765dcd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/enums/PdfWriterOptions.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; - namespace PdfSharp.Pdf.IO { /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/AnsiEncoding.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/AnsiEncoding.cs index 8cba2f9a..eec4f06b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/AnsiEncoding.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/AnsiEncoding.cs @@ -1,305 +1,314 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. +// Moved to PdfSharp.System +////// PDFsharp - A .NET library for processing PDF +////// See the LICENSE file in the solution root for more information. -using System.Text; -using PdfSharp.Fonts.Internal; +////using System.Text; -namespace PdfSharp.Pdf.Internal -{ - /// - /// An encoder use for PDF WinAnsi encoding. - /// It is by design not to use CodePagesEncodingProvider.Instance.GetEncoding(1252). - /// However, AnsiEncoding is equivalent to Windows-1252 (CP-1252), - /// see https://en.wikipedia.org/wiki/Windows-1252 - /// - public sealed class AnsiEncoding : Encoding - { - /// - /// Gets the byte count. - /// - public override int GetByteCount(char[] chars, int index, int count) => count; +////namespace PdfSharp.Pdf.Internal +////{ +//// /// +//// /// An encoder use for PDF WinAnsi encoding. +//// /// It is by design not to use CodePagesEncodingProvider.Instance.GetEncoding(1252). +//// /// However, AnsiEncoding is equivalent to Windows-1252 (CP-1252), +//// /// see https://en.wikipedia.org/wiki/Windows-1252 +//// /// +//// public sealed class AnsiEncoding : Encoding +//// { +//// /// +//// /// Gets the byte count. +//// /// +//// public override int GetByteCount(char[] chars, int index, int count) => count; - /// - /// Gets the bytes. - /// - public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) - { - int count = charCount; - for (; charCount > 0; byteIndex++, charIndex++, charCount--) - { - var ch = chars[charIndex]; - bytes[byteIndex] = (byte)UnicodeToAnsi(ch/*, ch*/); - } - return count; - } +//// /// +//// /// Gets the bytes. +//// /// +//// public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) +//// { +//// int count = charCount; +//// for (; charCount > 0; byteIndex++, charIndex++, charCount--) +//// { +//// var ch = chars[charIndex]; +//// bytes[byteIndex] = (byte)UnicodeToAnsi(ch/*, ch*/); +//// } +//// return count; +//// } - /// - /// Gets the character count. - /// - public override int GetCharCount(byte[] bytes, int index, int count) => count; +//// /// +//// /// Gets the character count. +//// /// +//// public override int GetCharCount(byte[] bytes, int index, int count) => count; - /// - /// Gets the chars. - /// - public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) - { - for (int idx = byteCount; idx > 0; byteIndex++, charIndex++, idx--) - chars[charIndex] = AnsiToUnicode[bytes[byteIndex]]; - return byteCount; - } +//// /// +//// /// Gets the chars. +//// /// +//// public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) +//// { +//// for (int idx = byteCount; idx > 0; byteIndex++, charIndex++, idx--) +//// chars[charIndex] = AnsiToUnicode[bytes[byteIndex]]; +//// return byteCount; +//// } - /// - /// When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters. - /// - /// The number of characters to encode. - /// - /// The maximum number of bytes produced by encoding the specified number of characters. - /// - public override int GetMaxByteCount(int charCount) => charCount; +//// /// +//// /// When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters. +//// /// +//// /// The number of characters to encode. +//// /// +//// /// The maximum number of bytes produced by encoding the specified number of characters. +//// /// +//// public override int GetMaxByteCount(int charCount) => charCount; - /// - /// When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes. - /// - /// The number of bytes to decode. - /// - /// The maximum number of characters produced by decoding the specified number of bytes. - /// - public override int GetMaxCharCount(int byteCount) => byteCount; +//// /// +//// /// When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes. +//// /// +//// /// The number of bytes to decode. +//// /// +//// /// The maximum number of characters produced by decoding the specified number of bytes. +//// /// +//// public override int GetMaxCharCount(int byteCount) => byteCount; - /// - /// Indicates whether the specified Unicode BMP character is available in the ANSI code page 1252. - /// - public static bool IsAnsi(char ch) - { - if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') - return true; +//// /// +//// /// Indicates whether the specified Unicode BMP character is available in the ANSI code page 1252. +//// /// +//// public static bool IsAnsi(char ch) +//// { +//// if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') +//// return true; - return ch switch - { - '\u20AC' => true, - '\u0081' => false, - '\u201A' => true, - '\u0192' => true, - '\u201E' => true, - '\u2026' => true, - '\u2020' => true, - '\u2021' => true, - '\u02C6' => true, - '\u2030' => true, - '\u0160' => true, - '\u2039' => true, - '\u0152' => true, - '\u008D' => false, - '\u017D' => true, - '\u008F' => false, - '\u0090' => false, - '\u2018' => true, - '\u2019' => true, - '\u201C' => true, - '\u201D' => true, - '\u2022' => true, - '\u2013' => true, - '\u2014' => true, - '\u02DC' => true, - '\u2122' => true, - '\u0161' => true, - '\u203A' => true, - '\u0153' => true, - '\u009D' => false, - '\u017E' => true, - '\u0178' => true, - _ => false - }; - } +//// return ch switch +//// { +//// '\u20AC' => true, +//// '\u0081' => false, +//// '\u201A' => true, +//// '\u0192' => true, +//// '\u201E' => true, +//// '\u2026' => true, +//// '\u2020' => true, +//// '\u2021' => true, +//// '\u02C6' => true, +//// '\u2030' => true, +//// '\u0160' => true, +//// '\u2039' => true, +//// '\u0152' => true, +//// '\u008D' => false, +//// '\u017D' => true, +//// '\u008F' => false, +//// '\u0090' => false, +//// '\u2018' => true, +//// '\u2019' => true, +//// '\u201C' => true, +//// '\u201D' => true, +//// '\u2022' => true, +//// '\u2013' => true, +//// '\u2014' => true, +//// '\u02DC' => true, +//// '\u2122' => true, +//// '\u0161' => true, +//// '\u203A' => true, +//// '\u0153' => true, +//// '\u009D' => false, +//// '\u017E' => true, +//// '\u0178' => true, +//// _ => false +//// }; +//// } - /// - /// Indicates whether the specified string is available in the ANSI code page 1252. - /// - public static bool IsAnsi(string s) - { - var length = s.Length; - for (int idx = 0; idx < length; idx++) - { - char ch = s[idx]; +//// /// +//// /// Indicates whether the specified string is available in the ANSI code page 1252. +//// /// +//// public static bool IsAnsi(string s) +//// { +//// var length = s.Length; +//// for (int idx = 0; idx < length; idx++) +//// { +//// char ch = s[idx]; - if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') - continue; +//// if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') +//// continue; - if (ch switch - { - '\u20AC' => true, - '\u0081' => false, // undefined - '\u201A' => true, - '\u0192' => true, - '\u201E' => true, - '\u2026' => true, - '\u2020' => true, - '\u2021' => true, - '\u02C6' => true, - '\u2030' => true, - '\u0160' => true, - '\u2039' => true, - '\u0152' => true, - '\u008D' => false, // undefined - '\u017D' => true, - '\u008F' => false, // undefined - '\u0090' => false, // undefined - '\u2018' => true, - '\u2019' => true, - '\u201C' => true, - '\u201D' => true, - '\u2022' => true, - '\u2013' => true, - '\u2014' => true, - '\u02DC' => true, - '\u2122' => true, - '\u0161' => true, - '\u203A' => true, - '\u0153' => true, - '\u009D' => false, // undefined - '\u017E' => true, - '\u0178' => true, - _ => false - } is false) - return false; - } - return true; - } +//// if (ch switch +//// { +//// '\u20AC' => true, +//// '\u0081' => false, // undefined +//// '\u201A' => true, +//// '\u0192' => true, +//// '\u201E' => true, +//// '\u2026' => true, +//// '\u2020' => true, +//// '\u2021' => true, +//// '\u02C6' => true, +//// '\u2030' => true, +//// '\u0160' => true, +//// '\u2039' => true, +//// '\u0152' => true, +//// '\u008D' => false, // undefined +//// '\u017D' => true, +//// '\u008F' => false, // undefined +//// '\u0090' => false, // undefined +//// '\u2018' => true, +//// '\u2019' => true, +//// '\u201C' => true, +//// '\u201D' => true, +//// '\u2022' => true, +//// '\u2013' => true, +//// '\u2014' => true, +//// '\u02DC' => true, +//// '\u2122' => true, +//// '\u0161' => true, +//// '\u203A' => true, +//// '\u0153' => true, +//// '\u009D' => false, // undefined +//// '\u017E' => true, +//// '\u0178' => true, +//// _ => false +//// } is false) +//// return false; +//// } +//// return true; +//// } - /// - /// Indicates whether all code points in the specified array are available in the ANSI code page 1252. - /// - public static bool IsAnsi(int[] codePoints) - { - var length = codePoints.Length; - for (int idx = 0; idx < length; idx++) - { - int ch = codePoints[idx]; +//// /// +//// /// Indicates whether all code points in the specified array are available in the ANSI code page 1252. +//// /// +//// public static bool IsAnsi(int[] codePoints) +//// { +//// var length = codePoints.Length; +//// for (int idx = 0; idx < length; idx++) +//// { +//// int ch = codePoints[idx]; - if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') - continue; +//// if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') +//// continue; - // There are 6 values between 128 and 255 that are not part of the original ANSI character set. - // U+00AD was later added for the soft hyphen. The remaining 5 undefined values (see below) are - // no valid ANSI characters. All of them are C1 control characters (from U+0080 to U+009F) in - // Unicode. Therefore, we return false here. - if (ch switch - { - '\u20AC' => true, - '\u0081' => false, // undefined - '\u201A' => true, - '\u0192' => true, - '\u201E' => true, - '\u2026' => true, - '\u2020' => true, - '\u2021' => true, - '\u02C6' => true, - '\u2030' => true, - '\u0160' => true, - '\u2039' => true, - '\u0152' => true, - '\u008D' => false, // undefined - '\u017D' => true, - '\u008F' => false, // undefined - '\u0090' => false, // undefined - '\u2018' => true, - '\u2019' => true, - '\u201C' => true, - '\u201D' => true, - '\u2022' => true, - '\u2013' => true, - '\u2014' => true, - '\u02DC' => true, - '\u2122' => true, - '\u0161' => true, - '\u203A' => true, - '\u0153' => true, - '\u009D' => false, // undefined - '\u017E' => true, - '\u0178' => true, - _ => false - } is false) - return false; - } - return true; - } +//// // There are 6 values between 128 and 255 that are not part of the original ANSI character set. +//// // U+00AD was later added for the soft hyphen. The remaining 5 undefined values (see below) are +//// // no valid ANSI characters. All of them are C1 control characters (from U+0080 to U+009F) in +//// // Unicode. Therefore, we return false here. +//// if (ch switch +//// { +//// '\u20AC' => true, +//// '\u0081' => false, // undefined +//// '\u201A' => true, +//// '\u0192' => true, +//// '\u201E' => true, +//// '\u2026' => true, +//// '\u2020' => true, +//// '\u2021' => true, +//// '\u02C6' => true, +//// '\u2030' => true, +//// '\u0160' => true, +//// '\u2039' => true, +//// '\u0152' => true, +//// '\u008D' => false, // undefined +//// '\u017D' => true, +//// '\u008F' => false, // undefined +//// '\u0090' => false, // undefined +//// '\u2018' => true, +//// '\u2019' => true, +//// '\u201C' => true, +//// '\u201D' => true, +//// '\u2022' => true, +//// '\u2013' => true, +//// '\u2014' => true, +//// '\u02DC' => true, +//// '\u2122' => true, +//// '\u0161' => true, +//// '\u203A' => true, +//// '\u0153' => true, +//// '\u009D' => false, // undefined +//// '\u017E' => true, +//// '\u0178' => true, +//// _ => false +//// } is false) +//// return false; +//// } +//// return true; +//// } - /// - /// Maps Unicode to ANSI (CP-1252). - /// Return an ANSI code in a char or the value of parameter nonAnsi - /// if Unicode value has no ANSI counterpart. - /// - public static char UnicodeToAnsi(char ch, char nonAnsi = '\u003F') - { - if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') - return ch; +//// /// +//// /// Maps Unicode to ANSI (CP-1252). +//// /// Return an ANSI code in a char or the value of parameter nonAnsi +//// /// if Unicode value has no ANSI counterpart. +//// /// +//// public static char UnicodeToAnsi(char ch, char nonAnsi = '\u003F') +//// { +//// if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') +//// return ch; - // Unicode code points from U-0080 to U-009F are no - // valid ANSI characters in a PDF file. - // But the 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to - // themselves. This is the same as .NET handles them. - return ch switch - { - '\u20AC' => '\u0080', - '\u0081' => '\u0081', // undefined, but in ANSI range. - '\u201A' => '\u0082', - '\u0192' => '\u0083', - '\u201E' => '\u0084', - '\u2026' => '\u0085', - '\u2020' => '\u0086', - '\u2021' => '\u0087', - '\u02C6' => '\u0088', - '\u2030' => '\u0089', - '\u0160' => '\u008A', - '\u2039' => '\u008B', - '\u0152' => '\u008C', - '\u008D' => '\u008D', // undefined, but in ANSI range. - '\u017D' => '\u008E', - '\u008F' => '\u008F', // undefined, but in ANSI range. - '\u0090' => '\u0090', // undefined, but in ANSI range. - '\u2018' => '\u0091', - '\u2019' => '\u0092', - '\u201C' => '\u0093', - '\u201D' => '\u0094', - '\u2022' => '\u0095', - '\u2013' => '\u0096', - '\u2014' => '\u0097', - '\u02DC' => '\u0098', - '\u2122' => '\u0099', - '\u0161' => '\u009A', - '\u203A' => '\u009B', - '\u0153' => '\u009C', - '\u009D' => '\u009D', // undefined, but in ANSI range. - '\u017E' => '\u009E', - '\u0178' => '\u009F', - _ => nonAnsi - }; - } +//// // Unicode code points from U-0080 to U-009F are no +//// // valid ANSI characters in a PDF file. +//// // But the 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to +//// // themselves. This is the same as .NET handles them. +//// return ch switch +//// { +//// '\u20AC' => '\u0080', +//// '\u0081' => '\u0081', // undefined, but in ANSI range. +//// '\u201A' => '\u0082', +//// '\u0192' => '\u0083', +//// '\u201E' => '\u0084', +//// '\u2026' => '\u0085', +//// '\u2020' => '\u0086', +//// '\u2021' => '\u0087', +//// '\u02C6' => '\u0088', +//// '\u2030' => '\u0089', +//// '\u0160' => '\u008A', +//// '\u2039' => '\u008B', +//// '\u0152' => '\u008C', +//// '\u008D' => '\u008D', // undefined, but in ANSI range. +//// '\u017D' => '\u008E', +//// '\u008F' => '\u008F', // undefined, but in ANSI range. +//// '\u0090' => '\u0090', // undefined, but in ANSI range. +//// '\u2018' => '\u0091', +//// '\u2019' => '\u0092', +//// '\u201C' => '\u0093', +//// '\u201D' => '\u0094', +//// '\u2022' => '\u0095', +//// '\u2013' => '\u0096', +//// '\u2014' => '\u0097', +//// '\u02DC' => '\u0098', +//// '\u2122' => '\u0099', +//// '\u0161' => '\u009A', +//// '\u203A' => '\u009B', +//// '\u0153' => '\u009C', +//// '\u009D' => '\u009D', // undefined, but in ANSI range. +//// '\u017E' => '\u009E', +//// '\u0178' => '\u009F', +//// _ => nonAnsi +//// }; +//// } - /// - /// Maps WinAnsi to Unicode characters. - /// The 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to - /// the C1 control code. This is the same as .NET handles them. - /// - static readonly char[] AnsiToUnicode = - [ - // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F - /* 00 */ '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F', - /* 10 */ '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', - /* 20 */ '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', - /* 30 */ '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F', - /* 40 */ '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F', - /* 50 */ '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F', - /* 60 */ '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', - /* 70 */ '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u007F', - /* 80 */ '\u20AC', '\u0081', '\u201A', '\u0192', '\u201E', '\u2026', '\u2020', '\u2021', '\u02C6', '\u2030', '\u0160', '\u2039', '\u0152', '\u008D', '\u017D', '\u008F', - /* 90 */ '\u0090', '\u2018', '\u2019', '\u201C', '\u201D', '\u2022', '\u2013', '\u2014', '\u02DC', '\u2122', '\u0161', '\u203A', '\u0153', '\u009D', '\u017E', '\u0178', - /* A0 */ '\u00A0', '\u00A1', '\u00A2', '\u00A3', '\u00A4', '\u00A5', '\u00A6', '\u00A7', '\u00A8', '\u00A9', '\u00AA', '\u00AB', '\u00AC', '\u00AD', '\u00AE', '\u00AF', - /* B0 */ '\u00B0', '\u00B1', '\u00B2', '\u00B3', '\u00B4', '\u00B5', '\u00B6', '\u00B7', '\u00B8', '\u00B9', '\u00BA', '\u00BB', '\u00BC', '\u00BD', '\u00BE', '\u00BF', - /* C0 */ '\u00C0', '\u00C1', '\u00C2', '\u00C3', '\u00C4', '\u00C5', '\u00C6', '\u00C7', '\u00C8', '\u00C9', '\u00CA', '\u00CB', '\u00CC', '\u00CD', '\u00CE', '\u00CF', - /* D0 */ '\u00D0', '\u00D1', '\u00D2', '\u00D3', '\u00D4', '\u00D5', '\u00D6', '\u00D7', '\u00D8', '\u00D9', '\u00DA', '\u00DB', '\u00DC', '\u00DD', '\u00DE', '\u00DF', - /* E0 */ '\u00E0', '\u00E1', '\u00E2', '\u00E3', '\u00E4', '\u00E5', '\u00E6', '\u00E7', '\u00E8', '\u00E9', '\u00EA', '\u00EB', '\u00EC', '\u00ED', '\u00EE', '\u00EF', - /* F0 */ '\u00F0', '\u00F1', '\u00F2', '\u00F3', '\u00F4', '\u00F5', '\u00F6', '\u00F7', '\u00F8', '\u00F9', '\u00FA', '\u00FB', '\u00FC', '\u00FD', '\u00FE', '\u00FF' - ]; - } -} +//// //// HA_CK for WpfPdfRenderTarget. +//// //public static char AnsiToUnicodeHack(char ch) +//// //{ +//// // if (ch < 256) +//// // return AnsiToUnicode[ch]; +//// // return (char)unchecked((ushort)(short)-1); +//// //} + + +//// /// +//// /// Maps WinAnsi to Unicode characters. +//// /// The 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to +//// /// the C1 control code. This is the same as .NET handles them. +//// /// +//// static readonly char[] AnsiToUnicode = +//// [ +//// // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F +//// /* 00 */ '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F', +//// /* 10 */ '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', +//// /* 20 */ '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', +//// /* 30 */ '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F', +//// /* 40 */ '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F', +//// /* 50 */ '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F', +//// /* 60 */ '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', +//// /* 70 */ '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u007F', +//// /* 80 */ '\u20AC', '\u0081', '\u201A', '\u0192', '\u201E', '\u2026', '\u2020', '\u2021', '\u02C6', '\u2030', '\u0160', '\u2039', '\u0152', '\u008D', '\u017D', '\u008F', +//// /* 90 */ '\u0090', '\u2018', '\u2019', '\u201C', '\u201D', '\u2022', '\u2013', '\u2014', '\u02DC', '\u2122', '\u0161', '\u203A', '\u0153', '\u009D', '\u017E', '\u0178', +//// /* A0 */ '\u00A0', '\u00A1', '\u00A2', '\u00A3', '\u00A4', '\u00A5', '\u00A6', '\u00A7', '\u00A8', '\u00A9', '\u00AA', '\u00AB', '\u00AC', '\u00AD', '\u00AE', '\u00AF', +//// /* B0 */ '\u00B0', '\u00B1', '\u00B2', '\u00B3', '\u00B4', '\u00B5', '\u00B6', '\u00B7', '\u00B8', '\u00B9', '\u00BA', '\u00BB', '\u00BC', '\u00BD', '\u00BE', '\u00BF', +//// /* C0 */ '\u00C0', '\u00C1', '\u00C2', '\u00C3', '\u00C4', '\u00C5', '\u00C6', '\u00C7', '\u00C8', '\u00C9', '\u00CA', '\u00CB', '\u00CC', '\u00CD', '\u00CE', '\u00CF', +//// /* D0 */ '\u00D0', '\u00D1', '\u00D2', '\u00D3', '\u00D4', '\u00D5', '\u00D6', '\u00D7', '\u00D8', '\u00D9', '\u00DA', '\u00DB', '\u00DC', '\u00DD', '\u00DE', '\u00DF', +//// /* E0 */ '\u00E0', '\u00E1', '\u00E2', '\u00E3', '\u00E4', '\u00E5', '\u00E6', '\u00E7', '\u00E8', '\u00E9', '\u00EA', '\u00EB', '\u00EC', '\u00ED', '\u00EE', '\u00EF', +//// /* F0 */ '\u00F0', '\u00F1', '\u00F2', '\u00F3', '\u00F4', '\u00F5', '\u00F6', '\u00F7', '\u00F8', '\u00F9', '\u00FA', '\u00FB', '\u00FC', '\u00FD', '\u00FE', '\u00FF' +//// ]; +//// } +////} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/GlobalObjectTable.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/GlobalObjectTable.cs index 34c27a1a..4f6d3838 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/GlobalObjectTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/GlobalObjectTable.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.Collections.Generic; - namespace PdfSharp.Pdf.Internal { #if true_ diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugItem.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugItem.cs new file mode 100644 index 00000000..bee7ee86 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugItem.cs @@ -0,0 +1,40 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.IO; + +namespace PdfSharp.Pdf.Internal +{ + // TODO Move to new file. + /// + /// Represents a PDF item literally as a string. + /// This class exists for debugging and testing purposes only, + /// e.g. to create a non-existing reference for parser testing. + /// It is not needed to create valid PDF documents. + /// + public sealed class PdfDebugItem : PdfPrimitive + { + // Note that this class does the same as PdfLiteral, but it has a different intended purpose. + + /// + /// Creates a new PdfDebugItem from a string. + /// + public PdfDebugItem(string value) + { + _value = value; + } + + internal override void WriteObject(PdfWriter writer) + { + var literal = new PdfLiteral(_value); + writer.Write(literal); + } + + /// + /// Returns a string representation of the object. + /// + public override String ToString() => _value; + + readonly string _value; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugObject.cs new file mode 100644 index 00000000..463619e7 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfDebugObject.cs @@ -0,0 +1,40 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.IO; + +namespace PdfSharp.Pdf.Internal // #FOLDER Pdf.Internal +{ + // TODO Move to new file. + /// + /// Represents a PDF item literally as a string. + /// This class exists for debugging and testing purposes only, + /// e.g. to create a non-existing reference for parser testing. + /// It is not needed to create valid PDF documents. + /// + public sealed class PdfDebugObject : PdfPrimitiveObject + { + /// + /// Creates a new PdfRawItem from a raw string. + /// + public PdfDebugObject(PdfDocument doc, string value) + : base(doc, true) + { + _value = value; + } + + internal override void WriteObject(PdfWriter writer) + { + writer.WriteBeginObject(this); + writer.Write(new PdfLiteral(_value)); + writer.WriteEndObject(); + } + + /// + /// Returns a string representation of the object. + /// + public override String ToString() => _value; + + readonly string _value; + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfEncoders.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfEncoders.cs index 31ad4021..7a6245b4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfEncoders.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfEncoders.cs @@ -3,6 +3,7 @@ using System.Text; using Microsoft.Extensions.Logging; +using PdfSharp.Internal; using PdfSharp.Drawing; using PdfSharp.Logging; using PdfSharp.Pdf.Security; @@ -17,26 +18,39 @@ namespace PdfSharp.Pdf.Internal public static class PdfEncoders { /// - /// Gets the raw encoding. + /// Gets the PDFsharp specific encoder RawEncoding. + /// Ray encoding allows wo work with string instead of byte array. + /// A raw encoded string is equivalent to a byte array of the same length + /// where each sting character represents one byte. + /// Therefore, each character of a raw string has a value less than 256. /// public static Encoding RawEncoding => _rawEncoding ??= new RawEncoding(); static Encoding? _rawEncoding; + internal static Encoding ByteStringEncoding => _rawEncoding ??= new RawEncoding(); // new name?? + /// /// Gets the raw Unicode encoding. /// public static Encoding RawUnicodeEncoding => _rawUnicodeEncoding ??= new RawUnicodeEncoding(); static Encoding? _rawUnicodeEncoding; + /// + /// Gets the Windows 1252 (ANSI) encoding. + /// + public static Encoding AnsiEncoding + { + get => field ??= new AnsiEncoding(); + } + /// /// Gets the Windows 1252 (ANSI) encoding. /// public static Encoding WinAnsiEncoding { - // We consistently use our own WinAnsiEncoding implementation in PDFsharp. - get => _winAnsiEncoding ??= new AnsiEncoding(); + //[Obsolete("Use AnsiEncoding")] + get => AnsiEncoding; } - static Encoding? _winAnsiEncoding; /// /// Gets the PDF DocEncoding encoding. @@ -244,7 +258,14 @@ public static string ToHexStringLiteral(byte[]? bytes, bool unicode, bool prefix public static byte[] FormatStringLiteral(byte[]? bytes, bool unicode, bool prefix, bool hex, PdfStandardSecurityHandler? effectiveSecurityHandler) { if (bytes == null || bytes.Length == 0) - return hex ? [(byte)'<', (byte)'>'] : [(byte)'(', (byte)')']; + { + byte[] result = hex ? [(byte)'<', (byte)'>'] : [(byte)'(', (byte)')']; + + byte[] test = hex ? "<>"u8.ToArray() : "()"u8.ToArray(); + Debug.Assert(result == test); + + return result; + } Debug.Assert(!unicode || bytes.Length % 2 == 0, "Odd number of bytes in Unicode string."); @@ -365,37 +386,37 @@ public static byte[] FormatStringLiteral(byte[]? bytes, bool unicode, bool prefi for (var idx = 0; idx < count; idx += 2) { pdf.Append($"{bytes[idx]:X2}{bytes[idx + 1]:X2}"); - if (idx != 0 && (idx % 48) == 0) - pdf.Append('\n'); + //if (idx != 0 && (idx % 48) == 0) + // pdf.Append('\n'); } pdf.Append('>'); } return RawEncoding.GetBytes(pdf.ToString()); } - /// - /// Converts WinAnsi to DocEncode characters. Incomplete, just maps € and some other characters. - /// - static byte[] docencode_______ = - [ - // TODO_OLD: ??? Note: See table in DocEncoding.cs. - 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, - 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, - 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, - 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, - 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, - 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, - 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, - 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, - 0xA0, 0x7F, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, - 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x8A, 0x8C, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, - 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, - 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, - 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, - 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, - 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, - 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF - ]; + ///// + ///// Converts WinAnsi to DocEncode characters. Incomplete, just maps € and some other characters. + ///// + //static byte[] docencode_______ = + //[ + // // TODO_OLD: ??? See table in DocEncoding.cs. + // 0x00, 0x01, 0x02, 0x03, 0x04, 0x05, 0x06, 0x07, 0x08, 0x09, 0x0A, 0x0B, 0x0C, 0x0D, 0x0E, 0x0F, + // 0x10, 0x11, 0x12, 0x13, 0x14, 0x15, 0x16, 0x17, 0x18, 0x19, 0x1A, 0x1B, 0x1C, 0x1D, 0x1E, 0x1F, + // 0x20, 0x21, 0x22, 0x23, 0x24, 0x25, 0x26, 0x27, 0x28, 0x29, 0x2A, 0x2B, 0x2C, 0x2D, 0x2E, 0x2F, + // 0x30, 0x31, 0x32, 0x33, 0x34, 0x35, 0x36, 0x37, 0x38, 0x39, 0x3A, 0x3B, 0x3C, 0x3D, 0x3E, 0x3F, + // 0x40, 0x41, 0x42, 0x43, 0x44, 0x45, 0x46, 0x47, 0x48, 0x49, 0x4A, 0x4B, 0x4C, 0x4D, 0x4E, 0x4F, + // 0x50, 0x51, 0x52, 0x53, 0x54, 0x55, 0x56, 0x57, 0x58, 0x59, 0x5A, 0x5B, 0x5C, 0x5D, 0x5E, 0x5F, + // 0x60, 0x61, 0x62, 0x63, 0x64, 0x65, 0x66, 0x67, 0x68, 0x69, 0x6A, 0x6B, 0x6C, 0x6D, 0x6E, 0x6F, + // 0x70, 0x71, 0x72, 0x73, 0x74, 0x75, 0x76, 0x77, 0x78, 0x79, 0x7A, 0x7B, 0x7C, 0x7D, 0x7E, 0x7F, + // 0xA0, 0x7F, 0x82, 0x83, 0x84, 0x85, 0x86, 0x87, 0x88, 0x89, 0x8A, 0x8B, 0x8C, 0x8D, 0x8E, 0x8F, + // 0x90, 0x91, 0x92, 0x93, 0x94, 0x95, 0x8A, 0x8C, 0x98, 0x99, 0x9A, 0x9B, 0x9C, 0x9D, 0x9E, 0x9F, + // 0xA0, 0xA1, 0xA2, 0xA3, 0xA4, 0xA5, 0xA6, 0xA7, 0xA8, 0xA9, 0xAA, 0xAB, 0xAC, 0xAD, 0xAE, 0xAF, + // 0xB0, 0xB1, 0xB2, 0xB3, 0xB4, 0xB5, 0xB6, 0xB7, 0xB8, 0xB9, 0xBA, 0xBB, 0xBC, 0xBD, 0xBE, 0xBF, + // 0xC0, 0xC1, 0xC2, 0xC3, 0xC4, 0xC5, 0xC6, 0xC7, 0xC8, 0xC9, 0xCA, 0xCB, 0xCC, 0xCD, 0xCE, 0xCF, + // 0xD0, 0xD1, 0xD2, 0xD3, 0xD4, 0xD5, 0xD6, 0xD7, 0xD8, 0xD9, 0xDA, 0xDB, 0xDC, 0xDD, 0xDE, 0xDF, + // 0xE0, 0xE1, 0xE2, 0xE3, 0xE4, 0xE5, 0xE6, 0xE7, 0xE8, 0xE9, 0xEA, 0xEB, 0xEC, 0xED, 0xEE, 0xEF, + // 0xF0, 0xF1, 0xF2, 0xF3, 0xF4, 0xF5, 0xF6, 0xF7, 0xF8, 0xF9, 0xFA, 0xFB, 0xFC, 0xFD, 0xFE, 0xFF + //]; //public static string DocEncode(string text, bool unicode)//, PdfStandardSecurityHandler securityHandler) //{ @@ -581,6 +602,7 @@ public static string ToString(XColor color, PdfColorMode colorMode) /// /// Converts an XMatrix into a string with up to 4 decimal digits and a decimal point. /// + [Obsolete] public static string ToString(XMatrix matrix) { const string format = Config.SignificantDecimalPlaces4; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfRawDictionary.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfRawDictionary.cs new file mode 100644 index 00000000..23e91b72 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/PdfRawDictionary.cs @@ -0,0 +1,126 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.IO; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Internal +{ + // TODO implementation + /// + /// Represents a PDF dictionary object xxxxxxxxxxXXXXXXXXXXXXXXXX TODO make ready + /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + public sealed class PdfDebugDictionary : PdfDictionary + { + /// + /// Initializes a new instance of the class. + /// + public PdfDebugDictionary() + { } + + ///// + ///// Initializes a new instance of the class. + ///// + //public PdfDebugDictionary(int value) + //{ } + + /// + /// Initializes a new instance of the class. + /// + public PdfDebugDictionary(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfDebugDictionary(PdfDictionary dict) + : base(dict) + { } + + /// + /// Gets or sets the StreamLength of the stream. + /// Value is not checked and can be different from the actual length of the stream. + /// + public int StreamLength { get; set; } // TODO Enforce setting + + /// + /// Writes the integer literal. + /// + internal override void WriteObject(PdfWriter writer) + { + writer.WriteBeginObject(this); + + if (Stream is not null && writer.EffectiveSecurityHandler != null) + { + // Encryption could change the size of the stream. + // Encrypt the bytes before writing the dictionary to get and update the actual size. + var bytes = (byte[])Stream.Value.Clone(); + writer.EffectiveSecurityHandler.EncryptStream(ref bytes, this); + Stream.Value = bytes; + } + Elements[PdfStream.Keys.Length] = new PdfInteger(StreamLength); + var keys = Elements.KeyNames; + +#if DEBUG + // Sort keys for debugging purposes. Comparing PDF files with for example programs like + // Araxis Merge is easier with sorted keys. + if (writer.Layout == PdfWriterLayout.Verbose) + { + var list = new List(keys); + list.Sort(PdfName.Comparer); + list.CopyTo(keys, 0); + } +#endif + + foreach (var key in keys) + WriteDictionaryElement(writer, key); + if (Stream != null) + WriteDictionaryStream(writer); + writer.WriteEndObject(); + } + + /// + /// Gets the DebuggerDisplayAttribute text. + /// + //string DebuggerDisplay => Invariant($"PdfDebugDictionary({ObjectID.DebuggerDisplay},[{Elements.Count}])={_elements?.DebuggerDisplay}"); + string DebuggerDisplay => "TODO"; + } +} + +namespace PdfSharp.Pdf.Internal +{ + // TODO implementation + + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + public sealed class PdfDebugArray : PdfArray + { + /// + /// Initializes a new instance of the class. + /// + public PdfDebugArray() + { } + + ///// + ///// Initializes a new instance of the class. + ///// + //public PdfDebugDictionary(int value) + //{ } + + /// + /// Initializes a new instance of the class. + /// + public PdfDebugArray(PdfDocument document) + : base(document) + { } + + /// + /// Gets the DebuggerDisplayAttribute text. + /// + //string DebuggerDisplay => Invariant($"PdfDebugDictionary({ObjectID.DebuggerDisplay},[{Elements.Count}])={_elements?.DebuggerDisplay}"); + string DebuggerDisplay => "TODO"; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/RawEncoding.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/RawEncoding.cs index d1a4769b..c0b0fa72 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/RawEncoding.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/RawEncoding.cs @@ -3,16 +3,26 @@ using System.Text; +// v7.0.0 clean up + namespace PdfSharp.Pdf.Internal { + // TODO Merge documentation of RawEncoding and mention that C# raw strings is something completely different. /// /// An encoder for raw strings. The raw encoding is simply the identity relation between - /// characters and bytes. PDFsharp internally works with raw encoded strings instead of + /// characters and bytes. PDFsharp internally often works with raw encoded strings instead of /// byte arrays because strings are much more handy than byte arrays. /// /// /// Raw encoded strings represent an array of bytes. Therefore, a character greater than - /// 255 is not valid in a raw encoded string. + /// 255 is not valid in a raw encoded string.
+ /// + /// A byte array with n bytes is decoded to a string with n characters, where each character has + /// the value of the corresponding byte. I.e. each character has a value from 0 to 255. Its high + /// order byte is always 0
+ /// A raw string with n characters is encoded to a byte array with n bytes, where each byte is + /// the corresponding character cast to byte. If the high order byte of a character has an illegal + /// value other than 0 this value is ignored. ///
public sealed class RawEncoding : Encoding { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/ThreadLocalStorage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/ThreadLocalStorage.cs index 2cac4ff9..371da79d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/ThreadLocalStorage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Internal/ThreadLocalStorage.cs @@ -88,7 +88,7 @@ public void DetachDocument(PdfDocument.DocumentHandle handle) /// /// Maps path to document handle. /// - private readonly Dictionary _importedDocuments = []; + readonly Dictionary _importedDocuments = []; } } #endif \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/DocumentMetadataInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/DocumentMetadataInfo.cs new file mode 100644 index 00000000..27192989 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/DocumentMetadataInfo.cs @@ -0,0 +1,120 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.PdfA; + +// v7.0.0 Ready + +namespace PdfSharp.Pdf.Metadata +{ + /// + /// A collection of essential metadata from the PDF document. + /// All data is formatted as string and can directly be used in the XMP metadata stream. + /// + public class DocumentMetadataInfo // #Metadata + { + internal DocumentMetadataInfo(PdfDocument document) + { + var info = document.Info; + + Title = info.Title; + Author = info.Author; + Subject = info.Subject; + Keywords = info.Keywords; + Creator = info.Creator; + Producer = info.Producer; + +#if DEBUG && true_ + // Check from where Acrobat takes the text. + Title = "(xmp) " + info.Title; + Author = "(xmp) " + info.Author; + Subject = "(xmp) " + info.Subject; + Keywords = "(xmp) " + info.Keywords; + Creator = "(xmp) " + info.Creator; + Producer = "(xmp) " + info.Producer; +#endif + CreationDate = MetadataManager.ToXmpDateString(info.CreationDate); + ModificationDate = MetadataManager.ToXmpDateString(info.ModificationDate); + DocumentID = document.Internals.FirstDocumentID; + InstanceID = document.Internals.SecondDocumentID; + var pdfaManager = PdfAManager.ForDocument(document); + PdfAFormat = pdfaManager.IsPdfADocument ? pdfaManager.Format : null; + } + + // Reference 2.0: Table 349 — Entries in the document information dictionary / Page 716 + // The names of the properties comes from the document information dictionary. + // See summary for the entries in the document’s metadata. + + /// + /// Gets the document’s title.
+ /// Corresponds to “dc:title” entry in the document’s metadata stream. + ///
+ public string Title { get; } + + /// + /// Gets the name of the person who created the document.
+ /// Corresponds to “dc:creator” entry in the document’s metadata stream. + ///
+ public string Author { get; } + + /// + /// Gets the name of the subject of the document.
+ /// Corresponds to “dc:description” entry in the document’s metadata stream. + ///
+ public string Subject { get; } + + /// + /// Gets keywords associated with the document.
+ /// Corresponds to “pdf:Keywords” entry in the document’s metadata stream. + ///
+ public string Keywords { get; } + + /// + /// If the document was converted to PDF from another format, gets the name of the PDF processor + /// that created the original document from which it was converted.
+ /// Corresponds to “xmp:CreatorTool” entry in the document’s metadata stream. + ///
+ public string Creator { get; } + + /// + /// If the document was converted to PDF from another format, gets the name of the PDF processor + /// that converted it to PDF.
+ /// Corresponds to “pdf:Producer” entry in the document’s metadata stream. + ///
+ public string Producer { get; } + + /// + /// Gets the creation date of the document. + /// Can be an empty string.
+ /// Corresponds to “xmp:CreateDate” entry in the document’s metadata stream. + ///
+ public string CreationDate { get; } + + /// + /// Gets the modification date of the document. + /// Can be an empty string.
+ /// Corresponds to “xmp:ModifyDate” entry in the document’s metadata stream. + ///
+ public string ModificationDate { get; } + + // Information from trailer. + + /// + /// Gets the document ID of the document.
+ /// Corresponds to “xapMM:DocumentID” entry in the document’s metadata stream. + ///
+ public string DocumentID { get; } + + /// + /// Gets the instance ID of the document.
+ /// Corresponds to “xmpMM:InstanceID” entry in the document’s metadata stream. + ///
+ public string InstanceID { get; } + + /// + /// Gets the PDF/A part and conformance level, or null, + /// if the document is not a PDF/A document. + /// + public PdfAFormat? PdfAFormat { get; } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/MetadataManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/MetadataManager.cs new file mode 100644 index 00000000..c1c98a3b --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/MetadataManager.cs @@ -0,0 +1,159 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Events; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Internal; + +// v7.0.0 Review + +namespace PdfSharp.Pdf.Metadata +{ + /// + /// The MetadataManager provides functionality for easier handling of PDF metadata. + /// + public class MetadataManager : ManagerBase + { + /// + /// Initializes a new instance of this class. + /// + MetadataManager(PdfDocument document) : base(document) + { } + + /// + /// Gets the PDF metadata object from the catalog, or null, + /// if no such object exists. + /// + public PdfMetadata? GetMetadata() + { + return Document.Catalog.GetMetadata(); + } + + /// + /// Gets or sets the strategy how PDFsharp treads document metadata. + /// + /// DocumentMetadataStrategy was set more than once. + public DocumentMetadataStrategy Strategy + { + get => _strategy ?? DocumentMetadataStrategy.NoMetadata; + set + { + if (_strategy == value) + return; + + if (_strategy != null) + { + // Update of strategy set by PdfAManager is allowed. + if (!(_strategy == DocumentMetadataStrategy.AutoGenerate && value == DocumentMetadataStrategy.UserGenerated)) + throw new InvalidOperationException("DocumentMetadataStrategy can only be set once."); + } + _strategy = value; + } + } + DocumentMetadataStrategy? _strategy; + + /// + /// Adjusts the document metadata strategy for PDF/A generation if a PDF/A format is set. + /// + internal void AdjustStrategyForPdfA() + { + _strategy = _strategy switch + { + DocumentMetadataStrategy.KeepExisting => Fail(), + DocumentMetadataStrategy.NoMetadata => Fail(), + DocumentMetadataStrategy.AutoGenerate => DocumentMetadataStrategy.AutoGenerate, + DocumentMetadataStrategy.UserGenerated => DocumentMetadataStrategy.UserGenerated, + null or DocumentMetadataStrategy.NoMetadata => _strategy = DocumentMetadataStrategy.AutoGenerate, + _ => throw new ArgumentOutOfRangeException(nameof(_strategy)) + }; + return; + + //[DoesNotReturn] + DocumentMetadataStrategy Fail() + { + throw new InvalidOperationException( + $"With a document metadata strategy of '{_strategy}' the document cannot be a PDF/A document."); + } + } + + /// + /// + /// + /// + public void SetMetadata(byte[] xml) + { + Document.Catalog.GetOrCreateMetadata().SetMetadata(xml); + } + + /// + /// Gets the metadata info PDFsharp has collected for the current document. + /// This includes PdfDocumentInformation and the document IDs. + /// + public DocumentMetadataInfo GetMetadataInfo() + { + var md = new DocumentMetadataInfo(Document); + return md; + } + + internal void PrepareForSave() + { + var catalog = Document.Catalog; + + // If HasMetadata is true, but XML stream is empty, then remove the inconsistent Metadata. + // We always remove empty Metadata here, regardless of the strategy. + //if (catalog.HasMetadata && !(catalog.GetMetadata(false)!.Stream?.Value?.Length > 0)) + if (!(catalog.GetMetadata()?.Stream?.Value.Length > 0)) + catalog.Elements.Remove(PdfCatalog.Keys.Metadata); + + if (_strategy is null or DocumentMetadataStrategy.KeepExisting) + return; + + if (_strategy == DocumentMetadataStrategy.NoMetadata) + { + // Remove Metadata. + catalog.Elements.Remove(PdfCatalog.Keys.Metadata); + return; + } + + var metadata = catalog.GetOrCreateMetadata(); + if (_strategy == DocumentMetadataStrategy.AutoGenerate) + { + // Create default Metadata if it is currently empty. + if (!(catalog.GetMetadata()?.Stream?.Value.Length > 0)) // TODO PdfDictionary.StreamLength would be helpful. + { + var xml = metadata.CreateDefaultMetadata(); + metadata.SetMetadata(xml); + } + return; + } + + if (_strategy == DocumentMetadataStrategy.UserGenerated) + { + var metadataInfo = new DocumentMetadataInfo(Document); + var metadataArgs = new DocumentMetadataEventArgs(Document) + { + Metadata = metadata, + Info = metadataInfo + }; + Document.Events.OnCreateDocumentMetadata(Document, metadataArgs); + } + } + + /// + /// Converts a DateTimeOffset in an XMP metadata compatible string. + /// + internal static string ToXmpDateString(DateTimeOffset? dateTimeOffset) + { + var result = dateTimeOffset == null + ? "" + : Invariant($"{dateTimeOffset.Value:yyyy-MM-ddTHH:mm:ssK}"); + return result; + } + + /// + /// Gets or creates the MetadataManager for the specified document. + /// + public static MetadataManager ForDocument(PdfDocument document) + => document.MetadataManager ??= new(document); + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/enums/DocumentMetadataStrategy.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/enums/DocumentMetadataStrategy.cs new file mode 100644 index 00000000..3571e2dd --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Metadata/enums/DocumentMetadataStrategy.cs @@ -0,0 +1,32 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.Metadata +{ + /// + /// Defines how PDFsharp treads document metadata. + /// + public enum DocumentMetadataStrategy + { + /// + /// Do not generate metadata. + /// Keep the existing metadata of an imported PDF file. + /// + KeepExisting, + + /// + /// Do not generate metadata. + /// + NoMetadata, + + /// + /// Let PDFsharp generate a new metadata object. + /// + AutoGenerate, + + /// + /// Let the user code generate the metadata. + /// + UserGenerated, + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs similarity index 90% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs index f69b1cc8..16a6ec39 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs @@ -3,24 +3,24 @@ using PdfSharp.Pdf.Advanced; -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents the base class for all interactive field dictionaries. /// - public abstract class PdfAcroField : PdfDictionary + public abstract class PdfFormField : PdfDictionary // TODO: FormsCleanUp: Remove namespace and classes. { /// - /// Initializes a new instance of PdfAcroField. + /// Initializes a new instance of PdfFormField. /// - internal PdfAcroField(PdfDocument document) + internal PdfFormField(PdfDocument document) : base(document) { } /// - /// Initializes a new instance of the class. Used for type transformation. + /// Initializes a new instance of the class. Used for type transformation. /// - protected PdfAcroField(PdfDictionary dict) + protected PdfFormField(PdfDictionary dict) : base(dict) { } @@ -39,11 +39,11 @@ public string Name /// /// Gets the field flags of this instance. /// - public PdfAcroFieldFlags Flags => (PdfAcroFieldFlags)Elements.GetInteger(Keys.Ff); + public PdfFormFieldFlags Flags => (PdfFormFieldFlags)Elements.GetInteger(Keys.Ff); - internal PdfAcroFieldFlags SetFlags + internal PdfFormFieldFlags SetFlags { - get => (PdfAcroFieldFlags)Elements.GetInteger(Keys.Ff); + get => (PdfFormFieldFlags)Elements.GetInteger(Keys.Ff); set => Elements.SetInteger(Keys.Ff, (int)value); } @@ -69,25 +69,25 @@ public virtual PdfItem? Value ///
public bool ReadOnly { - get => (Flags & PdfAcroFieldFlags.ReadOnly) != 0; + get => (Flags & PdfFormFieldFlags.ReadOnly) != 0; set { if (value) - SetFlags |= PdfAcroFieldFlags.ReadOnly; + SetFlags |= PdfFormFieldFlags.ReadOnly; else - SetFlags &= ~PdfAcroFieldFlags.ReadOnly; + SetFlags &= ~PdfFormFieldFlags.ReadOnly; } } /// /// Gets the field with the specified name. /// - public PdfAcroField? this[string name] => GetValue(name); + public PdfFormField? this[string name] => GetValue(name); /// /// Gets a child field by name. /// - protected virtual PdfAcroField? GetValue(string name) + protected virtual PdfFormField? GetValue(string name) { if (String.IsNullOrEmpty(name)) return this; @@ -127,7 +127,7 @@ public string[] GetDescendantNames() List names = []; if (HasKids) { - PdfAcroFieldCollection fields = Fields; + PdfFormFieldCollection fields = Fields; fields.GetDescendantNames(ref names, null); } List temp = []; @@ -218,7 +218,7 @@ internal virtual void GetDescendantNames(ref List names, string? partial { if (HasKids) { - PdfAcroFieldCollection fields = Fields; + PdfFormFieldCollection fields = Fields; string t = Elements.GetString(Keys.T); Debug.Assert(t != ""); if (t.Length > 0) @@ -247,26 +247,26 @@ internal virtual void GetDescendantNames(ref List names, string? partial /// /// Gets the collection of fields within this field. /// - public PdfAcroFieldCollection Fields + public PdfFormFieldCollection Fields { get { if (_fields == null) { var o = Elements.GetValue(Keys.Kids, VCF.CreateIndirect); - _fields = (PdfAcroFieldCollection?)o ?? NRT.ThrowOnNull(); + _fields = (PdfFormFieldCollection?)o ?? NRT.ThrowOnNull(); } return _fields; } } - PdfAcroFieldCollection? _fields; + PdfFormFieldCollection? _fields; /// /// Holds a collection of interactive fields. /// - public sealed class PdfAcroFieldCollection : PdfArray + public sealed class PdfFormFieldCollection : PdfArray { - internal PdfAcroFieldCollection(PdfArray array) + internal PdfFormFieldCollection(PdfArray array) : base(array) { } @@ -323,7 +323,7 @@ internal void GetDescendantNames(ref List names, string? partialName) /// If the actual type cannot be guessed by PDFsharp the function returns an instance /// of PdfGenericField. ///
- public PdfAcroField this[int index] + public PdfFormField this[int index] { get { @@ -331,7 +331,7 @@ public PdfAcroField this[int index] Debug.Assert(item is PdfReference); PdfDictionary? dict = ((PdfReference)item).Value as PdfDictionary; Debug.Assert(dict != null); - PdfAcroField? field = dict as PdfAcroField; + PdfFormField? field = dict as PdfFormField; if (field == null && dict != null!) { // Do type transformation @@ -345,9 +345,9 @@ public PdfAcroField this[int index] /// /// Gets the field with the specified name. /// - public PdfAcroField? this[string name] => GetValue(name); + public PdfFormField? this[string name] => GetValue(name); - internal PdfAcroField? GetValue(string name) + internal PdfFormField? GetValue(string name) { if (String.IsNullOrEmpty(name)) return null; @@ -371,17 +371,17 @@ public PdfAcroField this[int index] /// If the actual cannot be guessed by PDFsharp the function returns an instance /// of PdfGenericField. ///
- PdfAcroField CreateAcroField(PdfDictionary dict) + PdfFormField CreateAcroField(PdfDictionary dict) { string ft = dict.Elements.GetName(Keys.FT); - PdfAcroFieldFlags flags = (PdfAcroFieldFlags)dict.Elements.GetInteger(Keys.Ff); + PdfFormFieldFlags flags = (PdfFormFieldFlags)dict.Elements.GetInteger(Keys.Ff); switch (ft) { case "/Btn": - if ((flags & PdfAcroFieldFlags.Pushbutton) != 0) + if ((flags & PdfFormFieldFlags.Pushbutton) != 0) return new PdfPushButtonField(dict); - if ((flags & PdfAcroFieldFlags.Radio) != 0) + if ((flags & PdfFormFieldFlags.Radio) != 0) return new PdfRadioButtonField(dict); return new PdfCheckBoxField(dict); @@ -390,7 +390,7 @@ PdfAcroField CreateAcroField(PdfDictionary dict) return new PdfTextField(dict); case "/Ch": - if ((flags & PdfAcroFieldFlags.Combo) != 0) + if ((flags & PdfFormFieldFlags.Combo) != 0) return new PdfComboBoxField(dict); else return new PdfListBoxField(dict); @@ -440,7 +440,7 @@ public class Keys : KeysBase /// /// (Optional) An array of indirect references to the immediate children of this field. /// - [KeyInfo(KeyType.Array | KeyType.Optional, typeof(PdfAcroFieldCollection))] + [KeyInfo(KeyType.Array | KeyType.Optional, typeof(PdfFormFieldCollection))] public const string Kids = "/Kids"; /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroForm.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroForm.cs similarity index 81% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroForm.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroForm.cs index d1763a9d..d1097078 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfAcroForm.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroForm.cs @@ -1,43 +1,45 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents an interactive form (or AcroForm), a collection of fields for /// gathering information interactively from the user. /// - public sealed class PdfAcroForm : PdfDictionary + public sealed class PdfFormForm : PdfDictionary { /// /// Initializes a new instance of AcroForm. /// - internal PdfAcroForm(PdfDocument document) + internal PdfFormForm(PdfDocument document) : base(document) - { - _document = document; - } + { } - internal PdfAcroForm(PdfDictionary dictionary) + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfFormForm(PdfDictionary dictionary) : base(dictionary) { } /// /// Gets the fields collection of this form. /// - public PdfAcroField.PdfAcroFieldCollection Fields + public PdfFormField.PdfFormFieldCollection Fields { get { if (_fields == null) { var o = Elements.GetValue(Keys.Fields, VCF.CreateIndirect); - _fields = (PdfAcroField.PdfAcroFieldCollection?)o ?? NRT.ThrowOnNull(); + _fields = (PdfFormField.PdfFormFieldCollection?)o ?? NRT.ThrowOnNull(); } return _fields; } } - PdfAcroField.PdfAcroFieldCollection? _fields; + PdfFormField.PdfFormFieldCollection? _fields; /// /// Predefined keys of this dictionary. @@ -51,7 +53,7 @@ public sealed class Keys : KeysBase /// (Required) An array of references to the document’s root fields (those with /// no ancestors in the field hierarchy). /// - [KeyInfo(KeyType.Array | KeyType.Required, typeof(PdfAcroField.PdfAcroFieldCollection))] + [KeyInfo(KeyType.Array | KeyType.Required, typeof(PdfFormField.PdfFormFieldCollection))] public const string Fields = "/Fields"; /// @@ -100,17 +102,8 @@ public sealed class Keys : KeysBase /// /// Gets the KeysMeta for these keys. /// - internal static DictionaryMeta Meta - { - get - { - if (s_meta == null) - s_meta = CreateMeta(typeof(Keys)); - return s_meta; - } - } - - static DictionaryMeta? s_meta; + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; // ReSharper restore InconsistentNaming } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfButtonField.cs similarity index 91% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfButtonField.cs index fb19b509..9d83aa86 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfButtonField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfButtonField.cs @@ -6,12 +6,12 @@ using System.Diagnostics; using PdfSharp.Pdf.Annotations; -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents the base class for all button fields. /// - public abstract class PdfButtonField : PdfAcroField + public abstract class PdfButtonField : PdfFormField { /// /// Initializes a new instance of the class. @@ -51,7 +51,7 @@ protected string GetNonOffValue() internal override void GetDescendantNames(ref List names, string? partialName) { - string t = Elements.GetString(PdfAcroField.Keys.T); + string t = Elements.GetString(PdfFormField.Keys.T); if (t == "") t = "???"; Debug.Assert(t != ""); @@ -68,7 +68,7 @@ internal override void GetDescendantNames(ref List names, string? partia /// Predefined keys of this dictionary. /// The description comes from PDF 1.4 Reference. /// - public new class Keys : PdfAcroField.Keys + public new class Keys : PdfFormField.Keys { // Pushbuttons have no additional entries. } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfCheckBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfCheckBoxField.cs similarity index 95% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfCheckBoxField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfCheckBoxField.cs index 85980bb1..a9456df4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfCheckBoxField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfCheckBoxField.cs @@ -4,7 +4,7 @@ using PdfSharp.Pdf.Annotations; using PdfSharp.Pdf.Advanced; -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents the check box field. @@ -17,9 +17,13 @@ public sealed class PdfCheckBoxField : PdfButtonField internal PdfCheckBoxField(PdfDocument document) : base(document) { - _document = document; + //_document = document; TODO: Correct? } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfCheckBoxField(PdfDictionary dict) : base(dict) { } @@ -183,14 +187,14 @@ public bool Checked { if (!HasKids) //R080317 { - string value = Elements.GetString(PdfAcroField.Keys.V); + string value = Elements.GetString(PdfFormField.Keys.V); return value.Length != 0 && value != "/Off"; } else //R080317 { if (Fields.Elements.Items.Length == 2) { - string value = ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.GetString(PdfAcroField.Keys.V); + string value = ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.GetString(PdfFormField.Keys.V); bool bReturn = value.Length != 0 && value != "/Off" && value != "/Nein"; //R081114 (3Std.!!) auch auf Nein prüfen; //TODO_OLD woher kommt der Wert? return bReturn; } @@ -203,7 +207,7 @@ public bool Checked if (!HasKids) { string name = value ? GetNonOffValue() : "/Off"; - Elements.SetName(PdfAcroField.Keys.V, name); + Elements.SetName(PdfFormField.Keys.V, name); Elements.SetName(PdfAnnotation.Keys.AS, name); } else @@ -234,7 +238,7 @@ public bool Checked } if (name1.Length != 0) { - ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfAcroField.Keys.V, name1); + ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfFormField.Keys.V, name1); ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfAnnotation.Keys.AS, name1); } @@ -257,7 +261,7 @@ public bool Checked } if (name1.Length != 0) { - ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfAcroField.Keys.V, name1); + ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfFormField.Keys.V, name1); ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfAnnotation.Keys.AS, name1); } @@ -283,7 +287,7 @@ public bool Checked } if (name1.Length != 0) { - ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfAcroField.Keys.V, name1); + ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfFormField.Keys.V, name1); ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[1])).Value)).Elements.SetName(PdfAnnotation.Keys.AS, name1); } @@ -305,7 +309,7 @@ public bool Checked } if (name1.Length != 0) { - ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfAcroField.Keys.V, name1); + ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfFormField.Keys.V, name1); ((PdfDictionary)(((PdfReference)(Fields.Elements.Items[0])).Value)).Elements.SetName(PdfAnnotation.Keys.AS, name1); } } @@ -356,7 +360,6 @@ public string UncheckedName /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfChoiceField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfChoiceField.cs similarity index 97% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfChoiceField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfChoiceField.cs index dcb24c62..12df21e4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfChoiceField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfChoiceField.cs @@ -1,12 +1,12 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents the base class for all choice field dictionaries. /// - public abstract class PdfChoiceField : PdfAcroField + public abstract class PdfChoiceField : PdfFormField { /// /// Initializes a new instance of the class. @@ -96,7 +96,7 @@ protected string ValueInOptArray(int index) /// Predefined keys of this dictionary. /// The description comes from PDF 1.4 Reference. /// - public new class Keys : PdfAcroField.Keys + public new class Keys : PdfFormField.Keys { // ReSharper disable InconsistentNaming @@ -132,7 +132,6 @@ protected string ValueInOptArray(int index) /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; // ReSharper restore InconsistentNaming diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfComboBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfComboBoxField.cs similarity index 81% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfComboBoxField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfComboBoxField.cs index a9e4c250..05bfb149 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfComboBoxField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfComboBoxField.cs @@ -1,7 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents the combo box field. @@ -15,6 +15,10 @@ internal PdfComboBoxField(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfComboBoxField(PdfDictionary dict) : base(dict) { } @@ -26,7 +30,7 @@ public int SelectedIndex { get { - string value = Elements.GetString(PdfAcroField.Keys.V); + string value = Elements.GetString(PdfFormField.Keys.V); return IndexInOptArray(value); } set @@ -34,7 +38,7 @@ public int SelectedIndex if (value != -1) { string key = ValueInOptArray(value); - Elements.SetString(PdfAcroField.Keys.V, key); + Elements.SetString(PdfFormField.Keys.V, key); Elements.SetInteger("/I", value); } } @@ -45,14 +49,14 @@ public int SelectedIndex /// public override PdfItem? Value { - get => Elements[PdfAcroField.Keys.V]!; + get => Elements[PdfFormField.Keys.V]; set { if (ReadOnly) throw new InvalidOperationException("The field is read only."); if (value is PdfString or PdfName) { - Elements[PdfAcroField.Keys.V] = value; + Elements[PdfFormField.Keys.V] = value; SelectedIndex = SelectedIndex; if (SelectedIndex == -1) { @@ -75,7 +79,7 @@ public override PdfItem? Value /// Predefined keys of this dictionary. /// The description comes from PDF 1.4 Reference. /// - public new class Keys : PdfAcroField.Keys + public new class Keys : PdfFormField.Keys { // Combo boxes have no additional entries. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfGenericField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfGenericField.cs similarity index 72% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfGenericField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfGenericField.cs index 97501e84..f64b2dad 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfGenericField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfGenericField.cs @@ -1,12 +1,12 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents a generic field. Used for AcroForm dictionaries unknown to PDFsharp. /// - public sealed class PdfGenericField : PdfAcroField + public sealed class PdfGenericField : PdfFormField { /// /// Initializes a new instance of PdfGenericField. @@ -15,6 +15,10 @@ internal PdfGenericField(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfGenericField(PdfDictionary dict) : base(dict) { } @@ -23,10 +27,9 @@ internal PdfGenericField(PdfDictionary dict) /// Predefined keys of this dictionary. /// The description comes from PDF 1.4 Reference. /// - public new class Keys : PdfAcroField.Keys + public new class Keys : PdfFormField.Keys { internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfListBoxField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfListBoxField.cs similarity index 81% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfListBoxField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfListBoxField.cs index 789a2488..3dc0ce6d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfListBoxField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfListBoxField.cs @@ -1,7 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents the list box field. @@ -15,6 +15,10 @@ internal PdfListBoxField(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfListBoxField(PdfDictionary dict) : base(dict) { } @@ -40,12 +44,11 @@ public int SelectedIndex /// Predefined keys of this dictionary. /// The description comes from PDF 1.4 Reference. /// - public new class Keys : PdfAcroField.Keys + public new class Keys : PdfFormField.Keys { // List boxes have no additional entries. internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfPushButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfPushButtonField.cs similarity index 72% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfPushButtonField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfPushButtonField.cs index 0079fdc7..614fb9e5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfPushButtonField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfPushButtonField.cs @@ -1,7 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents the push button field. @@ -14,9 +14,13 @@ public sealed class PdfPushButtonField : PdfButtonField internal PdfPushButtonField(PdfDocument document) : base(document) { - _document = document; + //_document = document; TODO: Correct? } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfPushButtonField(PdfDictionary dict) : base(dict) { } @@ -25,10 +29,9 @@ internal PdfPushButtonField(PdfDictionary dict) /// Predefined keys of this dictionary. /// The description comes from PDF 1.4 Reference. /// - public new class Keys : PdfAcroField.Keys + public new class Keys : PdfFormField.Keys { internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfRadioButtonField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfRadioButtonField.cs similarity index 55% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfRadioButtonField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfRadioButtonField.cs index 85ab84a5..133016a9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfRadioButtonField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfRadioButtonField.cs @@ -1,7 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// /// Represents the radio button field. @@ -14,9 +14,13 @@ public sealed class PdfRadioButtonField : PdfButtonField internal PdfRadioButtonField(PdfDocument document) : base(document) { - _document = document; + //_document = document; TODO: Correct? } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfRadioButtonField(PdfDictionary dict) : base(dict) { } @@ -28,7 +32,7 @@ public int SelectedIndex { get { - string value = Elements.GetString(PdfAcroField.Keys.V); + string value = Elements.GetString(PdfFormField.Keys.V); return IndexInOptStrings(value); } set @@ -36,14 +40,14 @@ public int SelectedIndex var opt = Elements[Keys.Opt] as PdfArray; if (opt == null) - opt = Elements[PdfAcroField.Keys.Kids] as PdfArray; + opt = Elements[PdfFormField.Keys.Kids] as PdfArray; if (opt != null) { int count = opt.Elements.Count; if (value < 0 || value >= count) throw new ArgumentOutOfRangeException(nameof(value)); - Elements.SetName(PdfAcroField.Keys.V, opt.Elements[value].ToString() ?? NRT.ThrowOnNull()); + Elements.SetName(PdfFormField.Keys.V, opt.Elements[value].ToString() ?? NRT.ThrowOnNull()); } } } @@ -66,33 +70,32 @@ int IndexInOptStrings(string value) return -1; } - /// - /// Predefined keys of this dictionary. - /// The description comes from PDF 1.4 Reference. - /// - public new class Keys : PdfButtonField.Keys - { /// - /// (Optional; inheritable; PDF 1.4) An array of text strings to be used in - /// place of the V entries for the values of the widget annotations representing - /// the individual radio buttons. Each element in the array represents - /// the export value of the corresponding widget annotation in the - /// Kids array of the radio button field. + /// Predefined keys of this dictionary. + /// The description comes from PDF 1.4 Reference. /// - [KeyInfo(KeyType.Array | KeyType.Optional)] - public const string Opt = "/Opt"; + public new class Keys : PdfButtonField.Keys + { + /// + /// (Optional; inheritable; PDF 1.4) An array of text strings to be used in + /// place of the V entries for the values of the widget annotations representing + /// the individual radio buttons. Each element in the array represents + /// the export value of the corresponding widget annotation in the + /// Kids array of the radio button field. + /// + [KeyInfo(KeyType.Array | KeyType.Optional)] + public const string Opt = "/Opt"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + } /// - /// Gets the KeysMeta for these keys. + /// Gets the KeysMeta of this dictionary type. /// - internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - - static DictionaryMeta? _meta; - } - - /// - /// Gets the KeysMeta of this dictionary type. - /// - internal override DictionaryMeta Meta => Keys.Meta; + internal override DictionaryMeta Meta => Keys.Meta; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfSignatureField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfSignatureField.cs new file mode 100644 index 00000000..54e59063 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfSignatureField.cs @@ -0,0 +1,109 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.IO; +using PdfSharp.Drawing; +using PdfSharp.Pdf.Annotations; + +namespace PdfSharp.Pdf.OldAcroForms +{ + /// + /// Represents the signature field. + /// + public sealed class PdfSignatureField : PdfFormField + { + /// + /// Initializes a new instance of PdfSignatureField. + /// + internal PdfSignatureField(PdfDocument document) + : base(document) + { + CustomAppearanceHandler = null!; + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfSignatureField(PdfDictionary dict) + : base(dict) + { + CustomAppearanceHandler = null!; + } + + /// + /// Handler that creates the visual representation of the digital signature in PDF. + /// + public IAnnotationAppearanceHandler CustomAppearanceHandler { get; internal set; } + + /// + /// Creates the custom appearance form X object for the annotation that represents + /// this acro form text field. + /// + void RenderCustomAppearance() + { + var rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect); + if (rect == null) + return; + + var visible = rect.X1 + rect.X2 + rect.Y1 + rect.Y2 != 0; + if (!visible) + return; + + if (CustomAppearanceHandler == null) + throw new Exception("AppearanceHandler is not set."); + + var form = new XForm(Document, rect.Size); + var gfx = XGraphics.FromForm(form); + + CustomAppearanceHandler.DrawAppearance(gfx, rect.ToXRect()); + + form.DrawingFinished(); + + // Get existing or create new appearance dictionary + if (Elements[PdfAnnotation.Keys.AP] is not PdfDictionary ap) + { + ap = new PdfDictionary(Document); + Elements[PdfAnnotation.Keys.AP] = ap; + } + + // Set XRef to normal state + ap.Elements["/N"] = form.PdfForm.RequiredReference; + + // PdfRenderer can be null. + form.PdfRenderer?.Close(); + } + + internal override void PrepareForSave() + { + base.PrepareForSave(); + if (CustomAppearanceHandler != null!) + RenderCustomAppearance(); + } + + /// + /// Predefined keys of this dictionary. + /// The description comes from PDF 1.4 Reference. + /// + public new class Keys : PdfFormField.Keys + { + /// + /// (Optional) The type of PDF object that this dictionary describes; if present, + /// must be Sig for a signature dictionary. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Type = "/Type"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfTextField.cs similarity index 84% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfTextField.cs index cefef326..ab1cbf63 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfTextField.cs @@ -6,12 +6,12 @@ using PdfSharp.Pdf.Annotations; using PdfSharp.Pdf.Internal; -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms // TODO: remove _ { /// /// Represents the text field. /// - public sealed class PdfTextField : PdfAcroField + public sealed class PdfTextField : PdfFormField { /// /// Initializes a new instance of PdfTextField. @@ -20,6 +20,10 @@ internal PdfTextField(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfTextField(PdfDictionary dict) : base(dict) { } @@ -29,8 +33,8 @@ internal PdfTextField(PdfDictionary dict) /// public string Text { - get => Elements.GetString(PdfAcroField.Keys.V); - set { Elements.SetString(PdfAcroField.Keys.V, value); RenderAppearance(); } //HACK_OLD in PdfTextField + get => Elements.GetString(PdfFormField.Keys.V); + set { Elements.SetString(PdfFormField.Keys.V, value); RenderAppearance(); } //HACK_OLD in PdfTextField } /// @@ -63,13 +67,13 @@ public int MaxLength /// public bool MultiLine { - get => (Flags & PdfAcroFieldFlags.Multiline) != 0; + get => (Flags & PdfFormFieldFlags.Multiline) != 0; set { if (value) - SetFlags |= PdfAcroFieldFlags.Multiline; + SetFlags |= PdfFormFieldFlags.Multiline; else - SetFlags &= ~PdfAcroFieldFlags.Multiline; + SetFlags &= ~PdfFormFieldFlags.Multiline; } } @@ -78,13 +82,13 @@ public bool MultiLine /// public bool Password { - get => (Flags & PdfAcroFieldFlags.Password) != 0; + get => (Flags & PdfFormFieldFlags.Password) != 0; set { if (value) - SetFlags |= PdfAcroFieldFlags.Password; + SetFlags |= PdfFormFieldFlags.Password; else - SetFlags &= ~PdfAcroFieldFlags.Password; + SetFlags &= ~PdfFormFieldFlags.Password; } } @@ -97,14 +101,14 @@ void RenderAppearance() #if true_ PdfFormXObject xobj = new PdfFormXObject(Owner); Owner.Internals.AddObject(xobj); - xobj.Elements["/BBox"] = new PdfLiteral("[0 0 122.653 12.707]"); - xobj.Elements["/FormType"] = new PdfLiteral("1"); - xobj.Elements["/Matrix"] = new PdfLiteral("[1 0 0 1 0 0]"); - PdfDictionary res = new PdfDictionary(Owner); + xobj.Elements["/BBox"] = new Pdf/Literal("[0 0 122.653 12.707]"); + xobj.Elements["/FormType"] = new Pdf/Literal("1"); + xobj.Elements["/Matrix"] = new Pdf/Literal("[1 0 0 1 0 0]"); + PdfDictionary res = new Pdf/Dictionary(Owner); xobj.Elements["/Resources"] = res; - res.Elements["/Font"] = new PdfLiteral("<< /Helv 28 0 R >> /ProcSet [/PDF /Text]"); - xobj.Elements["/Subtype"] = new PdfLiteral("/Form"); - xobj.Elements["/Type"] = new PdfLiteral("/XObject"); + res.Elements["/Font"] = new Pdf/Literal("<< /Helv 28 0 R >> /ProcSet [/PDF /Text]"); + xobj.Elements["/Subtype"] = new Pdf/Literal("/Form"); + xobj.Elements["/Type"] = new Pdf/Literal("/XObject"); string s = "/Tx BMC " + '\n' + @@ -186,8 +190,8 @@ void RenderAppearance() //mdict.Stream.Value = stream; #else - var rect = Elements.GetRectangle(PdfAnnotation.Keys.Rect); - var form = new XForm(_document, rect.Size); + var rect = Elements.GetRequiredRectangle(PdfAnnotation.Keys.Rect); + var form = new XForm(Document, rect.Size); var gfx = XGraphics.FromForm(form); if (BackColor != XColor.Empty) @@ -204,18 +208,18 @@ void RenderAppearance() // Get existing or create new appearance dictionary. if (Elements[PdfAnnotation.Keys.AP] is not PdfDictionary ap) { - ap = new PdfDictionary(_document); + ap = new PdfDictionary(Document); Elements[PdfAnnotation.Keys.AP] = ap; } // Set XRef to normal state. - ap.Elements["/N"] = form.PdfForm.Reference; + ap.Elements["/N"] = form.PdfForm.RequiredReference; form.PdfRenderer.Close(); var xobj = form.PdfForm; string s = xobj.Stream?.ToString() ?? ""; - // Thank you Adobe: Without putting the content in 'EMC brackets' + // Without putting the content in 'EMC brackets' // the text is not rendered by PDF Reader 9 or higher. s = "/Tx BMC\n" + s + "\nEMC"; if (xobj.Stream != null) @@ -233,7 +237,7 @@ internal override void PrepareForSave() /// Predefined keys of this dictionary. /// The description comes from PDF 1.4 Reference. ///
- public new class Keys : PdfAcroField.Keys + public new class Keys : PdfFormField.Keys { /// /// (Optional; inheritable) The maximum length of the field’s text, in characters. @@ -245,7 +249,6 @@ internal override void PrepareForSave() /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/enums/PdfAcroFieldFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/enums/PdfAcroFieldFlags.cs similarity index 98% rename from src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/enums/PdfAcroFieldFlags.cs rename to src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/enums/PdfAcroFieldFlags.cs index 22f39e51..b23e6ddd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/enums/PdfAcroFieldFlags.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/enums/PdfAcroFieldFlags.cs @@ -1,13 +1,13 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Pdf.AcroForms +namespace PdfSharp.Pdf.OldAcroForms { /// - /// Specifies the flags of AcroForm fields. + /// Specifies the flags of interactive form (AcroForm) fields. /// [Flags] - public enum PdfAcroFieldFlags + public enum PdfFormFieldFlags // Table 227 { // ----- Common to all fields ----------------------------------------------------------------- diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormat.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormat.cs new file mode 100644 index 00000000..94156bc1 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormat.cs @@ -0,0 +1,55 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.PdfA +{ + /// + /// Represents the part and conformance level of a PDF/A document. + /// + [DebuggerDisplay("({this.Name})")] + public readonly struct PdfAFormat + { + internal PdfAFormat(int part, char conformanceLevel) + { + Part = part; + ConformanceLevel = Char.ToUpper(conformanceLevel); + } + + /// + /// Gets the part number of PDF/A format. + /// E.g. 1, 2, 3, or 4. + /// + public int Part { get; } + + /// + /// Gets the level of conformance of PDF/A format. + /// E.g. B, A, or U. + /// + public char ConformanceLevel { get; } + + /// + /// Gets readable name of PDF/A format without the prefix PDF/A. + /// E.g. '3B'. + /// + public string Name => Invariant($"{Part}{ConformanceLevel}"); + + /// + /// Gets readable name of PDF/A format including the prefix PDF/A. + /// E.g. 'PDF/A-3B'. + /// + public string FullName => Invariant($"PDF/A-{Part}{ConformanceLevel}"); + + /// + /// Returns a string representation of the object. + /// + public override String ToString() => Part != 0 ? FullName : "(n/a)"; + + /// + /// Forces to create a format not defined in the class PdfAFormats. + /// May be useful for testing only. + /// + /// Part number of PDF/A format + /// Level of conformance of PDF/A format + public static PdfAFormat ForcePdfAFormat(int part, char conformanceLevel) => new(part, conformanceLevel); + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormats.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormats.cs new file mode 100644 index 00000000..cba55baa --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAFormats.cs @@ -0,0 +1,74 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// ReSharper disable InconsistentNaming + +namespace PdfSharp.Pdf.PdfA +{ + /// + /// Defines all well-defined PDF/A formats. + /// See https://en.wikipedia.org/wiki/PDF/A + /// + public static class PdfAFormats + { + /// + /// PDF/A-1b – Level B (Basic) conformance. + /// + public static PdfAFormat PdfA_1a { get; } = new(1, 'a'); + + /// + /// PDF/A-1a – Level A (Accessible) conformance. + /// + public static PdfAFormat PdfA_1b { get; } = new(1, 'b'); + + /// + /// PDF/A-2a – Level A (Accessible) conformance. + /// + public static PdfAFormat PdfA_2a { get; } = new(2, 'a'); + + /// + /// PDF/A-2b – Level B (Basic) conformance. + /// + public static PdfAFormat PdfA_2b { get; } = new(2, 'b'); + + /// + /// PDF/A-2u – Level U (Unicode) conformance. + /// + public static PdfAFormat PdfA_2u { get; } = new(2, 'b'); + + /// + /// PDF/A-3a – Level A (Accessible) conformance. + /// + public static PdfAFormat PdfA_3a { get; } = new(3, 'a'); + + /// + /// PDF/A-3b – Level B (Basic) conformance. + /// + public static PdfAFormat PdfA_3b { get; } = new(3, 'b'); + + /// + /// PDF/A-3u – Level U (Unicode) conformance. + /// + public static PdfAFormat PdfA_3u { get; } = new(3, 'u'); + + /// + /// PDF/A-4a – Level A (Accessible) conformance. + /// + public static PdfAFormat PdfA_4a { get; } = new(4, 'a'); + + /// + /// PDF/A-4b – Level B (Basic) conformance. + /// + public static PdfAFormat PdfA_4b { get; } = new(4, 'b'); + + /// + /// PDF/A-4f – Level F (Engineering) conformance. + /// + public static PdfAFormat PdfA_4e { get; } = new(4, 'e'); + + /// + /// PDF/A-4e – Level E (arbitrary Files) conformance. + /// + public static PdfAFormat PdfA_4f { get; } = new(4, 'f'); + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAManager.cs new file mode 100644 index 00000000..e241660e --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.PdfA/PdfAManager.cs @@ -0,0 +1,102 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 review + +using PdfSharp.Pdf.Internal; + +namespace PdfSharp.Pdf.PdfA +{ + /// + /// The PdfAManager bundles PDF/A specific functionality of a PDF document. + /// + public class PdfAManager : ManagerBase + { + // TODOs: + // Handle PDF/A for read documents. + // What else is missing? + + /// + /// Initialized a new instance of this class for the specified document + /// + /// + PdfAManager(PdfDocument document) : base(document) + { + Initialize(); + } + + /// + /// Sets PDF/A part and conformance level for the document. + /// After once set it cannot be changed. + /// + public void SetFormat(PdfAFormat format) + { + if (_formatSet) + { + throw new InvalidOperationException( + "PDF/A format of this document is already set and cannot be changed anymore."); + } + + if (!Document.IsImported) + { + // PDF document is newly created. + if (Document.PageCount > 0) + { + throw new InvalidOperationException( + "For a newly created document PDF/A settings must be done before any PDF content is created " + + "to ensure that all pages are PDF/A compatible."); + } + } + // PDF/a requires document metadata. + Document.GetMetadataManager().AdjustStrategyForPdfA(); + + Part = format.Part; + Level = format.ConformanceLevel; + _formatSet = true; + } + bool _formatSet; + + /// + /// Return true if the PDF document is a PDF/A document, false otherwise. + /// + public bool IsPdfADocument => Part != 0; + + /// + /// Gets the part number of a PDF/A document, or 0, if the document is + /// not a PDF/A document. + /// + public int Part { get; private set; } + + /// + /// Gets the conformance level of a PDF/A document, or ' ' (blank), if the document is + /// not a PDF/A document. + /// + public char Level { get; private set; } = ' '; + + /// + /// Gets the current PdfAFormat of a PDF/A document. + /// + public PdfAFormat Format => new(Part, Level); + + /// + /// Gets or creates the PdfAManager for the specified document. + /// + public static PdfAManager ForDocument(PdfDocument document) + => document.PdfAManager ??= new(document); + + void Initialize() + { + Document.EnsureNotDisposed(); + if (!_initialized) + { + _initialized = true; + Document.EnsureNotYetSaved(); + if (Document.IsImported) + { + // TODO: Get PDF/A conformance + } + } + } + bool _initialized; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs index 937b0dc1..c809387c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV1To4.cs @@ -2,6 +2,9 @@ // See the LICENSE file in the solution root for more information. using System.Security.Cryptography; +using Microsoft.Extensions.Logging; +using PdfSharp.Internal; +using PdfSharp.Logging; using PdfSharp.Pdf.Internal; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.IO; @@ -23,7 +26,7 @@ public void SetEncryptionToV1() { Initialize(1, 40); SecurityHandler.RemoveCryptFilters(); - SecurityHandler._document.SetRequiredVersion(12); + SecurityHandler.Document.SetRequiredVersion(12); } /// @@ -34,7 +37,7 @@ public void SetEncryptionToV2(int length = 40) { Initialize(2, length); SecurityHandler.RemoveCryptFilters(); - SecurityHandler._document.SetRequiredVersion(14); + SecurityHandler.Document.SetRequiredVersion(14); } /// @@ -46,7 +49,7 @@ public void SetEncryptionToV4UsingRC4(bool encryptMetadata = true) { Initialize(4, 128, encryptMetadata); SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToRC4ForV4(); - SecurityHandler._document.SetRequiredVersion(15); + SecurityHandler.Document.SetRequiredVersion(15); } /// @@ -57,7 +60,7 @@ public void SetEncryptionToV4UsingAES(bool encryptMetadata = true) { Initialize(4, 128, encryptMetadata); SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToAESForV4(); - SecurityHandler._document.SetRequiredVersion(16); + SecurityHandler.Document.SetRequiredVersion(16); } /// @@ -69,13 +72,20 @@ public override void InitializeFromLoadedSecurityHandler() RevisionValue = SecurityHandler.Elements.GetInteger(PdfStandardSecurityHandler.Keys.R); LengthValue = SecurityHandler.Elements.ContainsKey(PdfSecurityHandler.Keys.Length) ? SecurityHandler.Elements.GetInteger(PdfSecurityHandler.Keys.Length) : GetDefaultLength(); - EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. + // TODO GetBoolean(PdfStandardSecurityHandler.Keys.EncryptMetadata, true) + EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. // #US373 CheckVersionAndLength(VersionValue, LengthValue); var calculatedRevision = CalculateRevisionValue(VersionValue.Value, SecurityHandler.GetCorrectedPermissionsValue()); + if (calculatedRevision != RevisionValue) - Debug.Assert(calculatedRevision == RevisionValue); + { +#pragma warning disable CA2254 + PdfSharpLogHost.Logger.LogError($"Security Handler: Found revision {RevisionValue}, but expected revision {calculatedRevision}."); +#pragma warning restore CA2254 + // Don’t correct the revision value, as the wrong revision value was probably used to calculate the password hashes written in the file. + } } void Initialize(int versionValue, int lengthValue, bool encryptMetadata = true) @@ -146,7 +156,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne { SecurityHandler.Elements.SetBoolean(PdfStandardSecurityHandler.Keys.EncryptMetadata, EncryptMetadata); - var metadata = SecurityHandler._document.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata); + var metadata = SecurityHandler.Document.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata, VCF.Create); if (metadata is null) throw TH.InvalidOperationException_CouldNotFindMetadataDictionary(); @@ -160,7 +170,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne Debug.Assert(ownerPassword.Length > 0, "Empty owner password."); - var documentId = PdfEncoders.RawEncoding.GetBytes(SecurityHandler._document.Internals.FirstDocumentID); + var documentId = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Document.Internals.FirstDocumentID); var (userValueArray, ownerValueArray) = ComputeOwnerAndUserValues(userPassword, ownerPassword, documentId, permissionsValue); @@ -363,7 +373,7 @@ void ComputeAndStoreEncryptionKey(byte[] documentId, byte[] paddedPassword, byte // The encryption and MD5 hashing key length (in bytes) shall depend on the Length value (in bits). keyLength = LengthValue / 8; -#if !NET6_0_OR_GREATER +#if !NET8_0_OR_GREATER // We have to call Initialize here for .NET 4.6.2. // .NET 6/8 include Initialize in "_md5.TransformFinalBlock()". _md5.Initialize(); @@ -406,7 +416,7 @@ public override PasswordValidity ValidatePassword(string inputPassword) var userValue = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Elements.GetString(PdfStandardSecurityHandler.Keys.U)); var ownerValue = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Elements.GetString(PdfStandardSecurityHandler.Keys.O)); var permissionsValue = SecurityHandler.Elements.GetUnsignedInteger(PdfStandardSecurityHandler.Keys.P); - EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. + EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. // #US373 var documentId = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Owner.Internals.FirstDocumentID); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs index f387c904..bef96ca8 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security.Encryption/PdfEncryptionV5.cs @@ -2,13 +2,12 @@ // See the LICENSE file in the solution root for more information. using System.Numerics; -using PdfSharp.Pdf.IO; -using System.Security.Cryptography; using System.Text; +using System.Security.Cryptography; +using PdfSharp.Pdf.IO; using PdfSharp.Internal; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Internal; -using System.Runtime.CompilerServices; namespace PdfSharp.Pdf.Security.Encryption { @@ -19,7 +18,7 @@ class PdfEncryptionV5 : PdfEncryptionBase { public PdfEncryptionV5(PdfStandardSecurityHandler securityHandler) : base(securityHandler) { - SecurityHandler._document.SetRequiredVersion(20); + SecurityHandler.Document.SetRequiredVersion(20); } /// @@ -28,26 +27,23 @@ public PdfEncryptionV5(PdfStandardSecurityHandler securityHandler) : base(securi /// True, if the document metadata stream shall be encrypted (default: true). public void Initialize(bool encryptMetadata = true) { - VersionValue = 5; // Always 5 for PdfEncryptionV5. + VersionValue = 5; // Always 5 for PdfEncryptionV5. RevisionValue = 6; // Always 6 for PdfEncryptionV5. LengthValue = GetDefaultLength(); // Deprecated in PDF 2.0. But the adobe powered PDF viewer extension for edge cannot open files correctly, if length is missing. - + EncryptMetadata = encryptMetadata; SecurityHandler.GetOrAddStandardCryptFilter().SetEncryptionToAESForV5(); } - int GetDefaultLength() - { - return 256; // Always 256 for PdfEncryptionV5. - } + int GetDefaultLength() => 256; // Always 256 for PdfEncryptionV5. /// /// Initializes the PdfEncryptionV5 with the values that were saved in the security handler. /// public override void InitializeFromLoadedSecurityHandler() { - var encryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. + var encryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. // #US373 Initialize(encryptMetadata); } @@ -198,7 +194,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne { SecurityHandler.Elements.SetBoolean(PdfStandardSecurityHandler.Keys.EncryptMetadata, EncryptMetadata); - var metadata = SecurityHandler._document.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata); + var metadata = SecurityHandler.Document.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata, VCF.Create); if (metadata is null) throw TH.InvalidOperationException_CouldNotFindMetadataDictionary(); @@ -236,7 +232,7 @@ public override void PrepareEncryptionForSaving(string userPassword, string owne void CreateAndStoreEncryptionKey() { // The file encryption key shall be a 256-bit (32-byte) value generated with a strong random number generator. -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER _encryptionKey = RandomNumberGenerator.GetBytes(32); #else using var cryptoProvider = new RNGCryptoServiceProvider(); @@ -280,7 +276,7 @@ static byte[] CreateUtf8Password(string password) (byte[] UserValue, byte[] UserEValue) ComputeUserValues(byte[] utf8InputPassword) { // a) Generate random bytes for user validation salt and user key salt. -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var validationSalt = RandomNumberGenerator.GetBytes(8); var keySalt = RandomNumberGenerator.GetBytes(8); #else @@ -317,7 +313,7 @@ static byte[] CreateUtf8Password(string password) (byte[] OwnerValue, byte[] OwnerEValue) ComputeOwnerValues(byte[] utf8InputPassword, byte[] userValue) { // a) Generate random bytes for owner validation salt and owner key salt. -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var validationSalt = RandomNumberGenerator.GetBytes(8); var keySalt = RandomNumberGenerator.GetBytes(8); #else @@ -380,7 +376,7 @@ byte[] ComputeHashInternal(byte[] password, byte[] salt, bool computeOwnerHash, var input = password.Concat(salt); if (computeOwnerHash) input = input.Concat(userValue!); // Shall not be null, if computeOwnerHash is true. -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var k = SHA256.HashData(input.ToArray()); #else var sha = SHA256.Create(); @@ -415,15 +411,15 @@ byte[] ComputeHashInternal(byte[] password, byte[] salt, bool computeOwnerHash, // c) + d): Take the first 16 bytes of e as an unsigned big-endian integer. var e16 = e[..16]; -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var e16BigEndianUnsigned = new BigInteger(e16, true, true); #else - var e16BigEndianUnsigned = DotNetHelper.CreateBigInteger(e16, true, true); + var e16BigEndianUnsigned = BigInteger.CreateBigInteger(e16, true, true); #endif - // Calculate the remainder of the result by modulo 3 - // and according to that result choose the SHA algorithm to calculate the new k from e. + // Calculate the remainder of the result by modulo 3 and according to that result + // choose the SHA algorithm to calculate the new k from e. BigInteger.DivRem(e16BigEndianUnsigned, 3, out var remainder); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER if (remainder == 0) k = SHA256.HashData(e); else if (remainder == 1) @@ -457,7 +453,7 @@ static byte[] ComputeHashRevision5Internal(byte[] password, byte[] salt, bool co var input = password.Concat(salt); if (computeOwnerHash) input = input.Concat(userValue!); // Shall not be null, if computeOwnerHash is true. -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var k = SHA256.HashData(input.ToArray()); #else var sha = SHA256.Create(); @@ -497,7 +493,7 @@ byte[] ComputePermsValue(uint pValue) perms[11] = (byte)'b'; // e) Set bytes 12-15 to 4 bytes of random data, which will be ignored. -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var randomData = RandomNumberGenerator.GetBytes(4); #else var randomData = new byte[4]; @@ -538,7 +534,7 @@ public override PasswordValidity ValidatePassword(string inputPassword) var permissionsValue = SecurityHandler.Elements.GetUnsignedInteger(PdfStandardSecurityHandler.Keys.P); var permsValue = PdfEncoders.RawEncoding.GetBytes(SecurityHandler.Elements.GetString(PdfStandardSecurityHandler.Keys.Perms)); - EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. + EncryptMetadata = (SecurityHandler.Elements[PdfStandardSecurityHandler.Keys.EncryptMetadata] as PdfBoolean)?.Value ?? true; // GetBoolean() returns false if not existing, but default is true. // #US373 // 7.6.4.3.3 a) - b): Create UTF-8 password. var utf8InputPassword = CreateUtf8Password(inputPassword); @@ -706,7 +702,7 @@ bool ValidatePermissions(byte[] permsValue, uint pValue) return false; // Bytes 0-3 of the decrypted Perms entry, treated as a little-endian integer, are the user permissions. They should match the value in the P key. -#if NET6_0_OR_GREATER || USE_INDEX_AND_RANGE_ +#if NET8_0_OR_GREATER || USE_INDEX_AND_RANGE_ var pFromPerms = BitConverter.ToUInt32(permsDecrypted[..4]); // Little-endian is default, so we don’t have to change the order. #else var pFromPerms = BitConverter.ToUInt32(permsDecrypted.Take(4).ToArray(), 0); // Little-endian is default, so we don’t have to change the order. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/CryptFilterBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/CryptFilterBase.cs index 787a06f5..81891bc5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/CryptFilterBase.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/CryptFilterBase.cs @@ -22,12 +22,14 @@ protected CryptFilterBase(PdfDictionary dict) : base(dict) { } /// - /// Encrypts the given bytes. Returns true if the crypt filter encrypted the bytes, or false, if the security handler shall do it. + /// Encrypts the given bytes. Returns true if the crypt filter encrypted the bytes, + /// or false, if the security handler shall do it. /// internal abstract bool EncryptForEnteredObject(ref byte[] bytes); /// - /// Decrypts the given bytes. Returns true if the crypt filter decrypted the bytes, or false, if the security handler shall do it. + /// Decrypts the given bytes. Returns true if the crypt filter decrypted the bytes, + /// or false, if the security handler shall do it. /// internal abstract bool DecryptForEnteredObject(ref byte[] bytes); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/IdentityCryptFilter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/IdentityCryptFilter.cs index 2ce2e27f..4a15097d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/IdentityCryptFilter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/IdentityCryptFilter.cs @@ -1,15 +1,28 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + namespace PdfSharp.Pdf.Security { /// /// Represents the identity crypt filter, which shall be provided by a PDF processor and pass the data unchanged. /// - class IdentityCryptFilter : CryptFilterBase + public class IdentityCryptFilter : CryptFilterBase { + public IdentityCryptFilter() + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal IdentityCryptFilter(PdfDictionary dict) + : base(dict) + { } + internal static IdentityCryptFilter Instance { get; } = new(); - + /// /// Encrypts the given bytes. Returns true if the crypt filter encrypted the bytes, or false, if the security handler shall do it. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/MD5Managed.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/MD5Managed.cs index 127c2f71..dccc58db 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/MD5Managed.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/MD5Managed.cs @@ -23,6 +23,7 @@ // a security warning and PDFsharp does not work for users that must use code that is FIPS // compliant. We also need the code for PDFsharp running on Blazor and other platforms // where the .NET implementation lacks of the retired MD5 class. +// Also used in PdfEmbeddedFileStream to calculate the value of /CheckSum. using System.Security.Cryptography; @@ -48,6 +49,14 @@ class MD5Managed : HashAlgorithm public new static MD5Managed Create() => new(); + public static string ComputeHashHex(byte[] bytes) + { + var md5 = Create(); + var hash = md5.ComputeHash(bytes); + var hex = BitConverter.ToString(hash).Replace("-", ""); + return hex; + } + public sealed override void Initialize() { _data = new byte[64]; @@ -56,7 +65,7 @@ public sealed override void Initialize() _abcd = new() { // Initialize values as defined in RFC 1321. - // Note: The code below may look strange but is correct. + // Note that the code below may look strange but is correct. A = A, B = B, C = C, @@ -104,7 +113,7 @@ protected override byte[] HashFinal() MD5Core.ABCD _abcd = new() { // Initialize values as defined in RFC 1321. - // Note: The code below may look strange but is correct. + // Note that the code below may look strange but is correct. A = A, B = B, C = C, @@ -125,7 +134,7 @@ public static byte[] GetHash(byte[] input) throw new ArgumentNullException(nameof(input)); // Initialize values defined in RFC 1321. - // Note: The code below may look strange but is correct. + // Note that the code below may look strange but is correct. var abcd = new ABCD { A = A, @@ -153,7 +162,7 @@ internal static byte[] GetHashFinalBlock(byte[] input, int ibStart, int cbSize, byte[] length = BitConverter.GetBytes(len); // Padding is a single bit 1, followed by the number of 0s required to make size congruent to 448 modulo 512. Step 1 of RFC 1321 - // The CLR ensures that our buffer is 0-assigned, we don't need to explicitly set it. This is why it ends up being quicker to just + // The CLR ensures that our buffer is 0-assigned, we don’t need to explicitly set it. This is why it ends up being quicker to just // use a temporary array rather than doing in-place assignment (5% for small inputs). Array.Copy(input, ibStart, working, 0, cbSize); working[cbSize] = 0x80; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilter.cs index d36d13d4..6c0ae4fb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilter.cs @@ -18,9 +18,13 @@ public class PdfCryptFilter : CryptFilterBase public PdfCryptFilter(PdfStandardSecurityHandler? parentStandardSecurityHandler) { Initialize(parentStandardSecurityHandler); - _parentStandardSecurityHandler?._document.SetRequiredVersion(15); + _parentStandardSecurityHandler?.Document.SetRequiredVersion(15); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfCryptFilter(PdfDictionary dict) : base(dict) { } @@ -58,7 +62,7 @@ public void SetEncryptionToRC4ForV4(int length = 128) public void SetEncryptionToAESForV4() { Initialize(CryptFilterMethod.AESV2, 128); - _parentStandardSecurityHandler?._document.SetRequiredVersion(16); + _parentStandardSecurityHandler?.Document.SetRequiredVersion(16); } /// @@ -68,7 +72,7 @@ public void SetEncryptionToAESForV4() public void SetEncryptionToAESForV5() { Initialize(CryptFilterMethod.AESV3, 256); - _parentStandardSecurityHandler?._document.SetRequiredVersion(20); + _parentStandardSecurityHandler?.Document.SetRequiredVersion(20); } void Initialize(CryptFilterMethod method, int lengthValue = 40) @@ -218,19 +222,19 @@ internal override bool DecryptForEnteredObject(ref byte[] bytes) void SetCryptFilterMethod(CryptFilterMethod cryptFilterMethod) { _cryptFilterMethod = cryptFilterMethod; -#if NET6_0_OR_GREATER - Elements.SetName(Keys.CFM, Enum.GetName(cryptFilterMethod) ?? throw TH.InvalidOperationException_InvalidCryptFilterMethod()); +#if NET8_0_OR_GREATER + Elements.SetName(Keys.CFM, '/' + Enum.GetName(cryptFilterMethod) ?? throw TH.InvalidOperationException_InvalidCryptFilterMethod()); #else - Elements.SetName(Keys.CFM, Enum.GetName(typeof(CryptFilterMethod), cryptFilterMethod) ?? throw TH.InvalidOperationException_InvalidCryptFilterMethod()); + Elements.SetName(Keys.CFM, '/' + Enum.GetName(typeof(CryptFilterMethod), cryptFilterMethod) ?? throw TH.InvalidOperationException_InvalidCryptFilterMethod()); #endif } CryptFilterMethod GetCryptFilterMethod() { -#if NET6_0_OR_GREATER - _cryptFilterMethod ??= Enum.Parse(PdfName.RemoveSlash(Elements.GetName(Keys.CFM))); +#if NET8_0_OR_GREATER + _cryptFilterMethod ??= Enum.Parse(Name.RemoveSlash(Elements.GetName(Keys.CFM))); #else - _cryptFilterMethod ??= (CryptFilterMethod?)Enum.Parse(typeof(CryptFilterMethod), PdfName.RemoveSlash(Elements.GetName(Keys.CFM))); + _cryptFilterMethod ??= (CryptFilterMethod?)Enum.Parse(typeof(CryptFilterMethod), Name.RemoveSlash(Elements.GetName(Keys.CFM))); #endif return _cryptFilterMethod ?? CryptFilterMethod.None; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilters.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilters.cs index 3ef3cd99..5cab5ea4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilters.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfCryptFilters.cs @@ -17,9 +17,9 @@ public PdfCryptFilters() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// - /// internal PdfCryptFilters(PdfDictionary dict) : base(dict) { } @@ -28,8 +28,9 @@ internal PdfCryptFilters(PdfDictionary dict) : base(dict) /// public PdfCryptFilter? GetCryptFilter(string name) { - var key = PdfName.AddSlash(name); - var value = Elements[key]; + var key = Name.MakeName(name); + //var value = Elements[key]; // #US373: Should we expect references here? + var value = Elements.GetValue(key); // #US373: Should we expect references here? return value is null ? null : Convert(value, key); } @@ -39,7 +40,7 @@ internal PdfCryptFilters(PdfDictionary dict) : base(dict) /// public void AddCryptFilter(string name, PdfCryptFilter cryptFilter) { - var key = PdfName.AddSlash(name); + var key = Name.MakeName(name); Elements[key] = cryptFilter; } @@ -48,7 +49,7 @@ public void AddCryptFilter(string name, PdfCryptFilter cryptFilter) /// public bool RemoveCryptFilter(string name) { - var key = PdfName.AddSlash(name); + var key = Name.MakeName(name); return Elements.Remove(key); } @@ -62,8 +63,9 @@ public bool RemoveCryptFilter(string name) // Instead, enumerate Keys and get value via Elements[key], which shall be O(1). foreach (var key in Elements.Keys) { - var value = Elements[key]!; - var name = PdfName.RemoveSlash(key); + //var value = Elements[key]!; // #US373: Should we expect references here? + var value = Elements.GetValue(key)!; // #US373: Should we expect references here? + var name = Name.RemoveSlash(key); var cryptFilter = Convert(value, key); yield return (name, cryptFilter); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecurityHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecurityHandler.cs index 25e43cae..24e25116 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecurityHandler.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecurityHandler.cs @@ -14,6 +14,10 @@ internal PdfSecurityHandler(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfSecurityHandler(PdfDictionary dict) : base(dict) { } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecuritySettings.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecuritySettings.cs index 00685c68..6aa7d57f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecuritySettings.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfSecuritySettings.cs @@ -1,6 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; + namespace PdfSharp.Pdf.Security { /// @@ -16,17 +18,10 @@ internal PdfSecuritySettings(PdfDocument document) readonly PdfDocument _document; /// - /// Indicates whether the granted access to the document is 'owner permission'. Returns true if the document - /// is unprotected or was opened with the owner password. Returns false if the document was opened with the - /// user password. + /// Indicates whether the document is opened with full permission. + /// Returns true unless an owner password secured document was opened with the user password. /// - public bool HasOwnerPermissions - { - internal get => _hasOwnerPermissions; - set => _hasOwnerPermissions = value; - } - /*internal*/ - bool _hasOwnerPermissions = true; + public bool HasFullPermission { get; internal set; } = true; /// /// Sets the user password of the document. Setting a password automatically sets the @@ -62,6 +57,9 @@ internal bool CanSave(ref string message) if (effectiveSecurityHandler != null) { + if (effectiveSecurityHandler.DoNotResetEncryption) + return true; + if (String.IsNullOrEmpty(effectiveSecurityHandler.UserPassword) && String.IsNullOrEmpty(effectiveSecurityHandler.OwnerPassword)) { message = PsMsgs.UserOrOwnerPasswordRequired; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfStandardSecurityHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfStandardSecurityHandler.cs index 47b17375..dbfa98b3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfStandardSecurityHandler.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/PdfStandardSecurityHandler.cs @@ -5,6 +5,7 @@ using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Security.Encryption; +using PdfSharp.Pdf.Signatures; namespace PdfSharp.Pdf.Security { @@ -16,6 +17,10 @@ public sealed class PdfStandardSecurityHandler : PdfSecurityHandler internal PdfStandardSecurityHandler(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfStandardSecurityHandler(PdfDictionary dict) : base(dict) { } @@ -33,7 +38,6 @@ void SetDefaultEncryption() /// /// Do not encrypt the PDF file. Resets the user and owner password. /// - public void SetEncryptionToNoneAndResetPasswords() { _userPassword = ""; @@ -59,24 +63,31 @@ public void SetEncryption(PdfDefaultEncryption encryption) case PdfDefaultEncryption.None: SetEncryptionToNoneAndResetPasswords(); break; + case PdfDefaultEncryption.Default: SetDefaultEncryption(); break; + case PdfDefaultEncryption.V1: SetEncryptionToV1(); break; + case PdfDefaultEncryption.V2With40Bits: SetEncryptionToV2(); break; + case PdfDefaultEncryption.V2With128Bits: SetEncryptionToV2With128Bits(); break; + case PdfDefaultEncryption.V4UsingRC4: SetEncryptionToV4UsingRC4(); break; + case PdfDefaultEncryption.V4UsingAES: SetEncryptionToV4UsingAES(); break; + case PdfDefaultEncryption.V5: SetEncryptionToV5(); break; @@ -98,6 +109,10 @@ public void SetEncryptionToV1() /// The file encryption key length - a multiple of 8 from 40 to 128 bit. public void SetEncryptionToV2(int length = 40) { + // There is a specific problem with Microsoft Edge and encryption version 2. + // If the PDF is saved with an owner AND a user password with version 2 and 40 bit key length, the new PDF viewer of Edge 143.0.3650.96 is not + // able to open the file with the owner password. For a key length of 48 bit it’s the same. + // All other encryption versions work correctly in Edge, so use a newer encryption version or SetEncryptionToV2With128Bits() if version 2 is required. SetEncryptionFieldToV1To4().SetEncryptionToV2(length); } @@ -160,7 +175,12 @@ PdfEncryptionV5 SetEncryptionFieldToV5() /// internal PdfStandardSecurityHandler? GetIfEncryptionIsActive() => IsEncrypted ? this : null; - bool IsEncrypted => _encryption != null; + bool IsEncrypted => _encryption != null; + + /// + /// Indicates that the encryption of an opened document shall not be changed. + /// + public bool DoNotResetEncryption { get; set; } /// /// Sets the user password of the document. @@ -189,7 +209,7 @@ public string OwnerPassword } } - private string _ownerPassword = ""; + string _ownerPassword = ""; /// /// Gets or sets the user access permission represented as an unsigned 32-bit integer in the P key. @@ -215,7 +235,7 @@ internal uint GetCorrectedPermissionsValue() // Correct permission bits. permissionsValue &= 0xfffffffc; // 1... 1111 1111 1100 - Bit 1 & 2 must be 0. -#if true +#if true // TODO clean up //permissionsValue |= 0x000002c0; // 0... 0010 1100 0000 - Bit 7 & 8 must be 1. Also, Bit 10 is no longer used and shall be always set to 1. // Top-most bit not correct, but can also be read with PDFsharp up to 6.1.0. permissionsValue |= 0x7ffff2c0; // 01.. 1110 1100 0000 - Bit 7 & 8 & 13 through 32 must be 1. Also, Bit 10 is no longer used and shall be always set to 1. @@ -223,7 +243,6 @@ internal uint GetCorrectedPermissionsValue() // Include this later as files can not be read with PDFsharp up to 6.1.0. permissionsValue |= 0xfffff2c0; // 1... 1110 1100 0000 - Bit 7 & 8 & 13 through 32 must be 1. Also, Bit 10 is no longer used and shall be always set to 1. #endif - return permissionsValue; } @@ -252,7 +271,7 @@ bool IsSecurityHandler(PdfObject pdfObject) return false; // Incrementally updated PDFs contain multiple trailers. Check the SecurityHandler of each one. - var currentTrailer = _document.Trailer; + var currentTrailer = Document.Trailer; while (currentTrailer != null) { // Compare PdfReference, as currentTrailer.SecurityHandler contains the dictionary converted to PdfStandardSecurityHandler, @@ -288,9 +307,11 @@ public void DecryptObject(PdfObject value) case PdfDictionary vDict: DecryptDictionary(vDict); break; + case PdfArray vArray: DecryptArray(vArray); break; + case PdfStringObject vStr: DecryptString(vStr); break; @@ -304,16 +325,20 @@ public void DecryptObject(PdfObject value) /// void DecryptDictionary(PdfDictionary dict) { - foreach (var item in dict.Elements) + var elementsToDecrypt = GetElementsToDecrypt(dict); + + foreach (var item in elementsToDecrypt) { switch (item.Value) { case PdfString vStr: DecryptString(vStr); break; + case PdfDictionary vDict: DecryptDictionary(vDict); break; + case PdfArray vArray: DecryptArray(vArray); break; @@ -330,6 +355,33 @@ void DecryptDictionary(PdfDictionary dict) } } + IEnumerable> GetElementsToDecrypt(PdfDictionary dict) + { + var elements = dict.Elements; + + // According to PDF Reference 2.0, hexadecimal strings in the /Contents key of a signature dictionary shall not be encrypted (and so shall not decrypted). + + // However, it’s hard to identify a signature dictionary, as the /Type key is optional. You could identify it by being the dictionary referenced via /V key + // of a signature field, which itself could be identified by its /FT key set to /Sig. But as decryption is done while reading all indirect objects, the + // relationship between the signature field and the signature dictionary isn’t modelled yet. + // But due to the following specifications of PDF Reference 2.0, /Contents key must only be excluded from decryption, if /ByteRange key is present, which is + // a unique key, we can use for identification: + // - 7.6.2 Application of encryption: "Encryption applies to all strings and streams in the document's PDF file, with the following exceptions: [...] + // Any hexadecimal strings representing the value of the Contents key in a Signature dictionary" + // - Table 255 — Entries in a signature dictionary: + // - "ByteRange - array - [...] When a byte range digest is present, all values in the signature dictionary shall be direct objects. + // - "Contents - byte string - (Required) The signature value. When ByteRange is present, the value shall be a hexadecimal string (see 7.3.4.3, "Hexadecimal strings") + // representing the value of the byte range digest." + + // So checking for /ByteRange and /Contents keys is sufficient to identify the constellation, where /Contents key must not be decrypted. + var isSignatureDictWithHexByteRange = elements.ContainsKey(PdfSignature.Keys.ByteRange) && elements.ContainsKey(PdfSignature.Keys.Contents); + if (isSignatureDictWithHexByteRange) + return elements.Where(x => x.Key != PdfSignature.Keys.Contents); + + // For all other cases, all elements of the dictionary shall be processed. + return elements; + } + /// /// Decrypts an array. /// @@ -345,9 +397,11 @@ void DecryptArray(PdfArray array) case PdfString vStr: DecryptString(vStr); break; + case PdfDictionary vDict: DecryptDictionary(vDict); break; + case PdfArray vArray: DecryptArray(vArray); break; @@ -487,6 +541,9 @@ internal void PrepareForReading() SetEncryptionFieldToV1To4(); else if (PdfEncryptionV5.IsVersionSupported(versionValue)) SetEncryptionFieldToV5(); + else + throw TH.PdfReaderException_UnknownEncryption(); + GetEncryption().InitializeFromLoadedSecurityHandler(); // Load, initialize and prepare crypt filters. @@ -503,6 +560,10 @@ internal void PrepareForReading() /// internal void PrepareForWriting() { + // Return if encryption shall be left unchanged. + if (DoNotResetEncryption) + return; + Elements[PdfSecurityHandler.Keys.Filter] = new PdfName("/Standard"); GetEncryption().PrepareEncryptionForSaving(UserPassword, OwnerPassword); @@ -525,7 +586,7 @@ internal PasswordValidity ValidatePassword(string? inputPassword) inputPassword ??= ""; var passwordValidity = GetEncryption().ValidatePassword(inputPassword); - _document.SecuritySettings.HasOwnerPermissions = passwordValidity == PasswordValidity.OwnerPassword; + Document.SecuritySettings.HasFullPermission = passwordValidity == PasswordValidity.OwnerPassword; return passwordValidity; } @@ -726,7 +787,7 @@ void SetCryptFilterAsDefaultInternal(string key, string? name) return; } - if (name != PdfName.RemoveSlash(CryptFilterConstants.IdentityFilterValue)) + if (name != Name.RemoveSlash(CryptFilterConstants.IdentityFilterValue)) { var pdfCryptFilters = (PdfCryptFilters?)Elements.GetValue(PdfSecurityHandler.Keys.CF); if (pdfCryptFilters?.GetCryptFilter(name) is null) @@ -756,9 +817,9 @@ void LoadCryptFilters(bool initializeCryptFilters) loadedCryptFilter.Value.Initialize(this); } - _defaultCryptFilterStreams = GetDefaultCryptFilter(PdfName.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.StmF))); - _defaultCryptFilterStrings = GetDefaultCryptFilter(PdfName.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.StrF))); - _defaultCryptFilterEmbeddedFileStreams = GetDefaultCryptFilter(PdfName.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.EFF)), _defaultCryptFilterStreams); + _defaultCryptFilterStreams = GetDefaultCryptFilter(Name.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.StmF))); + _defaultCryptFilterStrings = GetDefaultCryptFilter(Name.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.StrF))); + _defaultCryptFilterEmbeddedFileStreams = GetDefaultCryptFilter(Name.RemoveSlash(Elements.GetName(PdfSecurityHandler.Keys.EFF)), _defaultCryptFilterStreams); } CryptFilterBase GetDefaultCryptFilter(string cryptFilterName) @@ -768,7 +829,7 @@ CryptFilterBase GetDefaultCryptFilter(string cryptFilterName) CryptFilterBase GetDefaultCryptFilter(string cryptFilterName, CryptFilterBase @default) { - if (cryptFilterName == PdfName.RemoveSlash(CryptFilterConstants.IdentityFilterValue)) + if (cryptFilterName == Name.RemoveSlash(CryptFilterConstants.IdentityFilterValue)) return IdentityCryptFilter.Instance; if (string.IsNullOrEmpty(cryptFilterName)) @@ -793,7 +854,7 @@ public void ResetCryptFilter(PdfDictionary dictionary) void ResetCryptFilterEntriesInAllElements() { - foreach (var iref in _document.IrefTable.AllReferences) + foreach (var iref in Document.IrefTable.AllReferences) { var pdfObject = iref.Value; if (pdfObject is not PdfDictionary dictionary) @@ -820,7 +881,7 @@ public void SetCryptFilter(PdfDictionary dictionary, string cryptFilterName) EnsureCryptFiltersAreSupported(); - if (PdfName.AddSlash(cryptFilterName) != CryptFilterConstants.IdentityFilterValue) + if (Name.MakeName(cryptFilterName) != CryptFilterConstants.IdentityFilterValue) { var cryptFilters = (PdfCryptFilters?)Elements.GetValue(PdfSecurityHandler.Keys.CF); if (cryptFilters?.GetCryptFilter(cryptFilterName) is null) @@ -829,7 +890,7 @@ public void SetCryptFilter(PdfDictionary dictionary, string cryptFilterName) dictionary.Elements.ArrayOrSingleItem.Add(PdfStream.Keys.Filter, new PdfName(CryptFilterConstants.FilterValue), true); - var decodeParams = new PdfDictionary(dictionary._document); + var decodeParams = new PdfDictionary(dictionary.Document); decodeParams.Elements.SetName(CryptFilterConstants.DecodeParmsTypeKey, CryptFilterConstants.DecodeParmsTypeValue); decodeParams.Elements.SetName(CryptFilterConstants.DecodeParmsNameKey, cryptFilterName); @@ -869,7 +930,7 @@ public void SetCryptFilter(PdfDictionary dictionary, string cryptFilterName) return IdentityCryptFilter.Instance; // For others try to load crypt filter form _loadedCryptFilters. - var cryptFilterName = PdfName.RemoveSlash(cryptFilterNameValue); + var cryptFilterName = Name.RemoveSlash(cryptFilterNameValue); if (!string.IsNullOrEmpty(cryptFilterName)) return _loadedCryptFilters![cryptFilterName]; } @@ -882,6 +943,18 @@ public void SetCryptFilter(PdfDictionary dictionary, string cryptFilterName) throw TH.InvalidOperationException_CryptFilterDecodeParmsNotInitializedCorrectly(); } + // In general the identity crypt filter should be added to not encrypted dictionaries. + // But some PDF tools don’t add it to the metadata dictionary if EncryptMetadata is set to false. + // So if we didn’t find a crypt filter, we check if a metadata dictionary is not encrypted and + // return the identity crypt filter in that case to avoid decrypting it. + type = dictionary.Elements.GetName(PdfMetadata.Keys.Type); + if (type == "/Metadata" && !GetEncryption().EncryptMetadata) + { + var possiblyReadableXml = dictionary.Stream?.ToString().TrimStart(); + if (possiblyReadableXml != null && possiblyReadableXml[0] == '<') + return IdentityCryptFilter.Instance; + } + if (PdfEmbeddedFileStream.IsEmbeddedFile(dictionary)) return _defaultCryptFilterEmbeddedFileStreams; @@ -1047,7 +1120,7 @@ static class CryptFilterConstants /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/SecurityManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/SecurityManager.cs new file mode 100644 index 00000000..009f3ced --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/SecurityManager.cs @@ -0,0 +1,81 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.Security +{ + using Internal; + + public class SecurityManager : ManagerBase + { + SecurityManager(PdfDocument document) : base(document) + { + Initialize(); + } + + public PdfSecuritySettings SecuritySettings + => Document.SecuritySettings; + + public string EncryptionType { get; } = "(TODO)"; // TODO + + public string? UserPassword // TODO + { + get => _userPassword; + set + { + if (value == null) + { + _userPassword = null; + } + else if (_userPassword is null) + { + _userPassword = value; + } + else + throw new InvalidOperationException("Cannot change user password if once set."); + } + } + string? _userPassword = "(TODO)"; // TODO + + public string? OwnerPassword // TODO + { + get => _ownerPassword; + set + { + var i = base.IsInitialized; + if (value == null) + { + _ownerPassword = null; + } + else if (_ownerPassword is null) + { + _ownerPassword = value; + } + else + throw new InvalidOperationException("Cannot change owner password if once set."); + } + } + string? _ownerPassword = "(TODO)"; // TODO + + /// + /// Gets or creates the SecurityManager singleton for the specified document. + /// + public static SecurityManager ForDocument(PdfDocument document) + => document.SecurityManager ??= new(document); + + void Initialize() + { + Document.EnsureNotDisposed(); + if (!IsInitialized) + { + IsInitialized = true; + Document.EnsureNotYetSaved(); + if (Document.IsImported) + { + // TODO: Get PDF/A conformance + } + } + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/enums/PdfUserAccessPermission.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/enums/PdfUserAccessPermission.cs index 52e27df5..9c3f4324 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/enums/PdfUserAccessPermission.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Security/enums/PdfUserAccessPermission.cs @@ -9,9 +9,6 @@ namespace PdfSharp.Pdf.Security [Flags] enum PdfUserAccessPermission : uint { - // PDF 2.0: 7.6.4.2 Standard encryption dictionary - // See table 22. - /// /// Permits everything. This is the default value. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DefaultSignatureAppearanceHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DefaultSignatureAppearanceHandler.cs index 7dd6f656..dd57066e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DefaultSignatureAppearanceHandler.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DefaultSignatureAppearanceHandler.cs @@ -5,6 +5,8 @@ using PdfSharp.Drawing.Layout; using PdfSharp.Pdf.Annotations; +// v7.0.0 TODO review + namespace PdfSharp.Pdf.Signatures { /// @@ -21,7 +23,7 @@ class DefaultSignatureAppearanceHandler : IAnnotationAppearanceHandler public void DrawAppearance(XGraphics gfx, XRect rect) { - var defaultText = $"Signed by: {Signer}\nLocation: {Location}\nReason: {Reason}\nDate: {DateTime.Now}"; + var defaultText = $"Signed by: {Signer}\nLocation: {Location}\nReason: {Reason}\nDate: {DateTimeOffset.Now}"; // You should write your own implementation of IAnnotationAppearanceHandler and ensure that the used font is available. var font = new XFont("Verdana", 7, XFontStyleEx.Regular); @@ -34,8 +36,8 @@ public void DrawAppearance(XGraphics gfx, XRect rect) // Leave 5% space on each side. txtFormat.DrawString(defaultText, font, - new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), - new XRect(currentPosition.X + width * .05, currentPosition.Y + height * .05, + XBrushes.Black, + new XRect(currentPosition.X + width * .05, currentPosition.Y + height * .05, width * .9, height * .9), XStringFormats.TopLeft); } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureHandler.cs new file mode 100644 index 00000000..a6d00f3c --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureHandler.cs @@ -0,0 +1,269 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#if !NET8_0_OR_GREATER +using System.Text; +#endif +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Forms; +using PdfSharp.Pdf.IO; +using System.Text; + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf.Signatures +{ + /// + /// PdfDocument signature handler. + /// Attaches a PKCS#7 signature digest to PdfDocument. + /// + public class DigitalSignatureHandler + { + /// + /// Large enough space reserved by PdfPlaceholder to be replaced by the actual computed value of the byte range to sign. + /// Worst case: signature dictionary is near the end of an 10 GB PDF file. So we reserve 10 digits. + /// However, the current implementation can only support 2 GB files. + /// + const int ByteRangePlaceholderLength = 36; // = "[0 9999999999 9999999999 9999999999]".Length + + DigitalSignatureHandler(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options) + { + Document = document ?? throw new ArgumentNullException(nameof(document)); + Signer = signer ?? throw new ArgumentNullException(nameof(signer)); + Options = options ?? throw new ArgumentNullException(nameof(options)); + + if (options.PageIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(options.PageIndex), + "Signature page index cannot be negative."); + } + + // TODO_OLD in document: Set document version depending on digest type from options. // TODO: what? + } + + /// + /// Gets or creates the digital signature handler for the specified document. + /// + public static DigitalSignatureHandler ForDocument(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options) + { + return document.DigitalSignatureHandler ??= new(document, signer, options); + } + + /// + /// Gets the PDF document the signature will be attached to. + /// + public PdfDocument Document { get; init; } + + /// + /// Gets the options for the digital signature. + /// + public DigitalSignatureOptions Options { get; init; } + + IDigitalSigner Signer { get; init; } + + /// + /// Computes the signature and byte range after the document stream was written. + /// + internal async Task ComputeSignatureAndRange(PdfWriter writer) + { + var (rangedStreamToSign, byteRangeArray) = GetRangeToSignAndByteRangeArray(writer.Stream); + + // Write the /ByteRange entry '[...2 times offset, length...]'. + Debug.Assert(_byteRangePlaceholder != null); + var sb = new StringBuilder(4096); + sb.Append(byteRangeArray); + // Length must match placeholder. + sb.Append(new String(' ', _byteRangePlaceholder.Length - sb.Length)); + _byteRangePlaceholder.SetValue(sb.ToString()); + _byteRangePlaceholder.WriteEffectiveValue(writer); + + // Computing signature from document’s digest. + var signature = await Signer.GetSignatureAsync(rangedStreamToSign).ConfigureAwait(false); + + Debug.Assert(_contentsPlaceholder != null); + //int expectedLength = (_contentsPlaceholder.Length - 2) / 2; + int contentsHexLength = 2 * signature.Length + 2; + if (contentsHexLength > _contentsPlaceholder.Length) + { + // This should not happen. + throw new InvalidOperationException( + $"The actual digest length '{contentsHexLength}' is larger than the approximation made '{_contentsPlaceholder.Length}'. " + + "Not enough space in the placeholder to fit the hex-encoded signature."); + } + + // Write the /Contents entry '<...signature hex string...>'. + // When the signature includes a timestamp, the exact length is unknown until the signature is definitely calculated. + // Therefore, we write the angle brackets here and override the placeholder white-spaces. + // According to the PDF reference, the Contents key of a signature dictionary shall not be encrypted. + sb.Clear(); + sb.Append('<'); + sb.Append(FormatHex(signature)); +#if true + // Filler bytes must be part of the hex string. Trailing blanks are invalid PDF. + //sb.Append(new String('0', 2 * (expectedLength - signature.Length))); + sb.Append(new String('0', _contentsPlaceholder.Length - sb.Length - 1)); + sb.Append('>'); +#else + sb.Append('>'); + sb.Append(new String(' ', 2 * (expectedLength - signature.Length))); +#endif + _contentsPlaceholder.SetValue(sb.ToString()); + _contentsPlaceholder.WriteEffectiveValue(writer); + } + + string FormatHex(byte[] bytes) // ...use RawEncoder + { +#if NET8_0_OR_GREATER + return Convert.ToHexString(bytes); +#else + var result = new StringBuilder(); + + for (int idx = 0; idx < bytes.Length; idx++) + result.AppendFormat("{0:X2}", bytes[idx]); + + return result.ToString(); +#endif + } + + /// + /// Get the bytes ranges to sign. + /// As recommended in PDF specs, whole document will be signed, except for the hexadecimal signature token value in the /Contents entry. + /// Example: '/Contents <aaaaa111111>' => '<aaaaa111111>' will be excluded from the bytes to sign. + /// + /// + (RangedStream rangedStream, PdfArray byteRangeArray) GetRangeToSignAndByteRangeArray(Stream stream) + { + Debug.Assert(_contentsPlaceholder != null, nameof(_contentsPlaceholder) + " must not be null here."); + + var firstRangeOffset = 0L; + var firstRangeLength = _contentsPlaceholder.StartPosition; + var secondRangeOffset = _contentsPlaceholder.EndPosition; + var secondRangeLength = stream.Length - _contentsPlaceholder.EndPosition; + + var byteRangeArray = new PdfArray(); +#if USE_LONG_SIZE + byteRangeArray.Elements.Add(new PdfLongInteger(firstRangeOffset)); + byteRangeArray.Elements.Add(new PdfLongInteger(firstRangeLength)); + byteRangeArray.Elements.Add(new PdfLongInteger(secondRangeOffset)); + byteRangeArray.Elements.Add(new PdfLongInteger(secondRangeLength)); +#else + byteRangeArray.Elements.Add(new PdfInteger(firstRangeOffset)); + byteRangeArray.Elements.Add(new PdfInteger(firstRangeLength)); + byteRangeArray.Elements.Add(new PdfInteger(secondRangeOffset)); + byteRangeArray.Elements.Add(new PdfInteger(secondRangeLength)); +#endif + var rangedStream = new RangedStream(stream, + [ + new(firstRangeOffset, firstRangeLength), + new(secondRangeOffset, secondRangeLength) + ]); + + return (rangedStream, byteRangeArray); + } + + /// + /// Adds the PDF objects required for a digital signature as placeholders. + /// + /// + internal async Task AddSignatureComponentsAsync() // #US321 TODO Use appropriate classes. + { + if (Options.PageIndex >= Document.PageCount) + throw new ArgumentOutOfRangeException($"Signature page doesn't exist, specified page was {Options.PageIndex + 1} but document has only {Document.PageCount} page(s)."); + + var signatureSize = await Signer.GetSignatureSizeAsync().ConfigureAwait(false); + _contentsPlaceholder = new(2 * signatureSize + 2); + _byteRangePlaceholder = new(ByteRangePlaceholderLength); + + var signatureDictionary = GetSignatureDictionary(_contentsPlaceholder, _byteRangePlaceholder); + var signatureField = GetSignatureField(signatureDictionary); + + var page = Document.Pages[Options.PageIndex]; + var annotations = page.Elements.GetArray(PdfPage.Keys.Annots); + if (annotations == null) + page.Elements.Add(PdfPage.Keys.Annots, new PdfArray(Document, signatureField)); + else + annotations.Elements.Add(signatureField); + + var catalog = Document.Catalog; + var acroForm = catalog.GetOrCreateAcroForm(); + + if (!acroForm.Elements.ContainsKey(PdfForm.Keys.SigFlags)) + acroForm.Elements.Add(PdfForm.Keys.SigFlags, new PdfInteger(3, true)); + else + { + var sigFlagVersion = acroForm.Elements.GetInteger(PdfForm.Keys.SigFlags); + if (sigFlagVersion < 3) + acroForm.Elements.SetIntegerFlag(PdfForm.Keys.SigFlags, 3); + } + + acroForm.Fields.Elements.Add(signatureField); + } + + PdfFormSignatureField GetSignatureField(PdfSignature signatureDic) // #US321 TODO Use appropriate classes. + { + var signatureField = new PdfFormSignatureField(Document); + + signatureField.Elements.Add(PdfFormField.Keys.V, signatureDic); + + // #AcroForms + // Annotation keys. + signatureField.Elements.Add(PdfFormField.Keys.FT, new PdfName(PdfFormFieldType.Signature)); + signatureField.Elements.Add(PdfFormField.Keys.T, new PdfString("Signature1")); // TODO If already exists, will it cause error? implement a name chooser if yes. + signatureField.Elements.Add(PdfFormField.Keys.Ff, new PdfInteger(132)); + // signatureField.Elements.Add(PdfFormField.Keys.DR, new PdfDictionary()); TODO COMPILE + signatureField.Elements.Add(PdfAnnotation.Keys.Type, new PdfName("/Annot")); + signatureField.Elements.Add(PdfAnnotation.Keys.Subtype, new PdfName("/Widget")); + signatureField.Elements.Add(PdfAnnotation.Keys.P, Document.Pages[Options.PageIndex]); + + signatureField.Elements.Add(PdfAnnotation.Keys.Rect, new PdfRectangle(Options.Rectangle)); + + // TODO COMPILE + signatureField.CustomAppearanceHandler = Options.AppearanceHandler ?? new DefaultSignatureAppearanceHandler() + { + Location = Options.Location, + Reason = Options.Reason, + Signer = Signer.CertificateName + }; + + // Call RenderCustomAppearance(); here. + signatureField.RenderAppearance(); + // Rendering the signature in PrepareForSave is too late and leads to inconsistent embedded fonts. + + //Document.Internals.AddObject(signatureField); AcroFields are already indirect. + + return signatureField; + } + + PdfSignature GetSignatureDictionary(PdfPlaceholder contents, PdfPlaceholder byteRange) // #US321 TODO Use appropriate classes. + { + PdfSignature signatureDic = new(Document); + + signatureDic.Elements.Add(PdfSignature.Keys.Type, new PdfName(PdfFormFieldType.Signature)); + signatureDic.Elements.Add(PdfSignature.Keys.Filter, new PdfName("/Adobe.PPKLite")); + signatureDic.Elements.Add(PdfSignature.Keys.SubFilter, new PdfName("/adbe.pkcs7.detached")); + signatureDic.Elements.Add(PdfSignature.Keys.M, new PdfDate(DateTimeOffset.Now)); + + signatureDic.Elements.Add(PdfSignature.Keys.Contents, contents); + signatureDic.Elements.Add(PdfSignature.Keys.ByteRange, byteRange); + signatureDic.Elements.Add(PdfSignature.Keys.Reason, new PdfString(Options.Reason)); + signatureDic.Elements.Add(PdfSignature.Keys.Location, new PdfString(Options.Location)); + + var properties = new PdfDictionary(Document); + signatureDic.Elements.Add("/Prop_Build", properties); + var propertyItems = new PdfDictionary(Document); + properties.Elements.Add("/App", propertyItems); + propertyItems.Elements.Add("/Name", + String.IsNullOrWhiteSpace(Options.AppName) ? + new PdfName("/PDFsharp http://www.pdfsharp.net") : + PdfName.FromString(Options.AppName)); + + Document.Internals.AddObject(signatureDic); + + return signatureDic; + } + + PdfPlaceholder? _contentsPlaceholder; + PdfPlaceholder? _byteRangePlaceholder; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureOptions.cs index c51bc051..ed4aacdc 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/DigitalSignatureOptions.cs @@ -4,6 +4,8 @@ using PdfSharp.Drawing; using PdfSharp.Pdf.Annotations; +// v7.0.0 Ready + namespace PdfSharp.Pdf.Signatures { /// @@ -34,7 +36,7 @@ public class DigitalSignatureOptions() /// /// Gets or sets the name of the application used to sign the document. /// - public string AppName { get; init; } = "PDFsharp http://www.pdfsharp.net"; + public string AppName { get; init; } = "PDFsharp http://www.pdfsharp.com"; /// /// The location of the visual representation on the selected page. @@ -42,7 +44,7 @@ public class DigitalSignatureOptions() public XRect Rectangle { get; init; } /// - /// The page index, zero-based, of the page showing the signature. + /// The page zero-based index of the page showing the signature. /// public int PageIndex { get; init; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs index 9df1197e..8a58effb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfPlaceholderObject.cs @@ -10,7 +10,7 @@ namespace PdfSharp.Pdf.Signatures /// It is used e.g. for digital signatures to reserve space for the ByteRange Array. /// /// The length of the reserved space. - sealed class PdfPlaceholderObject(int length) : PdfObject() + sealed class PdfPlaceholderObject_(int length) : PdfObject // TODO: use PdfPlaceholder DELETE { public int Length { get; init; } = length; @@ -45,9 +45,7 @@ internal void WriteActualObject(PdfObject obj, PdfWriter writer) var endPosition = writer.Position; var actualLength = endPosition - StartPosition; if (actualLength > Length) - { - throw new Exception($"The actual length {actualLength} of this object is larger than the length {Length} of its placeholder."); - } + throw new InvalidOperationException($"The actual length {actualLength} of this object is larger than the length {Length} of its placeholder."); // Restore writer position. writer.Stream.Position = initialPosition; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignature.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignature.cs index 364b50ca..48d1547a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignature.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignature.cs @@ -1,23 +1,32 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +// v7.0.0 TODO review + namespace PdfSharp.Pdf.Signatures { /// /// The signature dictionary added to a PDF file when it is to be signed. /// - public sealed class PdfSignature2 : PdfDictionary + public sealed class PdfSignature : PdfDictionary { + ///// + ///// Initializes a new instance of the class. + ///// + //public PdfSignature() + //{ } + /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// - public PdfSignature2() + public PdfSignature(PdfDocument document) : base(document) { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// - public PdfSignature2(PdfDocument dict) : base(dict) + internal PdfSignature(PdfDictionary dict) : base(dict) { } ///// @@ -29,5 +38,86 @@ public PdfSignature2(PdfDocument dict) : base(dict) ///// Decrypts the given bytes. Returns true if the crypt filter decrypted the bytes, or false, if the security handler shall do it. ///// //internal abstract bool DecryptForEnteredObject(ref byte[] bytes); + + /// + /// Predefined keys of this dictionary. + /// The description comes from PDF 1.4 Reference. + /// + public class Keys : KeysBase + { + /// + /// (Optional) The type of PDF object that this dictionary describes; if present, + /// must be Sig for a signature dictionary. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string Type = "/Type"; + + /// + /// (Required; inheritable) The name of the signature handler to be used for + /// authenticating the field’s contents, such as Adobe.PPKLite, Entrust.PPKEF, + /// CICI.SignIt, or VeriSign.PPKVS. + /// + [KeyInfo(KeyType.Name | KeyType.Required)] + public const string Filter = "/Filter"; + + /// + /// (Optional) The name of a specific submethod of the specified handler. + /// + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string SubFilter = "/SubFilter"; + + /// + /// (Required) An array of pairs of integers (starting byte offset, length in bytes) + /// describing the exact byte range for the digest calculation. Multiple discontinuous + /// byte ranges may be used to describe a digest that does not include the + /// signature token itself. + /// + [KeyInfo(KeyType.Array | KeyType.Required)] + public const string ByteRange = "/ByteRange"; + + /// + /// (Required) The encrypted signature token. + /// + [KeyInfo(KeyType.String | KeyType.Required)] + public const string Contents = "/Contents"; + + /// + /// (Optional) The name of the person or authority signing the document. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string Name = "/Name"; + + /// + /// (Optional) The time of signing. Depending on the signature handler, this + /// may be a normal unverified computer time or a time generated in a verifiable + /// way from a secure time server. + /// + [KeyInfo(KeyType.Date | KeyType.Optional)] + public const string M = "/M"; + + /// + /// (Optional) The CPU host name or physical location of the signing. + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string Location = "/Location"; + + /// + /// (Optional) The reason for the signing, such as (I agree…). + /// + [KeyInfo(KeyType.TextString | KeyType.Optional)] + public const string Reason = "/Reason"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs deleted file mode 100644 index f1f4b752..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignatureHandler.cs +++ /dev/null @@ -1,242 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -#if !NET6_0_OR_GREATER -using System.Text; -#endif -using PdfSharp.Pdf.AcroForms; -using PdfSharp.Pdf.Advanced; -using PdfSharp.Pdf.Internal; -using PdfSharp.Pdf.IO; - -namespace PdfSharp.Pdf.Signatures -{ - /// - /// PdfDocument signature handler. - /// Attaches a PKCS#7 signature digest to PdfDocument. - /// - // DigitalSignatureHandler rename file - public class DigitalSignatureHandler - { - /// - /// Big enough space reserved by PdfPlaceholderObject to be replaced by the actual computed value of the byte range to sign - /// Worst case: signature dictionary is near the end of an 10 GB PDF file. - /// - const int ByteRangePlaceholderLength = 36; // = "[0 9999999999 9999999999 9999999999]".Length - - DigitalSignatureHandler(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options) - { - Document = document ?? throw new ArgumentNullException(nameof(document)); - Signer = signer ?? throw new ArgumentNullException(nameof(signer)); - Options = options ?? throw new ArgumentNullException(nameof(options)); - - if (options.PageIndex < 0) - { - throw new ArgumentOutOfRangeException(nameof(options.PageIndex), - "Signature page index cannot be negative."); - } - - // TODO_OLD in document: Set document version depending on digest type from options. - } - - /// - /// Gets or creates the digital signature handler for the specified document. - /// - public static DigitalSignatureHandler ForDocument(PdfDocument document, IDigitalSigner signer, DigitalSignatureOptions options) - { - return document._digitalSignatureHandler ??= new(document, signer, options); - } - - - /// - /// Gets the PDF document the signature will be attached to. - /// - public PdfDocument Document { get; init; } - - /// - /// Gets the options for the digital signature. - /// - public DigitalSignatureOptions Options { get; init; } - - IDigitalSigner Signer { get; init; } - - internal async Task ComputeSignatureAndRange(PdfWriter writer) - { - var (rangedStreamToSign, byteRangeArray) = GetRangeToSignAndByteRangeArray(writer.Stream); - - Debug.Assert(_signatureFieldByteRangePlaceholder != null); - _signatureFieldByteRangePlaceholder.WriteActualObject(byteRangeArray, writer); - - // Computing signature from document’s digest. - var signature = await Signer.GetSignatureAsync(rangedStreamToSign).ConfigureAwait(false); - - Debug.Assert(_placeholderItem != null); - int expectedLength = _placeholderItem.Size; - if (signature.Length > expectedLength) - throw new Exception($"The actual digest length {signature.Length} is larger than the approximation made {expectedLength}. Not enough room in the placeholder to fit the signature."); - - // Write the signature at the space reserved by placeholder item. - writer.Stream.Position = _placeholderItem.StartPosition; - - // When the signature includes a timestamp, the exact length is unknown until the signature is definitely calculated. - // Therefore, we write the angle brackets here and override the placeholder white spaces. - writer.WriteRaw('<'); - writer.Write(PdfEncoders.RawEncoding.GetBytes(FormatHex(signature))); - - // Fill up the allocated placeholder. Signature is sometimes considered invalid if there are spaces after '>'. - for (int x = signature.Length; x < expectedLength; ++x) - writer.WriteRaw("00"); - - writer.WriteRaw('>'); - } - - string FormatHex(byte[] bytes) // ...use RawEncoder - { -#if NET6_0_OR_GREATER - return Convert.ToHexString(bytes); -#else - var result = new StringBuilder(); - - for (int idx = 0; idx < bytes.Length; idx++) - result.AppendFormat("{0:X2}", bytes[idx]); - - return result.ToString(); -#endif - } - - /// - /// Get the bytes ranges to sign. - /// As recommended in PDF specs, whole document will be signed, except for the hexadecimal signature token value in the /Contents entry. - /// Example: '/Contents <aaaaa111111>' => '<aaaaa111111>' will be excluded from the bytes to sign. - /// - /// - (RangedStream rangedStream, PdfArray byteRangeArray) GetRangeToSignAndByteRangeArray(Stream stream) - { - Debug.Assert( _placeholderItem !=null, nameof(_placeholderItem) + " must not be null here."); - - SizeType firstRangeOffset = 0; - SizeType firstRangeLength = _placeholderItem.StartPosition; - SizeType secondRangeOffset = _placeholderItem.EndPosition; - SizeType secondRangeLength = stream.Length - _placeholderItem.EndPosition; - - var byteRangeArray = new PdfArray(); - byteRangeArray.Elements.Add(new PdfLongInteger(firstRangeOffset)); - byteRangeArray.Elements.Add(new PdfLongInteger(firstRangeLength)); - byteRangeArray.Elements.Add(new PdfLongInteger(secondRangeOffset)); - byteRangeArray.Elements.Add(new PdfLongInteger(secondRangeLength)); - - var rangedStream = new RangedStream(stream, - [ - new(firstRangeOffset, firstRangeLength), - new(secondRangeOffset, secondRangeLength) - ]); - - return (rangedStream, byteRangeArray); - } - - /// - /// Adds the PDF objects required for a digital signature. - /// - /// - internal async Task AddSignatureComponentsAsync() - { - if (Options.PageIndex >= Document.PageCount) - throw new ArgumentOutOfRangeException($"Signature page doesn't exist, specified page was {Options.PageIndex + 1} but document has only {Document.PageCount} page(s)."); - - var signatureSize = await Signer.GetSignatureSizeAsync().ConfigureAwait(false); - _placeholderItem = new(signatureSize); - _signatureFieldByteRangePlaceholder = new PdfPlaceholderObject(ByteRangePlaceholderLength); - - var signatureDictionary = GetSignatureDictionary(_placeholderItem, _signatureFieldByteRangePlaceholder); - var signatureField = GetSignatureField(signatureDictionary); - - var annotations = Document.Pages[Options.PageIndex].Elements.GetArray(PdfPage.Keys.Annots); - if (annotations == null) - Document.Pages[Options.PageIndex].Elements.Add(PdfPage.Keys.Annots, new PdfArray(Document, signatureField)); - else - annotations.Elements.Add(signatureField); - - // acroform - - var catalog = Document.Catalog; - - if (catalog.Elements.GetObject(PdfCatalog.Keys.AcroForm) == null) - catalog.Elements.Add(PdfCatalog.Keys.AcroForm, new PdfAcroForm(Document)); - - if (!catalog.AcroForm.Elements.ContainsKey(PdfAcroForm.Keys.SigFlags)) - catalog.AcroForm.Elements.Add(PdfAcroForm.Keys.SigFlags, new PdfInteger(3)); - else - { - var sigFlagVersion = catalog.AcroForm.Elements.GetInteger(PdfAcroForm.Keys.SigFlags); - if (sigFlagVersion < 3) - catalog.AcroForm.Elements.SetInteger(PdfAcroForm.Keys.SigFlags, 3); - } - - if (catalog.AcroForm.Elements.GetValue(PdfAcroForm.Keys.Fields) == null) - catalog.AcroForm.Elements.SetValue(PdfAcroForm.Keys.Fields, new PdfAcroField.PdfAcroFieldCollection(new PdfArray())); - catalog.AcroForm.Fields.Elements.Add(signatureField); - } - - PdfSignatureField GetSignatureField(PdfSignature2 signatureDic) - { - var signatureField = new PdfSignatureField(Document); - - signatureField.Elements.Add(PdfAcroField.Keys.V, signatureDic); - - // Annotation keys. - signatureField.Elements.Add(PdfAcroField.Keys.FT, new PdfName("/Sig")); - signatureField.Elements.Add(PdfAcroField.Keys.T, new PdfString("Signature1")); // TODO_OLD If already exists, will it cause error? implement a name chooser if yes. - signatureField.Elements.Add(PdfAcroField.Keys.Ff, new PdfInteger(132)); - signatureField.Elements.Add(PdfAcroField.Keys.DR, new PdfDictionary()); - signatureField.Elements.Add(PdfSignatureField.Keys.Type, new PdfName("/Annot")); - signatureField.Elements.Add("/Subtype", new PdfName("/Widget")); - signatureField.Elements.Add("/P", Document.Pages[Options.PageIndex]); - - signatureField.Elements.Add("/Rect", new PdfRectangle(Options.Rectangle)); - - signatureField.CustomAppearanceHandler = Options.AppearanceHandler ?? new DefaultSignatureAppearanceHandler() - { - Location = Options.Location, - Reason = Options.Reason, - Signer = Signer.CertificateName - }; - // TODO_OLD Call RenderCustomAppearance(); here. - signatureField.PrepareForSave(); // TODO_OLD PdfSignatureField.PrepareForSave() is not triggered automatically so let's call it manually from here, but it would be better to be called automatically. - - Document.Internals.AddObject(signatureField); - - return signatureField; - } - - PdfSignature2 GetSignatureDictionary(PdfSignaturePlaceholderItem contents, PdfPlaceholderObject byteRange) - { - PdfSignature2 signatureDic = new(Document); - - signatureDic.Elements.Add(PdfSignatureField.Keys.Type, new PdfName("/Sig")); - signatureDic.Elements.Add(PdfSignatureField.Keys.Filter, new PdfName("/Adobe.PPKLite")); - signatureDic.Elements.Add(PdfSignatureField.Keys.SubFilter, new PdfName("/adbe.pkcs7.detached")); - signatureDic.Elements.Add(PdfSignatureField.Keys.M, new PdfDate(DateTime.Now)); - - signatureDic.Elements.Add(PdfSignatureField.Keys.Contents, contents); - signatureDic.Elements.Add(PdfSignatureField.Keys.ByteRange, byteRange); - signatureDic.Elements.Add(PdfSignatureField.Keys.Reason, new PdfString(Options.Reason)); - signatureDic.Elements.Add(PdfSignatureField.Keys.Location, new PdfString(Options.Location)); - - var properties = new PdfDictionary(Document); - signatureDic.Elements.Add("/Prop_Build", properties); - var propertyItems = new PdfDictionary(Document); - properties.Elements.Add("/App", propertyItems); - propertyItems.Elements.Add("/Name", - String.IsNullOrWhiteSpace(Options.AppName) ? - new PdfName("/PDFsharp http://www.pdfsharp.net") : - PdfName.FromString(Options.AppName)); - - Document.Internals.AddObject(signatureDic); - - return signatureDic; - } - - PdfSignaturePlaceholderItem? _placeholderItem; - PdfPlaceholderObject? _signatureFieldByteRangePlaceholder; - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignaturePlaceholderItem.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignaturePlaceholderItem.cs deleted file mode 100644 index 7f0d2ab2..00000000 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/PdfSignaturePlaceholderItem.cs +++ /dev/null @@ -1,51 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using PdfSharp.Pdf.IO; - -namespace PdfSharp.Pdf.Signatures -{ - /// - /// Initializes a new instance of the class. - /// It represents a placeholder for a digital signature. - /// Note that the placeholder must be completely overridden, if necessary the signature - /// must be padded with trailing zeros. Blanks between the end of the hex-sting and - /// the end of the reserved space is considered as a certificate error by Acrobat. - /// - /// The size of the signature in bytes. - [DebuggerDisplay("({" + nameof(Size) + "})")] - sealed class PdfSignaturePlaceholderItem(int size) : PdfItem - { - /// - /// Returns the placeholder string padded with question marks to ensure that the code - /// fails if it is not correctly overridden. - /// - public override string ToString() => "<" + new string('?', 2 * Size)+ ">"; - - /// - /// Writes the item DocEncoded. - /// - internal override void WriteObject(PdfWriter writer) - => writer.Write(this, out _startPosition, out _endPosition); - - /// - /// Gets the number of bytes of the signature. - /// - public int Size { get; init; } = size; - - /// - /// Position of the first byte of this item in PdfWriter’s stream. - /// Precisely: The index of the '<'. - /// - public SizeType StartPosition => _startPosition; - SizeType _startPosition; - - /// - /// Position of the last byte of this item in PdfWriter’s stream. - /// Precisely: The index of the line feed behind '>'. - /// For timestamped signatures, the maximum length must be used. - /// - public SizeType EndPosition => _endPosition; - SizeType _endPosition; - } -} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/RangedStream.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/RangedStream.cs index 0746144e..01ed3178 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/RangedStream.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Signatures/RangedStream.cs @@ -1,6 +1,10 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +// v7.0.0 TODO review + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + namespace PdfSharp.Pdf.Signatures { /// @@ -8,15 +12,8 @@ namespace PdfSharp.Pdf.Signatures /// It is based on a stream plus a collection of ranges that define the significant content of this stream. /// The ranges are used to exclude one or more areas of the original stream. /// - class RangedStream : Stream // StL: Can I say 'RangedStream' in English? SlicedStream? + class RangedStream : Stream // StL: Can I say 'RangedStream' in English? SlicedStream? TODO SlicesdStream { - internal class Range(SizeType offset, SizeType length) - { - public SizeType Offset { get; set; } = offset; - - public SizeType Length { get; set; } = length; - } - public RangedStream(Stream originalStream, List ranges) { if (originalStream.CanRead != true) @@ -45,10 +42,10 @@ public RangedStream(Stream originalStream, List ranges) public override long Length => _ranges.Sum(item => item.Length); - private IEnumerable GetPreviousRanges(long position) + IEnumerable GetPreviousRanges(long position) => _ranges.Where(item => item.Offset < position && item.Offset + item.Length < position); - private Range? GetCurrentRange(long position) + Range? GetCurrentRange(long position) => _ranges.FirstOrDefault(item => item.Offset <= position && item.Offset + item.Length > position); public override long Position @@ -74,7 +71,8 @@ public override long Position } } - public override void Flush() => throw new NotImplementedException(nameof(Flush)); + public override void Flush() + => throw new NotImplementedException(nameof(Flush)); public override int Read(byte[] buffer, int offset, int count) { @@ -104,9 +102,9 @@ public override int Read(byte[] buffer, int offset, int count) // We come here e.g. with Bouncy Castle signer. // We calculate the current range for each byte in the stream using LINQ. - // This works, but is very slow. If we get performance issues + // This works, but is very inefficient. If we get performance issues // it should be reimplemented by using the ranges here. - // But this works, so YAGNI. + // But this is correct and works, so YAGNI. for (int i = 0; i < count; i++) { if (Stream.Position == length) @@ -128,14 +126,87 @@ void PerformSkipIfNeeded() Range GetNextRange() => _ranges.OrderBy(item => item.Offset).First(item => item.Offset > Stream.Position); - public override long Seek(long offset, SeekOrigin origin) => throw new NotImplementedException(nameof(Seek)); + public override long Seek(long offset, SeekOrigin origin) + => throw new NotImplementedException(nameof(Seek)); - public override void SetLength(long value) => throw new NotImplementedException(nameof(SetLength)); + public override void SetLength(long value) + => throw new NotImplementedException(nameof(SetLength)); - public override void Write(byte[] buffer, int offset, int count) => throw new NotImplementedException(nameof(Write)); + public override void Write(byte[] buffer, int offset, int count) + => throw new NotImplementedException(nameof(Write)); readonly Range[] _ranges; Stream Stream { get; } + + internal class Range(SizeType offset, SizeType length) + { + public SizeType Offset { get; set; } = offset; + + public SizeType Length { get; set; } = length; + } + } +} + +namespace PdfSharp.Pdf.Internal +{ + public class ManagerBase + { + public ManagerBase(PdfDocument document) + { + if (document == null!) + throw new ArgumentNullException(nameof(document)); + Document = document; + } + + internal void Foo2() { } + + internal bool IsInitialized; + + internal PdfDocument Document { get; } + } +} + +namespace PdfSharp.Pdf.Signatures +{ + using PdfSharp.Pdf.Internal; + + /// + /// The PdfAManager bundles PDF/A specific functionality of a PDF document. + /// + public class SigningManager : ManagerBase // RENAME? + { + // TODOs: + + /// + /// Initialized a new instance of this class for the specified document + /// + /// + SigningManager(PdfDocument document) : base(document) + { + Initialize(); + } + + public string SignatureType { get; } = "(TODO)"; // TODO + + /// + /// Gets or creates the PdfAManager for the specified document. + /// + public static SigningManager ForDocument(PdfDocument document) + => document.SigningManager ??= new(document); + + void Initialize() + { + Document.EnsureNotDisposed(); + if (!IsInitialized) + { + IsInitialized = true; + Document.EnsureNotYetSaved(); + if (Document.IsImported) + { + // TODO: Get PDF/A conformance + } + } + } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfAttributesBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfAttributesBase.cs index a39c5243..81ef95de 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfAttributesBase.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfAttributesBase.cs @@ -8,6 +8,12 @@ namespace PdfSharp.Pdf.Structure /// public abstract class PdfAttributesBase : PdfDictionary { + /// + /// Constructor of the abstract class. + /// + protected PdfAttributesBase() + { } + /// /// Constructor of the abstract class. /// @@ -17,9 +23,11 @@ internal PdfAttributesBase(PdfDocument document) { } /// - /// Constructor of the abstract class. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// - protected PdfAttributesBase() + internal PdfAttributesBase(PdfDictionary dict) + : base(dict) { } /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfLayoutAttributes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfLayoutAttributes.cs index 58ff0b13..2c9fa7f6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfLayoutAttributes.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfLayoutAttributes.cs @@ -18,6 +18,16 @@ internal PdfLayoutAttributes(PdfDocument document) SetOwner(); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfLayoutAttributes(PdfDictionary dict) + : base(dict) + { + SetOwner(); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkInformation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkInformation.cs index 47d1e933..9841db90 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkInformation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkInformation.cs @@ -22,6 +22,14 @@ public PdfMarkInformation(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfMarkInformation(PdfDictionary dict) + : base(dict) + { } + /// /// Predefined keys of this dictionary. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkedContentReference.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkedContentReference.cs index e0fabff2..3e4d58ef 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkedContentReference.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfMarkedContentReference.cs @@ -26,6 +26,16 @@ public PdfMarkedContentReference(PdfDocument document) Elements.SetName(Keys.Type, "/MCR"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfMarkedContentReference(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.Type, "/MCR"); + } + /// /// Predefined keys of this dictionary. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfObjectReference.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfObjectReference.cs index 7083e785..c9a800ae 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfObjectReference.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfObjectReference.cs @@ -26,6 +26,16 @@ public PdfObjectReference(PdfDocument document) Elements.SetName(Keys.Type, "/OBJR"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfObjectReference(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.Type, "/OBJR"); + } + /// /// Predefined keys of this dictionary. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureElement.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureElement.cs index 2886964e..9b487e84 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureElement.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureElement.cs @@ -28,6 +28,16 @@ public PdfStructureElement(PdfDocument document) Elements.SetName(Keys.Type, "/StructElem"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfStructureElement(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.Type, "/StructElem"); + } + internal override void PrepareForSave() { SimplifyKidsArray(); @@ -44,6 +54,7 @@ internal static IEnumerable GetKids(DictionaryElements? elements) { if (elements != null) { + //TODO: #warning What is the expectation GetObject should return? var k = elements.GetObject(Keys.K); // If k is holding an array, return all elements. @@ -80,7 +91,8 @@ internal static IEnumerable GetKids(DictionaryElements? elements) /// void SimplifyKidsArray() { - if (Elements[Keys.K] is PdfArray k && k.Elements.Count == 1) + //if (Elements[Keys.K] is PdfArray k && k.Elements.Count == 1) + if (Elements.GetArray(Keys.K) is { Elements.Count: 1 } k) // #US373 { var item = k.Elements[0]; Elements[Keys.K] = item; @@ -92,7 +104,8 @@ void SimplifyKidsArray() /// void SimplifyAttributes() { - var a = Elements[Keys.A]; + //var a = Elements[Keys.A]; + var a = Elements.GetValue(Keys.A); // #US373 if (a is PdfArray array) { @@ -145,7 +158,8 @@ bool AttributeDictionaryIsEmpty(PdfDictionary dictionary) T GetAttributes() where T : PdfAttributesBase, new() { - var a = Elements[Keys.A]; + //var a = Elements[Keys.A]; + var a = Elements.GetValue(Keys.A); // #US373 var array = a as PdfArray; if (array == null) { @@ -165,7 +179,7 @@ bool AttributeDictionaryIsEmpty(PdfDictionary dictionary) return item; } - // Create and add a new instance of T, if there’s no one. + // Create and add a new instance of T, if there’s none. var t = new T { Document = Owner }; array.Elements.Add(t); return t; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureTreeRoot.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureTreeRoot.cs index 0a54a11d..cfb395d3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureTreeRoot.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfStructureTreeRoot.cs @@ -28,6 +28,16 @@ public PdfStructureTreeRoot(PdfDocument document) Elements.SetName(Keys.Type, "/StructTreeRoot"); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfStructureTreeRoot(PdfDictionary dict) + : base(dict) + { + Elements.SetName(Keys.Type, "/StructTreeRoot"); + } + internal override void PrepareForSave() { foreach (var k in PdfStructureElement.GetKids(Elements)) @@ -71,7 +81,7 @@ internal class Keys : KeysBase /// Each integer key in the number tree corresponds to a single page of the /// document or to an individual object (such as an annotation or an XObject) /// that is a content item in its own right. The integer key is given as the - /// value of the StructParent or StructParents entry in that object. + /// value of the ParentInfo or StructParents entry in that object. /// The form of the associated value depends on the nature of the object: /// • For an object that is a content item in its own right, the value is an /// indirect reference to the object’s parent element (the structure element diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfTableAttributes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfTableAttributes.cs index 626e1114..e18d52a5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfTableAttributes.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Structure/PdfTableAttributes.cs @@ -18,6 +18,16 @@ internal PdfTableAttributes(PdfDocument document) SetOwner(); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfTableAttributes(PdfDictionary dict) + : base(dict) + { + SetOwner(); + } + /// /// Initializes a new instance of the class. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ArrayOrSingleItemHelper.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ArrayOrSingleItemHelper.cs index 9b0fc35f..0ef4d6db 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ArrayOrSingleItemHelper.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ArrayOrSingleItemHelper.cs @@ -1,12 +1,14 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Pdf.Advanced; + namespace PdfSharp.Pdf { /// /// Provides methods to handle keys that may contain a PdfArray or a single PdfItem. /// - public class ArrayOrSingleItemHelper + public class ArrayOrSingleItemHelper // #US373 StL: review { /// /// Initializes ArrayOrSingleItemHelper with PdfDictionary.DictionaryElements to work with. @@ -14,7 +16,7 @@ public class ArrayOrSingleItemHelper public ArrayOrSingleItemHelper(PdfDictionary.DictionaryElements elements) { _elements = elements; - _dictionary = elements.Owner; + _dictionary = elements.OwningDictionary; } /// @@ -26,21 +28,33 @@ public ArrayOrSingleItemHelper(PdfDictionary.DictionaryElements elements) /// True, if value shall be prepended instead of appended. public void Add(string key, PdfItem value, bool prepend = false) { - var obj = _elements[key]; + // #US373 + if (value is PdfReference reference) + { + if (reference.Value != null!) + { + value = reference.Value; + // Error + } + } - var array = obj as PdfArray; + //var oldValue = _elements[key]; // #US373: Should we expect references here? + var oldValue = _elements.GetValue(key); // #US373 + // oldValue can be null, a PDF item, or an array. + var array = oldValue as PdfArray; // If key is not yet set or key contains array without elements, assign value directly to key. - if (obj is null || array is not null && array.Elements.Count == 0) + if (oldValue is null || array is not null && array.Elements.Count == 0) { _elements[key] = value; return; } - // If not yet existing, create an array and assign the current directly assigned obj. + // If array is not yet existing, create one and assign the current directly assigned oldValue. if (array is null) { - array = new PdfArray(_dictionary._document, obj); + // oldValue is not null here. + array = new PdfArray(_dictionary.Document, oldValue); _elements[key] = array; } @@ -57,21 +71,26 @@ public void Add(string key, PdfItem value, bool prepend = false) /// The key in the dictionary to work with. public IEnumerable GetAll(string key) { - var obj = _elements[key]; - - if (obj is PdfArray array) + var value = _elements.GetValue(key); + if (value is PdfArray array) { foreach (var item in array.Elements) yield return item; } - else if (obj is not null) - yield return obj; + else + { + if (value is not null) + yield return value; + else + { + // TODO: Breaking change. Empty enumeration instead of null item. + yield break; + } + } } IEnumerable Get(string key, Func predicate) - { - return GetAll(key).Where(predicate); - } + => GetAll(key).Where(predicate); /// /// Gets the PdfItem(s) of type T saved in the given key, that match a predicate. @@ -79,9 +98,7 @@ IEnumerable Get(string key, Func predicate) /// The key in the dictionary to work with. /// The predicate, that shall be true for the desired item(s). public IEnumerable Get(string key, Func predicate) where T : PdfItem - { - return Get(key, x => x is T xT && predicate(xT)).Cast(); - } + => Get(key, x => x is T xT && predicate(xT)).Cast(); /// /// Gets the PdfItem(s) of type T saved in the given key, that are equal to value. @@ -90,9 +107,7 @@ public IEnumerable Get(string key, Func predicate) where T : PdfI /// The value, the desired item(s) shall be equal to. // Allows to call Equals with object. public IEnumerable Get(string key, object value) where T : PdfItem - { - return Get(key, x => x.Equals(value)); - } + => Get(key, x => x.Equals(value)); /// /// Gets the PdfItem(s) of type T saved in the given key, that are equal to value. @@ -101,9 +116,7 @@ public IEnumerable Get(string key, object value) where T : PdfItem /// The value, the desired item(s) shall be equal to. // Allows to omit the type parameter in the call. public IEnumerable Get(string key, T value) where T : PdfItem - { - return Get(key, x => x.Equals(value)); - } + => Get(key, x => x.Equals(value)); /// /// Returns true if the given key contains a PdfItem of type T matching a predicate. @@ -111,9 +124,7 @@ public IEnumerable Get(string key, T value) where T : PdfItem /// The key in the dictionary to work with. /// The predicate, that shall be true for the desired item(s). public bool Contains(string key, Func predicate) where T : PdfItem - { - return Get(key, x => x is T xT && predicate(xT)).Any(); - } + => Get(key, x => x is T xT && predicate(xT)).Any(); /// /// Returns true if the given key contains a PdfItem of type T, that is equal to value. @@ -122,9 +133,7 @@ public bool Contains(string key, Func predicate) where T : PdfItem /// The value, the desired item(s) shall be equal to. // Allows to call Equals with object. public bool Contains(string key, object value) where T : PdfItem - { - return Contains(key, x => x.Equals(value)); - } + => Contains(key, x => x.Equals(value)); /// /// Returns true if the given key contains a PdfItem of type T, that is equal to value. @@ -133,13 +142,12 @@ public bool Contains(string key, object value) where T : PdfItem /// The value, the desired item(s) shall be equal to. // Allows to omit the type parameter in the call. public bool Contains(string key, T value) where T : PdfItem - { - return Contains(key, x => x.Equals(value)); - } + => Contains(key, x => x.Equals(value)); bool Remove(string key, Func predicate) { - var obj = _elements[key]; + //var obj = _elements[key]; // #US373: Should we expect references here? + var obj = _elements.GetValue(key); // #US373 if (obj is null) return false; @@ -186,9 +194,7 @@ bool Remove(string key, Func predicate) /// The key in the dictionary to work with. /// The predicate, that shall be true for the desired item(s). public bool Remove(string key, Func predicate) where T : PdfItem - { - return Remove(key, x => x is T xT && predicate(xT)); - } + => Remove(key, x => x is T xT && predicate(xT)); /// /// Removes the PdfItem(s) of type T saved in the given key, that are equal to value. @@ -199,9 +205,7 @@ public bool Remove(string key, Func predicate) where T : PdfItem /// The value, the desired item(s) shall be equal to. // Allows to call Equals with object. public bool Remove(string key, object value) where T : PdfItem - { - return Remove(key, x => x.Equals(value)); - } + => Remove(key, x => x.Equals(value)); /// /// Removes the PdfItem(s) of type T saved in the given key, that are equal to value. @@ -212,9 +216,7 @@ public bool Remove(string key, object value) where T : PdfItem /// The value, the desired item(s) shall be equal to. // Allows to omit the type parameter in the call. public bool Remove(string key, T value) where T : PdfItem - { - return Remove(key, x => x.Equals(value)); - } + => Remove(key, x => x.Equals(value)); readonly PdfDictionary _dictionary; readonly PdfDictionary.DictionaryElements _elements; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ElementsBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ElementsBase.cs new file mode 100644 index 00000000..495e3500 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ElementsBase.cs @@ -0,0 +1,307 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; + +namespace PdfSharp.Pdf +{ + /// + /// Base class for both ArrayElements and DictionaryElements. + /// + /// The PdfContainer this instance belongs to. + public abstract class ElementsBase(PdfContainer owningContainer) + { + /// + /// Gets the PDF array or dictionary the elements belong to. + /// + public PdfContainer OwningContainer { get; internal set; } = owningContainer; + + /// + /// Marks a PDF item as 'not in use' anymore. + /// For a PDF reference or an indirect object the reference counter is decremented. + /// (Note that the reference counter is not yet used in PDFsharp.) + /// For a direct object the structure parent is set to null and the object can be + /// reused as a direct object or became an indirect object. + /// + protected static void ReleaseItem(PdfItem item) + { + //if (item == null) + // return; + if (item is PdfObject obj) + { + var reference = obj.Reference; + if (reference != null) + { + Debug.Assert(obj.ParentInfo == null, "Indirect PDF object must not have a ParentInfo."); + reference.Release(); + } + else + { + Debug.Assert(obj.ParentInfo != null, "Direct PDF object must have a ParentInfo."); + obj.SetStructureParentNull(); + // The object is not dead and can be reused. + } + } + else if (item is PdfReference reference) + { + // reference.Value can be null during reading a PDF document. + Debug.Assert(reference.Value?.ParentInfo == null, "Indirect PDF object must not have a ParentInfo."); + reference.Release(); + } + } + + /// + /// Creates a container of the specified type depending on an optional old container. + /// + /// The type of the object to be created. + /// Container its elements are moved to the newly created type. + /// If true creates an indirect object. + protected internal PdfContainer CreateContainer( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type, PdfContainer? oldContainer, bool createIndirect) + { +#if DEBUG_ + //if (ShouldBreak1 || oldContainer?.Aaa == "ABC") + // Debugger.Break(); + if (type == typeof(PdfFormXObject)) + _ = typeof(int); +#endif + PdfContainer cont; + if (oldContainer == null) + { + // Try to get constructor with signature 'Ctor(PdfDocument, bool)'. + var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [typeof(PdfDocument), typeof(bool)], null); + + if (ctorInfo != null) + { + cont = (PdfContainer)ctorInfo.Invoke([OwningContainer.Owner, false]); + } + else + { + // Try to get constructor with signature 'Ctor(PdfDocument)'. + ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [typeof(PdfDocument)], null); + if (ctorInfo != null) + { + cont = (PdfContainer)ctorInfo.Invoke([OwningContainer.Owner]); + } + else + { + // Try to get with signature 'Ctor()'. + ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [], null); + + if (ctorInfo != null) + { + cont = (PdfContainer)ctorInfo.Invoke([]); + } + else + throw new InvalidOperationException($"No appropriate constructor found for type {type.FullName}."); + } + } + Debug.Assert(cont?.IsTransformed is false); + cont.SetTransformed(); + } + else + { + if (type.IsInstanceOfType(oldContainer)) + Debug.Assert(false, "Should not happen."); + + ConstructorInfo? ctorInfo; + if (typeof(PdfDictionary).IsAssignableFrom(type)) + { + // Use constructor with signature 'Ctor(PdfDictionary array)'. + ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, types: [typeof(PdfDictionary)], null); + } + else if (typeof(PdfArray).IsAssignableFrom(type)) + { + // Use constructor with signature 'Ctor(PdfArray dict)'. + ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, types: [typeof(PdfArray)], null); + } + else + { + if (type == typeof(PdfRectangle)) + { + // IMPROVE see US291 + throw new InvalidOperationException("PdfRectangle is not implemented as an array, try use GetRectangle."); + } + throw new InvalidOperationException($"Type {type.Name} is not allowed here"); + } + + Debug.Assert(ctorInfo != null, $"No appropriate constructor found for type: {type.Name}."); + cont = (PdfContainer)ctorInfo.Invoke([oldContainer]); + //Debug.Assert(dict?.IsTransformed is false); + if (cont?.IsTransformed == false) + { + Debugger.Break(); + cont?.SetTransformed(); + } + } + Debug.Assert(cont?.IsTransformed is true); + return cont ?? NRT.ThrowOnNull(); + } + +#if true_ // KEEP for reference DELETE 25-12-31 + protected PdfArray CreateArray( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type, PdfArray? oldArray) + { +#if DEBUG_ + if (ShouldBreak1 || oldArray?.Aaa == "Stefan") + Debugger.Break(); +#endif + // PagesArray is a PdfArray. + //Debug.Assert(type != typeof(PdfArray)); + Debug.Assert(typeof(PdfArray).IsAssignableFrom(type)); + + PdfArray? array; + if (oldArray == null) + { + // Try constructor with signature 'Ctor(PdfDocument owner)'. + var ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [typeof(PdfDocument)], null); +#if true + if (ctorInfo != null) + { + array = ctorInfo.Invoke([OwningContainer.Owner]) as PdfArray; + } + else + { + // Try constructor with signature 'Ctor()'. + ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [], null); + + if (ctorInfo == null) + throw new InvalidOperationException( + $"No appropriate constructor found for type {type.FullName}."); + array = ctorInfo.Invoke([]) as PdfArray; + } + Debug.Assert(array?.IsTransformed is false); + array.SetTransformed(); +#else // DELETE + Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name); + array = ctorInfo.Invoke([OwningContainer.Owner]) as PdfArray; +#endif + } + else + { + // Use constructor with signature 'Ctor(PdfArray array)'. + var ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, types: [typeof(PdfArray)], null); + if (ctorInfo == null) + throw new InvalidOperationException($"No appropriate constructor found for type {type.FullName}."); + + array = ctorInfo.Invoke([oldArray]) as PdfArray; + //Debug.Assert(array?.IsTransformed is false); + array?.SetTransformed(); + } + return array ?? NRT.ThrowOnNull(); + } +#endif + +#if true_ // KEEP for reference + protected PdfDictionary CreateDictionary( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type, PdfDictionary? oldDictionary) + { + //Debug.Assert(type != typeof(PdfDictionary)); + Debug.Assert(typeof(PdfDictionary).IsAssignableFrom(type)); + + ConstructorInfo? ctorInfo; + PdfDictionary? dict; + if (oldDictionary == null) + { + // Try constructor with signature 'Ctor(PdfDocument owner)'. + ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [typeof(PdfDocument)], null); + + if (ctorInfo != null) + { + dict = ctorInfo.Invoke([OwningContainer.Owner]) as PdfDictionary; + } + else + { + // Try constructor with signature 'Ctor()'. + ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [], null); + + if (ctorInfo == null) + throw new InvalidOperationException($"No appropriate constructor found for type {type.FullName}."); + dict = ctorInfo.Invoke([]) as PdfDictionary; + } + Debug.Assert(dict?.IsTransformed is false); + dict.SetTransformed(); + } + else + { + // Use constructor with signature 'Ctor(PdfDictionary dict)'. + ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [typeof(PdfDictionary)], null); + if (ctorInfo == null) + throw new InvalidOperationException($"No appropriate constructor found for type {type.FullName}."); + + dict = ctorInfo.Invoke([oldDictionary]) as PdfDictionary; + //Debug.Assert(dict?.IsTransformed is false); + if (dict?.IsTransformed == false) + { + Debugger.Break(); + dict?.SetTransformed(); + } + } + Debug.Assert(dict?.IsTransformed is true); + return dict ?? NRT.ThrowOnNull(); + } +#endif + /// + /// Ensures that the specified value type is at least + /// of the specified generic type. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + protected internal void EnsureValueType(Type valueType) + { + + if (typeof(T).IsAssignableFrom(valueType)) + return; + ThrowInappropriateValueType(typeof(T), valueType); + } + + void ThrowInappropriateValueType(Type required, Type specified) + { + throw new InvalidOperationException($"The specified value type '{specified.FullName}' is not derived from '{required.FullName}'."); + } + + /// + /// Must not use direct primitive objects. + /// Use e.g. PdfString instead of PdfStringObject. + /// + protected static void FailForDirectPrimitiveObject(PdfObject obj) + { + // Case: Direct primitive object + // Use e.g. PdfString instead of PdfStringObject. + + Debug.Assert(obj is PdfPrimitiveObject); + + var name = obj.GetType().Name; + var replaceType = "(unknown)"; + if (!name.EndsWith("Object", StringComparison.Ordinal)) + throw new InvalidOperationException("Use Array, Dictionary, or Simple type."); + replaceType = name[..^"Object".Length]; + var message = $"{name} can only be used as an indirect object. Use {replaceType} instead."; + throw new InvalidOperationException(message); + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/EntryInfoAttribute.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/EntryInfoAttribute.cs index 78dcf9b6..391ace27 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/EntryInfoAttribute.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/EntryInfoAttribute.cs @@ -6,44 +6,54 @@ namespace PdfSharp.Pdf { /// - /// Specifies the type of a key’s value in a dictionary. + /// Specifies the type of the key’s value in a dictionary. /// [Flags] enum KeyType { - Name = 0x00000001, - String = 0x00000002, - Boolean = 0x00000003, - Integer = 0x00000004, - Real = 0x00000005, - Date = 0x00000006, - Rectangle = 0x00000007, - Array = 0x00000008, - Dictionary = 0x00000009, - Stream = 0x0000000A, - NumberTree = 0x0000000B, - Function = 0x0000000C, - TextString = 0x0000000D, - ByteString = 0x0000000E, - NameTree = 0x0000000F, - FileSpecification = 0x00000010, + // @@@ Review Flags + Name = 0x000_00001, + String = 0x0000_0002, + Boolean = 0x0000_0003, + Integer = 0x0000_0004, + Real = 0x0000_0005, + Date = 0x0000_0006, + Rectangle = 0x0000_0007, // IMPROVE: See #US291 + Array = 0x0000_0008, + Dictionary = 0x0000_0009, + Stream = 0x0000_000A, + NumberTree = 0x0000_000B, + Function = 0x0000_000C, + TextString = 0x0000_000D, + ByteString = 0x0000_000E, + NameTree = 0x0000_000F, + FileSpecification = 0x0000_0010, - NameOrArray = 0x00000100, - NameOrDictionary = 0x00000200, - ArrayOrDictionary = 0x00000300, - StreamOrArray = 0x00000400, - StreamOrName = 0x00000500, - ArrayOrNameOrString = 0x00000600, - FunctionOrName = 0x000000700, - Various = 0x000000800, + NameOrArray = 0x0000_0100, + NameOrDictionary = 0x0000_0200, + ArrayOrDictionary = 0x0000_0300, + StreamOrArray = 0x0000_0400, + StreamOrName = 0x0000_0500, + StreamOrDictionary = 0x0000_0600, + ArrayOrNameOrString = 0x0000_0700, + FunctionOrName = 0x0000_0800, + Various = 0x00000_0900, + ArrayOfDictionaries = 0x00000_0A00, + NameOrByteStringOrArray = 0x0000_0B00, // #US373: TODO Check String, ByteString, TextString - check if we have duplicates. + StringOrDictionary = 0x0000_0C00, + TextStringOrStream = 0x0000_0D00, + TextStringOrTextStream = 0x0000_0E00, + BooleanOrDictionary = 0x00000_0F00, - TypeMask = 0x00000FFF, + TypeMask = 0x0000_0FFF, - Optional = 0x00001000, - Required = 0x00002000, - Inheritable = 0x00004000, - MustBeIndirect = 0x00010000, - MustNotBeIndirect = 0x00020000, + Optional = 0x0000_1000, + Required = 0x0000_2000, + Inheritable = 0x0000_4000, + MustBeIndirect = 0x0001_0000, + MustNotBeIndirect = 0x0002_0000, + + DeprecatedIn20 = 0x000F_0000 } /// @@ -100,9 +110,10 @@ public KeyType KeyType KeyType _entryType; [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - public Type ObjectType + // ReSharper disable once ConvertToAutoProperty + public Type? ObjectType { - get => _objectType!; // ?? NRT.ThrowOnNull(); Can be null. + get => _objectType!; set => _objectType = value; } [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] @@ -110,7 +121,7 @@ public Type ObjectType public string FixedValue { - get => _fixedValue!; // ?? NRT.ThrowOnNull(); Can be null. + get => _fixedValue!; set => _fixedValue = value; } string? _fixedValue; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs index 1c38d1ed..a9c62141 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/KeysMeta.cs @@ -1,9 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Diagnostics.CodeAnalysis; using System.Reflection; -using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Attachments; namespace PdfSharp.Pdf { @@ -24,6 +23,10 @@ public KeyDescriptor(KeyInfoAttribute attribute) FixedValue = attribute.FixedValue; ObjectType = attribute.ObjectType; + if (ObjectType != null! + && !typeof(PdfArray).IsAssignableFrom(ObjectType) && !typeof(PdfDictionary).IsAssignableFrom(ObjectType)) + throw new InvalidOperationException($"The ObjectType '{ObjectType.FullName}' must be derived from PdfArray or PdfDictionary."); + if (Version == "") Version = "1.0"; } @@ -35,12 +38,12 @@ public KeyDescriptor(KeyInfoAttribute attribute) public KeyType KeyType { get; set; } - public string KeyValue { get; set; } = default!; + public string KeyValue { get; set; } = ""; public string FixedValue { get; } [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - public Type ObjectType { get; set; } + public Type? ObjectType { get; set; } public bool CanBeIndirect => (KeyType & KeyType.MustNotBeIndirect) == 0; @@ -48,7 +51,7 @@ public KeyDescriptor(KeyInfoAttribute attribute) /// Returns the type of the object to be created as value for the described key. /// [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - public Type GetValueType() + public Type? GetValueType() { var type = ObjectType; if (type == null!) @@ -61,6 +64,7 @@ public Type GetValueType() break; case KeyType.String: + case KeyType.TextString: // #US373 type = typeof(PdfString); break; @@ -96,6 +100,10 @@ public Type GetValueType() type = typeof(PdfDictionary); break; + case KeyType.StreamOrDictionary: + type = typeof(PdfDictionary); + break; + case KeyType.NumberTree: type = typeof(PdfNumberTreeNode); break; @@ -107,7 +115,18 @@ public Type GetValueType() case KeyType.FileSpecification: type = typeof(PdfFileSpecification); break; - +#if true + case KeyType.NameOrArray: + case KeyType.NameOrDictionary: // #US373 + case KeyType.ArrayOrDictionary: + case KeyType.StreamOrArray: + case KeyType.ArrayOrNameOrString: + case KeyType.NameOrByteStringOrArray: + case KeyType.StringOrDictionary: + case KeyType.TextStringOrStream: + case KeyType.TextStringOrTextStream: + return null!; +#else // The following types are not yet used. case KeyType.NameOrArray: @@ -121,8 +140,8 @@ public Type GetValueType() case KeyType.ArrayOrNameOrString: return null!; // HACK_OLD: Make PdfOutline work - //throw new NotImplementedException("KeyType.ArrayOrNameOrString"); - + //throw new NotImplementedException("KeyType.ArrayOrNameOrString"); +#endif default: Debug.Assert(false, "Invalid KeyType: " + KeyType); break; @@ -137,7 +156,9 @@ public Type GetValueType() /// class DictionaryMeta { - public DictionaryMeta([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type) + public DictionaryMeta( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + Type type) { var fields = type.GetFields(BindingFlags.Public | BindingFlags.Static | BindingFlags.FlattenHierarchy); foreach (var field in fields) @@ -148,7 +169,7 @@ public DictionaryMeta([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes var attribute = (KeyInfoAttribute)attributes[0]; var descriptor = new KeyDescriptor(attribute) { - KeyValue = (string)field.GetValue(null)! + KeyValue = (string)(field.GetValue(null) ?? throw new InvalidOperationException("Key field has no key name value.")) }; _keyDescriptors[descriptor.KeyValue] = descriptor; } @@ -162,7 +183,9 @@ public DictionaryMeta([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes /// The type. /// Default type of the content key. /// Default type of the content. - public DictionaryMeta([DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] Type type, + public DictionaryMeta( + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicFields)] + Type type, KeyType defaultContentKeyType, [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] Type defaultContentType) : this(type) @@ -181,7 +204,7 @@ public KeyDescriptor? this[string key] return keyDescriptor ?? _defaultContentKeyDescriptor; } } - readonly Dictionary _keyDescriptors = new(); + readonly Dictionary _keyDescriptors = []; /// /// The default content key descriptor used if no descriptor exists for a given key. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/Name.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/Name.cs new file mode 100644 index 00000000..b120910e --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/Name.cs @@ -0,0 +1,456 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Text; +using Microsoft.Extensions.Logging; +using PdfSharp.Logging; +using PdfSharp.Pdf.Internal; + +// v7.0 Ready + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf +{ + /// + /// Implements a PDF name as a value type. + /// + public readonly struct Name : IComparer, IEquatable + { + // Reference 2.0: 7.3.5 Name objects / Page 27 + + // ReSharper disable GrammarMistakeInComment + + // “Literal name” and “resulting name” are defined in PDF specs. + // See Table 4 — Examples of literal names on page 28. + // + // Canonical name is what we use during programming + // + // Literal name | Resulting name | << Terms used by Adobe/ISO + // Literal name | (not used) | Canonical name << Terms used in PDFsharp + // ----------------+------------------+------------------ + // /Name1 | Name1 | /Name1 + // /Lime#20Green | Lime Green | /Lime Green + // /Mambo#20#235 | Mambo #5 | /Mambo #5 + + // In one sentence: + // In PDFsharp, the canonical name is the resulting name prefixed with a '/'. + // This is compatible with the previous implementation of PdfName. + + // ReSharper restore GrammarMistakeInComment + + // Literal: What’s written into or coming from a PDF file. + // Canonical: What’s in the .NET string and used in C# code. + // Regular: Literal and canonical name are identical because + // there are characters that are escaped. + // + // The term resulting name used by PDF specification is identical with + // the canonical name, except the resulting name has no slash but the + // canonical name has one. + + // What happens if a '\0' character appears in a canonical name? + // PDFsharp skips this character. + // What happens if a "#00", "#0", or "#xx" character appears in a literal name? + // PDFsharp skips this character. + + /// + /// Initializes a new instance of the struct + /// with the empty name "/". + /// + public Name() + { + _literalName = _canonicalName = "/"; + } + + /// + /// Initializes a new instance of the struct + /// with a canonical name. The string must start with a "/". + /// + public Name(string canonicalName) + { + if (String.IsNullOrEmpty(canonicalName)) + throw new ArgumentException("A PDF name must not be null or empty.", nameof(canonicalName)); + + if (canonicalName[0] != '/') + throw new ArgumentException("A PDF name must start with a '/'."); + + _literalName = BuildLiteralName(canonicalName); + _canonicalName = canonicalName; + } + + internal Name(string literalName, string canonicalName) + { + _literalName = literalName; + _canonicalName = canonicalName; + } + + /// + /// Creates a object from an enum value. + /// + public static Name FromEnum(T value) where T : Enum + => new('/' + value.ToString()); + + /// + /// Gets the literal value of the name. + /// That is the form which is written in a PDF file. + /// + // ReSharper disable once ConvertToAutoPropertyWhenPossible + public string LiteralValue => _literalName; + + /// + /// Gets the canonical value of the name. + /// That is the form used as keys in PdfDictionary. + /// + // ReSharper disable once ConvertToAutoPropertyWhenPossible + public string Value => _canonicalName; + + public bool IsEmpty => _canonicalName.Length == 1; + + /// + /// Ensures that the name is formally correct. + /// It must be a non-empty string starting with a '/'. + /// + public static void EnsureName(string name) + { + if (String.IsNullOrEmpty(name)) + throw new ArgumentNullException(nameof(name)); + + if (name[0] != '/') + throw new ArgumentException($"The PDF name '{name}' must start with a slash ('/')."); + } + + /// + /// Converts a string into a canonical name by adding a '/' + /// as prefix to the string. + /// If the specified string already starts with a '/' + /// no action is taken. + /// + public static string MakeName(string name) + { + if (String.IsNullOrEmpty(name)) + return "/"; + if (name[0] != '/') + return String.Concat('/', name); + return name; + } + + /// + /// Removes the slash from a string, that is needed at the beginning of a PDF name. + /// + public static string RemoveSlash(string value) + { + return value.Length == 0 || value[0] != '/' + ? value + : value[1..]; + } + /// + /// Creates a new instance of Name from a literal name. + /// + public static Name FromLiteralName(string literalName) + { + if (String.IsNullOrEmpty(literalName)) + throw new ArgumentNullException(nameof(literalName)); + + if (literalName[0] != '/') + throw new ArgumentException($"Literal name '{literalName}' must start with a slash ('/')."); + + var (literal, canonical) = BuildCanonicalName(literalName); + return new(literal, canonical); + } + + /// + /// Creates a new instance of Name from a canonical name. + /// + public static Name FromCanonicalName(string canonicalName) + { + if (String.IsNullOrEmpty(canonicalName)) + throw new ArgumentNullException(nameof(canonicalName)); + + if (canonicalName[0] != '/') + throw new ArgumentException($"Canonical name '{canonicalName}' must start with a slash ('/')."); + + return new(BuildLiteralName(canonicalName), canonicalName); + } + + public static readonly Name Empty = new(); + + static string BuildLiteralName(string canonicalName) + { + // Case: The canonical name is defined and the literal name is created. + // ISSUE: What if we get a '\0'? -> ignore it + + + Debug.Assert(!String.IsNullOrEmpty(canonicalName) && canonicalName[0] == '/'); + + // The specification says: + // “Regular characters that are outside the range EXCLAMATION MARK(21h) (!) to + // TILDE (7Eh) (~) should be written using the hexadecimal notation.” + // and + // “ + // a) A NUMBER SIGN (23h) (#) in a name shall be written by using its 2-digit hexadecimal + // code (23), preceded by the NUMBER SIGN. + // b) Any character in a name that is a regular character (other than NUMBER SIGN) shall + // be written as itself or by using its 2-digit hexadecimal code, preceded by the + // NUMBER SIGN. + // c) Any character that is not a regular character shall be written using its 2-digit + // hexadecimal code, preceded by the NUMBER SIGN only. + // ” + // PDFsharp escapes the 10 regular characters that are used as delimiters defined in + // “Table 2 — Delimiter characters” on page 23. + + // Step 1 + // Encode to raw string using UTF-8 encoding if any char is larger than 126. + // 127 [DEL] is not a valid value and is also encoded. + string name = canonicalName; + int length = name.Length; + for (int idx = 1; idx < length; idx++) + { + char ch = name[idx]; + if (ch == '\0') + { + // PDFsharp skips null characters. + idx++; + continue; + } + if (ch >= 127) + { + // Case: First non-ASCII character found - convert whole string to raw UTF-8. + + // Interpret name as Unicode and get the bytes. + var bytes = Encoding.UTF8.GetBytes(name); + // Convert bytes into a PDFsharp raw string. + name = PdfEncoders.RawEncoding.GetString(bytes); + break; + } + } + + // Step 2 + // Escape characters/bytes not allowed in PDF names. + // Avoid allocation of StringBuilder if name is regular. + StringBuilder? sb = null; + length = name.Length; + for (int idx = 1; idx < length; idx++) + { + var ch = name[idx]; + Debug.Assert(ch < 256); + // Must escape this character/byte? + if (ch switch + { + < '!' or > '~' => true, + '#' => true, // PDF name escaping + // Reference 2.0: 7.1 Table 2 — Delimiter characters / Page 23 + '(' => true, // PDF string delimiter + ')' => true, // " + '<' => true, // PDF dictionary delimiter + '>' => true, // " + '[' => true, // PDF array delimiter + ']' => true, // " + '{' => true, // Type 4 PostScript calculator functions + '}' => true, // " + '/' => true, // PDF names delimiter + '%' => true, // PDF comments delimiter + _ => false + }) + { + sb ??= new(name[..idx]); + sb.Append('#'); + byte hi = (byte)((ch >> 4) + '0'); + byte lo = (byte)((ch & 0xF) + '0'); + sb.Append((char)(hi < ':' ? hi : hi + ('A' - ':'))); + sb.Append((char)(lo < ':' ? lo : lo + ('A' - ':'))); + } + else + { + // No string builder means no escaping needed yet. + sb?.Append(ch); + } + } + // String builder is null if name is regular. + return sb?.ToString() ?? name; + } + + static (string Literal, string Canonical) BuildCanonicalName(string literalName) + { + // ISSUE: What if we get a '#00'? Solved: ignore. + + // Case: The literal name is defined and the canonical name is created. + + Debug.Assert(true); // Dummy statement to prevent ill formatted code because of label. + TryAgain: + string name = literalName; + // Step 1 + // Unescape all characters/bytes. + // Avoid allocation of StringBuilder if name is regular. + StringBuilder? sb = null; + int length = name.Length; + bool illegalLiteralCharacterFound = false; + for (int idx = 1; idx < length; idx++) + { + var ch = name[idx]; + if (ch == '#') + { + sb ??= new(name[..idx]); + + // Check the bs stuff. + if (idx + 2 >= length) + { + // Just add some filler and don’t care. + literalName += idx + 1 == length ? "00" : '0'; + goto TryAgain; + } + + char hi = name[++idx]; + char lo = name[++idx]; + ch = (char)((hi switch + { + >= '0' and <= '9' => hi - '0', + >= 'A' and <= 'F' => hi - ('A' - 10), // Without parenthesis the expressions are not + >= 'a' and <= 'f' => hi - ('a' - 10), // optimized as constant expressions in IL. + _ => LogError(hi) + } << 4) + lo switch + { + >= '0' and <= '9' => lo - '0', + >= 'A' and <= 'F' => lo - ('A' - 10), + >= 'a' and <= 'f' => lo - ('a' - 10), + _ => LogError(lo) + }); + if (ch == '\0') + { + PdfSharpLogHost.Logger.LogError("Name '{name}' contains illegal '#' sequence that evaluates to '\\0' and is skipped.", name); + illegalLiteralCharacterFound = true; + continue; + } + static int LogError(int ch) + { + PdfSharpLogHost.Logger.LogError("Name contains illegal character '{char}' in escape sequence.", ch); + return 0; + } + sb.Append(ch); + } + else + { + // What if a literal name contains illegal characters? + if (ch switch + { + < '!' or > '~' => true, + // Already checked: '#' => true, // PDF name escaping + // Reference 2.0: 7.1 Table 2 — Delimiter characters / Page 23 + '(' => true, // PDF string delimiter + ')' => true, // " + '<' => true, // PDF dictionary delimiter + '>' => true, // " + '[' => true, // PDF array delimiter + ']' => true, // " + '{' => true, // Type 4 PostScript calculator functions + '}' => true, // " + '/' => true, // PDF names delimiter + '%' => true, // PDF comments delimiter + _ => false + }) + { + // The literal name contains at least one illegal character. + illegalLiteralCharacterFound = true; + sb ??= new(name[..idx]); + // Append the UTF-8 bytes of the illegal character as a raw string. + sb.Append(PdfEncoders.RawEncoding.GetString(Encoding.UTF8.GetBytes([ch]))); + } + else + { + sb?.Append(ch); + } + } + } + + // Step 2 + // If a StringBuilder was created, at least one either escaped or an illegal character was + // found. As the PDF specs recommend, we assume that the string is UTF-8 encoded. + // If no string builder was created, name is regular and therefore always legal. + if (sb != null) + { + // Convert raw name into a Unicode string using UTF-8 decoding. This is our canonical name. + name = Encoding.UTF8.GetString(PdfEncoders.RawEncoding.GetBytes(sb.ToString())); + } + + if (illegalLiteralCharacterFound) + { + // Rebuild literal name because it was not a legal literal one. + literalName = BuildLiteralName(name); + } + return (literalName, name); + } + + public bool Equals(Name other) + => Comparer.Compare(this, other) == 0; + + public override bool Equals(object? obj) + { + if (obj is Name name) + return Comparer.Compare(this, name) == 0; + return true; + } + + public override int GetHashCode() + { + unchecked + { + return (_literalName.GetHashCode() * 397) ^ _canonicalName.GetHashCode(); + } + } + + public static implicit operator Name(string name) => new(name); + public static implicit operator String(Name name) => name.Value; + + public static bool operator ==(Name l, Name r) + => Comparer.Compare(l, r) == 0; + + public static bool operator !=(Name l, Name r) + => !(l == r); + + /// + /// Compares two names. + /// + public int Compare(Name l, Name r) + => Comparer.Compare(l, r); + + /// + /// Gets the comparer for this type. + /// + public static NameComparer Comparer => _comparer ??= new(); + + /// + /// Implements a comparer that compares Name objects. + /// + public class NameComparer : IComparer + { + /// + /// Compares two names and returns a value indicating whether one is less than, equal to, or greater than the other. + /// + public int Compare(Name? l, Name? r) + { + // Compare the canonical name. + if (l != null) + { + return r != null + ? String.Compare(l.Value.Value, r.Value.Value, StringComparison.Ordinal) + : 1; + } + return r == null ? 0 : -1; + } + } + + // Recall that Name is a value type. Therefore, it makes no sense to + // lazy evaluate one of the two strings when the other was specified. + + /// + /// The literal name with escaped characters. + /// + readonly string _literalName; + + /// + /// The resulting name with solidus. + /// + readonly string _canonicalName; + + static NameComparer? _comparer; + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ParentInfo.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ParentInfo.cs new file mode 100644 index 00000000..e7942667 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/ParentInfo.cs @@ -0,0 +1,82 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf +{ + /// + /// Represents a reference to a PdfDictionary.DictionaryElements or PdfArray.ArrayElements + /// a direct PdfObject belongs to. Only a direct PdfObject can have a structure parent. + /// + internal class ParentInfo + { + /// + /// Creates a new ParentInfo instance for a PdfObject that is owned by a PdfDictionary. + /// + /// The dictionary elements that owns the owned object belongs to. + /// The key in the owning dictionary. + public ParentInfo(PdfDictionary.DictionaryElements elements, string key) + { + OwningElements = elements; + Key = key; + } + + /// + /// Creates a new ParentInfo instance for a PdfObject that is owned by a PdfArray. + /// + /// The array elements that owns the owned object belongs to. + /// The index in the owning array. + public ParentInfo(PdfArray.ArrayElements elements, int index) + { + OwningElements = elements; + Index = index; + } + + /// + /// Returns true if this ParentInfo belongs to a PdfArray, false otherwise. + /// + public bool IsArray => Index != -1; + + /// + /// Returns true if this ParentInfo belongs to a PdfDictionary, false otherwise. + /// + public bool IsDictionary => Key.Length > 0; + + /// + /// Gets the index of the PdfObject in the PDF array if it is owned by an array, + /// -1 otherwise. + /// + public int Index { get; private set; } = -1; + + /// + /// Gets the of the PdfObject in the PDF dictionary if it is owned by a dictionary, + /// empty string otherwise. + /// + public string Key { get; } = ""; + + public ElementsBase OwningElements { get; private set; } + + /// + /// Gets the owning PdfArray if the PdfObject is owned by an array, + /// null otherwise. + /// + public PdfArray OwningArray => IsArray ? (PdfArray)OwningElements.OwningContainer : null!; + + /// + /// Gets the owning PdfDictionary if the PdfObject is owned by a dictionary, + /// null otherwise. + /// + public PdfDictionary OwningDictionary => IsDictionary ? (PdfDictionary)OwningElements.OwningContainer : null!; + + /// + /// Adjust the index if the owning array is modified. + /// This happens when an item is inserted or deleted from an array before this index. + /// + /// The correction value added to the current index. + internal void AdjustIndex(int offset) + { + Debug.Assert(IsArray); + Index += offset; + Debug.Assert(Index >= 0 && Index < ((PdfArray.ArrayElements)OwningElements).Count); + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs index 273e9f2f..5c4d3a95 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArray.cs @@ -1,32 +1,47 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using System.Collections; +using System.Runtime.CompilerServices; using System.Text; +using Microsoft.Extensions.Logging; +using PdfSharp.Internal; +using PdfSharp.Logging; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.IO; +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + namespace PdfSharp.Pdf { /// /// Represents a PDF array object. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public class PdfArray : PdfObject, IEnumerable + public class PdfArray : PdfContainer, IEnumerable { + // Reference 1.7: 3.2.5 Array Objects / Page 58 + // Reference 2.0: 7.3.6 Array objects / Page 29 + /// /// Initializes a new instance of the class. /// public PdfArray() - { } + { + // Direct object. + ItemFlags |= ItemFlags.IsArray; + } /// /// Initializes a new instance of the class. /// /// The document. - public PdfArray(PdfDocument document) - : base(document) - { } + /// If true creates an indirect object. + public PdfArray(PdfDocument document, bool createIndirect = false) + : base(document, createIndirect) + { + ItemFlags |= ItemFlags.IsArray; + } /// /// Initializes a new instance of the class. @@ -38,21 +53,62 @@ public PdfArray(PdfDocument document, params PdfItem[] items) { foreach (var item in items) Elements.Add(item); + + ItemFlags |= ItemFlags.IsArray; + } + + public PdfArray(params PdfItem[] items) : this() + { + foreach (var item in items) + Elements.Add(item); + } + + internal PdfArray(params int[] items) : this() + { + foreach (var item in items) + Elements.Add(new PdfInteger(item)); + } + + internal PdfArray(params double[] items) : this() + { + foreach (var item in items) + Elements.Add(new PdfReal(item)); } /// - /// Initializes a new instance from an existing dictionary. Used for object type transformation. + /// Initializes a new instance from an existing array. + /// Used for object type transformation. /// - /// The array. protected PdfArray(PdfArray array) : base(array) { - if (array._elements != null) - array._elements.ChangeOwner(this); +#if DEBUG + // Protect against unintended invocation of this cont. + var oldType = array.GetType(); + if (oldType != typeof(PdfArray)) + { + var newType = GetType(); + if (oldType == newType) + { + throw new InvalidOperationException($"You try to convert a PDF array into type '{newType.FullName}', " + + $"but the array is already of this type."); + } + + if (!oldType.IsAssignableFrom(newType)) + { + throw new InvalidOperationException($"You try to convert type '{oldType.FullName}' into type '{newType.FullName}', " + + $"but '{newType.Name}' is not derived from '{oldType.Name}'."); + } + } +#endif + // Move ownership of elements to this instance. + array._elements?.ChangeOwner(this); + array.SetDead(); + ItemFlags |= ItemFlags.IsArray | ItemFlags.IsTransformed; } /// - /// Creates a copy of this array. Direct elements are deep copied. + /// Creates a copy of this array. Direct elements are deep-copied. /// Indirect references are not modified. /// public new PdfArray Clone() @@ -63,25 +119,34 @@ protected PdfArray(PdfArray array) /// protected override object Copy() { + // Clone array. var array = (PdfArray)base.Copy(); - if (array._elements != null) + var elements = array._elements; + if (elements != null) { - array._elements = array._elements.Clone(); - int count = array._elements.Count; - for (int idx = 0; idx < count; idx++) - { - PdfItem item = array._elements[idx]; - if (item is PdfObject) - array._elements[idx] = item.Clone(); - } + elements = elements.Clone(); + array._elements = elements; + elements.ChangeOwner(array); } return array; } /// - /// Gets the collection containing the elements of this object. + /// Gets the collection containing the elements of this array. + /// + public ArrayElements Elements + { + get + { + EnsureAlive(); + return _elements ??= new(this); + } + } + + /// + /// The elements of the array. /// - public ArrayElements Elements => _elements ??= new ArrayElements(this); + ArrayElements? _elements; /// /// Returns an enumerator that iterates through a collection. @@ -93,48 +158,126 @@ IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// - /// Returns a string with the content of this object in a readable form. Useful for debugging purposes only. + /// Returns a string with the content of this object in a readable form. + /// Useful for debugging purposes only. /// public override string ToString() { - var pdf = new StringBuilder(); - pdf.Append("[ "); + var text = new StringBuilder(); + text.Append('['); int count = Elements.Count; for (int idx = 0; idx < count; idx++) - pdf.Append(Elements[idx] + " "); - pdf.Append("]"); - return pdf.ToString(); + { + if (idx != 0) + text.Append(' '); + text.Append(Elements[idx]); + } + text.Append(']'); + return text.ToString(); } internal override void WriteObject(PdfWriter writer) { writer.WriteBeginObject(this); int count = Elements.Count; +#if TEST_CODE_ + // Ensure that PDFsharp does not use PdfLiterals anymore (PdfMatrix, PdfOutline, ...). + //if (count == 4 && Elements.GetName(1) == "/XYZ" && Elements.GetInteger(2) == 842) + if (count is > 3 and <= 5) + { + if (ParentInfo?.OwningElements.OwningContainer.ObjectID.ObjectNumber == 9658) + Debugger.Break(); + var item0 = Elements[0]; + var item1 = Elements[1]; + var item2 = Elements[2]; + if (item0 is PdfReference iref && iref.ObjectNumber == 29) + { + _ = typeof(int); + } + if (item1 is PdfName name && name.Value == "/XYZ") + // && + //(item2 is PdfInteger integer && integer.Value == 842)) + { + _ = typeof(int); + } + } +#endif for (int idx = 0; idx < count; idx++) { - PdfItem value = Elements[idx]; + var value = Elements[idx]; value.WriteObject(writer); } writer.WriteEndObject(); } /// - /// Represents the elements of an PdfArray. + /// Clones the elements of the specified PDF dictionary. + /// + internal void CloneElementsOf(PdfArray array) + { + _elements = array.Elements.Clone(); + // Note that ParentInfo is still null for each item. + _elements.OwningContainer = this; + } + + // There is no "ArrayMetadata". + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + new void EnsureAlive() + { + if (IsDead) + { + throw new InvalidOperationException( + "This array cannot be used anymore, because its content was now owned by an object of a derived class."); + } + } + + /// + /// Represents the elements of a PdfArray. /// - public sealed class ArrayElements : IList, ICloneable + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + public sealed class ArrayElements : ElementsBase, IList, ICloneable { - internal ArrayElements(PdfArray array) + internal ArrayElements(PdfArray owningArray) + : base(owningArray) { - _elements = new List(); - _ownerArray = array; + Debug.Assert(ReferenceEquals(OwningArray, owningArray)); } object ICloneable.Clone() { - var elements = (ArrayElements)MemberwiseClone(); - elements._elements = [..elements._elements]; - elements._ownerArray = null; - return elements; + // Shallow clone the list elements. + var arrayElements = (ArrayElements)MemberwiseClone(); + var elements = new List(arrayElements._elements); + arrayElements._elements = elements; + arrayElements.OwningContainer = null!; + + // Clone all direct objects. + int count = Count; + for (int idx = 0; idx < count; idx++) + { + var item = elements[idx]; + if (item is PdfObject obj) + { + // Case: item is a direct object. + + Debug.Assert(obj.IsIndirect is false); + Debug.Assert(obj.ParentInfo is not null); + obj = obj.Clone(); + Debug.Assert(obj.ParentInfo is null); + obj.SetStructureParent(arrayElements, idx); + elements[idx] = obj; + } + else if (item is PdfReference reference) + { + reference.AddRef(); + } + else + { + _ = typeof(int); + } + } + return arrayElements; } /// @@ -146,221 +289,1208 @@ public ArrayElements Clone() /// /// Moves this instance to another array during object type transformation. /// - internal void ChangeOwner(PdfArray array) + internal void ChangeOwner(PdfArray newOwningArray) { - if (_ownerArray != null) + if (OwningArray != null!) { - // ??? + // Can this assertion really fail? + Debug.Assert(ReferenceEquals(this, OwningArray._elements)); + + // Disconnect old owner from this ArrayElements. + OwningArray._elements = null; } // Set new owner. - _ownerArray = array; + OwningContainer = newOwningArray; // Set owners elements to this. - array._elements = this; + newOwningArray._elements = this; } + // ===== PdfBoolean ===== + /// - /// Converts the specified value to boolean. - /// If the value does not exist, the function returns false. - /// If the value is not convertible, the function throws an InvalidCastException. - /// If the index is out of range, the function throws an ArgumentOutOfRangeException. + /// Gets the boolean value for the specified index. + /// If the value exists but is neither a boolean nor a PDF reference to a boolean, + /// the function throws an InvalidOperationException. /// public bool GetBoolean(int index) { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3); + TryGetBooleanInternal(index, out bool result, true); + return result; + } + + /// + /// Tries to get the boolean value for the specified index. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetBoolean(int index, out bool result) + => TryGetBooleanInternal(index, out result, false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetBooleanInternal(int index, out bool result, bool throwOnTypeMismatch) + { + EnsureIndex(index, true); - object obj = this[index]; - //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21 - return obj switch + var value = this[index]; + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - null => false, PdfBoolean boolean => boolean.Value, - PdfBooleanObject booleanObject => booleanObject.Value, - _ => throw new InvalidCastException("GetBoolean: Object is not a boolean.") + PdfBooleanObject boolean => boolean.Value, + _ => Fail() }; + return success; + + bool Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfBoolean), value.GetType()).Message); + } + success = false; + return false; + } + } + + /// + /// Sets the entry to the specified value. + /// + public void SetBoolean(int index, bool value) + { + SetValueInternal(index, new PdfBoolean(value)); } + // ===== PdfInteger ===== + /// - /// Converts the specified value to integer. - /// If the value does not exist, the function returns 0. - /// If the value is not convertible, the function throws an InvalidCastException. - /// If the index is out of range, the function throws an ArgumentOutOfRangeException. + /// Gets the integer value for the specified index. + /// If the value exists but is neither an integer nor a PDF reference to an integer, + /// the function throws an InvalidOperationException. /// public int GetInteger(int index) { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3); + TryGetIntegerInternal(index, out int result, true); + return result; + } + + /// + /// Tries to get the integer value for the specified index. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetInteger(int index, out int result) + => TryGetIntegerInternal(index, out result, false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetIntegerInternal(int index, out int result, bool throwOnTypeMismatch) + { + EnsureIndex(index, true); - object obj = this[index]; - //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21 - return obj switch + var value = this[index]; + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - null => 0, PdfInteger integer => integer.Value, - PdfIntegerObject integerObject => integerObject.Value, - _ => throw new InvalidCastException("GetInteger: Object is not an integer.") + PdfIntegerObject integer => integer.Value, + _ => Fail() }; + return success; + + int Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfInteger), value.GetType()).Message); + } + success = false; + return 0; + } } /// - /// Converts the specified value to double. - /// If the value does not exist, the function returns 0. - /// If the value is not convertible, the function throws an InvalidCastException. - /// If the index is out of range, the function throws an ArgumentOutOfRangeException. + /// Sets the entry to the specified value. /// - public double GetReal(int index) + public void SetInteger(int index, int value) + { + EnsureIndex(index, true); + + SetValueInternal(index, new PdfInteger(value)); + } + + // No GetUnsignedInteger or TryGetUnsignedInteger yet. + // Will be implemented if we find a use case. + + // ===== PdfLongInteger ===== + + /// + /// Gets the long integer value for the specified index. + /// If the value exists but is neither an integer or a long integer + /// nor a PDF reference to an integer or a long integer, + /// the function throws an InvalidOperationException. + /// + public long GetLongInteger(int index) + { + TryGetLongIntegerInternal(index, out long result, true); + return result; + } + + /// + /// Tries to get the long integer value for the specified index. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetLongInteger(int index, out long result) + => TryGetLongIntegerInternal(index, out result, false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetLongIntegerInternal(int index, out long result, bool throwOnTypeMismatch) { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3); + EnsureIndex(index, true); - object obj = this[index]; - //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21 - if (obj is PdfReference reference) + var value = this[index]; + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - //Debug.Assert(false, "Check why this is not PdfRealObject or PdfIntegerObject."); + PdfInteger integer => integer.Value, + PdfIntegerObject integer => integer.Value, + PdfLongInteger integer => integer.Value, + PdfLongIntegerObject integer => integer.Value, + _ => Fail() + }; + return success; - // ReSharper disable once RedundantCast - obj = (object)reference.Value switch + long Fail() + { + if (throwOnTypeMismatch) { - PdfReal real => real, - PdfInteger integer => integer, - PdfRealObject realObject => realObject, - PdfIntegerObject integerObject => integerObject, - _ => throw new InvalidCastException("GetReal: Referenced object is not a number.") - }; + throw new InvalidOperationException( + SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfLongInteger), value.GetType()).Message); + } + success = false; + return 0; } + } + + /// + /// Sets the entry to the specified value. + /// + public void SetLongInteger(int index, long value) + { + SetValueInternal(index, new PdfLongInteger(value)); + } + + // ===== PdfReal ===== + + /// + /// Gets the real value for the specified index. + /// If the value exists but is neither an integer nor a real + /// nor a PDF reference to an integer or a real, + /// the function throws an InvalidOperationException. + /// + public double GetReal(int index) + { + TryGetRealInternal(index, out double result, true); + return result; + } - return obj switch + /// + /// Tries to get the real value for the specified index. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetReal(int index, out double result) + => TryGetRealInternal(index, out result, false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetRealInternal(int index, out double result, bool throwOnTypeMismatch) + { + EnsureIndex(index, true); + + var value = this[index]; + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - null => 0, - PdfReal real => real.Value, - PdfRealObject realObject => realObject.Value, PdfInteger integer => integer.Value, - PdfIntegerObject integerObject => integerObject.Value, - _ => throw new InvalidCastException("GetReal: Object is not a number.") + PdfIntegerObject integer => integer.Value, + PdfLongInteger integer => integer.Value, + PdfLongIntegerObject integer => integer.Value, + PdfReal real => real.Value, + PdfRealObject real => real.Value, + _ => Fail() }; + return success; + + double Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfReal), value.GetType()).Message); + } + success = false; + return 0; + } + } + + /// + /// Sets the entry to the specified value. + /// + public void SetReal(int index, double value) + { + SetValueInternal(index, new PdfReal(value)); } + // ===== PdfReal as nullable value ===== + /// - /// Converts the specified value to double?. - /// If the value does not exist, the function returns null. - /// If the value is not convertible, the function throws an InvalidCastException. - /// If the index is out of range, the function throws an ArgumentOutOfRangeException. + /// Gets the double? value for the specified index. + /// If the value exists but is neither a null object nor a real or integer + /// nor a PDF reference to a null object, a real or integer, + /// the function throws an InvalidOperationException. /// - public double? GetNullableReal(int index) + public double? GetNullableReal(int index/*, double? defaultValue=*/) { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3); + // Note that this function exists for arrays only. It makes no sense for dictionaries, + // because entries like "/SomeNumber null" do not exist. + + TryGetNullableRealInternal(index, out double? result, true); + return result; + } + + /// + /// Tries to get the double? value for the specified index. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetNullableReal(int index, out double? result) + => TryGetNullableRealInternal(index, out result, false); - object obj = this[index]; - //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21 - return obj switch + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetNullableRealInternal(int index, out double? result, bool throwOnTypeMismatch) + { + var value = this[index]; + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - null => null, PdfNull => null, PdfNullObject => null, - PdfReal real => real.Value, - PdfRealObject realObject => realObject.Value, PdfInteger integer => integer.Value, - PdfIntegerObject integerObject => integerObject.Value, - _ => throw new InvalidCastException("GetReal: Object is not a number.") + PdfIntegerObject integer => integer.Value, + PdfLongInteger integer => integer.Value, + PdfLongIntegerObject integer => integer.Value, + PdfReal real => real.Value, + PdfRealObject real => real.Value, + _ => Fail() }; + return success; + + double? Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfReal), value.GetType()).Message); + } + success = false; + return null; + } } + // No SetNullableReal because it makes no sense. + + // ===== PdfString ===== + /// - /// Converts the specified value to string. - /// If the value does not exist, the function returns the empty string. - /// If the value is not convertible, the function throws an InvalidCastException. - /// If the index is out of range, the function throws an ArgumentOutOfRangeException. + /// Gets the string value for the specified index. + /// If the value exists but is neither a string nor a PDF reference to a string, the function throws an InvalidOperationException. /// public string GetString(int index) { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3); + TryGetStringInternal(index, out string result, true); + return result; + } + + /// + /// Tries to get the string value for the specified index. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetString(int index, out string result) + => TryGetStringInternal(index, out result, false); - object obj = this[index]; - //object? obj = GetObject(index); // TODO_OLD Do this for all conversions! 2023-06-21 - return obj switch + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetStringInternal(int index, out string result, bool throwOnTypeMismatch) + { + var value = this[index]; + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - null => "", PdfString str => str.Value, - PdfStringObject strObject => strObject.Value, - _ => throw new InvalidCastException("GetString: Object is not a string.") + PdfStringObject str => str.Value, + _ => Fail() }; + return success; + + string Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfString), value.GetType()).Message); + } + success = false; + return ""; + } } /// - /// Converts the specified value to a name. - /// If the value does not exist, the function returns the empty string. - /// If the value is not convertible, the function throws an InvalidCastException. - /// If the index is out of range, the function throws an ArgumentOutOfRangeException. + /// Sets the entry to the specified value. + /// + public void SetString(int index, string value) + { + SetValueInternal(index, new PdfString(value)); + } + + // ===== PdfName ===== + + /// + /// Gets the name value for the specified index. + /// If the value exists but is neither a name nor a PDF reference to a name, + /// the function throws an InvalidOperationException. /// public string GetName(int index) { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3); + TryGetNameInternal(index, out string result, true); + return result; + } - var obj = this[index]; - if (obj == null!) - return ""; + /// + /// Tries to get the name value for the specified index. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetName(int index, out string result) + => TryGetNameInternal(index, out result, false); - var name = obj as PdfName; - if (name != null!) - return name.Value; + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetNameInternal(int index, out string result, bool throwOnTypeMismatch) + { + var value = this[index]; + PdfReference.Dereference(ref value); + bool success = true; + result = value switch + { + PdfName name => name.Value, + PdfNameObject name => name.Value, + _ => Fail() + }; + return success; + + string Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.ArrayEntryIsOfWrongType(index, typeof(PdfInteger), value.GetType()).Message); + } + success = false; + return "/"; + } + } - var nameObject = obj as PdfNameObject; - if (nameObject != null!) - return nameObject.Value; + /// + /// Sets a PDF name at the specified index. + /// + public void SetName(int index, string value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); - throw new InvalidCastException("GetName: Object is not a name."); + // Ensure that value already starts with a slash. + if (value.Length == 0 || value[0] != '/') + { + PdfSharpLogHost.Logger.LogWarning("A PDF name must start with a '/'."); + value = String.Concat("/", value); + } + SetValueInternal(index, new PdfName(value)); + } + + // ===== GetValue ===== + + /// // TODO + /// Gets the value for the specified key. + /// Unsinn: If the value does not exist, it is optionally created. + /// + public PdfItem? GetValue(int index, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { +#if PDFSHARP_DEBUG_ + if (ShouldBreak5) + Debugger.Break(); +#endif + EnsureIndex(index, false); + EnsureNoCreationFlags(options); + if (valueType != null) + EnsureValueType(valueType); + + return GetValueInternal(index, options, valueType, valueType != null); } /// - /// Gets the PdfObject with the specified index, or null if no such object exists. If the index refers to - /// a reference, the referenced PdfObject is returned. + /// * throws if valueType does not match /// - public PdfObject? GetObject(int index) + public PdfItem GetRequiredValue(int index, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) { - if (index < 0 || index >= Count) - throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3); + return GetValue(index, options, valueType) + ?? throw ExceptionOnNull(index); + } - var item = this[index]; - if (item is PdfReference reference) - return reference.Value; + public bool TryGetValue(int index, [MaybeNullWhen(false)] out PdfItem value) + => TryGetValue(index, out value, null); + + /// + /// * throws if result is not of type T. + /// + public bool TryGetValue(int index, [MaybeNullWhen(false)] out PdfItem value, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType) + { + value = null; + var result = GetValueInternal(index, VCF.NoTransform, valueType, false); + if (result != null) + { + value = result; + return true; + } + return false; - return item as PdfObject; + //value = GetValueInternal(index, VCF.NoTransform, valueType, false); + //return value != null; } /// - /// Gets the PdfArray with the specified index, or null if no such object exists. If the index refers to - /// a reference, the referenced PdfArray is returned. + /// * throws if result is not of type T. /// - public PdfDictionary? GetDictionary(int index) - => GetObject(index) as PdfDictionary; + public T? GetValue< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, VCF options = VCF.None) where T : PdfItem + { + EnsureIndex(index, false); + EnsureNoCreationFlags(options); + + var value = (T?)GetValueInternal(index, options, typeof(T), true); + return value; + } /// - /// Gets the PdfArray with the specified index, or null if no such object exists. If the index refers to - /// a reference, the referenced PdfArray is returned. + /// * throws if result is not of type T. /// - public PdfArray? GetArray(int index) - => GetObject(index) as PdfArray; + public T GetRequiredValue< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, VCF options = VCF.None) where T : PdfItem + { + return GetValue(index, options) + ?? throw ExceptionOnNull(index); + } + + public bool TryGetValue< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, [MaybeNullWhen(false)] out T value) where T : PdfItem + { + value = null; + if (GetValueInternal(index, VCF.NoTransform, typeof(T), false) is T valueOfT) + { + value = valueOfT; + return true; + } + return false; + } /// - /// Gets the PdfReference with the specified index, or null if no such object exists. + /// Sets the entry with the specified value. DON’T USE THIS FUNCTION - IT MAY BE REMOVED. // PDFsharp/NT + /// + public void SetValue(int index, PdfItem value) + { + SetValueInternal(index, value); + } + + // ===== GetObject ===== + + /// + /// Gets the PdfObject with the specified index, or null if no such object exists. + /// If the index refers to a reference, the referenced PdfObject is returned. + /// + public PdfObject? GetObject(int index, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + EnsureIndex(index, false); + EnsureNoCreationFlags(options); + if (valueType != null) + EnsureValueType(valueType); + //else + // valueType = typeof(PdfObject); + + var value = GetValueInternal(index, options, valueType, false); + return value as PdfObject; + } + + public PdfObject GetRequiredObject(int index, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + return GetObject(index, options, valueType) + ?? throw ExceptionOnNull(index); + } + + /// + /// Gets a PDF object of type T from the specified index. + /// If the found object is a PDF reference or a PDF container (array or dictionary) but not of type T + /// it is tried to be transformed into this type and replaces the old object. + /// If the transformation is not possible, e.g. because T does not match with the found type, + /// an exception is thrown. + /// Returns null if the found object is neither a PDF reference nor a PDF container. + /// + /// The type of the PDF object to get. + /// The 0-based index of the object. + /// + public T? GetObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, VCF options = VCF.None) where T : PdfObject + { + EnsureIndex(index, false); + EnsureNoCreationFlags(options); + + // TODO: Never throw? + bool throwOnTypeMismatch = typeof(PdfContainer).IsAssignableFrom(typeof(T)); + return GetValueInternal(index, options, typeof(T), throwOnTypeMismatch) as T; + } + + public T GetRequiredObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, VCF options = VCF.None) where T : PdfObject + { + return GetObject(index, options) + ?? throw ExceptionOnNull(index); + } + + public void SetObject(int index, PdfObject obj) // Used in PDFsharp + { + EnsureIndex(index, false); + + SetValueInternal(index, obj); + } + + // ===== GetArray ===== + + /// + /// Gets the PdfArray with the specified index, or null if no such object exists. + /// If the index refers to a reference, the referenced PdfArray is returned. + /// + public PdfArray? GetArray(int index, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + EnsureIndex(index, false); + EnsureNoCreationFlags(options); + if (valueType != null) + EnsureValueType(valueType); + + return GetValueInternal(index, options, valueType, valueType != null) as PdfArray; + } + + /// + /// Gets the PdfArray with the specified index. + /// An InvalidOperationException is thrown if the object does not exist. + /// If the index refers to a reference, the referenced PdfArray is returned. + /// + public PdfArray GetRequiredArray(int index, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + return GetArray(index, options, valueType) + ?? throw ExceptionOnNull(index); + } + + public bool TryGetArray(int index, [MaybeNullWhen(false)] out PdfArray array) + => TryGetArray(index, out array, null); + + public bool TryGetArray(int index, [MaybeNullWhen(false)] out PdfArray array, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType) + { + EnsureIndex(index, false); + if (valueType != null) + EnsureValueType(valueType); + + array = null; + if (GetValueInternal(index, VCF.NoTransform, valueType, false) is PdfArray value) + { + array = value; + return true; + } + return false; + } + + public T? GetArray< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, VCF options = VCF.None) where T : PdfArray + { + EnsureIndex(index, false); + EnsureNoCreationFlags(options); + + var array = GetValueInternal(index, options, typeof(T), true) as T; + return array; + } + + public T GetRequiredArray< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, VCF options = VCF.None) where T : PdfArray + { + return GetArray(index, options) + ?? throw ExceptionOnNull(index); + } + + public bool TryGetArray< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, [MaybeNullWhen(false)] out T array) where T : PdfArray + { + EnsureIndex(index, false); + + array = null; + if (GetValueInternal(index, VCF.NoTransform, typeof(T), false) is T valueOfT) + { + array = valueOfT; + return true; + } + return false; + } + + // ===== GetDictionary ===== + + /// + /// Gets the PdfDictionary with the specified index, or null if no such object exists. + /// If the index refers to a reference, the referenced PdfDictionary is returned. + /// + public PdfDictionary? GetDictionary(int index, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + EnsureIndex(index, false); + EnsureNoCreationFlags(options); + if (valueType != null) + EnsureValueType(valueType); + + return GetValueInternal(index, options, valueType, valueType != null) as PdfDictionary; + } + + public PdfDictionary GetRequiredDictionary(int index, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + return GetDictionary(index, options, valueType) + ?? throw ExceptionOnNull(index); + } + + public bool TryGetDictionary(int index, [MaybeNullWhen(false)] out PdfDictionary dict) + => TryGetDictionary(index, out dict, null); + + public bool TryGetDictionary(int index, [MaybeNullWhen(false)] out PdfDictionary dict, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType) + { + EnsureIndex(index, false); + if (valueType != null) + EnsureValueType(valueType); + + dict = null; + if (GetValueInternal(index, VCF.NoTransform, valueType, false) is PdfDictionary value) + { + dict = value; + return true; + } + return false; + } + + public T? GetDictionary< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, VCF options = VCF.None) where T : PdfDictionary + { + EnsureIndex(index, false); + EnsureNoCreationFlags(options); + + var value = GetValueInternal(index, options, typeof(T), true); + return value as T; + } + + public T GetRequiredDictionary< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, VCF options = VCF.None) where T : PdfDictionary + { + return GetDictionary(index, options) + ?? throw ExceptionOnNull(index); + } + + public bool TryGetDictionary< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (int index, [MaybeNullWhen(false)] out T dict) where T : PdfDictionary + { + EnsureIndex(index, false); + + dict = null; + if (GetValueInternal(index, VCF.NoTransform, typeof(T), false) is T valueOfT) + { + dict = valueOfT; + return true; + } + return false; + } + + // ===== PdfReference ===== + + /// + /// Gets a PDF reference from the specified index, or null if no such object exists. /// public PdfReference? GetReference(int index) { - var item = this[index]; - return item as PdfReference; + EnsureIndex(index, false); + + return _elements[index] as PdfReference; } + /// + /// Gets a PDF reference from the specified index, or throws an exception, + /// if no such object exists. + /// + public PdfReference GetRequiredReference(int index) + { + EnsureIndex(index, false); + + return _elements[index] as PdfReference + ?? throw new InvalidOperationException(SyMsgs.IndirectReferenceMustNotBeNull.Message); + } + + /// + /// Sets the entry to an indirect reference. + /// + public void SetReference(int index, PdfReference iref) + { + EnsureIndex(index, true); + + if (iref is null) + throw new ArgumentNullException(nameof(iref)); + SetValueInternal(index, iref); + } + + // ===== PdfItem ===== + /// /// Gets all items of this array. /// public PdfItem[] Items => _elements.ToArray(); + // ===== Internal ===== + + /// + /// Gets the value for the specified index. + /// There is no create option because a value cannot not exist. + /// A value can be optionally transformed to a derived type. + /// + PdfItem? GetValueInternal(int index, VCF options, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType, bool throwOnTypeMismatch) + { +#if DEBUG_ // KEEP Test in more detail and report a bug. TODO + // TODO: Why leads 'var valueTest' to type 'PdfItem?'? + var valueTest2 = this[index]; // Is of type 'PdfItem' not of type 'PdfItem?'. + valueTest2 = null; // No Warning hint. +#endif +#if DEBUG_ + // Are we reading a PDF document? + if ((OwningArray.Document?.IrefTable ?? null) is { IsUnderConstruction: true }) + { + //Debugger.Break(); + _ = typeof(int); + } +#endif + Debug.Assert(options is VCF.Create or VCF.CreateIndirect is false); + + // ReSharper suggested to use var, but this would make the type nullable. ???TODO: Why? + var valueTest = _elements[index]; // Is always of type PdfItem + // ReSharper disable once SuggestVarOrType_SimpleTypes because value is of type PdfItem? if I use var. + PdfItem value = _elements[index]; // Is always of type PdfItem. + + Debug.Assert(value is not null); + + // Case: The value exists and can be returned. But for imported documents check for necessary + // object type transformation. + + if (value is PdfReference iref) + { +#if DEBUG_ + var irefTable = OwningArray.Document?.IrefTable ?? null; + if (irefTable is { IsUnderConstruction: true }) + { + //Debug.Assert(false, "Should not happen anymore."); + _ = typeof(int); + } +#endif + // Case: value is an indirect object. It can only be a container. + +#if true_ // TODO Create US for this case and DELETE the code. + // Check a very particular case first that can happen during + // the timespan when the IrefTable is not completely read. + var irefTable = OwningArray.Document?.IrefTable ?? null; + if (irefTable is { IsUnderConstruction: true }) + { + // Case: During the import of a PDF document GetValue is called. + // This happens only during encryption. + // IMPROVE: Prevent from coming here. Check if we need temp irefs anymore. + var newIref = irefTable[iref.Value.ObjectID]; + if (newIref == null) + { + // Case: Cannot happen. We can have irefs with no value, but not + // indirect objects with no reference. + _ = typeof(int); + } + else if (ReferenceEquals(iref, newIref) is false) + { + // Case: We are reading a PDF document that hast more than one XRef table or xref streams. + // A top level object can contain an indirect reference to an object with an ID that does not yet exist. + // TODO more checks... + if (ReferenceEquals(iref.Value, newIref.Value)) + { + iref = newIref; + iref.Value.Reference = iref; + UpdateValueInternal(index, iref); + } + } + else + { + _ = typeof(int); + } + } +#endif + // Is PdfReference explicitly requested? + if (valueType == typeof(PdfReference)) + return value; + + // In all other cases use the referenced value. + value = iref.Value; + if (value == null) + { + // If we come here PDF file is corrupted. + // TODO: What if we have a reference to a non-existing object? + throw new InvalidOperationException("Indirect reference without value."); + } + + // The nesting can be simplified, but keep it as it is for better understandability. + if (options != VCF.NoTransform) + { + // //Get type from parameter or metadata. + // In contrast to PdfDictionary we have no metadata for arrays. + var type = valueType /*?? GetValueType(key)*/; + + // Try transformation only once. If it fails, don’t try again. + // Should we try transformation? + if (value.ShouldTryTransformation + || type != null + || value.GetType().IsAssignableFrom(type)) + { + // Do we have a type anyway? + if (type != null) + { + // Is value already of the expected type? + if (type.IsInstanceOfType(value)) + { + // Case: The value is already of the appropriate type. + + // Set to transformed, but only if the requested type is not a base type. + if (type != typeof(PdfDictionary) && type != typeof(PdfArray)) + value.SetTransformed(); + } + else if (value is PdfContainer cont) + { + // Case: Transform array or dictionary. + + // TODO: Test to transform twice. + + //Debug.Assert(cont.IsIndirect is false); + //Debug.Assert(cont.IsTransformed is false); + Debug.Assert(cont.ParentInfo is null); + value = CreateContainer(type, cont, cont.IsIndirect); + //Not for indirect objects UpdateValueInternal(index, value); + Debug.Assert(cont.IsDead); + Debug.Assert(value.IsTransformed); + Debug.Assert(cont.ParentInfo is null); + //Debug.Assert(((PdfContainer)value).ParentInfo is not null); + } + //else + //{ + // throw new NotImplementedException("Type is not a PDF container."); + //} + else if (value is PdfPrimitiveObject) + { + throw new InvalidOperationException( + $"Primitive indirect object of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'."); + } + else + { + // Exotic case: Reference to e.g. PdfDocument. + // Just throw. + throw new InvalidOperationException( + $"Indirect object of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'."); + } + + // TODO: Should fail because it is a Reference - test this. + // TODO: Do not call if value is same. + SetValueInternal(index, value); + } + else + { + // TODO: Should be possible to transform later. + value.SetTransformationTried(); + } + } + } + + Debug.Assert(value != null); + //return value; + } + else + { + // Case: value is a direct object. + + // Transformation is possible after PDF import or the user + // creates a less derived container (e.g. in unit tests). + if (options != VCF.NoTransform) + { + // Try transformation only once. If it fails, don’t try again. + // Should we try transformation? + if (value.ShouldTryTransformation) + { + // In contrast to PdfDictionary we have no metadata for arrays. + var type = valueType; // There is no 'GetValueType(index)'. + + // Do we have a type anyway? + if (type != null) + { + // Case: We have a type and an existing primitive or direct object. + + // Handle special case PdfRectangle first. + // Make no sense in PdfArray. + if (type == typeof(PdfRectangle)) + { + throw new InvalidOperationException( + "PdfRectangle is not an appropriate type for an item of a PdfArray."); + } + if (type.IsInstanceOfType(value)) + { + // Case: The value is already of the appropriate type. + + // Set to transformed, but only if the requested type is not a base type. + if (type != typeof(PdfDictionary) && type != typeof(PdfArray)) + value.SetTransformed(); + } + else if (value is PdfContainer cont) + { + // Case: Transform direct array or dictionary. + + Debug.Assert(cont.IsIndirect is false); + Debug.Assert(cont.IsTransformed is false); + Debug.Assert(cont.ParentInfo is not null); + + value = CreateContainer(type, cont, false /*cont.IsIndirect*/); + SetValueInternal(index, value); + + Debug.Assert(cont.IsTransformed); + Debug.Assert(cont.IsDead); + Debug.Assert(value.IsTransformed); // TODO: Can transform twice? + Debug.Assert(cont.ParentInfo is null); + Debug.Assert(((PdfDictionary)value).ParentInfo is not null); + } + else + { + // Exotic case: value is not a PDF array or dictionary, but not from the requested type. + // Just throw. + throw new InvalidOperationException( + $"Value of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'."); + } + } + else + { + // TODO: Should be possible to transform later. + value.SetTransformationTried(); + } + } + } + } + + // Ensure not a type mismatch. + if (valueType != null) + { + if (!valueType.IsInstanceOfType(value)) + { + if (throwOnTypeMismatch) + throw ExceptionOnTypeMismatch(index, valueType, value.GetType()); + return null; + } + } + Debug.Assert(value != null); // TODO: Ensure. + return value; + } + + /// + /// Implementation of SetValue. + /// Handles setting same value, setting indirect object, + /// and releasing old value. + /// Keep in sync with PdfDictionary. + /// + void SetValueInternal(int index, PdfItem value) + { +#if DEBUG_ + //if (ShouldBreak1) + // Debugger.Break(); + if (value is PdfReference { ObjectID.ObjectNumber: 5 }) + _ = typeof(int); + +#endif +#if true_ // TODO REMOVE or throw exception in DEBUG +#if DEBUG + // Are we reading a PDF document? + if ((OwningArray.Document?.IrefTable ?? null) is { IsUnderConstruction: true }) + { + //Debugger.Break(); + _ = typeof(int); + } +#endif +#endif + EnsureIndex(index, true); + + // Already checked by caller. + Debug.Assert(value is not null); + + if (value.IsDead) + throw new InvalidOperationException("TODO: Is Dead."); // /messages/ObjectIsDead.html + + // Special treatment for PdfRectangle. + if (value is PdfRectangle rect) + value = rect.GetAsArrayOfValues(); + else + PdfReference.ToReference(ref value); + + PdfItem? oldItem = null; + if (index < Count) + { + oldItem = _elements[index]; + if (ReferenceEquals(oldItem, value)) + { + LogWarning(); + return; + } + } + else + { + if (index > Count) + { + throw new IndexOutOfRangeException( + "Index must not be greater than Count."); // /messages/ObjectIsDead.html + } + // else: value is appended. + } + + if (value is PdfObject obj) + { + if (obj.Reference != null) + { + Debug.Assert(false, "Should not come here anymore."); + + // Case: Indirect object. + + Debug.Assert(obj.ParentInfo is null, "An indirect object must not have a structure parent."); + value = obj.Reference; + if (ReferenceEquals(oldItem, value)) + { + LogWarning(); + return; + } + } + else if (obj is PdfPrimitiveObject) + { + // Case: Direct primitive object. + // E.g. non-indirect PdfStringObject is used instead of PdfString. + FailForDirectPrimitiveObject(obj); + } + else + { + // Case: Direct container object. + Debug.Assert(obj is PdfContainer); + + if (obj.ParentInfo != null) + throw new InvalidOperationException("A direct object can only be added once."); + + obj.SetStructureParent(this, index); + } + } + else + { + // Case: value is just a PdfItem - nothing special to do. + // Case: value is PDF reference or primitive - nothing special to do. + Debug.Assert(value is PdfReference or PdfPrimitive); + } + + if (index < Count) + _elements[index] = value; + else + _elements.Add(value); + + // oldItem can be null here if index was equal to Count + // or value was inserted. + if (oldItem != null) + ReleaseItem(oldItem); + + return; + + void LogWarning() + { + //PdfSharpLogHost.Logger.LogWarning("Setting same value in dictionary."); + } + } + + /// + /// The array this elements object belongs to. + /// + PdfArray OwningArray => (PdfArray)OwningContainer; + #region IList Members /// @@ -371,15 +1501,21 @@ public string GetName(int index) /// /// Gets or sets an item at the specified index. /// - /// public PdfItem this[int index] { + // Always get the raw value. get => _elements[index]; set { - if (value == null!) + if (value == null) throw new ArgumentNullException(nameof(value)); - _elements[index] = value; + + //// Note that if index is Count value is appended. + //if (index < 0 || index > Count) + // throw new ArgumentOutOfRangeException(nameof(index), index, SyMsgs.IndexOutOfRange3); + EnsureIndex(index, true); + + SetValueInternal(index, value); } } @@ -388,7 +1524,10 @@ public PdfItem this[int index] /// public void RemoveAt(int index) { + var oldItem = _elements[index]; _elements.RemoveAt(index); + ReleaseItem(oldItem); + FixIndexInParentInfo(index, -1); } /// @@ -396,7 +1535,13 @@ public void RemoveAt(int index) /// public bool Remove(PdfItem item) { - return _elements.Remove(item); + int index = IndexOf(item); + if (index >= 0) + { + RemoveAt(index); + return true; + } + return false; } /// @@ -404,7 +1549,14 @@ public bool Remove(PdfItem item) /// public void Insert(int index, PdfItem value) { - _elements.Insert(index, value); + // TODO: Check reuse, check moved objects. + //_elements.Insert(index, value); + // TODO: Let the element know its index. + + // Make space by inserting a dummy value. + _elements.Insert(index, PdfNull.Value); + FixIndexInParentInfo(index + 1, 1); + SetValueInternal(index, value); } /// @@ -412,6 +1564,9 @@ public void Insert(int index, PdfItem value) /// public bool Contains(PdfItem value) { + if (value is PdfObject { Reference: not null } obj) + value = obj.Reference; + return _elements.Contains(value); } @@ -420,7 +1575,10 @@ public bool Contains(PdfItem value) /// public void Clear() { + var oldItems = _elements.ToArray(); _elements.Clear(); + foreach (var item in oldItems) + ReleaseItem(item); } /// @@ -428,6 +1586,12 @@ public void Clear() /// public int IndexOf(PdfItem value) { + if (value is PdfReference iref) + return _elements.IndexOf(value); + + if (value is PdfObject { Reference: not null } obj) + return _elements.IndexOf(obj.RequiredReference); + return _elements.IndexOf(value); } @@ -436,14 +1600,10 @@ public int IndexOf(PdfItem value) /// public void Add(PdfItem value) { - // TODO_OLD: ??? - //Debug.Assert((value is PdfObject && ((PdfObject)value).Reference == null) | !(value is PdfObject), - // "You try to set an indirect object directly into an array."); + if (value == null!) + throw new ArgumentNullException(nameof(value)); - if (value is PdfObject { IsIndirect: true } obj) - _elements.Add(obj.Reference!); - else - _elements.Add(value); + SetValueInternal(Count, value); } /// @@ -469,17 +1629,13 @@ public void Add(PdfItem value) /// Copies the elements of the array to the specified array. /// public void CopyTo(PdfItem[] array, int index) - { - _elements.CopyTo(array, index); - } + => _elements.CopyTo(array, index); /// /// The current implementation return null. /// public object SyncRoot => null!; - #endregion - /// /// Returns an enumerator that iterates through the array. /// @@ -489,34 +1645,77 @@ public IEnumerator GetEnumerator() IEnumerator IEnumerable.GetEnumerator() => _elements.GetEnumerator(); + #endregion + /// - /// The elements of the array. + /// If an item is added or removed from the array, the subsequent direct object’s + /// index in their ParentInfo must be adjusted accordingly. /// - List _elements; + /// Index of the first item to be adjusted. + /// 1 or -1, depending on insert or delete. + void FixIndexInParentInfo(int index, int offset) + { + for (int idx = index; idx < _elements.Count; idx++) + { + var obj = _elements[idx] as PdfObject; + var parentInfo = obj?.ParentInfo; + if (parentInfo is { IsArray: true }) + parentInfo.AdjustIndex(offset); + } + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void EnsureIndex(int index, bool writeValue) + { + // TODO: IL code shows that the function is not inlined. + // Even in release build. Analyse why. + + var count = _elements.Count; + if (index < 0 || writeValue ? index > count : index >= count) + ThrowIndexOutOfRange(index, writeValue); + } + + void ThrowIndexOutOfRange(int index, bool writeValue) + { + throw new IndexOutOfRangeException( + $"Index '{index}' is out of range for {(writeValue ? "writing into" : "reading from")} PdfArray."); + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + void EnsureNoCreationFlags(VCF options) + { + if (options is VCF.Create or VCF.CreateIndirect) + ThrowCreationFlags(options); + } + + void ThrowCreationFlags(VCF options) + { + throw new InvalidOperationException( + $"Flag 'ACF.{options.ToString()}' must not be set for array elements because an array entry cannot be undefined."); + } + + Exception ExceptionOnNull(int index) + { + return new InvalidOperationException($"Value at index '{index}' has wrong type."); + } + + Exception ExceptionOnTypeMismatch(int index, Type expected, Type found) + { + return new InvalidOperationException($"Value at index '{index}' is expected to be of type {expected.FullName}, but is of type {found.FullName}."); + } /// - /// The array this object belongs to. + /// The elements of the array. /// - PdfArray? _ownerArray; + List _elements = []; } - ArrayElements? _elements; - /// /// Gets the DebuggerDisplayAttribute text. /// // ReSharper disable UnusedMember.Local string DebuggerDisplay + => Invariant($"{GetType().Name}({ObjectID.DebuggerDisplay}, count={_elements?.Count ?? 0})"); // ReSharper restore UnusedMember.Local - { - get - { -#if true - return String.Format(CultureInfo.InvariantCulture, "array({0},[{1}])", ObjectID.DebuggerDisplay, _elements?.Count ?? 0); -#else - return String.Format(CultureInfo.InvariantCulture, "array({0},[{1}])", ObjectID.DebuggerDisplay, _elements == null ? 0 : _elements.Count); -#endif - } - } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayExtensions/PdfArrayExtensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayExtensions/PdfArrayExtensions.cs new file mode 100644 index 00000000..48a346da --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayExtensions/PdfArrayExtensions.cs @@ -0,0 +1,35 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf.PdfArrayExtensions // #PDFsharp/NT +{ + /// + /// Extension methods for PDF arrays. NYI + /// + public static class PdfArrayExtensions + { + ///// + ///// NYI + ///// + ///// + ///// + ///// + //public static PdfItem GetRawValue(this PdfArray array, int index) + //{ + // return array.Elements[index]; + //} + + //public static PdfItem GetItem(this PdfArray array, int index) + //{ + // return array.Elements[index]; + //} + + //public static void SetItem(this PdfArray array, int index, PdfItem value) + //{ + // if (value == null) + // throw new ArgumentNullException(nameof(value)); + + // array.Elements.SetValue(index, value); + //} + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayOfDictionaries.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayOfDictionaries.cs new file mode 100644 index 00000000..63db1545 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfArrayOfDictionaries.cs @@ -0,0 +1,66 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PdfSharp.Pdf +{ + /// + /// Represents a PdfArray of PDF dictionaries. + /// Used e.g. for the /AF entry in the PDF catalog. + /// + public class PdfArrayOfDictionaries : PdfArray + { + /// + /// Initialize a new instance of this class. + /// + public PdfArrayOfDictionaries() + { } + + /// + /// Initializes a new instance of this class. + /// + public PdfArrayOfDictionaries(PdfDocument document) + : base(document) + { } + + /// + /// Initializes a new instance from an existing object. Used for object type transformation. + /// + public PdfArrayOfDictionaries(PdfArray array) + : base(array) + { } + + /// + /// Adds a PDF dictionary to the array. + /// The dictionary must be an indirect object. + /// + /// The indirect dictionary to add. + public void AddDictionary(PdfDictionary dict) + { + var reference = dict.Reference + ?? throw new ArgumentException("Dictionary must be an indirect object.", nameof(dict)); + if (Elements.Contains(reference)) + throw new InvalidOperationException("Dictionary already in array."); + Elements.Add(reference); + } + + /// + /// Removes a PDF dictionary from the array. + /// Returns true if the dictionary was successfully removed, false otherwise. + /// + /// + public bool RemoveDictionary(PdfDictionary dict) + { + var reference = dict.Reference + ?? throw new ArgumentException("Dictionary must be an indirect object.", nameof(dict)); + + var result = Elements.Remove(reference); + return result; + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs index 380b66c4..4e300532 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBoolean.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using PdfSharp.Pdf.IO; @@ -9,7 +9,7 @@ namespace PdfSharp.Pdf /// Represents a direct boolean value. /// [DebuggerDisplay("({" + nameof(Value) + "})")] - public sealed class PdfBoolean : PdfItem + public sealed class PdfBoolean : PdfPrimitive { /// /// Initializes a new instance of the class. @@ -47,7 +47,7 @@ public override string ToString() => Value ? bool.TrueString : bool.FalseString; /// - /// Writes 'true' or 'false'. + /// Writes ‘true’ or ‘false’. /// internal override void WriteObject(PdfWriter writer) => writer.Write(this); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBooleanObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBooleanObject.cs index cf463f0c..1abe007b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBooleanObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfBooleanObject.cs @@ -10,7 +10,7 @@ namespace PdfSharp.Pdf /// an external PDF file, the value is converted into a direct object. /// [DebuggerDisplay("({" + nameof(Value) + "})")] - public sealed class PdfBooleanObject : PdfObject + public sealed class PdfBooleanObject : PdfPrimitiveObject { /// /// Initializes a new instance of the class. @@ -30,7 +30,21 @@ public PdfBooleanObject(bool value) /// Initializes a new instance of the class. /// public PdfBooleanObject(PdfDocument document, bool value) - : base(document) + : base(document, true) + { + _value = value; + } + + /// + /// Initializes a new instance of the class + /// without making it indirect. + /// Used in PDF parser only. + /// + /// The document. + /// The initial value. + /// If true creates an indirect object. + internal PdfBooleanObject(PdfDocument document, bool value, bool createIndirect) + : base(document, createIndirect) { _value = value; } @@ -45,7 +59,7 @@ public PdfBooleanObject(PdfDocument document, bool value) /// /// Returns "false" or "true". /// - public override string ToString() + public override string ToString() => _value ? bool.TrueString : bool.FalseString; /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfContainer.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfContainer.cs new file mode 100644 index 00000000..da11cf04 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfContainer.cs @@ -0,0 +1,75 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 Ready + +namespace PdfSharp.Pdf +{ + // Re/Sharper enable GrammarMistakeInComment + + /// + /// Common abstract base class for both PdfArray and PdfDictionary. + /// For technical purposes only, e.g. as marker class. There is no counterpart of + /// this class in the PDF specification. + /// + public abstract class PdfContainer : PdfObject + { + /// + /// Initializes a new instance of the class. + /// + protected internal PdfContainer() + { + // Only PdfArray or PdfDictionary are allowed to derive from PdfContainer. + Debug.Assert(this is PdfArray or PdfDictionary); + } + + /// + /// Initializes a new instance of the class. + /// + protected internal PdfContainer(PdfDocument doc, bool createIndirect = false) + : base(doc, createIndirect) + { + // Only PdfArray or PdfDictionary are allowed to derive from PdfContainer. + Debug.Assert(this is PdfArray or PdfDictionary); + } + + /// + /// Initializes a new instance of the class. + /// + protected internal PdfContainer(PdfContainer obj) : base(obj) + { + // Only PdfArray or PdfDictionary are allowed to derive from PdfContainer. + Debug.Assert(this is PdfArray or PdfDictionary); + } + + /// + /// Transforms a container to a derived. If the container already is of the requested type, + /// no action is taken. + /// + protected internal T TransformTo< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (PdfContainer obj) where T : PdfContainer + { + //if (typeof(T).IsInstanceOfType(obj))//obj.GetType().IsAssignableTo(typeof(T)))) + if (obj is T result) //obj.GetType().IsAssignableTo(typeof(T)))) + return result; + + Debug.Assert(obj.GetType().IsAssignableFrom(typeof(T))); + + switch (obj) + { + case PdfDictionary dict: + result = (T)dict.Elements.CreateContainer(typeof(T), obj, dict.IsIndirect); + return result; + + case PdfArray array: + result = (T)array.Elements.CreateContainer(typeof(T), obj, array.IsIndirect); + return result; + + default: + throw new InvalidOperationException("Object is neither a PDF array nor a dictionary."); + } + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValue.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValue.cs index 913ffd31..9cce5162 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValue.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValue.cs @@ -30,6 +30,10 @@ internal PdfCustomValue(PdfDocument document) CreateStream([]); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfCustomValue(PdfDictionary dict) : base(dict) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValues.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValues.cs index db3b7faa..1b377218 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValues.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfCustomValues.cs @@ -17,6 +17,10 @@ internal PdfCustomValues(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfCustomValues(PdfDictionary dict) : base(dict) { } @@ -60,7 +64,7 @@ public PdfCustomValue? this[string key] else { Owner.Internals.AddObject(value); - Elements.SetReference(key, value); + Elements.SetObject(key, value); } } #if old @@ -108,13 +112,13 @@ public static void ClearAllCustomValues(PdfDocument document) internal static PdfCustomValues Get(DictionaryElements elem) { - string key = elem.Owner.Owner.Internals.CustomValueKey; + string key = elem.OwningContainer.Owner.Internals.CustomValueKey; PdfCustomValues? customValues; var dict = elem.GetDictionary(key); if (dict == null) { customValues = new PdfCustomValues(); - elem.Owner.Owner.Internals.AddObject(customValues); + elem.OwningContainer.Owner.Internals.AddObject(customValues); elem.Add(key, customValues); } else @@ -128,7 +132,7 @@ internal static PdfCustomValues Get(DictionaryElements elem) internal static void Remove(DictionaryElements elem) { - elem.Remove(elem.Owner.Owner.Internals.CustomValueKey); + elem.Remove(elem.OwningContainer.Owner.Internals.CustomValueKey); } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs index b5bb90de..61c60354 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDate.cs @@ -1,50 +1,136 @@ -// PDFsharp - A .NET library for processing PDF +// 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; +// v7.0.0 Ready + namespace PdfSharp.Pdf { /// /// Represents a direct date value. /// [DebuggerDisplay("({" + nameof(Value) + "})")] - public sealed class PdfDate : PdfItem + public sealed class PdfDate : PdfPrimitive { + // Reference 2.0: 7.9.4 Dates / Page 717 + // See also: https://learn.microsoft.com/en-us/dotnet/standard/datetime/converting-between-datetime-and-offset + /// /// Initializes a new instance of the class. + /// The Value property becomes null. /// public PdfDate() { } /// /// Initializes a new instance of the class. + /// If the string is not a valid PDF date the Value property becomes null. /// - public PdfDate(string value) + public PdfDate(string date) { - Value = Parser.ParseDateTime(value, DateTime.MinValue); + if (!Parser.TryParseDate(date, out var value)) + { + var message = $"""The string "{date}" is not a valid PDF date."""; + PdfSharpLogHost.PdfReadingLogger.LogError(message); + } + Value = value; } /// /// Initializes a new instance of the class. /// + [Obsolete("Use a DateTimeOffset for creating a PdfDate.")] public PdfDate(DateTime value) + { + // Let .NET do the conversion. We do not try to ‘optimize’ it. + Value = value; + } + + /// + /// Initializes a new instance of the class. + /// + public PdfDate(DateTimeOffset value) { Value = value; } /// - /// Gets the value as DateTime. + /// Gets the value as DateTimeOffset. + /// Use ToString to get the PDF date string. + /// The value is null if the class was not initialized with a valid PDF date. /// - public DateTime Value { get; } + public DateTimeOffset? Value { get; } /// - /// Returns the value in the PDF date format. + /// Returns the value as a PDF date formatted string, + /// or the empty string if no value was set. + /// The string looks like (D:YYYYMMDDHHmmSSOHH'mm'). /// public override string ToString() { - string delta = Value.ToString("zzz").Replace(':', '\''); - return $"D:{Value:yyyyMMddHHmmss}{delta}'"; + return ToPdfString(Value); + + // #DELETE 25-12-31 + //////#if old_code + //// if (Value == null) + //// return ""; + + //// // TO/DO clean this all up #US270 / use DateTimeOffset instead of Date/Time + ////#if DEBUG_ + //// // TO/DO https://stackoverflow.com/questions/65004352/c-sharp-datetime-tostring-with-zzz-breaks-in-dotnet-framework-but-not-in-dotnet + //// var kind = Value.Kind; + //// // TO/DO Here we have a difference between .NET Framework and .NET + //// // See also SpecifyLocalDateTimeKindIfUnspecified + //// // Date/Time today = Date/Time.UtcNow; + //// // Console.WriteLine(String.Format("{0:%z}, {0:zz}, {0:zzz}", today)); + //// // // Displays -7, -07, -07:00 on .NET Framework + //// // // Displays +0, +00, +00:00 on .NET Core and .NET 5+ + ////#endif + + //// // Fix bug in .NET Framework: + //// // offset is always "00'00" in UTC (that’s why it’s called UTC). + //// string offset = Value.Kind == DateTimeKind.Utc + //// ? "00'00" + //// : Value.ToString("zzz").Replace(':', '\''); + //// // The trailing ‘'’ was part of the Syntax in PDF 1.x. + //// // Since PDF 2.0 it is not part of the syntax anymore. + //// // return $"D:{Value:yyyyMMddHHmmss}{offset}'"; + + //// // Page 118ff + //// // NOTE 1 A date string can be any valid PDF string object as described in 7.3.4, "String objects". + //// // The description above relates to the text string value after appropriate processing. + //// // NOTE 2 PDF versions up to and including 1.7 defined a date string to include a terminating apostrophe. + //// // PDF processors are recommended to accept date strings that still follow that convention. + //// // NOTE 3 The letter Z can optionally be followed by hour and minute offsets, which are zero in this case. + + //// // We keep trailing ‘'’ and write ‘Z00'00’ instead of ‘Z’ for maximum compatibility. + //// var result = Value.Kind == DateTimeKind.Utc + //// ? $"D:{Value:yyyyMMddHHmmss}Z00'00'" // TO/DO verify: "Z" or "Z00'00"? + //// : $"D:{Value:yyyyMMddHHmmss}{Value.ToString("zzz").Replace(':', '\'')}'"; + //// return result; + ////#endif + } + + /// + /// Converts a DateTimeOffset into a PDF date string. + /// Returns the empty string if value is null. + /// + static string ToPdfString(DateTimeOffset? dateTimeOffset) + { + var result = ""; + if (dateTimeOffset != null) + { + result = $"D:{dateTimeOffset:yyyyMMddHHmmss}"; + if (dateTimeOffset.Value.Offset == TimeSpan.Zero) + result += "Z"; + else + // We keep trailing ‘'’ if offset is not 0 for maximum compatibility. + result += dateTimeOffset.Value.ToString("zzz").Replace(':', '\'') + '\''; + } + return result; } /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs index 62d014f3..3dc6bed9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionary.cs @@ -1,109 +1,115 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Runtime.CompilerServices; using System.Collections; -using System.Reflection; using System.Text; +using Microsoft.Extensions.Logging; +using PdfSharp.Internal; +using PdfSharp.Logging; using PdfSharp.Drawing; -using PdfSharp.Pdf.IO; -using PdfSharp.Pdf.Filters; using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Filters; using PdfSharp.Pdf.Internal; -using System.Diagnostics.CodeAnalysis; +using PdfSharp.Pdf.IO; + +// TODO REMOVE +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf { - /// - /// Value creation flags. Specifies whether and how a value that does not exist is created. - /// - // ReSharper disable InconsistentNaming - public enum VCF - // ReSharper restore InconsistentNaming - { - /// - /// Don’t create the value. - /// - None, - - /// - /// Create the value as direct object. - /// - Create, - - /// - /// Create the value as indirect object. - /// - CreateIndirect, - } - /// /// Represents a PDF dictionary object. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public class PdfDictionary : PdfObject, IEnumerable> + public class PdfDictionary : PdfContainer, IEnumerable> // TODO: explain why not PdfItem? anymore { - // Reference: 3.2.6 Dictionary Objects / Page 59 + // Reference 1.7: 3.2.6 Dictionary Objects / Page 59 + // Reference 2.0: 7.3.7 Dictionary objects / Page 30 /// /// Initializes a new instance of the class. /// public PdfDictionary() - { } + { + // Direct object. + ItemFlags |= ItemFlags.IsDictionary; + } /// /// Initializes a new instance of the class. /// /// The document. - public PdfDictionary(PdfDocument document) - : base(document) - { } + /// If true creates an indirect object. + public PdfDictionary(PdfDocument document, bool createIndirect = false) + : base(document, createIndirect) + { + ItemFlags |= ItemFlags.IsDictionary; + } /// - /// Initializes a new instance from an existing dictionary. Used for object type transformation. + /// Initializes a new instance from an existing dictionary. + /// Used for object type transformation. /// protected PdfDictionary(PdfDictionary dict) : base(dict) { - if (dict._elements != null) - dict._elements.ChangeOwner(this); - if (dict.Stream != null!) - dict.Stream.ChangeOwner(this); +#if DEBUG + // Protect against unintended invocation. + var oldType = dict.GetType(); + if (oldType != typeof(PdfDictionary)) + { + var newType = GetType(); + if (oldType == newType) + { + throw new InvalidOperationException($"You try to convert a PDF dictionary into type '{newType.FullName}', " + + $"but the dictionary is already of this type."); + } + + if (!oldType.IsAssignableFrom(newType)) + { + throw new InvalidOperationException($"You try to convert type '{oldType.FullName}' into type '{newType.FullName}', " + + $"but '{newType.Name}' is not derived from '{oldType.Name}'."); + } + } +#endif + // Move ownership of elements and stream to this instance. + dict._elements?.ChangeOwner(this); + dict.Stream?.ChangeOwner(this); + dict.SetDead(); + ItemFlags |= ItemFlags.IsDictionary | ItemFlags.IsTransformed; } /// - /// Creates a copy of this dictionary. Direct values are deep copied. Indirect references are not - /// modified. + /// Creates a copy of this dictionary. + /// Direct values are deep-copied. Indirect references are not modified. /// public new PdfDictionary Clone() => (PdfDictionary)Copy(); - /// + /// // TODO check /// This function is useful for importing objects from external documents. The returned object is not /// yet complete. irefs refer to external objects and directed objects are cloned but their document /// property is null. A cloned dictionary or array needs a 'fix-up' to be a valid object. /// protected override object Copy() { + // Clone dictionary. var dict = (PdfDictionary)base.Copy(); - if (dict._elements != null) + var elements = dict._elements; + if (elements != null) { - dict._elements = dict._elements.Clone(); - dict._elements.ChangeOwner(dict); - var names = dict._elements.KeyNames; - foreach (var name in names) - { - if (dict._elements[name] is PdfObject obj) - { - obj = obj.Clone(); - // Recall that obj.Document is now null. - dict._elements[name] = obj; - } - } + elements = elements.Clone(); + dict._elements = elements; + elements.ChangeOwner(dict); } - if (dict.Stream != null!) + + var stream = dict.Stream; + if (stream != null) { - dict.Stream = dict.Stream.Clone(); - dict.Stream.ChangeOwner(dict); + stream = stream.Clone(); + dict.Stream = stream; + stream.ChangeOwner(dict); } return dict; } @@ -111,45 +117,62 @@ protected override object Copy() /// /// Gets the dictionary containing the elements of this dictionary. /// - public DictionaryElements Elements => _elements ??= new DictionaryElements(this); + public DictionaryElements Elements + { + get + { + EnsureAlive(); + return _elements ??= new(this); + } + set + { + if (_elements != null && value != null) + throw new InvalidOperationException("Elements cannot be set if already created."); + _elements = value; + } + } /// /// The elements of the dictionary. /// - // ReSharper disable once InconsistentNaming - internal DictionaryElements? _elements; + DictionaryElements? _elements; /// /// Returns an enumerator that iterates through the dictionary elements. /// - public IEnumerator> GetEnumerator() + public IEnumerator> GetEnumerator() => Elements.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); /// - /// Returns a string with the content of this object in a readable form. Useful for debugging purposes only. + /// Returns a string with the content of this object in a readable form. + /// Useful for debugging purposes only. /// public override string ToString() { // Get keys and sort. PdfName[] keys = Elements.KeyNames; List list = [.. keys]; - list.Sort(PdfName.Comparer); + list.Sort(/*PdfName.*/Comparer); list.CopyTo(keys, 0); - var pdf = new StringBuilder(); - pdf.Append("<< "); - foreach (PdfName key in keys) - pdf.Append(key + " " + Elements[key] + " "); - pdf.Append(">>"); + var text = new StringBuilder(); + text.Append("<<"); + foreach (var key in keys) + text.Append(key + " " + Elements[key] + " "); // Need PdfReference here. + text.Append(">>"); - return pdf.ToString(); + return text.ToString(); } internal override void WriteObject(PdfWriter writer) { +#if DEBUG + if (ObjectID.ObjectNumber == 1) + _ = typeof(int); +#endif writer.WriteBeginObject(this); //int count = Elements.Count; PdfName[] keys = Elements.KeyNames; @@ -174,7 +197,7 @@ internal override void WriteObject(PdfWriter writer) if (writer.IsVerboseLayout) { var list = new List(keys); - list.Sort(PdfName.Comparer); + list.Sort(/*PdfName.*/Comparer); list.CopyTo(keys, 0); } @@ -186,40 +209,50 @@ internal override void WriteObject(PdfWriter writer) } /// - /// Writes a key/value pair of this dictionary. This function is intended to be overridden - /// in derived classes. + /// Writes a key-value pair of this dictionary. + /// This function is intended to be overridden in derived classes. /// - internal virtual void WriteDictionaryElement(PdfWriter writer, PdfName key) + internal void WriteDictionaryElement(PdfWriter writer, PdfName key) { Debug.Assert(key != null); -#if DEBUG - if (key == "/Kids") - _ = typeof(int); -#endif - var item = Elements[key]!; + var item = Elements[key]!; // We need references here. GetValue is harmful and leads to broken files. // #US373 key.WriteObject(writer); + //if (writer.Layout == PdfWriterLayout.Verbose) + // writer.WriteSpace(); item.WriteObject(writer); - if (writer.Layout == PdfWriterLayout.Verbose) + if (writer.IsVerboseLayout) writer.NewLine(); -} + } /// - /// Writes the stream of this dictionary. This function is intended to be overridden - /// in a derived class. + /// Writes the stream of this dictionary. + /// This function is intended to be overridden in a derived class. /// internal virtual void WriteDictionaryStream(PdfWriter writer) { + if (!IsIndirect) + throw new InvalidOperationException("Direct PDF dictionary must not have a PDF stream."); + writer.WriteStream(this, (writer.Options & PdfWriterOptions.OmitStream) == PdfWriterOptions.OmitStream); } /// - /// Gets or sets the PDF stream belonging to this dictionary. Returns null if the dictionary has + /// Gets the PDF stream belonging to this dictionary. Returns null if the dictionary has /// no stream. To create the stream, call the CreateStream function. /// - public PdfStream Stream + public PdfStream? Stream { - get => _stream!; // ?? NRT.ThrowOnNull(); // Can be null. - set => _stream = value; + get + { + EnsureAlive(); + return _stream; + } + + internal set + { + EnsureAlive(); + _stream = value; + } } PdfStream? _stream; @@ -229,37 +262,97 @@ public PdfStream Stream /// public PdfStream CreateStream(byte[] value) { + EnsureAlive(); + + // Ensure that the object is an indirect object. + if (_stream != null) throw new InvalidOperationException("The dictionary already has a stream."); - Stream = new PdfStream(value, this); + // OK, you can create a PDF stream for a direct object. But it must become indirect before saving. + //if (!IsIndirect) + // throw new InvalidOperationException("Cannot create a stream for a direct PDF dictionary."); + + Stream = new(value, this); // Always set the length. Elements["/Length"] = new PdfInteger(Stream.Length); return Stream; } + /// + /// Clones the elements of the specified PDF dictionary. + /// + internal void CloneElementsOf(PdfDictionary dic) + { + _elements = dic.Elements.Clone(); + // Note that ParentInfo is still null for each item. + _elements.OwningContainer = this; + } + /// /// When overridden in a derived class, gets the KeysMeta of this dictionary type. /// internal virtual DictionaryMeta Meta => null!; + [MethodImpl(MethodImplOptions.AggressiveInlining)] + new void EnsureAlive() + { + if (IsDead) + { + throw new InvalidOperationException( + "This dictionary cannot be used anymore, because its content is now owned by an object of a derived class."); + } + } + /// /// Represents the interface to the elements of a PDF dictionary. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public sealed class DictionaryElements : IDictionary, ICloneable + public sealed class DictionaryElements : ElementsBase, IDictionary, ICloneable { - internal DictionaryElements(PdfDictionary ownerDictionary) + // Note that the value type of IDictionary is not nullable anymore. + // This is because the value of a dictionary element is always a PdfItem and cannot be null. + // However, the index still returns PdfItem? because it returns null if the key does not exist. + + internal DictionaryElements(PdfDictionary owningDictionary) + : base(owningDictionary) { - _elements = new Dictionary(); - _ownerDictionary = ownerDictionary; + Debug.Assert(ReferenceEquals(OwningDictionary, owningDictionary)); } object ICloneable.Clone() { + // Shallow clone the Dictionary elements. var dictionaryElements = (DictionaryElements)MemberwiseClone(); - dictionaryElements._elements = new Dictionary(dictionaryElements._elements); - dictionaryElements._ownerDictionary = null!; + var elements = new Dictionary(_elements); + dictionaryElements._elements = elements; + dictionaryElements.OwningContainer = null!; + + // Clone all direct objects. + var names = _elements.Keys; // Take original dictionary… + foreach (var name in names) + { + var item = elements[name]; + if (item is PdfObject obj) + { + Debug.Assert(obj.IsIndirect is false); + Debug.Assert(obj.ParentInfo is not null); + obj = obj.Clone(); + Debug.Assert(obj.ParentInfo is null); + obj.SetStructureParent(dictionaryElements, name); + elements[name] = obj; // … because we change the clone during iteration. + } + else if (item is PdfReference reference) + { + reference.AddRef(); + } + else + { + // Nothing to do for PDF primitives. + //_ = typeof(int); + } + } + return dictionaryElements; } @@ -272,365 +365,738 @@ public DictionaryElements Clone() /// /// Moves this instance to another dictionary during object type transformation. /// - internal void ChangeOwner(PdfDictionary ownerDictionary) + internal void ChangeOwner(PdfDictionary newOwningDictionary) { - if (_ownerDictionary != null!) + if (OwningDictionary != null!) { - // ??? + // Can this assertion really fail? + Debug.Assert(ReferenceEquals(this, OwningDictionary._elements)); //TODO: Check why it fails. + + // Disconnect old owner from this DictionaryElements. + OwningDictionary._elements = null; } // Set new owner. - _ownerDictionary = ownerDictionary; + OwningContainer = newOwningDictionary; // Set owners elements to this. - ownerDictionary._elements = this; + newOwningDictionary._elements = this; } /// - /// Gets the dictionary to which this elements object belongs to. + /// Determines whether the specified key has a value. /// - internal PdfDictionary Owner => _ownerDictionary ?? NRT.ThrowOnNull(); + public bool HasValue(string key) + => _elements.ContainsKey(key); + + // ===== PdfBoolean ===== /// - /// Converts the specified value to boolean. - /// If the value does not exist, the function returns false. - /// If the value is not convertible, the function throws an InvalidCastException. + /// Gets the boolean value that corresponds to the specified key. + /// If the key does not exist and create is false, the function returns the defaultValue. + /// If the key does not exist and create is true, + /// a direct value will be created using the defaultValue. + /// If the key exists but the value is neither a boolean nor a PDF reference to a boolean, + /// the function throws an InvalidOperationException. + /// + public bool GetBoolean(string key, bool create = false, bool defaultValue = false) + { + TryGetBooleanInternal(key, out var result, create, defaultValue, true); + return result; + } + + /// + /// Tries to get the boolean value of the PDF object with the specified key. + /// Returns true on success, false otherwise. /// - public bool GetBoolean(string key, bool create) + public bool TryGetBoolean(string key, out bool result) + => TryGetBooleanInternal(key, out result, false, false, false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetBooleanInternal(string key, out bool result, bool create, bool defaultValue, + bool throwOnTypeMismatch) { - object? obj = this[key]; - if (obj == null) + Name.EnsureName(key); + //var value = this[key]; + var value = GetValue(key); // #US373 + if (value == null) { + result = defaultValue; if (create) - this[key] = new PdfBoolean(); + { + SetValueInternal(key, new PdfBoolean(defaultValue)); + return true; + } return false; } - //if (obj is PdfReference reference) - // obj = reference.Value; - PdfReference.Dereference(ref obj); - - return obj switch + // PdfReference.Dereference(ref value); // #US373 + bool success = true; + result = value switch { PdfBoolean boolean => boolean.Value, - PdfBooleanObject booleanObject => booleanObject.Value, - _ => throw new InvalidCastException("GetBoolean: Object is not a boolean.") + PdfBooleanObject boolean => boolean.Value, + _ => Fail() }; - } + return success; - /// - /// Converts the specified value to boolean. - /// If the value does not exist, the function returns false. - /// If the value is not convertible, the function throws an InvalidCastException. - /// - public bool GetBoolean(string key) - => GetBoolean(key, false); + bool Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfBoolean), value.GetType()).Message); + } + + success = false; + return defaultValue; + } + } /// /// Sets the entry to a direct boolean value. /// public void SetBoolean(string key, bool value) - => this[key] = new PdfBoolean(value); + => SetValueInternal(key, new PdfBoolean(value)); + + // ===== PdfInteger ===== /// - /// Converts the specified value to integer. - /// If the value does not exist, the function returns 0. - /// If the value is not convertible, the function throws an InvalidCastException. + /// Gets the integer value that corresponds to the specified key. + /// If the key does not exist and create is false, the function returns the defaultValue. + /// If the key does not exist and create is true, + /// a direct value will be created using the defaultValue. + /// If the key exists but the value is neither an integer nor a PDF reference to an integer, + /// the function throws an InvalidOperationException. /// - public int GetInteger(string key, bool create) + public int GetInteger(string key, bool create = false, int defaultValue = 0) { - object? obj = this[key]; - if (obj == null) - { - if (create) - this[key] = new PdfInteger(); - return 0; - } - - if (obj is PdfNull) - return 0; - - if (obj is PdfReference reference) - obj = reference.Value; - - return obj switch - { - PdfInteger integer => integer.Value, - PdfIntegerObject integerObject => integerObject.Value, - _ => throw new InvalidCastException("GetInteger: Object is not an integer.") - }; + TryGetIntegerInternal(key, out int result, create, defaultValue, true); + return result; } /// - /// Converts the specified value to integer. - /// If the value does not exist, the function returns 0. - /// If the value is not convertible, the function throws an InvalidCastException. + /// Tries to get the integer value for the specified key. + /// Returns true on success, false if value is of wrong type. /// - public int GetInteger(string key) - => GetInteger(key, false); + public bool TryGetInteger(string key, out int result) + => TryGetIntegerInternal(key, out result, false, 0, false); - /// - /// Converts the specified value to unsigned integer. - /// If the value does not exist, the function returns 0. - /// If the value is not convertible, the function throws an InvalidCastException. - /// - public uint GetUnsignedInteger(string key, bool create) + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetIntegerInternal(string key, out int result, bool create, int defaultValue, + bool throwOnTypeMismatch) { - object? obj = this[key]; - if (obj == null) + Name.EnsureName(key); + //var value = this[key]; + var value = GetValue(key, VCF.NoTransform); // #US373 + if (value == null) { + result = defaultValue; if (create) - this[key] = new PdfInteger(); - return 0; + { + SetValueInternal(key, new PdfInteger(defaultValue)); + return true; + } + return false; } - if (obj is PdfNull) - return 0; + // PdfReference.Dereference(ref value); // #US373 + bool success = true; + result = value switch + { + PdfInteger integer => integer.Value, + PdfIntegerObject integer => integer.Value, + PdfLongInteger integer => LongInteger(integer.Value), + PdfLongIntegerObject integer => LongInteger(integer.Value), + _ => Fail() + }; + return success; - if (obj is PdfReference reference) - obj = reference.Value; + int LongInteger(long l) + { + if (l is >= Int32.MinValue and <= Int32.MaxValue) + return (int)l; + return Fail(); + } - return obj switch + int Fail() { - PdfInteger integer => (uint)integer.Value, - PdfIntegerObject integerObject => (uint)integerObject.Value, - PdfLongInteger longInteger => longInteger.Value is >= 0 and <= uint.MaxValue ? (uint)longInteger.Value : throw new InvalidCastException("GetUnsignedInteger: Long integer object is not an integer."), - _ => throw new InvalidCastException("GetUnsignedInteger: Object is not an integer.") - }; - } + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfInteger), value.GetType()).Message); + } - /// - /// Converts the specified value to integer. - /// If the value does not exist, the function returns 0. - /// If the value is not convertible, the function throws an InvalidCastException. - /// - public uint GetUnsignedInteger(string key) - => GetUnsignedInteger(key, false); + success = false; + return defaultValue; + } + } /// /// Sets the entry to a direct integer value. /// public void SetInteger(string key, int value) - => this[key] = new PdfInteger(value); + => SetValueInternal(key, new PdfInteger(value)); /// - /// Converts the specified value to double. - /// If the value does not exist, the function returns 0. - /// If the value is not convertible, the function throws an InvalidCastException. + /// Sets the entry to a direct integer value that is used as a flag. /// - public double GetReal(string key, bool create) - { - object? obj = this[key]; - if (obj == null) - { - if (create) - this[key] = new PdfReal(); - return 0; - } - - if (obj is PdfReference reference) - obj = reference.Value; + public void SetIntegerFlag(string key, int value) + => SetValueInternal(key, new PdfInteger(value, true)); - return obj switch - { - PdfReal real => real.Value, - PdfRealObject realObject => realObject.Value, - PdfInteger integer => integer.Value, - PdfIntegerObject integerObject => integerObject.Value, - _ => throw new InvalidCastException("GetReal: Object is not a number.") - }; - } + // ===== PdfInteger as unsigned integer ===== /// - /// Converts the specified value to double. - /// If the value does not exist, the function returns 0. - /// If the value is not convertible, the function throws an InvalidCastException. + /// Gets the unsigned integer value that corresponds to the specified key. + /// If the key does not exist and create is false, the function returns the defaultValue. + /// If the key does not exist and create is true, + /// a direct value will be created using the defaultValue. + /// If the key exists but the value is neither an integer nor a PDF reference to an integer, + /// the function throws an InvalidOperationException. /// - public double GetReal(string key) - => GetReal(key, false); + public uint GetUnsignedInteger(string key, bool create = false, uint defaultValue = 0) // TOD + { + TryGetUnsignedIntegerInternal(key, out uint result, create, defaultValue, true); + return result; + } /// - /// Sets the entry to a direct double value. + /// Tries to get the unsigned integer value for the specified key. + /// Returns true on success, false if value is of wrong type. /// - public void SetReal(string key, double value) - => this[key] = new PdfReal(value); + public bool TryGetUnsignedInteger(string key, out uint result) +#if true + => TryGetUnsignedIntegerInternal(key, out result, false, 0, false); +#else + { + bool success = TryGetLongIntegerInternal(key, out long longResult, false, 0, false); + if (!success || longResult < Int32.MinValue || longResult > UInt32.MaxValue) + { + result = 0; + return false; + } - /// - /// Converts the specified value to String. - /// If the value does not exist, the function returns the empty string. - /// - public string GetString(string key, bool create) + result = unchecked((uint)longResult); + return true; + } +#endif + bool TryGetUnsignedIntegerInternal(string key, out uint result, bool create, uint defaultValue, bool throwOnTypeMismatch) { - object? obj = this[key]; - if (obj == null) + // Background: PDF treats flags (like permissions) as a 32-bit unsigned integer. + // If the most significant bit is set, PDFsharp writes it as a negative integer. + // But some producer apps write it as a positive value in the range [int.MaxValue+1..uint.MaxValue]. + // PDFsharp parses such a number as PdfLongInteger. + // This code handles that case. + + Name.EnsureName(key); + var value = this[key]; // #US373 Also use GetValue here? + if (value == null) { + result = defaultValue; if (create) - this[key] = new PdfString(); - return ""; - } + { + SetValueInternal(key, new PdfInteger(unchecked((int)defaultValue))); + return true; + } - if (obj is PdfReference reference) - obj = reference.Value; + return false; + } - return obj switch + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - PdfString str => str.Value, - PdfStringObject strObject => strObject.Value, - PdfName name => name.Value, - PdfNameObject nameObject => nameObject.Value, - _ => throw new InvalidCastException("GetString: Object is not a string.") + PdfInteger integer => FromInteger(integer.Value), + PdfIntegerObject integer => FromInteger(integer.Value), + PdfLongInteger longInteger => FromLongInteger(longInteger.Value), + PdfLongIntegerObject longInteger => FromLongInteger(longInteger.Value), + _ => Fail() }; - } + return success; - /// - /// Converts the specified value to String. - /// If the value does not exist, the function returns the empty string. - /// - public string GetString(string key) - => GetString(key, false); - - /// - /// Tries to get the string. TODO_OLD: more TryGet... - /// - public bool TryGetString(string key, [MaybeNullWhen(false)] out string value) - { - value = null; - var obj = this[key]; - if (obj == null) - return false; + uint FromInteger(int value) + { + return unchecked((uint)value); + } - if (obj is PdfReference reference) - obj = reference.Value; + uint FromLongInteger(long value) + { + // if (value is >= 0 and <= UInt32.MaxValue) + if (value is >= Int32.MinValue and <= UInt32.MaxValue) + return unchecked((uint)value); + return Fail(); + } - switch (obj) + uint Fail() { - case PdfString str: - value = str.Value; - return true; - case PdfStringObject strObject: - value = strObject.Value; - return true; - case PdfName name: - value = name.Value; - return true; - case PdfNameObject nameObject: - value = nameObject.Value; - return true; - default: - return false; + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfInteger), value.GetType()).Message); + } + + success = false; + return defaultValue; } } + // ===== PdfLongInteger ===== + /// - /// Sets the entry to a direct string value. + /// Gets the integer or long integer value that corresponds to the specified key. + /// If the key does not exist and create is false, the function returns the defaultValue. + /// If the key does not exist and create is true, + /// a direct value will be created using the defaultValue. + /// If the key exists but the value is neither an integer or a long integer + /// nor a PDF reference to an integer or a long integer, + /// the function throws an InvalidOperationException. /// - public void SetString(string key, string value) - => this[key] = new PdfString(value); + public long GetLongInteger(string key, bool create = false, long defaultValue = 0) + { + TryGetLongIntegerInternal(key, out long result, create, defaultValue, true); + return result; + } /// - /// Converts the specified value to a name. - /// If the value does not exist, the function returns the empty string. + /// Tries to get the long integer value for the specified key. + /// Returns true on success, false if value is of wrong type. /// - public string GetName(string key) + public bool TryGetLongInteger(string key, out long result) + => TryGetLongIntegerInternal(key, out result, false, 0, false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetLongIntegerInternal(string key, out long result, bool create, long defaultValue, + bool throwOnTypeMismatch) { - var obj = this[key]; - if (obj == null) + Name.EnsureName(key); + var value = this[key]; + if (value == null) { - //if (create) - // this[key] = new Pdf(); - return ""; - } + result = defaultValue; + if (create) + { + SetValueInternal(key, new PdfLongInteger(defaultValue)); + return true; + } - if (obj is PdfReference reference) - obj = reference.Value; + return false; + } - return obj switch + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - PdfName name => name.Value, - PdfNameObject nameObject => nameObject.Value, - _ => throw new InvalidCastException("GetName: Object is not a name.") + PdfInteger integer => integer.Value, + PdfIntegerObject integer => integer.Value, + PdfLongInteger longInteger => longInteger.Value, + PdfLongIntegerObject longInteger => longInteger.Value, + _ => Fail() }; + return success; + + long Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfLongInteger), value.GetType()).Message); + } + + success = false; + return defaultValue; + } } /// - /// Sets the specified name value. - /// If the value doesn’t start with a slash, it is added automatically. + /// Sets the entry to a direct long integer value. /// - public void SetName(string key, string value) - { - if (value == null) - throw new ArgumentNullException(nameof(value)); + public void SetLongInteger(string key, long value) + => SetValueInternal(key, new PdfLongInteger(value)); - if (value.Length == 0 || value[0] != '/') - value = "/" + value; + // ===== PdfBoolean ===== - this[key] = new PdfName(value); + /// + /// Gets the real or integer value that corresponds to the specified key. + /// If the key does not exist and create is false, the function returns the defaultValue. + /// If the key does not exist and create is true, + /// a direct value will be created using the defaultValue. + /// If the key exists but the value is neither a real or an integer + /// nor a PDF reference to a real or an integer, the function throws an InvalidOperationException. + /// + public double GetReal(string key, bool create = false, double defaultValue = 0.0) + { + TryGetRealInternal(key, out double result, create, defaultValue, true); + return result; } /// - /// Converts the specified value to PdfRectangle. - /// If the value does not exist, the function returns an empty rectangle. - /// If the value is not convertible, the function throws an InvalidCastException. + /// Tries to get the real value for the specified key. + /// Returns true on success, false if value is of wrong type. /// - public PdfRectangle GetRectangle(string key, bool create) + public bool TryGetReal(string key, out double result) + => TryGetRealInternal(key, out result, false, 0, false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetRealInternal(string key, out double result, bool create, double defaultValue, + bool throwOnTypeMismatch) { - var obj = this[key]; - if (obj == null) + Name.EnsureName(key); + var value = this[key]; + if (value == null) { + result = defaultValue; if (create) - return (PdfRectangle)(this[key] = new PdfRectangle()); - return new(); + { + SetValueInternal(key, new PdfReal(defaultValue)); + return true; + } + + return false; } - if (obj is PdfReference reference) - obj = reference.Value; - if (obj is PdfArray { Elements.Count: 4 } array) + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - return (PdfRectangle)(this[key] = - new PdfRectangle(array.Elements.GetReal(0), array.Elements.GetReal(1), - array.Elements.GetReal(2), array.Elements.GetReal(3))); - } + PdfReal real => real.Value, + PdfRealObject realObject => realObject.Value, + PdfInteger integer => integer.Value, + PdfIntegerObject integer => integer.Value, + PdfLongInteger longInteger => longInteger.Value, + PdfLongIntegerObject longInteger => longInteger.Value, + _ => Fail() + }; + return success; - if (obj is PdfRectangle rectangle) - return rectangle; + double Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfReal), value.GetType()).Message); + } - throw new InvalidOperationException($"PDF item is '{obj.GetType().FullName}', but PdfRectangle expected."); + success = false; + return defaultValue; + } } /// - /// Converts the specified value to PdfRectangle. - /// If the value does not exist, the function returns an empty rectangle. - /// If the value is not convertible, the function throws an InvalidCastException. + /// Sets the entry to a direct double value. + /// + public void SetReal(string key, double value) + => SetValueInternal(key, new PdfReal(value)); + + // No SetNullableReal because there is no use case. + + // ===== PdfString ===== + + /// + /// Gets the string value that corresponds to the specified key. + /// If the key does not exist and create is false, the function returns the defaultValue. + /// If the key does not exist and create is true, + /// a direct value will be created using the defaultValue. + /// If the key exists but the value is neither a string or name + /// nor a PDF reference to a string or a name, the function throws an InvalidOperationException. + /// + public string GetString(string key, bool create = false, string defaultValue = "") + { + TryGetStringInternal(key, out string result, create, defaultValue, true); + return result; + } + + /// + /// Tries to get the name value for the specified key. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetString(string key, [MaybeNullWhen(false)] out string result) + => TryGetStringInternal(key, out result, false, "", false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetStringInternal(string key, out string result, bool create, + string defaultValue, bool throwOnTypeMismatch) + { + Name.EnsureName(key); + var value = this[key]; + if (value == null) + { + result = defaultValue; + if (create) + { + SetValueInternal(key, new PdfString(defaultValue)); + return true; + } + + return false; + } + + PdfReference.Dereference(ref value); + bool success = true; + result = value switch + { + PdfString str => str.Value, + PdfStringObject strObject => strObject.Value, + PdfName name => name.Value, + PdfNameObject nameObject => nameObject.Value, + _ => Fail() + }; + return success; + + string Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfString), value.GetType()).Message); + } + + success = false; + return defaultValue; + } + } + + /// + /// Sets the entry to a direct string value. + /// + public void SetString(string key, string value) + => SetValueInternal(key, new PdfString(value)); + + // ===== PdfName ===== + + /// + /// Gets the name value that corresponds to the specified key. + /// If the key does not exist and create is false, the function returns the defaultValue. + /// If the key does not exist and create is true, + /// a direct value will be created using the defaultValue. + /// If the key exists but the value is neither a name nor a PDF reference to a name, + /// the function throws an InvalidOperationException. + /// Note that the default defaultValue is the empty name ("/"), not the empty string. + /// + public string GetName(string key, bool create = false, string defaultValue = "/") + { + TryGetNameInternal(key, out string result, create, defaultValue, true); + return result; + } + + /// + /// Tries to get the name value for the specified key. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetName(string key, out string result) + => TryGetNameInternal(key, out result, false, "/", false); + + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetNameInternal(string key, out string result, bool create, + string defaultValue, bool throwOnTypeMismatch) + { + Name.EnsureName(key); + var value = this[key]; + if (value == null) + { + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract + result = defaultValue ?? "/"; + if (create) + { + SetValueInternal(key, new PdfName(result)); // Fail in Name if defaultValue is empty. + return true; + } + + return false; + } + + PdfReference.Dereference(ref value); + bool success = true; + result = value switch + { + PdfName name => name.Value, + PdfNameObject name => name.Value, + _ => Fail() + }; + return success; + + string Fail() + { + if (throwOnTypeMismatch) + { + throw new InvalidOperationException( + SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfName), value.GetType()).Message); + } + + success = false; + // ReSharper disable once NullCoalescingConditionIsAlwaysNotNullAccordingToAPIContract + return defaultValue ?? "/"; + } + } + + /// + /// Sets a PDF name at the specified key. + /// If the value doesn’t start with a slash, it is added automatically, + /// but starting with PDFsharp version 6.3 a warning is logged. + /// + public void SetName(string key, string value) + { + Name.EnsureName(key); + + if (value == null) + throw new ArgumentNullException(nameof(value)); + + // Ensure that value already starts with a slash. + if (value.Length == 0 || value[0] != '/') + { + PdfSharpLogHost.Logger.LogWarning("A PDF name must start with a '/'."); + value = String.Concat("/", value); + } + + SetValueInternal(key, new PdfName(value)); + } + + public void SetName(string key, T value) where T : Enum + { + SetName(key, Name.FromEnum(value).Value); + } + + // ===== PdfRectangle ===== + + /// + /// Gets the rectangle value that corresponds to the specified key. + /// If the key does not exist and create is false, the function returns the defaultValue. + /// If the key does not exist and create is true, + /// a direct value will be created using the defaultValue. + /// If the key exists but the value is neither a rectangle or an array with four elements + /// nor a PDF reference to a rectangle or an array with four elements, + /// the function throws an InvalidOperationException. + /// + public PdfRectangle? GetRectangle(string key, bool create = false, PdfRectangle? defaultValue = null) + { + TryGetRectangleInternal(key, out var result, create, defaultValue, true); + return result; + } + + public PdfRectangle GetRequiredRectangle(string key, bool create = false, PdfRectangle? defaultValue = null) + { + if (create && defaultValue == null) + throw new InvalidOperationException("Cannot create a rectangle if the default value is null."); + + if (TryGetRectangleInternal(key, out var result, create, defaultValue, true) + || result != null) + return result; + throw ExceptionOnNull(key); + } + + /// + /// Tries to get the rectangle value for the specified key. + /// Returns true on success, false if value is of wrong type. /// - public PdfRectangle GetRectangle(string key) - => GetRectangle(key, false); + public bool TryGetRectangle(string key, [MaybeNullWhen(false)] out PdfRectangle result) + => TryGetRectangleInternal(key, out result, false, null, false); + + // Re/Sharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetRectangleInternal(string key, [MaybeNullWhen(false)] out PdfRectangle result, bool create, + PdfRectangle? defaultValue, bool throwOnTypeMismatch) + { + Name.EnsureName(key); + var value = this[key]; + if (value == null) + { + result = defaultValue; + if (create) + { + if (result == null) + throw new InvalidOperationException("You cannot create a PdfRectangle if the default value is null."); + SetValueInternal(key, result); + return true; + } + return false; + } + + PdfReference.Dereference(ref value); + bool success = true; + result = value switch + { + // PdfRectangle is replaced by PdfArray in SetValueInternal. + PdfRectangle rect => throw new InvalidOperationException("Should not come here anymore"), + PdfArray array => FromArray(array), + _ => Fail() + }; + return success; + + //PdfRectangle FromRectangle(PdfRectangle rc) // DELETE + //{ + // // PdfRectangle objects should be replaced by PdfArray. + // throw new InvalidOperationException("Should not come here anymore"); + //} + + PdfRectangle? FromArray(PdfArray array) + { + if (array.Elements.Count == 4) + { + var rectangle = new PdfRectangle(array.Elements.GetReal(0), array.Elements.GetReal(1), + array.Elements.GetReal(2), array.Elements.GetReal(3)); + + return rectangle; + } + return Fail("A PdfRectangle expects a PdfArray with 4 real values."); + } + + PdfRectangle? Fail(string? message = "") + { + if (throwOnTypeMismatch) + { + message ??= SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfArray), value.GetType()).Message; + throw new InvalidOperationException(message); + } + success = false; + return defaultValue; //?? new PdfRectangle(); TODO test + } + } /// /// Sets the entry to a direct rectangle value, represented by an array with four values. /// public void SetRectangle(string key, PdfRectangle rect) - => _elements[key] = rect; + { + // Setting PdfRectangle is handled as a special case in SetValueInternal. + // This is because dict.SetValue(key, rect) also must be handled correctly. + SetValueInternal(key, rect); + } + + // ===== XMatrix ===== + // IMPROVE: Should be extension method to separate XGraphics from PDF Core. /// Converts the specified value to XMatrix. /// If the value does not exist, the function returns an identity matrix. /// If the value is not convertible, the function throws an InvalidCastException. - public XMatrix GetMatrix(string key, bool create) + public XMatrix GetMatrix(string key, bool create = false, XMatrix? defaultMatrix = null) { - var obj = this[key]; - if (obj == null) + //var item = this[key]; + var item = GetValue(key); // #US373 + if (item == null) { if (create) - this[key] = new PdfLiteral("[1 0 0 1 0 0]"); // cannot be parsed, implement a PdfMatrix... - return XMatrix.Identity; + { + PdfArray array; + if (defaultMatrix is null) + { + array = new(new PdfInteger(1), new PdfInteger(0), + new PdfInteger(0), new PdfInteger(1), + new PdfInteger(0), new PdfInteger(0)); + } + else + { + array = new(new PdfReal(defaultMatrix.Value.M11), new PdfReal(defaultMatrix.Value.M12), + new PdfReal(defaultMatrix.Value.M21), new PdfReal(defaultMatrix.Value.M22), + new PdfReal(defaultMatrix.Value.OffsetX), new PdfReal(defaultMatrix.Value.OffsetY)); + } + SetValueInternal(key, array); + } + return defaultMatrix ?? XMatrix.Identity; } - if (obj is PdfReference reference) - obj = reference.Value; - - return obj switch + //PdfReference.Dereference(ref item); + return item switch { PdfArray { Elements.Count: 6 } array => - new(array.Elements.GetReal(0), array.Elements.GetReal(1), + new(array.Elements.GetReal(0), array.Elements.GetReal(1), array.Elements.GetReal(2), array.Elements.GetReal(3), array.Elements.GetReal(4), array.Elements.GetReal(5)), PdfLiteral => throw new NotImplementedException("Parsing matrix from literal."), @@ -638,499 +1104,1138 @@ public XMatrix GetMatrix(string key, bool create) }; } - /// Converts the specified value to XMatrix. - /// If the value does not exist, the function returns an identity matrix. - /// If the value is not convertible, the function throws an InvalidCastException. - public XMatrix GetMatrix(string key) - => GetMatrix(key, false); + //public bool TryGetMatrix(string key, out XMatrix result) + //{ + // //TODO? + // throw new NotImplementedException(); + //} + + //public bool TryGetMatrixInternal(string key, out XMatrix result) + //{ + // //TODO? + // throw new NotImplementedException(); + //} /// /// Sets the entry to a direct matrix value, represented by an array with six values. /// public void SetMatrix(string key, XMatrix matrix) - => _elements[key] = PdfLiteral.FromMatrix(matrix); + { + SetValueInternal(key, new PdfArray( + new PdfReal(matrix.M11), new PdfReal(matrix.M12), + new PdfReal(matrix.M21), new PdfReal(matrix.M22), + new PdfReal(matrix.OffsetX), new PdfReal(matrix.OffsetY))); + } + + // ===== PdfDate ===== /// - /// Converts the specified value to DateTime. + /// Converts the specified value to DateTimeOffset. /// If the value does not exist, the function returns the specified default value. - /// If the value is not convertible, the function throws an InvalidCastException. + /// If the value is not convertible, the function throws an InvalidOperationException. /// - public DateTime GetDateTime(string key, DateTime defaultValue) + public DateTimeOffset? GetDateTime(string key, DateTimeOffset? defaultValue = null, bool create = false) { - var obj = this[key]; - if (obj == null) - return defaultValue; + TryGetDateTimeInternal(key, out DateTimeOffset? result, create, defaultValue, true); + return result; + } - //PdfReference.Dereference(ref obj); - if (obj is PdfReference reference) - obj = reference.Value; + public DateTimeOffset GetRequiredDateTime(string key, DateTimeOffset? defaultValue = null, bool create = false) + { + TryGetDateTimeInternal(key, out DateTimeOffset? result, create, defaultValue, true); + if (result == null) + throw ExceptionOnNull(key); + return result.Value; + } - if (obj is PdfDate date) - return date.Value; + /// + /// Tries to get the date value for the specified key. + /// Returns true on success, false if value is of wrong type. + /// + public bool TryGetDateTime(string key, out DateTimeOffset? result) + { + return TryGetDateTimeInternal(key, out result, false, null, false); + } - string strDate; - if (obj is PdfString pdfString) - strDate = pdfString.Value; - else + // ReSharper disable once ParameterOnlyUsedForPreconditionCheck.Local + bool TryGetDateTimeInternal(string key, [MaybeNullWhen(false)] out DateTimeOffset? result, bool create, + DateTimeOffset? defaultValue, bool throwOnTypeMismatch) + { + Name.EnsureName(key); + + result = null; + var value = this[key]; + if (value == null) { - if (obj is PdfStringObject stringObject) - strDate = stringObject.Value; - else - throw new InvalidCastException("GetName: Object is not a name."); + result = defaultValue; + if (create) + { + if (result == null) + { + // Cannot create value without default value. + return false; + } + SetValueInternal(key, new PdfDate(result.Value)); + return true; + } + return false; } - if (strDate != "") + PdfReference.Dereference(ref value); + bool success = true; + result = value switch { - try + PdfDate date => date.Value, + PdfString date => DateFromString(date.Value), + PdfStringObject date => DateFromString(date.Value), + _ => Fail() + }; + return success; + + DateTimeOffset? DateFromString(string pdfDate) + { + if (Parser.TryParseDate(pdfDate, out var dateTime)) + return dateTime; + // TODO Throw exception here, indicating malformed date string? + return Fail(); + } + + DateTimeOffset? Fail() + { + if (throwOnTypeMismatch) { - defaultValue = Parser.ParseDateTime(strDate, defaultValue); + throw new InvalidOperationException( + SyMsgs.DictionaryEntryIsOfWrongType(key, typeof(PdfDate), value.GetType()).Message); } - // ReSharper disable EmptyGeneralCatchClause - catch { } - // ReSharper restore EmptyGeneralCatchClause + + success = false; + return defaultValue; } - return defaultValue; } /// /// Sets the entry to a direct datetime value. /// + [Obsolete("Use DateTimeOffset as parameter.")] public void SetDateTime(string key, DateTime value) - => _elements[key] = new PdfDate(value); + { + Name.EnsureName(key); + + SetValueInternal(key, new PdfDate(value)); + } + + /// + /// Sets the entry to a direct datetime value. + /// + public void SetDateTime(string key, DateTimeOffset value) + { + Name.EnsureName(key); + + SetValueInternal(key, new PdfDate(value)); + } + + // ===== GetValue ===== + + /// + /// Gets the value for the specified key. + /// If the value does not exist, it is optionally created. + /// If the value is a PDF reference the referenced object is returned, except the itemType + /// is set to PdfReference. + /// + public PdfItem? GetValue(string key, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { +#if PDFSHARP_DEBUG_ + if (ShouldBreak5) + Debugger.Break(); +#endif + Name.EnsureName(key); + if (valueType != null) + EnsureValueType(valueType); + + return GetValueInternal(key, options, valueType, valueType != null); + } + + /// + /// Gets the result as GetValue if it is not null. + /// Otherwise, an exception is thrown. + /// + public PdfItem GetRequiredValue(string key, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + return GetValue(key, options, valueType) + ?? throw ExceptionOnNull(key); + } + + // For interface IDictionary.TryGetValue(string, out PdfItem) + public bool TryGetValue(string key, [MaybeNullWhen(false)] out PdfItem value) + => TryGetValue(key, out value, null); + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out PdfItem value, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType) + { + Name.EnsureName(key); + if (valueType != null) + EnsureValueType(valueType); + + value = null; + var result = GetValueInternal(key, VCF.NoTransform, valueType, false); + if (result != null) + { + value = result; + return true; + } + + return false; + } + + public T? GetValue< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, VCF options = VCF.None) where T : PdfItem + { + Name.EnsureName(key); + + var value = GetValueInternal(key, options, typeof(T), true) as T; + return value; + } + + public T GetRequiredValue< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, VCF options = VCF.None) where T : PdfItem + { + return GetValue(key, options) + ?? throw ExceptionOnNull(key); + } + + public bool TryGetValue(string key, [MaybeNullWhen(false)] out T value) where T : PdfItem + { + Name.EnsureName(key); + + value = null; + if (GetValueInternal(key, VCF.NoTransform, null, false) is T valueOfT) + { + value = valueOfT; + return true; + } + + return false; + } + + /// + /// Sets the entry with the specified value. DON’T USE THIS FUNCTION - IT MAY BE REMOVED. // PDFsharp/NT + /// + public void SetValue(string key, PdfItem value) + { + Name.EnsureName(key); + + SetValueInternal(key, value); // TODO: Check for indirect objects. + } + + // ===== GetObject ===== + + /// + /// Gets the PdfObject with the specified key, or null if no such object exists. + /// If the key refers to a reference, the referenced PdfObject is returned. + /// * + /// + public PdfObject? GetObject(string key, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + Name.EnsureName(key); + if (valueType != null) + EnsureValueType(valueType); + //else + // valueType = typeof(PdfObject); + + var value = GetValueInternal(key, options, valueType, false); + return value as PdfObject; + } + + public PdfObject GetRequiredObject(string key, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + return GetObject(key, options, valueType) + ?? throw ExceptionOnNull(key); + } + + public T? GetObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, VCF options = VCF.None) where T : PdfObject + { + Name.EnsureName(key); + + // TODO: Never throw? + bool throwOnTypeMismatch = typeof(PdfContainer).IsAssignableFrom(typeof(T)); + return GetValueInternal(key, options, typeof(T), throwOnTypeMismatch) as T; + } + + public T GetRequiredObject< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, VCF options = VCF.None) where T : PdfObject + { + return GetObject(key, options) + ?? throw ExceptionOnNull(key); + } + + /// + /// Sets the entry to the specified object. + /// + public void SetObject(string key, PdfObject obj) // Used in PDFsharp + { + Name.EnsureName(key); + + SetValueInternal(key, obj); + } + + // ===== GetArray ===== + + /// + /// Gets the PdfArray with the specified key, or null, if no such object exists and should not be created. + /// If the key refers to a reference, the referenced PdfArray is returned. + /// If the array exists, but cannot be transformed to type T, an exception is thrown. + /// + /// The key. + /// The creation options. + /// + public PdfArray? GetArray(string key, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + Name.EnsureName(key); + if (valueType != null) + EnsureValueType(valueType); + + return GetValueInternal(key, options, valueType, valueType != null) as PdfArray; + } + + public PdfArray GetRequiredArray(string key, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + + { + return GetArray(key, options, valueType) + ?? throw ExceptionOnNull(key); + } + + public bool TryGetArray(string key, [MaybeNullWhen(false)] out PdfArray array) + => TryGetArray(key, out array, null); + + + public bool TryGetArray(string key, [MaybeNullWhen(false)] out PdfArray array, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType) + { + Name.EnsureName(key); + if (valueType != null) + EnsureValueType(valueType); + + array = null; + if (GetValueInternal(key, VCF.NoTransform, valueType, false) is PdfArray value) + { + array = value; + return true; + } + return false; + } + + /// + /// Gets the PdfArray with the specified key, or null, if no such object exists and should not be created. + /// If the key refers to a reference, the referenced PdfArray is returned. + /// If the array exists, but cannot be transformed to type T, an exception is thrown. + /// + /// The key. + /// The creation options. + public T? GetArray< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, VCF options = VCF.None) where T : PdfArray + { + Name.EnsureName(key); + + var array = GetValueInternal(key, options, typeof(T), true) as T; + return array; + } + + /// + /// Gets the PdfArray with the specified key, or throws an exception, if no such object exists and + /// should not be created. + /// If the key refers to a reference, the referenced PdfArray is returned. + /// If the array exists, but cannot be transformed to type T, an exception is thrown. + /// + /// The key. + /// The creation options. + public T GetRequiredArray< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, VCF options = VCF.None) where T : PdfArray + { + Name.EnsureName(key); + + return GetArray(key, options) + ?? throw ExceptionOnNull(key); + } + + public bool TryGetArray< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, [MaybeNullWhen(false)] out T array) where T : PdfArray + { + Name.EnsureName(key); + + array = null; + if (GetValueInternal(key, VCF.NoTransform, typeof(T), false) is T valueOfT) + { + array = valueOfT; + return true; + } + return false; + } + + // ===== GetDictionary ===== + + /// + /// Gets the PdfDictionary with the specified key, or null, if no such object exists + /// and should not be created. + /// If the key refers to a reference, the referenced PdfDictionary is returned. + /// + public PdfDictionary? GetDictionary(string key, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + Name.EnsureName(key); + if (valueType != null) + EnsureValueType(valueType); + + return GetValueInternal(key, options, valueType, valueType != null) as PdfDictionary; + } + + public PdfDictionary GetRequiredDictionary(string key, VCF options = VCF.None, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType = null) + { + return GetDictionary(key, options, valueType) + ?? throw ExceptionOnNull(key); + } + + public bool TryGetDictionary(string key, [MaybeNullWhen(false)] out PdfDictionary dict) + => TryGetDictionary(key, out dict, null); + + public bool TryGetDictionary(string key, [MaybeNullWhen(false)] out PdfDictionary dict, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType) + { + Name.EnsureName(key); + if (valueType != null) + EnsureValueType(valueType); + + dict = null; + if (GetValueInternal(key, VCF.NoTransform, valueType, false) is PdfDictionary value) + { + dict = value; + return true; + } + return false; + } + + /// + /// Gets the PdfDictionary with the specified key, or null, if no such object exists and + /// should not be created. + /// If the key refers to a reference, the referenced PdfDictionary is returned. + /// If the dictionary exists, but cannot be transformed to type T, an exception is thrown. + /// + /// The type of the PDF dictionary. + /// The key. + /// The creation options. + public T? GetDictionary< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, VCF options = VCF.None) where T : PdfDictionary + { + Name.EnsureName(key); + + var value = GetValueInternal(key, options, typeof(T), true); + return value as T; + } + + /// + /// Gets the PdfDictionary with the specified key, or throws an exception, if no such object exists and + /// should not be created. + /// If the key refers to a reference, the referenced PdfDictionary is returned. + /// If the dictionary exists, but cannot be transformed to type T, an exception is thrown. + /// + /// The type of the PDF dictionary. + /// The key. + /// The creation options. + public T GetRequiredDictionary< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, VCF options = VCF.None) where T : PdfDictionary + { + return GetDictionary(key, options) + ?? throw ExceptionOnNull(key); + } + + public bool TryGetDictionary< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] T> + (string key, [MaybeNullWhen(false)] out T dict) where T : PdfDictionary + { + Name.EnsureName(key); + + dict = null; + if (GetValueInternal(key, VCF.NoTransform, typeof(T), false) is T valueOfT) + { + dict = valueOfT; + return true; + } + return false; + } + + // ===== PdfReference ===== + + /// + /// Gets the PdfReference with the specified key, or null if no such object exists. + /// + public PdfReference? GetReference(string key) + { + Name.EnsureName(key); + + // Always get the raw value. + if (_elements.TryGetValue(key, out var value)) + return value as PdfReference; + return null; + } + + /// + /// Gets the PdfReference with the specified key, or throws an exception, + /// if no such object exists. + /// + public PdfReference GetRequiredReference(string key) + { + Name.EnsureName(key); + + return GetReference(key) + ?? throw new InvalidOperationException(SyMsgs.IndirectReferenceMustNotBeNull.Message); + } + + /// + /// Sets the entry to an indirect reference. + /// The specified object must be an indirect object, + /// otherwise an exception is raised. + /// + [Obsolete("Use SetObject with an indirect object.")] + public void SetReference(string key, PdfObject obj) + { + Name.EnsureName(key); + + if (obj.Reference == null) + throw new ArgumentException("PdfObject is not an indirect object.", nameof(obj));// #SyMsg + SetValueInternal(key, obj.Reference); + } + + /// + /// Sets the entry to an indirect reference. + /// + public void SetReference(string key, PdfReference iref) + { + Name.EnsureName(key); + + if (iref is null) + throw new ArgumentNullException(nameof(iref)); + SetValueInternal(key, iref); + } - internal int GetEnumFromName(string key, object defaultValue, bool create) + /// + /// Access a key that may contain an array or a single item for working with its value(s). + /// + public ArrayOrSingleItemHelper ArrayOrSingleItem => new(this); // TODO_OLD PDFsharp6: Naming. + + // ===== Enum ===== + + public int GetEnumFromName(string key, object defaultValue, bool create = false) // TODO: review, object => Enum? { - if (defaultValue is not Enum) - throw new ArgumentException(nameof(defaultValue)); + Name.EnsureName(key); - var obj = this[key]; - if (obj == null) + var item = this[key]; + if (item == null) { if (create) - this[key] = new PdfName(defaultValue.ToString()!); + SetValueInternal(key, new PdfName(defaultValue.ToString()!)); // ReSharper disable once PossibleInvalidCastException because Enum objects can always be cast to int. return (int)defaultValue; } - return (int)Enum.Parse(defaultValue.GetType(), obj.ToString()?.Substring(1) ?? "", false); - } - internal int GetEnumFromName(string key, object defaultValue) - => GetEnumFromName(key, defaultValue, false); + PdfReference.Dereference(ref item); // Could be a reference. #US373 + return (int)Enum.Parse(defaultValue.GetType(), item.ToString()?[1..] ?? "", false); + } - internal void SetEnumAsName(string key, object value) + public void SetEnumAsName(string key, object value) { + Name.EnsureName(key); + if (value is not Enum) - throw new ArgumentException(nameof(value)); - _elements[key] = new PdfName("/" + value); + throw new ArgumentException("value is not an Enum.", nameof(value)); + SetValueInternal(key, new PdfName("/" + value)); } - /// - /// Gets the value for the specified key. If the value does not exist, it is optionally created. + public bool TryGetEnum(string key, out TEnum result) where TEnum : struct, Enum + { + result = default; + + if (TryGetInteger(key, out var value)) + { + result = (TEnum)(value as object); + return true; + } + + if (TryGetName(key, out var name)) + { + // Name must always have a leading solidus. + Debug.Assert(!String.IsNullOrEmpty(name)); + + result = Enum.Parse(name?[1..] ?? "", false); + + return true; + } + return false; + } + + public TEnum? GetEnum(string key) where TEnum : struct, Enum + { + if (TryGetEnum(key, out var result)) + { + return result; + } + return null; + } + + + // ===== Internal ===== + + /// // TODO more docs + /// Gets the value for the specified key. + /// If the value does not exist, it is optionally created. + /// If the value exists, it can be optionally transformed to a derived type. + /// If the value is a PdfReference, the indirect object is returned. /// - public PdfItem? GetValue(string key, VCF options) + PdfItem? GetValueInternal(string key, VCF options, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? valueType, bool throwOnTypeMismatch) { - var value = this[key]; - if (value is null or PdfNull or PdfReference { Value: PdfNullObject }) +#if DEBUG_ + // Are we reading a PDF document? + if ((OwningDictionary.Document?.IrefTable ?? null) is { IsUnderConstruction: true }) + { + //Debugger.Break(); + _ = typeof(int); + } +#endif +#if DEBUG_ + if (typeof(PdfNameDictionary) == valueType) + _ = typeof(int); +#endif + // Name.EnsureName(key) must be done by caller. + + bool valueIsAlreadyOfSuitableType = false; + if (!_elements.TryGetValue(key, out var value)) + { + // Case: Dictionary entry with specified key does not exist. + + // Create the value if requested. + if (options is VCF.Create or VCF.CreateIndirect) + { + var type = valueType ?? GetValueType(key); + if (type != null) + { + // Case: value is null, but we have a type to create it. + + //Debug.Assert(typeof(PdfObject).IsAssignableFrom(type), "Type not allowed."); + //PdfObject? obj; + + if (typeof(PdfContainer).IsAssignableFrom(type)) + { + // Case: Create PDF array or dictionary. + var obj = CreateContainer(type, null, options is VCF.CreateIndirect); + + if (options is VCF.CreateIndirect) + { + // There are dictionaries (e.g. PdfNameDictionary) that are always created as indirect + // in their constructor. + if (obj.IsIndirect is false) // TODO Check this everywhere + OwningDictionary.Owner.IrefTable.Add(obj); + Debug.Assert(obj.Reference is not null); + } + else // if (options is VCF.Create) is always true here. + { + Debug.Assert(obj.Reference is null); + } + value = obj; + } + // Note that the cases below are not real world scenarios, but you cannot force a developer not to write code like this: + // var item = new PdfDictionary().Elements.GetValue("/SomeKey", VCF.Create) + // This creates an empty PdfString which is senseless, but valid. + else if (typeof(PdfPrimitive).IsAssignableFrom(type)) + { + // Case: Create a PDF primitive. This makes no sense because it is immutable, and we have no default value. + + // Primitives cannot be indirect objects. + if (options is VCF.CreateIndirect) + throw new InvalidOperationException($"Cannot create an indirect object of type '{type.FullName}'."); + + value = (PdfItem)(Activator.CreateInstance(type) + ?? throw new InvalidOperationException($"Cannot create instance of type '{type.FullName}'.")); + + value.SetTransformed(); + } + else if (typeof(PdfPrimitiveObject).IsAssignableFrom(type)) + { + // Case: Create a PDF primitive object. This makes even less sense and is not implemented. + + // Primitive objects cannot be direct. + if (options is VCF.Create) + throw new InvalidOperationException($"Cannot create a direct object of type '{type.FullName}'."); + + throw new InvalidOperationException( + $"Cannot create instance of type '{type.FullName}' " + + "because creating of indirect primitive PDF objects makes less sense and is not implemented."); + } + else if (typeof(PdfReference) == type) + { + // Case: itemType is PdfReference in combination with Create/CreateIndirect. + + throw new InvalidOperationException($"Cannot combine type '{type.FullName}' with creation flags."); + } + else + { + // Case: We only come here for special types like PdfDocument which are derived from PdfObject but are not containers. + // Just throw. + + throw new InvalidOperationException($"Cannot create instance of type '{type.FullName}' " + + "because this is not intended for this PDF type."); + } + + Debug.Assert(value.IsTransformed); + + SetValueInternal(key, value); + + Debug.Assert(valueType == null || valueType.IsInstanceOfType(value)); + valueIsAlreadyOfSuitableType = true; + //return value; + } + else + { + // Case: We have no type and cannot create an instance. + // But check if this is caused by to aggressive trimming the assembly. + + // /Info is the first type created by object type transformation. + // In case somebody trimmed PDFsharp to hard, we come here und try to give a + // meaningful explanation. + if (key == "/Info") + { + // We come here if PDFsharp was fully trimmed and meta-data could not be found by reflection. + throw new InvalidOperationException( + "PDFsharp relies on reflection and does not work when a fully-trimmed self-contained file is used.\r\n" + + "See https://docs.pdfsharp.net/ for further information."); + } + + // Maybe missing or wrong meta configuration. + throw new InvalidOperationException( + $"Cannot create value for key '{key}' because no type was found."); + } + } + } + else { - if (options != VCF.None) + // Case: The value exists and can be returned. + // But /*for imported documents*/ check for object type transformation. + + if (value is PdfReference iref) { #if DEBUG_ - if (key == "/Resources") - Debug-Break.Break(); + var irefTable = OwningDictionary.Document?.IrefTable ?? null; + if (irefTable is { IsUnderConstruction: true }) + { + //Debug.Assert(false, "Should not happen anymore."); + _ = typeof(int); + } #endif - var type = GetValueType(key); - if (type != null) + // Case: value is an indirect object. It can only be a container. + +#if true_ // TODO Create US for this case and DELETE the code. + // Check a very particular case first that can happen during + // the timespan when the IrefTable is not completely read. + var irefTable = OwningDictionary.Document?.IrefTable ?? null; + if (irefTable is { IsUnderConstruction: true }) { - Debug.Assert(typeof(PdfItem).IsAssignableFrom(type), "Type not allowed."); - PdfObject? obj; - if (typeof(PdfDictionary).IsAssignableFrom(type)) + // Case: During the import of a PDF document GetValue is called. + // This happens only during encryption. + // IMPROVE: Prevent from coming here. Check if we need temp irefs anymore. + var newIref = irefTable[iref.Value.ObjectID]; + if (newIref == null) { - value = obj = CreateDictionary(type, null); + // Case: Cannot happen. We can have irefs with no value, but not + // indirect objects with no reference. + _ = typeof(int); } - else if (typeof(PdfArray).IsAssignableFrom(type)) + else if (ReferenceEquals(iref, newIref) is false) { - value = obj = CreateArray(type, null); + // Case: We are reading a PDF document that hast more than one XRef table or xref streams. + // A top level object can contain an indirect reference to an object with an ID that does not yet exist. + // TODO more checks... + if (ReferenceEquals(iref.Value, newIref.Value)) + { + iref = newIref; + iref.Value.Reference = iref; + UpdateValueInternal(key, iref); + } } else - throw new NotImplementedException("Type other than array or dictionary."); - - if (options == VCF.CreateIndirect) { - _ownerDictionary?.Owner.IrefTable.Add(obj); - this[key] = obj.Reference; + _ = typeof(int); } - else - this[key] = obj; } - else +#if DEBUG + else if (irefTable != null) { - if (key == "/Info") + // TO/DO: Check if already required. + var altIref = irefTable[iref.Value.ObjectID]; + if (altIref == null) { - // We come here if PDFsharp was fully trimmed and meta-data could not be found by reflection. - // Note: Should not occur since we added attributes that prevent trimming of certain parts. - throw new InvalidOperationException($"PDFsharp relies on reflection and does not work when a fully-trimmed self-contained file is used.\r\n" + - $"See {UrlLiterals.LinkToRoot} for further information."); + // Should not come here. + _ = typeof(int); } - else + else if (ReferenceEquals(iref, altIref) is false) { - throw new InvalidOperationException("Cannot create value for key: " + key); + // Should not come here. + iref = altIref; + UpdateValueInternal(key, iref); } } - } - } - else - { - // The value exists and can be returned. But for imported documents check for necessary - // object type transformation. - PdfReference? iref; - if ((iref = value as PdfReference) != null) - { - // Case: value is an indirect reference. +#endif +#endif + // Is PdfReference explicitly requested? + if (valueType == typeof(PdfReference)) + return value; + + // In all other cases use the referenced value. value = iref.Value; if (value == null) { // If we come here PDF file is corrupted. + // TODO: What if we have a reference to a non-existing object? + // A reference without a value can only happen during loading an existing PDF file. throw new InvalidOperationException("Indirect reference without value."); } - if (true) // || _owner.Document.IsImported) + // The nesting can be simplified, but keep it as it is for better understandability. + if (options != VCF.NoTransform) { - var type = GetValueType(key); - Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support."); + // Get type from parameter or metadata. + var type = valueType ?? GetValueType(key); + + // Try transformation only once. If it fails, don’t try again. + // Should we try transformation? - if (type != null! && type != value.GetType()) + // TODO: What if valueType more derived than metadata type? Transform again? + if (value.ShouldTryTransformation + || type != null + || value.GetType().IsAssignableFrom(type)) { - if (typeof(PdfDictionary).IsAssignableFrom(type)) + // Do we have a type anyway? + if (type != null) { - Debug.Assert(value is PdfDictionary, "Bug in PDFsharp. Please send this file to PDFsharp support."); - value = CreateDictionary(type, (PdfDictionary)value); + // Case: We have a type and an existing indirect object. + + if (type.IsInstanceOfType(value)) + { + // Case: The value is already of the appropriate type. + + // Set to transformed, but only if the requested type is not one of the base types. + if (type != typeof(PdfDictionary) && type != typeof(PdfArray)) + value.SetTransformed(); + } + else if (value is PdfContainer cont) + { + // Case: Transform array or dictionary. + + // TODO: Test to transform twice. + + //Debug.Assert(cont.IsTransformed is false or true); + Debug.Assert(cont.ParentInfo is null); + //if (OwningDictionary.Document.IrefTable.IsUnderConstruction) + value = CreateContainer(type, cont, cont.IsIndirect); + Debug.Assert(cont.IsDead); + Debug.Assert(value.IsTransformed); + Debug.Assert(cont.ParentInfo is null); + + } + else if (value is PdfPrimitiveObject) + { + throw new InvalidOperationException( + $"Primitive indirect object of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'."); + } + else + { + // Exotic case: Reference to e.g. PdfDocument. + // Just throw. + throw new InvalidOperationException( + $"Indirect object of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'."); + } + // TODO: Should fail because it is a Reference - test this. + SetValueInternal(key, value); } - else if (typeof(PdfArray).IsAssignableFrom(type)) + else { - Debug.Assert(value is PdfArray, "Bug in PDFsharp. Please send this file to PDFsharp support."); - value = CreateArray(type, (PdfArray)value); + // TODO: Should be possible to transform later. + value.SetTransformationTried(); } - else - throw new NotImplementedException("Type other than array or dictionary."); } } - return value; + Debug.Assert(value != null); + //return value; } - - // Transformation is only possible after PDF import. - if (true) // || _owner.Document.IsImported) + else { - // Case: value is a direct object - //if ((dict = value as PdfDictionary) != null) - if (value is PdfDictionary dict) + // Case: value is a direct object. + + // Transformation is possible after PDF import or the user + // creates a less derived container (e.g. in unit tests). + if (options != VCF.NoTransform) { - Debug.Assert(!dict.IsIndirect); + // Try transformation only once. If it fails, don’t try again. + // Should we try transformation? + if (value.ShouldTryTransformation) + { + // Get type from parameter or metadata. + var type = valueType ?? GetValueType(key); - var type = GetValueType(key); - Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support."); - if (dict.GetType() != type) - dict = CreateDictionary(type, dict); - return dict; + // Do we have a type anyway? + if (type != null) + { + // Case: We have a type and an existing primitive or direct object. + + // Handle special case PdfRectangle first. + if (type == typeof(PdfRectangle)) // TODO in Array and in non-else case above. + { + if (value is PdfArray { Elements.Count: 4 } array) + { + value = new PdfRectangle(array); + } + else + { + if (throwOnTypeMismatch) + throw new InvalidOperationException($"Cannot create PdfRectangle from type '{value.GetType().FullName}'."); + value = null; + } + } + else if (type.IsInstanceOfType(value)) + { + // Case: The value is already of the appropriate type. + + // Set to transformed, but only if the requested type is not one of the base types. + if (type != typeof(PdfDictionary) && type != typeof(PdfArray)) + value.SetTransformed(); + } + else if (value is PdfContainer cont) + { + // Case: Transform direct array or dictionary. + + Debug.Assert(cont.IsIndirect is false); + Debug.Assert(cont.IsTransformed is false); + Debug.Assert(cont.ParentInfo is not null); + + value = CreateContainer(type, cont, false /*cont.IsIndirect*/); + SetValueInternal(key, value); + + Debug.Assert(cont.IsDead); + Debug.Assert(value.IsTransformed); // TODO: Can transform twice? + Debug.Assert(cont.ParentInfo is null); + Debug.Assert(((PdfContainer)value).ParentInfo is not null); + } + else if (value is PdfNull nullObject) + { + // TODO Add in Array + _ = typeof(int); + } + // #US373 begin + else if (value is PdfInteger integer && + type == typeof(PdfReal)) + { + value = new PdfReal(integer.Value); + SetValueInternal(key, value); + } + // #US373 end + else + { + // Exotic case: value is not a PDF array or dictionary, but not from the requested type. + // Just throw. + throw new InvalidOperationException( + $"Value of type '{value.GetType().FullName}' cannot be transformed to type '{type.FullName}'."); + } + } + else + { + // TODO: Should be possible to transform later. + value.SetTransformationTried(); + } + } } + } + } - //if ((array = value as PdfArray) != null) - if (value is PdfArray array) - { - Debug.Assert(!array.IsIndirect); - - var type = GetValueType(key); - // This is more complicated. If type is null do nothing - //Debug.Assert(type != null, "No value type specified in meta information. Please send this file to PDFsharp support."); - if (type != null && type != array.GetType()) - array = CreateArray(type, array); - return array; - } + // Ensure not a type mismatch. + if (!valueIsAlreadyOfSuitableType && value != null && valueType != null) + { + //if (!valueType.IsAssignableFrom(value.GetType())) + if (!valueType.IsInstanceOfType(value)) + { + if (throwOnTypeMismatch) + throw ExceptionOnTypeMismatch(key, valueType, value.GetType()); + return null; } } return value; } /// - /// Short cut for GetValue(key, VCF.None). - /// - public PdfItem? GetValue(string key) - => GetValue(key, VCF.None); - - /// - /// Returns the type of the object to be created as value of the specified key. + /// Implementation of SetValue. + /// Handles setting same value, setting indirect object, + /// and releasing old value. + /// Keep in sync with PdfArray. /// - [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type? GetValueType(string key) // TODO_OLD: move to PdfObject + void SetValueInternal(string key, PdfItem value, bool mustNotExist = false) { - Type? type = null; - var meta = _ownerDictionary.Meta; - if (meta != null!) +#if DEBUG_ + // Are we reading a PDF document? + if ((OwningDictionary.Document?.IrefTable ?? null) is { IsUnderConstruction: true }) { - var kd = meta[key]; - if (kd != null) - type = kd.GetValueType(); - //else - // Deb/ug.WriteLine("Warning: Key not descriptor table: " + key); // TODO_OLD: check what this means... + //Debugger.Break(); + _ = typeof(int); } - //else - // Deb/ug.WriteLine("Warning: No meta provided for type: " + _owner.GetType().Name); // TODO_OLD: check what this means... - return type; - } +#endif + // Already asserted from caller. + Debug.Assert(value is not null); - PdfArray CreateArray( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type type, - PdfArray? oldArray) - { -#if true - PdfArray? array; - if (oldArray == null) - { - // Use constructor with signature 'Ctor(PdfDocument owner)'. - var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, [typeof(PdfDocument)], null); - Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name); - //array = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfArray; - array = ctorInfo.Invoke([_ownerDictionary.Owner]) as PdfArray; - } - else - { - // Use constructor with signature 'Ctor(PdfDictionary dict)'. - var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, types: [typeof(PdfArray)], null); - Debug.Assert(ctorInfo != null, $"No appropriate constructor found for type: {type.Name}."); - //array = ctorInfo.Invoke(new object[] { oldArray }) as PdfArray; - array = ctorInfo.Invoke([oldArray]) as PdfArray; - } - return array ?? NRT.ThrowOnNull(); -#else - // Rewritten Win_RT style. - PdfArray array = null; - if (oldArray == null) - { - // Use constructor with signature 'Ctor(PdfDocument owner)'. - var ctorInfos = type.GetTypeInfo().DeclaredConstructors; //.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - //null, new Type[] { typeof(PdfDocument) }, null); - foreach (var ctorInfo in ctorInfos) - { - var parameters = ctorInfo.GetParameters(); - if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument)) - { - array = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfArray; - break; - } - } - Debug.Assert(array != null, "No appropriate constructor found for type: " + type.Name); - } + if (value.IsDead) + throw new InvalidOperationException("TODO: Is Dead."); // /messages/ObjectIsDead.html + + // Special treatment for PdfRectangle. + // Convert to direct PdfArray. + if (value is PdfRectangle rect) + value = rect.GetAsArrayOfValues(); else + PdfReference.ToReference(ref value); + + // Is value already set? + if (_elements.TryGetValue(key, out var oldItem)) { - // Use constructor with signature 'Ctor(PdfDictionary dict)'. - var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // .GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - //null, new Type[] { typeof(PdfArray) }, null); - foreach (var ctorInfo in ctorInfos) - { - var parameters = ctorInfo.GetParameters(); - if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfArray)) - { - array = ctorInfo.Invoke(new object[] { oldArray }) as PdfArray; - break; - } - } - Debug.Assert(array != null, "No appropriate constructor found for type: " + type.Name); + if (mustNotExist) + throw new InvalidOperationException($"Key '{key}' already exists."); } - return array; -#endif - } - PdfDictionary CreateDictionary( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type type, - PdfDictionary? oldDictionary) - { -#if true - ConstructorInfo? ctorInfo; - PdfDictionary? dict; - if (oldDictionary == null) + // Check self-assignment because of SetDead. + if (ReferenceEquals(oldItem, value)) { - // Use constructor with signature 'Ctor(PdfDocument owner)'. - ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, [typeof(PdfDocument)], null); - Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name); - dict = ctorInfo.Invoke([_ownerDictionary.Owner]) as PdfDictionary; + LogWarning(); + return; } - else + + if (value is PdfObject obj) { - // Use constructor with signature 'Ctor(PdfDictionary dict)'. - ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, [typeof(PdfDictionary)], null); - Debug.Assert(ctorInfo != null, "No appropriate constructor found for type: " + type.Name); - dict = ctorInfo.Invoke([oldDictionary]) as PdfDictionary; - } - return dict ?? NRT.ThrowOnNull(); -#else - // Rewritten Win_RT style. - PdfDictionary dict = null; - if (oldDictionary == null) - { - // Use constructor with signature 'Ctor(PdfDocument owner)'. - var ctorInfos = type.GetTypeInfo().DeclaredConstructors; //GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - //null, new Type[] { typeof(PdfDocument) }, null); - foreach (var ctorInfo in ctorInfos) + if (obj.Reference != null) { - var parameters = ctorInfo.GetParameters(); - if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument)) + Debug.Assert(false, "Should not come here anymore."); + + // Case: Indirect object + + Debug.Assert(obj.ParentInfo is null, "An indirect object must not have a structure parent."); + value = obj.Reference; + if (ReferenceEquals(oldItem, value)) { - dict = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfDictionary; - break; + LogWarning(); + return; } } - Debug.Assert(dict != null, "No appropriate constructor found for type: " + type.Name); - } - else - { - var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDictionary) }, null); - foreach (var ctorInfo in ctorInfos) + else if (obj is PdfPrimitiveObject) { - var parameters = ctorInfo.GetParameters(); - if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDictionary)) - { - dict = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfDictionary; - break; - } - } - Debug.Assert(dict != null, "No appropriate constructor found for type: " + type.Name); - } - return dict; -#endif - } + // Case: Direct non-container object - PdfItem CreateValue( - [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] - Type type, - PdfDictionary? oldValue) - { -#if true - var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, - null, [typeof(PdfDocument)], null); - var obj = ctorInfo!.Invoke([_ownerDictionary.Owner]) as PdfObject; - if (oldValue != null) - { - obj!.Reference = oldValue.Reference; - if (obj.Reference != null) - { - obj.Reference.Value = obj; - if (obj is PdfDictionary dict) - dict._elements = oldValue._elements; + FailForDirectPrimitiveObject(obj); } - } - return obj ?? NRT.ThrowOnNull(); -#else - // Rewritten Win_RT style. - PdfObject obj = null; - var ctorInfos = type.GetTypeInfo().DeclaredConstructors; // GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, null, new Type[] { typeof(PdfDocument) }, null); - foreach (var ctorInfo in ctorInfos) - { - var parameters = ctorInfo.GetParameters(); - if (parameters.Length == 1 && parameters[0].ParameterType == typeof(PdfDocument)) + else { - obj = ctorInfo.Invoke(new object[] { _ownerDictionary.Owner }) as PdfObject; - break; + // Case: Direct container object + Debug.Assert(obj is PdfContainer); + + if (obj.ParentInfo != null) + throw new InvalidOperationException("A direct object can only be added once to a container."); // TODO Error code + + obj.SetStructureParent(this, key); } } - Debug.Assert(obj != null, "No appropriate constructor found for type: " + type.Name); - if (oldValue != null) + else { - obj.Reference = oldValue.Reference; - obj.Reference.Value = obj; - if (obj is PdfDictionary) - { - PdfDictionary dict = (PdfDictionary)obj; - dict._elements = oldValue._elements; - } + // Case: PdfPrimitive or PdfReference + Debug.Assert(value is PdfReference or PdfPrimitive); } - return obj; -#endif - } - - /// - /// Sets the entry with the specified value. DON’T USE THIS FUNCTION - IT MAY BE REMOVED. - /// - public void SetValue(string key, PdfItem value) - { - //Debug.Assert((value is PdfObject && ((PdfObject)value).Reference == null) | !(value is PdfObject), - Debug.Assert(value is PdfObject { Reference: null } or not PdfObject, - "You try to set an indirect object directly into a dictionary."); - // Hammer the value in without further checks. _elements[key] = value; - } - - /// - /// Gets the PdfObject with the specified key, or null if no such object exists. If the key refers to - /// a reference, the referenced PdfObject is returned. - /// - public PdfObject? GetObject(string key) - { - var item = this[key]; - if (item is PdfReference reference) - return reference.Value; - return item as PdfObject; - } - - /// - /// Gets the PdfDictionary with the specified key, or null if no such object exists. If the key refers to - /// a reference, the referenced PdfDictionary is returned. - /// - public PdfDictionary? GetDictionary(string key) - => GetObject(key) as PdfDictionary; - /// - /// Gets the PdfArray with the specified key, or null if no such object exists. If the key refers to - /// a reference, the referenced PdfArray is returned. - /// - public PdfArray? GetArray(string key) - => GetObject(key) as PdfArray; + if (oldItem != null) + ReleaseItem(oldItem); - /// - /// Gets the PdfReference with the specified key, or null if no such object exists. - /// - public PdfReference? GetReference(string key) - => this[key] as PdfReference; + return; - /// - /// Sets the entry to the specified object. The object must not be an indirect object, - /// otherwise an exception is raised. - /// - public void SetObject(string key, PdfObject obj) - { - //if (obj.Reference is not null) - if (obj.IsIndirect) - throw new ArgumentException("PdfObject must not be an indirect object.", nameof(obj)); - this[key] = obj; + void LogWarning() + { + //PdfSharpLogHost.Logger.LogWarning("Setting same value in dictionary."); + } } /// - /// Sets the entry as a reference to the specified object. The object must be an indirect object, - /// otherwise an exception is raised. + /// Returns the type of the object to be created as value of the specified key. /// - public void SetReference(string key, PdfObject obj) + [return: DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | + DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type? GetValueType(string key) { - //if (obj.Reference is null) - if (obj.IsIndirect is false) - throw new ArgumentException("PdfObject must be an indirect object.", nameof(obj)); - this[key] = obj.Reference; + Type? type = null; + var meta = OwningDictionary.Meta; + if (meta != null!) + { + var kd = meta[key]; + if (kd != null) + type = kd.GetValueType(); + } + return type; } /// - /// Sets the entry as a reference to the specified iref. + /// The dictionary this elements object belongs to. /// - public void SetReference(string key, PdfReference iref) - { - if (iref is null) - throw new ArgumentNullException(nameof(iref)); - this[key] = iref; - } + public PdfDictionary OwningDictionary => (PdfDictionary)OwningContainer; #region IDictionary Members @@ -1142,25 +2247,44 @@ public void SetReference(string key, PdfReference iref) /// /// Returns an object for the object. /// - public IEnumerator> GetEnumerator() - { - return _elements.GetEnumerator(); - } + //IEnumerator> IEnumerable>.GetEnumerator() + // => (IEnumerator>)GetEnumerator(); + + public IEnumerator> GetEnumerator() + => _elements.GetEnumerator(); IEnumerator IEnumerable.GetEnumerator() + => ((ICollection)_elements).GetEnumerator(); + + PdfItem IDictionary.this[string key] { - return ((ICollection)_elements).GetEnumerator(); + [Obsolete("Make sure references are handled correctly.")] + get => this[key]!; // TODO?? Key not found exception? BREAKING? + set => this[key] = value; } /// /// Gets or sets an entry in the dictionary. The specified key must be a valid PDF name /// starting with a slash '/'. This property provides full access to the elements of the /// PDF dictionary. Wrong use can lead to errors or corrupt PDF files. + /// TODO: review: + /// New in 6.3: + /// Gets or sets an entry in the dictionary. + /// The getter returns null if no value with the specified key exists. + /// If the value is a PdfReference this value is returned, i.e. in contrast to GetValue + /// it is not dereferenced to its indirect object. + /// The setter throws an ArgumentNullException if the value is null. + /// If you try to set an indirect object its PdfReference is set instead. + /// It is not possible to have a .NET reference to an indirect PdfObject. /// public PdfItem? this[string key] { + // #US373 [Obsolete("Make sure references are handled correctly.")] get { + Name.EnsureName(key); + + // Always get the raw (not dereferenced) value. _elements.TryGetValue(key, out var item); return item; } @@ -1168,65 +2292,64 @@ public PdfItem? this[string key] { if (value == null) throw new ArgumentNullException(nameof(value)); -#if DEBUG_ - if (key == "/MediaBox") - _ = typeof(int); - - //if (value is PdfObject) - //{ - // PdfObject obj = (PdfObject)value; - // if (obj.Reference != null) - // throw new ArgumentException("An object with an indirect reference cannot be a direct value. Try to set an indirect reference."); - //} - if (value is PdfDictionary) - { - PdfDictionary dict = (PdfDictionary)value; - if (dict._stream != null) - throw new ArgumentException("A dictionary with stream cannot be a direct value."); - } -#endif - if (value is PdfObject { IsIndirect: true } obj) - value = obj.Reference; - _elements[key] = value; + + SetValueInternal(key, value); } } + /// + /// Gets or sets an entry in the dictionary identified by a Name. + /// + public PdfItem? this[Name name] + { + // #US373 [Obsolete("Make sure references are handled correctly.")] + get => this[name.Value]; + set => this[name.Value] = value; + } + /// /// Gets or sets an entry in the dictionary identified by a PdfName object. /// public PdfItem? this[PdfName key] { + // #US373 [Obsolete("Make sure references are handled correctly.")] get => this[key.Value]; - set - { - if (value == null) - throw new ArgumentNullException(nameof(value)); - -#if DEBUG - if (value is PdfDictionary dictionary) - { - var dict = dictionary; - if (dict.Stream != null) - throw new ArgumentException("A dictionary with stream cannot be a direct value."); - } -#endif - - if (value is PdfObject { IsIndirect: true } obj) - value = obj.Reference; - _elements[key.Value] = value; - } + set => this[key.Value] = value; } /// /// Removes the value with the specified key. /// - public bool Remove(string key) => _elements.Remove(key); + public bool Remove(string key) + { + // ReSharper disable once InvertIf + // ReSharper disable once CanSimplifyDictionaryRemovingWithSingleCall because this is not in .NET Standard + if (_elements.TryGetValue(key, out var oldItem)) + { + _elements.Remove(key); + ReleaseItem(oldItem); + return true; + } + return false; + } /// /// Removes the value with the specified key. /// - public bool Remove(KeyValuePair item) - => throw new NotImplementedException(); + public bool Remove(KeyValuePair item) + { + // ReSharper disable once InvertIf + if (_elements.TryGetValue(item.Key, out var value)) + { + // ReSharper disable once InvertIf + if (ReferenceEquals(item.Value, value)) + { + Remove(item.Key); + return true; + } + } + return false; + } /// /// Determines whether the dictionary contains the specified name. @@ -1234,38 +2357,59 @@ public bool Remove(KeyValuePair item) public bool ContainsKey(string key) => _elements.ContainsKey(key); /// - /// Determines whether the dictionary contains a specific value. + /// Determines whether the dictionary contains a specific key. /// - public bool Contains(KeyValuePair item) - => throw new NotImplementedException(); + public bool Contains(KeyValuePair item) + { + if (_elements.TryGetValue(item.Key, out var value)) + { + return ReferenceEquals(item.Value, value); + } + return false; + } /// /// Removes all elements from the dictionary. /// - public void Clear() => _elements.Clear(); + public void Clear() + { + var oldItems = _elements.Values.ToArray(); + _elements.Clear(); + foreach (var oldItem in oldItems) + ReleaseItem(oldItem); + } /// /// Adds the specified value to the dictionary. + /// If the value is an indirect object, the PdfReference is added instead. + /// If the value is a direct object that war previously added to another + /// dictionary or array, an exception is thrown. /// - public void Add(string key, PdfItem? value) - { - if (String.IsNullOrEmpty(key)) - throw new ArgumentNullException(nameof(key)); + //void IDictionary.Add(string key, PdfItem? value) + // => Add(key, value ?? throw new ArgumentNullException(nameof(value))); - if (key[0] != '/') - throw new ArgumentException("The key must start with a slash '/'."); + public void Add(string key, PdfItem value) + { + //if (String.IsNullOrEmpty(key)) + // throw new ArgumentNullException(nameof(key)); + //if (key[0] != '/') + // throw new ArgumentException("The key must start with a slash '/'."); + Name.EnsureName(key); - // If object is indirect automatically convert value to reference. - if (value is PdfObject { IsIndirect: true } obj) - value = obj.Reference; + if (value == null) + throw new ArgumentNullException(nameof(value)); - _elements.Add(key, value); + SetValueInternal(key, value); } /// /// Adds an item to the dictionary. /// - public void Add(KeyValuePair item) => Add(item.Key, item.Value); + /// If item.Value is null + //void ICollection>.Add(KeyValuePair item) => + // Add(item.Key, item.Value ?? throw new ArgumentNullException(nameof(item.Value))); + + public void Add(KeyValuePair item) => Add(item.Key, item.Value); /// /// Gets all keys currently in use in this dictionary as an array of PdfName objects. @@ -1276,9 +2420,9 @@ public PdfName[] KeyNames { ICollection values = _elements.Keys; int count = values.Count; - string[] strings = new string[count]; + var strings = new string[count]; values.CopyTo(strings, 0); - PdfName[] names = new PdfName[count]; + var names = new PdfName[count]; for (int idx = 0; idx < count; idx++) names[idx] = new PdfName(strings[idx]); return names; @@ -1301,16 +2445,12 @@ public ICollection Keys } } - /// - /// Gets the value associated with the specified key. - /// - public bool TryGetValue(string key, out PdfItem? value) => _elements.TryGetValue(key, out value); - /// /// Gets all values currently in use in this dictionary as an array of PdfItem objects. /// - //public ICollection Values - public ICollection Values + //ICollection IDictionary.Values => (ICollection)Values; + + public ICollection Values { // It is by design not to return _elements.Values, but a copy. get @@ -1344,9 +2484,10 @@ public ICollection Values /// /// Copies the elements of the dictionary to an array, starting at a particular index. /// - public void CopyTo(KeyValuePair[] array, int arrayIndex) + public void CopyTo(KeyValuePair[] array, int arrayIndex) { - throw new NotImplementedException(); + foreach (var element in _elements) + array[arrayIndex++] = element; } /// @@ -1356,11 +2497,6 @@ public void CopyTo(KeyValuePair[] array, int arrayIndex) #endregion - /// - /// Access a key that may contain an array or a single item for working with its value(s). - /// - public ArrayOrSingleItemHelper ArrayOrSingleItem => new(this); // TODO_OLD PDFsharp6: Naming. - /// /// Gets the DebuggerDisplayAttribute text. /// @@ -1371,7 +2507,7 @@ internal string DebuggerDisplay get { var sb = new StringBuilder(); - sb.AppendFormat(CultureInfo.InvariantCulture, "key={0}:(", _elements.Count); + sb.AppendFormat(Invariant($"count={_elements.Count} [")); var addSpace = false; ICollection keys = _elements.Keys; foreach (var key in keys) @@ -1381,31 +2517,47 @@ internal string DebuggerDisplay addSpace = true; sb.Append(key); } - sb.Append(')'); + sb.Append(']'); return sb.ToString(); } } + static Exception ExceptionOnNull(string key) + { + return new InvalidOperationException($"Value at key '{key}' is null."); + } + + static Exception ExceptionOnTypeMismatch(string key, Type expected, Type found) + { + return new InvalidOperationException($"Value at key '{key}' is expected to be of type {expected.FullName}, but is of type {found.FullName}."); + } + /// /// The elements of the dictionary with a string as key. /// Because the string is a name it starts always with a '/'. /// - Dictionary _elements; - - /// - /// The dictionary this object belongs to. - /// - PdfDictionary _ownerDictionary; + Dictionary _elements = []; } /// - /// The PDF stream objects. + /// Represents the optional PDF stream of a dictionary. /// public sealed class PdfStream { internal PdfStream(PdfDictionary ownerDictionary) { _ownerDictionary = ownerDictionary ?? throw new ArgumentNullException(nameof(ownerDictionary)); + + // TODO EnsureIndirect() + if (ownerDictionary.IsIndirect is false) + { + //TODO: Does not work on all tests. + // throw new InvalidOperationException("Cannot create a stream for a direct object."); + + // This happened when we create a stream for a PdfDictionary during its creation. + // The Dictionary must have an owner, but is not yet added to the iRef table. + ownerDictionary.SetMustBeIndirect(); + } } /// @@ -1414,6 +2566,7 @@ internal PdfStream(PdfDictionary ownerDictionary) internal PdfStream(byte[] value, PdfDictionary owner) : this(owner) { + // TODO EnsureIndirect() _value = value; } @@ -1437,10 +2590,11 @@ public PdfStream Clone() /// internal void ChangeOwner(PdfDictionary dict) { - if (_ownerDictionary != null!) - { - // ??? - } + //if (_ownerDictionary != null!) + //{ + // // ??? + // Debug.Assert(false); + //} // Set new owner. _ownerDictionary = dict; @@ -1486,10 +2640,11 @@ public byte[] UnfilteredValue byte[]? bytes = null; if (_value != null) { - var filter = _ownerDictionary.Elements["/Filter"]; + var filter = _ownerDictionary.Elements.GetValue(Keys.Filter); + //var filter = _ownerDictionary.Elements[Keys.Filter]; #US373 if (filter != null) { - var decodeParms = _ownerDictionary.Elements[Keys.DecodeParms]; + var decodeParms = _ownerDictionary.Elements.GetValue(Keys.DecodeParms); bytes = Filtering.Decode(_value, filter, decodeParms); if (bytes == null!) { @@ -1503,6 +2658,7 @@ public byte[] UnfilteredValue _value.CopyTo(bytes, 0); } } + return bytes ?? []; } } @@ -1516,18 +2672,18 @@ public byte[] UnfilteredValue [Obsolete("Not correctly implemented. Use the function TryUncompress.")] public bool TryUnfilter() { - // Keep old code for not break existing code. + // Keep old code, do not break existing code. if (_value != null) { - var filter = _ownerDictionary.Elements["/Filter"]; + var filter = _ownerDictionary.Elements.GetValue(Keys.Filter/*, VCF.NoTransform*/); if (filter != null) { // PDFsharp can only uncompress streams that are compressed with the ZIP or LZH algorithm. - var decodeParms = _ownerDictionary.Elements[Keys.DecodeParms]; + var decodeParms = _ownerDictionary.Elements.GetValue(Keys.DecodeParms/*, VCF.NoTransform*/); var bytes = Filtering.Decode(_value, filter, decodeParms); if (bytes != null!) { - _ownerDictionary.Elements.Remove("/Filter"); + _ownerDictionary.Elements.Remove(Keys.Filter); _ownerDictionary.Elements.Remove(Keys.DecodeParms); Value = bytes; } @@ -1535,6 +2691,7 @@ public bool TryUnfilter() return false; } } + return true; } @@ -1548,19 +2705,28 @@ public bool TryUncompress() { if (_value != null) { - var filter = _ownerDictionary.Elements["/Filter"]; + var filter = _ownerDictionary.Elements.GetValue(Keys.Filter); + // var filter = _ownerDictionary.Elements[Keys.Filter]; #US373 if (filter == null) return false; // filter can be an array. We only try to unzip a single filter name. - var filterName = filter.ToString()!.TrimStart('/'); + string filterName; + if (filter is PdfArray { Elements.Count: 1 } array) + { + filterName = array.Elements[0].ToString()!.TrimStart('/'); + } + else + { + filterName = filter.ToString()!.TrimStart('/'); + } // PDF 1.7 specs say that the abbreviations are also allowed as filter values. if (filterName is PdfFilterNames.LzwDecode or PdfFilterNames.LzwDecodeAbbreviation or PdfFilterNames.FlateDecode or PdfFilterNames.FlateDecodeAbbreviation) { // PDFsharp can only uncompress streams that are compressed with the ZIP or LZH algorithm. - var decodeParms = _ownerDictionary.Elements[Keys.DecodeParms]; + var decodeParms = _ownerDictionary.Elements.GetValue(Keys.DecodeParms/*, VCF.NoTransform*/); var bytes = Filtering.Decode(_value, filter, decodeParms); // Remove the filter and optional decode parameters. @@ -1583,9 +2749,8 @@ public bool IsFiltered() { if (_value != null) { - var filter = _ownerDictionary.Elements[Keys.Filter]; - if (filter != null) - return true; + // TODO #US373: Check documentation. Returns false if Filter is set, but stream is not. + return _ownerDictionary.Elements.HasValue(Keys.Filter); // #US373 } return false; } @@ -1601,9 +2766,9 @@ public void Zip() if (!_ownerDictionary.Elements.ContainsKey(Keys.Filter)) { - _value = Filtering.FlateDecode.Encode(_value, _ownerDictionary._document.Options.FlateEncodeMode); - _ownerDictionary.Elements["/Filter"] = new PdfName("/FlateDecode"); - _ownerDictionary.Elements["/Length"] = new PdfInteger(_value.Length); + _value = Filtering.FlateDecode.Encode(_value, _ownerDictionary.Document.Options.FlateEncodeMode); + _ownerDictionary.Elements.SetName(Keys.Filter, "/FlateDecode"); + _ownerDictionary.Elements.SetInteger(Keys.Length, _value.Length); } } @@ -1615,21 +2780,21 @@ public override string ToString() if (_value == null) return "«null»"; - string stream; - var filter = _ownerDictionary.Elements["/Filter"]; + string content; + var filter = _ownerDictionary.Elements.GetValue(Keys.Filter); if (filter != null) { - var decodeParms = _ownerDictionary.Elements[Keys.DecodeParms]; + var decodeParms = _ownerDictionary.Elements.GetValue(Keys.DecodeParms); var bytes = Filtering.Decode(_value, filter, decodeParms); if (bytes != null!) - stream = PdfEncoders.RawEncoding.GetString(bytes, 0, bytes.Length); + content = PdfEncoders.RawEncoding.GetString(bytes, 0, bytes.Length); else throw new NotImplementedException("Unknown filter"); } else - stream = PdfEncoders.RawEncoding.GetString(_value, 0, _value.Length); + content = PdfEncoders.RawEncoding.GetString(_value, 0, _value.Length); - return stream; + return content; } /// @@ -1708,11 +2873,46 @@ public class Keys : KeysBase } } + /// + /// Implements a comparer that is used to sort the keys of a dictionary. + /// It puts /Type and /Subtype at the top. + /// + class KeyComparer : IComparer + { + public int Compare(PdfName? l, PdfName? r) + { + if (l != null) + { + if (r != null) + { + if (l.Value.Equals("/Type")) + return -1; + if (r.Value.Equals("/Type")) + return 1; + + if (l.Value.Equals("/Subtype")) + return -1; + if (r.Value.Equals("/Subtype")) + return 1; + + return String.Compare(l.Value, r.Value, StringComparison.Ordinal); + } + return 1; + } + return r == null ? 0 : -1; + } + } + + static KeyComparer Comparer => _keyComparer ??= new(); + + static KeyComparer? _keyComparer; + /// /// Gets the DebuggerDisplayAttribute text. /// // ReSharper disable UnusedMember.Local - string DebuggerDisplay => Invariant($"dictionary({ObjectID.DebuggerDisplay},[{Elements.Count}])={_elements?.DebuggerDisplay}"); + string DebuggerDisplay + => Invariant($"{GetType().Name}({ObjectID.DebuggerDisplay}, count={_elements?.Count ?? 0}, cont={_elements?.DebuggerDisplay})"); // ReSharper restore UnusedMember.Local } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionaryExtensions/PdfDictionaryExtensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionaryExtensions/PdfDictionaryExtensions.cs new file mode 100644 index 00000000..10391623 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDictionaryExtensions/PdfDictionaryExtensions.cs @@ -0,0 +1,70 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +//using System.Xml.Linq; + +using System.Diagnostics.CodeAnalysis; +using System.Reflection; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Pdf.PdfDictionaryExtensions +{ + /// + /// Extension methods for PDF dictionaries. + /// + public static class PdfDictionaryExtensions + { + public static PdfDictionary Transform(this PdfDictionary dict, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type) + { + return TransformInternal(dict, type); + } + + public static T Transform< + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + T> + (this PdfDictionary dict) where T : PdfDictionary + { + return (T)TransformInternal(dict, typeof(T)); + + } + + static PdfDictionary TransformInternal(PdfDictionary dict, + [DynamicallyAccessedMembers(DynamicallyAccessedMemberTypes.PublicConstructors | DynamicallyAccessedMemberTypes.NonPublicConstructors)] + Type type) + { + if (dict.GetType() == type) + return dict; + + var ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, types: [typeof(PdfDictionary)], null); + if (ctorInfo == null) + throw new InvalidOperationException($"Type '{type.FullName}' has no appropriate constructor for object type transformation."); + dict = (PdfDictionary)ctorInfo.Invoke([dict]); + return dict; + } + + //public static PdfItem? this[this, string name] => null; + + //public static PdfItem? GetRawValue(this PdfDictionary dict, string key) + //{ + // return dict.Elements[key]; + //} + + //public static PdfItem? GetItem(this PdfDictionary dict, string key) + //{ + // return dict.Elements[key]; + //} + + //public static void SetItem(this PdfDictionary dict, string key, PdfItem value) + //{ + // if (value == null) + // throw new ArgumentNullException(nameof(value)); + + // dict.Elements[key] = value; + //} + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index d1eb2a39..777f441f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -1,33 +1,39 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Reflection; -using System.Runtime.InteropServices; using Microsoft.Extensions.Logging; +using PdfSharp.Internal; +using PdfSharp.Internal.OpenType; using PdfSharp.Drawing; using PdfSharp.Events; -using PdfSharp.Fonts.Internal; using PdfSharp.Logging; using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Attachments; +using PdfSharp.Pdf.Filters; +using PdfSharp.Pdf.Forms; using PdfSharp.Pdf.Internal; using PdfSharp.Pdf.IO; -using PdfSharp.Pdf.AcroForms; -using PdfSharp.Pdf.Filters; +using PdfSharp.Pdf.Metadata; +using PdfSharp.Pdf.PdfA; using PdfSharp.Pdf.Security; using PdfSharp.Pdf.Signatures; using PdfSharp.Pdf.Structure; +using PdfSharp.PlugIn; using PdfSharp.UniversalAccessibility; +using System.Reflection; +using System.Runtime.InteropServices; +using static PdfSharp.Pdf.PdfDictionary; -// ReSharper disable InconsistentNaming -// ReSharper disable ConvertPropertyToExpressionBody +// Re/Sharper disable InconsistentNaming +// Re/Sharper disable ConvertPropertyToExpressionBody namespace PdfSharp.Pdf { /// /// Represents a PDF document. /// - [DebuggerDisplay("(Name={" + nameof(Name) + "})")] // A name makes debugging easier. - public sealed class PdfDocument : PdfObject, IDisposable + [DebuggerDisplay("(Name={" + nameof(Name) + "})")] // A unique name makes debugging easier. + public sealed class PdfDocument : IDisposable { /// /// Creates a new PDF document in memory. @@ -36,13 +42,11 @@ public sealed class PdfDocument : PdfObject, IDisposable public PdfDocument() { PdfSharpLogHost.Logger.PdfDocumentCreated(Name); - //PdfDocument.Gob.AttachDocument(Handle); - _document = this; - _creation = DateTime.Now; - _state = DocumentState.Created; + CreationDate = DateTimeOffset.Now; + State = DocumentState.Created; _version = 17; Initialize(); - Info.CreationDate = _creation; + Info.CreationDate = CreationDate; } /// @@ -64,23 +68,19 @@ public PdfDocument(string outputFilename) : this() /// public PdfDocument(Stream outputStream) { - _document = this; - _creation = DateTime.Now; - _state = DocumentState.Created; + CreationDate = DateTimeOffset.Now; + State = DocumentState.Created; _version = 14; Initialize(); - Info.CreationDate = _creation; + Info.CreationDate = CreationDate; OutStream = outputStream; } internal PdfDocument(Lexer lexer) { - //PdfDocument.Gob.AttachDocument(Handle); - - _document = this; - _creation = DateTime.Now; - _state = DocumentState.Imported; + CreationDate = DateTimeOffset.Now; + State = DocumentState.Imported; //_info = new PdfInfo(this); //_pages = new PdfPages(this); @@ -98,8 +98,8 @@ void Initialize() //_info = new PdfInfo(this); _fontTable = new PdfFontTable(this); _imageTable = new PdfImageTable(this); - Trailer = new PdfTrailer(this); IrefTable = new PdfCrossReferenceTable(this); + Trailer = new PdfTrailer(this); Trailer.CreateNewDocumentIDs(); } @@ -124,8 +124,10 @@ public void Dispose() void Dispose(bool disposing) { - if (_state != DocumentState.Disposed) + if (State != DocumentState.Disposed) { + Events.OnDisposed(this, new DocumentEventArgs(this)); // MaOs4StLa Review. + if (disposing) { // Dispose managed resources. @@ -136,7 +138,8 @@ void Dispose(bool disposing) } //PdfDocument.Gob.DetachDocument(Handle); } - _state = DocumentState.Disposed | DocumentState.Saved; + + State = DocumentState.Disposed | DocumentState.Saved; } /// @@ -146,31 +149,30 @@ void Dispose(bool disposing) public object? Tag { get; set; } /// - /// Temporary hack to set a value that tells PDFsharp to create a PDF/A conform document. + /// Sets PDF/A part and conformance level for the document. + /// After once set it cannot be changed. /// - public void SetPdfA() // HACK_OLD - { - _isPdfA = true; - _ = UAManager.ForDocument(this); - } + public void SetPdfA(PdfAFormat format) + => GetPdfAManager().SetFormat(format); /// /// Gets a value indicating that you create a PDF/A conform document. /// This function is temporary and will change in the future. /// - public bool IsPdfA => _isPdfA; // HACK_OLD - bool _isPdfA; + public bool IsPdfA => PdfAManager is { IsPdfADocument: true }; /// /// Encapsulates the document’s events. /// public DocumentEvents Events => _documentEvents ??= new(); + DocumentEvents? _documentEvents; /// /// Encapsulates the document’s render events. /// public RenderEvents RenderEvents => _renderEvents ??= new(); + RenderEvents? _renderEvents; /// @@ -185,15 +187,21 @@ public void SetPdfA() // HACK_OLD static string NewName() { #if DEBUG_ - if (PdfDocument.nameCount == 57) - PdfDocument.nameCount.GetType(); + if (PdfDocument._nameCount == 57) + _ = typeof(int); #endif return "Document #" + ++_nameCount; } + static int _nameCount; - //internal bool CanModify => true; - internal bool CanModify => _openMode == PdfDocumentOpenMode.Modify; + internal bool CanModify => OpenMode == PdfDocumentOpenMode.Modify; + + /// + /// Gets or sets a value indicating whether to save a document even if it is imported. + /// For development and unit testing only. + /// + internal bool SaveAnyway { get; set; } /// /// Closes this instance. @@ -211,7 +219,7 @@ public void Close() // Get security handler if document gets encrypted. var effectiveSecurityHandler = SecuritySettings.EffectiveSecurityHandler; - var writer = new PdfWriter(OutStream, _document, effectiveSecurityHandler); + var writer = new PdfWriter(OutStream, this, effectiveSecurityHandler); try { DoSaveAsync(writer).GetAwaiter().GetResult(); @@ -241,11 +249,11 @@ public async Task SaveAsync(string path) { EnsureNotYetSaved(); - if (!CanModify) + if (!SaveAnyway && !CanModify) throw new InvalidOperationException(PsMsgs.CannotModify); // We need ReadWrite when adding a signature. Write is sufficient if not adding a signature. - var fileAccess = _digitalSignatureHandler == null ? FileAccess.Write : FileAccess.ReadWrite; + var fileAccess = DigitalSignatureHandler == null ? FileAccess.Write : FileAccess.ReadWrite; // ReSharper disable once UseAwaitUsing because we need no DisposeAsync for a simple FileStream. using var stream = new FileStream(path, FileMode.Create, fileAccess, FileShare.None); @@ -273,7 +281,7 @@ public async Task SaveAsync(Stream stream, bool closeStream = false) if (!stream.CanWrite) throw new InvalidOperationException(PsMsgs.StreamMustBeWritable); - if (!CanModify) + if (!SaveAnyway && !CanModify) throw new InvalidOperationException(PsMsgs.CannotModify); // #PDF-A @@ -291,8 +299,7 @@ public async Task SaveAsync(Stream stream, bool closeStream = false) PdfWriter? writer = null; try { - Debug.Assert(ReferenceEquals(_document, this)); - writer = new(stream, _document, effectiveSecurityHandler); + writer = new(stream, this, effectiveSecurityHandler); await DoSaveAsync(writer).ConfigureAwait(false); } finally @@ -309,6 +316,7 @@ public async Task SaveAsync(Stream stream, bool closeStream = false) stream.Position = 0; // Reset the stream position if the stream is kept open. } } + writer?.Close(closeStream); } } @@ -325,16 +333,20 @@ async Task DoSaveAsync(PdfWriter writer) if (OutStream != null) { // Give feedback if the wrong constructor was used. - throw new InvalidOperationException("Cannot save a PDF document with no pages. Do not use \"public PdfDocument(string filename)\" or \"public PdfDocument(Stream outputStream)\" if you want to open an existing PDF document from a file or stream; use PdfReader.Open() for that purpose."); + throw new InvalidOperationException( + "Cannot save a PDF document with no pages. Do not use \"public PdfDocument(string filename)\" or " + + "\"public PdfDocument(Stream outputStream)\" if you want to open an existing PDF document from a file or stream. " + + "Use PdfReader.Open() for that purpose."); } + throw new InvalidOperationException("Cannot save a PDF document with no pages."); } try { // Prepare for signing. - if (_digitalSignatureHandler != null) - await _digitalSignatureHandler.AddSignatureComponentsAsync().ConfigureAwait(false); + if (DigitalSignatureHandler != null) + await DigitalSignatureHandler.AddSignatureComponentsAsync().ConfigureAwait(false); // Remove XRefTrailer if (Trailer is PdfCrossReferenceStream crossReferenceStream) @@ -349,7 +361,7 @@ async Task DoSaveAsync(PdfWriter writer) IrefTable.Add(effectiveSecurityHandler); else Debug.Assert(IrefTable.Contains(effectiveSecurityHandler.ObjectID)); - Trailer.Elements[PdfTrailer.Keys.Encrypt] = _securitySettings!.SecurityHandler.Reference; + Trailer.Elements[PdfTrailer.Keys.Encrypt] = _securitySettings!.SecurityHandler.RequiredReference; } else Trailer.Elements.Remove(PdfTrailer.Keys.Encrypt); @@ -365,7 +377,7 @@ async Task DoSaveAsync(PdfWriter writer) { PdfReference iref = irefs[idx]; #if DEBUG_ - if (iref.ObjectNumber == 378) + if (iref.ObjectNumber == 18) _ = typeof(int); #endif iref.Position = writer.Position; @@ -381,7 +393,7 @@ async Task DoSaveAsync(PdfWriter writer) // Leaving only the last indirect object in SecurityHandler is sufficient, as this is the first time no indirect object is entered anymore. effectiveSecurityHandler?.LeaveObject(); - // ReSharper disable once RedundantCast. Redundant only if 64 bit. + // ReSharper disable once RedundantCast because it is redundant only if SizeType is 64 bit. var startXRef = (SizeType)writer.Position; IrefTable.WriteObject(writer); writer.WriteRaw("trailer\n"); @@ -391,8 +403,8 @@ async Task DoSaveAsync(PdfWriter writer) // #Signature: What about encryption + signing ?? // Prepare for signing. - if (_digitalSignatureHandler != null) - await _digitalSignatureHandler.ComputeSignatureAndRange(writer).ConfigureAwait(false); + if (DigitalSignatureHandler != null) + await DigitalSignatureHandler.ComputeSignatureAndRange(writer).ConfigureAwait(false); //if (encrypt) //{ @@ -402,20 +414,20 @@ async Task DoSaveAsync(PdfWriter writer) } finally { - //await writer.Stream.FlushAsync().ConfigureAwait(false); + //await writer.Stream.FlushAsync().ConfigureAwait(false); // TODO StL: better call the async version? writer.Stream.Flush(); // Do not close the stream writer here. - _state |= DocumentState.Saved; + State |= DocumentState.Saved; } } - void PrepareForPdfA() // Just a first hack. + void PrepareForPdfA() // Just a first hack. { var internals = Internals; - Debug.Assert(_uaManager != null); + //Debug.Assert(_uaManager != null); // UAManager sets MarkInformation. - if (_uaManager == null) + if (UAManager == null) { // Marked must be true in MarkInfo. var markInfo = new PdfMarkInformation(); @@ -437,8 +449,9 @@ void PrepareForPdfA() // Just a first hack. outputIntents.Elements.Add("/RegistryName", new PdfString("http://www.color.org")); outputIntents.Elements.Add("/Info", new PdfString("Creator: ColorOrg Manufacturer:IEC Model:sRGB")); - var profileStream = Assembly.GetExecutingAssembly().GetManifestResourceStream("PdfSharp.Resources.sRGB2014.icc") - ?? throw new InvalidOperationException("Embedded color profile was not found."); + var profileStream = Assembly.GetExecutingAssembly() + .GetManifestResourceStream("PdfSharp.Resources.sRGB2014.icc") + ?? throw new InvalidOperationException("Embedded color profile was not found."); var profile = new byte[profileStream.Length]; var read = profileStream.Read(profile, 0, (int)profileStream.Length); @@ -451,11 +464,11 @@ void PrepareForPdfA() // Just a first hack. var profileObject = new PdfDictionary(this); IrefTable.Add(profileObject); profileObject.Stream = new PdfDictionary.PdfStream(profileCompressed, profileObject); - profileObject.Elements["/N"] = new PdfInteger(3); - profileObject.Elements["/Length"] = new PdfInteger(profileCompressed.Length); - profileObject.Elements["/Filter"] = new PdfName("/FlateDecode"); + profileObject.Elements.SetInteger("/N", 3); + profileObject.Elements.SetInteger(PdfStream.Keys.Length, profileCompressed.Length); + profileObject.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); - outputIntents.Elements.Add("/DestOutputProfile", profileObject.Reference); + outputIntents.Elements.Add("/DestOutputProfile", profileObject.RequiredReference); //internals.Catalog.Elements.SetReference(PdfCatalog.Keys.OutputIntents, outputIntentsArray); internals.Catalog.Elements.Add(PdfCatalog.Keys.OutputIntents, outputIntentsArray); } @@ -463,25 +476,28 @@ void PrepareForPdfA() // Just a first hack. /// /// Dispatches PrepareForSave to the objects that need it. /// - internal override void PrepareForSave() + void PrepareForSave() { - PdfDocumentInformation info = Info; - - // The Creator is called 'Application' in Acrobat. - // The Producer is call "Created by" in Acrobat. + // The Creator is called “Application” in Acrobat. + // The Producer is call “Created by” in Acrobat. // Set Creator if value is undefined. This is the 'application' in Adobe Reader. - if (info.Elements[PdfDocumentInformation.Keys.Creator] is null) - info.Creator = PdfSharpProductVersionInformation.Producer; + //if (Info.Elements[PdfDocumentInformation.Keys.Creator] is null) // TODO #US373 Just a null check. + if (!Info.Elements.HasValue(PdfDocumentInformation.Keys.Creator)) // #US373 + Info.Creator = PdfSharpProductVersionInformation.Producer; // We set Producer if it is not yet set. +#if true // TODO clean up + var pdfProducer = DefaultProducer; +#else var pdfProducer = PdfSharpProductVersionInformation.Creator; #if DEBUG // Add OS suffix only in DEBUG build. pdfProducer += $" under {RuntimeInformation.OSDescription}"; #endif - // Keep original producer if file was imported. This is 'PDF created by' in Adobe Reader. - string producer = info.Producer; +#endif + // Keep original producer if file was imported. This is “PDF created by” in Adobe Reader. + string producer = Info.Producer; if (producer.Length == 0) { producer = pdfProducer; @@ -492,7 +508,8 @@ internal override void PrepareForSave() if (!producer.StartsWith(PdfSharpProductVersionInformation.Title, StringComparison.Ordinal)) producer = $"{pdfProducer} (Original: {producer})"; } - info.Elements.SetString(PdfDocumentInformation.Keys.Producer, producer); + + Info.Elements.SetString(PdfDocumentInformation.Keys.Producer, producer); // Prepare used fonts. _fontTable?.PrepareForSave(); @@ -500,19 +517,20 @@ internal override void PrepareForSave() // Let catalog do the rest. Catalog.PrepareForSave(); + // #PDF-UA // TODO rewrite + // Create PdfMetadata now to include the final document information in XMP generation. + //Catalog.Elements.SetReference(PdfCatalog.Keys.Metadata, new PdfMetadata(this)); #if true // Remove all unreachable objects (e.g. from deleted pages). int removed = IrefTable.Compact(); if (removed != 0 && PdfSharpLogHost.Logger.IsEnabled(LogLevel.Information)) { - PdfSharpLogHost.Logger.LogInformation($"PrepareForSave: Number of deleted unreachable objects: {removed}"); + PdfSharpLogHost.Logger.LogInformation( + $"PrepareForSave: Number of deleted unreachable objects: {removed}"); } + IrefTable.Renumber(); #endif - - // #PDF-UA - // Create PdfMetadata now to include the final document information in XMP generation. - Catalog.Elements.SetReference(PdfCatalog.Keys.Metadata, new PdfMetadata(this)); } /// @@ -523,9 +541,13 @@ public bool CanSave(ref string message) if (!SecuritySettings.CanSave(ref message)) return false; - if ((_state & DocumentState.Saved) != 0) + if ((State & DocumentState.Saved) != 0) + { + message = "The document was already saved and cannot be saved twice."; return false; + } + message = ""; return true; } @@ -571,7 +593,10 @@ public int Version _version = value; } } - internal int _version; + + internal void SetVersion(int version) => _version = version; + + int _version; /// /// Adjusts the version if the current version is lower than the required version. @@ -601,18 +626,22 @@ public int PageCount { EnsureNotYetSaved(); +#if true + return Pages.Count; +#else if (CanModify) return Pages.Count; // PdfOpenMode is InformationOnly. var pageTreeRoot = (PdfDictionary?)Catalog.Elements.GetObject(PdfCatalog.Keys.Pages); return pageTreeRoot?.Elements.GetInteger(PdfPages.Keys.Count) ?? 0; +#endif } } /// /// Gets the file size of the document. /// - public long FileSize { get; internal set; } + public SizeType FileSize { get; internal set; } /// /// Gets the full qualified file name if the document was read form a file, or an empty string otherwise. @@ -622,12 +651,12 @@ public int PageCount /// /// Gets a Guid that uniquely identifies this instance of PdfDocument. /// - public Guid Guid => _guid; + public Guid Guid { get; } = Guid.NewGuid(); - readonly Guid _guid = Guid.NewGuid(); + //readonly Guid _guid = Guid.NewGuid(); internal DocumentHandle Handle - => _handle ??= new DocumentHandle(this); + => _handle ??= new(this); DocumentHandle? _handle; @@ -635,25 +664,19 @@ internal DocumentHandle Handle /// Returns a value indicating whether the document was newly created or opened from an existing document. /// Returns true if the document was opened with the PdfReader.Open function, false otherwise. /// - public bool IsImported => (_state & DocumentState.Imported) != 0; + public bool IsImported => (State & DocumentState.Imported) != 0; /// /// Returns a value indicating whether the document is read only or can be modified. /// - public bool IsReadOnly => (_openMode != PdfDocumentOpenMode.Modify); - - //internal Exception DocumentNotImported() - //{ - // return new InvalidOperationException("Document not imported."); - //} + public bool IsReadOnly => (OpenMode != PdfDocumentOpenMode.Modify); /// /// Gets information about the document. /// - public PdfDocumentInformation Info - => _info ??= Trailer.Info; + public PdfDocumentInformation Info => _info ??= Trailer.Info; - PdfDocumentInformation? _info; // Never changes if once created. + PdfDocumentInformation? _info; // Never changes if once created. /// /// This function is intended to be undocumented. @@ -670,15 +693,15 @@ public PdfCustomValues? CustomValues _customValues = null; } } + PdfCustomValues? _customValues; /// /// Get the pages dictionary. /// - public PdfPages Pages - => _pages ??= Catalog.Pages; + public PdfPages Pages => _pages ??= Catalog.Pages; - PdfPages? _pages; // Never changes if once created. + PdfPages? _pages; // Never changes if once created. /// /// Gets or sets a value specifying the page layout to be used when the document is opened. @@ -719,9 +742,10 @@ public PdfPageMode PageMode public PdfOutlineCollection Outlines => Catalog.Outlines; /// - /// Get the AcroForm dictionary. + /// Get the AcroForm (interactive form) dictionary. /// - public PdfAcroForm AcroForm => Catalog.AcroForm; + [Obsolete("Use Catalog.GetAcroForm or Catalog.GetOrCreateAcroForm.")] + public PdfForm AcroForm => Catalog.AcroForm; /// /// Gets or sets the default language of the document. @@ -737,7 +761,8 @@ public string Language /// public PdfSecuritySettings SecuritySettings => _securitySettings ??= new(this); - internal PdfSecuritySettings? _securitySettings; + + PdfSecuritySettings? _securitySettings; /// /// Adds characters whose glyphs have to be embedded in the PDF file. @@ -763,6 +788,7 @@ public void AddCharacters(XFont font, string chars) /// internal PdfFontTable FontTable => _fontTable ??= new(this); + PdfFontTable? _fontTable; /// @@ -770,13 +796,15 @@ internal PdfFontTable FontTable /// internal PdfImageTable ImageTable => _imageTable ??= new(this); + PdfImageTable? _imageTable; /// /// Gets the document form table that holds all form external objects used in the current document. /// - internal PdfFormXObjectTable FormTable // TODO_OLD: Rename to ExternalDocumentTable. + internal PdfFormXObjectTable FormTable // TODO_OLD: Rename to ExternalDocumentTable. => _formTable ??= new(this); + PdfFormXObjectTable? _formTable; /// @@ -784,6 +812,7 @@ internal PdfImageTable ImageTable /// internal PdfExtGStateTable ExtGStateTable => _extGStateTable ??= new(this); + PdfExtGStateTable? _extGStateTable; /// @@ -791,24 +820,78 @@ internal PdfExtGStateTable ExtGStateTable /// internal PdfFontDescriptorCache PdfFontDescriptorCache => _pdfFontDescriptorCache ??= new(this); + PdfFontDescriptorCache? _pdfFontDescriptorCache; /// /// Gets the PdfCatalog of the current document. /// - internal PdfCatalog Catalog + public PdfCatalog Catalog => _catalog ??= Trailer.Root; - PdfCatalog? _catalog; // never changes if once created + PdfCatalog? _catalog; // never changes if once created /// /// Gets the PdfInternals object of this document, that grants access to some internal structures /// which are not part of the public interface of PdfDocument. /// - public new PdfInternals Internals - => _internals ??= new PdfInternals(this); + public PdfDocumentInternals Internals + => _internals ??= new PdfDocumentInternals(this); + + PdfDocumentInternals? _internals; + + //public MetadataManager Metadata + // => _metadataManager ??= new MetadataManager(this); - PdfInternals? _internals; + //MetadataManager? _metadataManager; + + //public PdfAManager PdfA + // => _pdfAManager ??= new PdfAManager(this); + + //PdfAManager? _pdfAManager; + + //public EmbeddedFilesManager EmbeddedFiles + // => _embeddedFilesManager ??= new EmbeddedFilesManager(this); + + //EmbeddedFilesManager? _embeddedFilesManager; + + internal DocumentState State { get; set; } + + internal PdfDocumentOpenMode OpenMode { get; set; } + + internal MetadataManager? MetadataManager { get; set; } + + internal MetadataManager GetMetadataManager() + => MetadataManager ?? MetadataManager.ForDocument(this); + + internal SecurityManager? SecurityManager { get; set; } + + internal SecurityManager GetSecurityManager() + => SecurityManager ?? SecurityManager.ForDocument(this); + + internal SigningManager? SigningManager { get; set; } + + internal SigningManager GetSigningManager() + => SigningManager ?? SigningManager.ForDocument(this); + + internal EmbeddedFilesManager? EmbeddedFilesManager { get; set; } + + internal EmbeddedFilesManager GetEmbeddedFilesManager() + => EmbeddedFilesManager ?? EmbeddedFilesManager.ForDocument(this); + + internal PdfAManager? PdfAManager { get; set; } + + internal PdfAManager GetPdfAManager() + => PdfAManager ??= PdfAManager.ForDocument(this); + + // ReSharper disable once InconsistentNaming + internal UAManager? UAManager { get; set; } + + // ReSharper disable once InconsistentNaming + internal UAManager GetUAManager() + => UAManager ??= UAManager.ForDocument(this); + + internal DigitalSignatureHandler? DigitalSignatureHandler { get; set; } /// /// Creates a new page and adds it to this document. @@ -871,7 +954,8 @@ public PdfPage InsertPage(int index, PdfPage page) /// The Named Destination’s name. /// The page to navigate to. /// The PdfNamedDestinationParameters defining the named destination’s parameters. - public void AddNamedDestination(string destinationName, int destinationPage, PdfNamedDestinationParameters parameters) + public void AddNamedDestination(string destinationName, int destinationPage, + PdfNamedDestinationParameters parameters) => Internals.Catalog.Names.AddNamedDestination(destinationName, destinationPage, parameters); /// @@ -879,6 +963,7 @@ public void AddNamedDestination(string destinationName, int destinationPage, Pdf /// /// The name used to refer and to entitle the embedded file. /// The path of the file to embed. + [Obsolete("Use EmbeddedFilesManager.ForDocument(document).Add(embeddedFileInfo)")] public void AddEmbeddedFile(string name, string path) { var stream = new FileStream(path, FileMode.Open); @@ -886,24 +971,67 @@ public void AddEmbeddedFile(string name, string path) } /// + /// This function is deprecated. TODO /// Adds an embedded file to the document. /// /// The name used to refer and to entitle the embedded file. /// The stream containing the file to embed. + [Obsolete("Use EmbeddedFilesManager.ForDocument(document).Add(embeddedFileInfo)")] public void AddEmbeddedFile(string name, Stream stream) - => Internals.Catalog.Names.AddEmbeddedFile(name, stream); + { + var length = (int)stream.Length; + var bytes = new byte[length]; + var read = stream.Read(bytes, 0, length); + if (read != length) + throw new InvalidOperationException($"Try to read {length} bytes, but read {read} bytes."); + + var efi = new EmbeddedFileInfo + { + NamesKey = name, + FileName = name, + FileType = "", + Description = "", + CreationTime = DateTimeOffset.Now, + ModificationTime = null, + Data = bytes, + AFRelationship = PdfAFRelationship.Unspecified + }; + EmbeddedFilesManager.ForDocument(this).AddFile(efi); + } /// /// Flattens a document (make the fields non-editable). /// + [Obsolete("This function does nothing useful and was removed.")] public void Flatten() + => throw new InvalidOperationException("This function does nothing useful and was removed."); + + /// + /// TODO. Experimental, do no use. + /// + /// + /// + internal void RegisterPlugIn(IPdfSharpPlugInV0 plugIn) { - for (int idx = 0; idx < AcroForm.Fields.Count; idx++) + var key = plugIn.ID; + if (_plugIns.TryGetValue(key, out var item)) { - AcroForm.Fields[idx].ReadOnly = true; + if (ReferenceEquals(item, plugIn)) + return; + throw new InvalidOperationException("PlugIn with same ID already registered."); } + + _plugIns[key] = plugIn; + } + + internal IPdfSharpPlugInV0? GetPlugIn(Guid id) + { + _plugIns.TryGetValue(id, out var plugIn); + return plugIn; } + readonly Dictionary _plugIns = []; + /// /// Gets the standard security handler and creates it, if not existing. /// @@ -914,16 +1042,34 @@ public void Flatten() /// internal PdfStandardSecurityHandler? EffectiveSecurityHandler => Trailer.EffectiveSecurityHandler; - internal PdfTrailer Trailer { get; set; } = default!; + internal PdfTrailer Trailer { get; set; } = null!; - internal PdfCrossReferenceTable IrefTable { get; set; } = default!; + internal PdfCrossReferenceTable IrefTable { get; set; } = null!; internal Stream? OutStream { get; set; } // Imported Document. internal Lexer? _lexer; - internal DateTime _creation; + internal DateTimeOffset CreationDate { get; } + + internal string DefaultProducer + { + get + { + if (_defaultProducer == null) + { + // We set Producer if it is not yet set. + _defaultProducer = PdfSharpProductVersionInformation.Creator; +#if DEBUG + // Add OS suffix only in DEBUG build. + _defaultProducer += $" under {RuntimeInformation.OSDescription}"; +#endif + } + return _defaultProducer; + } + } + string? _defaultProducer; /// /// Occurs when the specified document is not used anymore for importing content. @@ -957,7 +1103,7 @@ internal class DocumentHandle(PdfDocument document) readonly WeakReference _weakRef = new(document); - public readonly string ID = document._guid.ToString("B").ToUpper(); + public readonly string ID = document.Guid.ToString("B").ToUpper(); @@ -984,7 +1130,7 @@ public override bool Equals(object? obj) internal void EnsureNotYetSaved() { - if ((_state & DocumentState.Saved) == 0) + if ((State & DocumentState.Saved) == 0) return; var message = "The document was already saved and cannot be modified anymore. " + @@ -994,17 +1140,25 @@ internal void EnsureNotYetSaved() throw new InvalidOperationException(message); } - internal DocumentState _state; - internal PdfDocumentOpenMode _openMode; - internal UAManager? _uaManager; - internal DigitalSignatureHandler? _digitalSignatureHandler; - } + internal void EnsureNotDisposed() + { + // TODO + } -#if true_ - // UNDER_CONSTRUCTION - static class PDFA_ - { - public static bool IsPdfA => true; + // TODO: No internal fields anymore! + // /*internal*/ + // DocumentState _state; + // /*internal*/ + // PdfDocumentOpenMode _openMode; + // /*internal*/ + // MetadataManager? _metadataManager; + // /*internal*/ + // EmbeddedFilesManager? _embeddedFilesManager; + // /*internal*/ + // PdfAManager? _pdfAManager; + // /*internal*/ + // UAManager? _uaManager; + // /*internal*/ + // DigitalSignatureHandler? _digitalSignatureHandler; } -#endif } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentInformation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentInformation.cs index 7856b948..0b37aaa1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentInformation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentInformation.cs @@ -1,6 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +// v7.0.0 TODO review and sync with metadata, DateTimeOffset review + namespace PdfSharp.Pdf { /// @@ -8,13 +10,21 @@ namespace PdfSharp.Pdf /// public sealed class PdfDocumentInformation : PdfDictionary { + // Reference 2.0: 14.3.3 Document information dictionary / Page 716 + /// /// Initializes a new instance of the class. /// public PdfDocumentInformation(PdfDocument document) : base(document) - { } + { + Producer = document.DefaultProducer; + } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfDocumentInformation(PdfDictionary dict) : base(dict) { } @@ -33,12 +43,15 @@ public string Title /// public string Author { + // Field is "Creator" in XMP. "Author" from document info will be used if XMP metadata + // is not used. If XMP metadata is used, the field Author can be empty if Creator cannot + // be found in XMP; Author from document info will then be ignored. get => Elements.GetString(Keys.Author); set => Elements.SetString(Keys.Author, value); } /// - /// Gets or sets the name of the subject of the document. + /// Gets or sets the subject of the document. /// public string Subject { @@ -67,90 +80,151 @@ public string Creator /// /// Gets the producer application (for example, PDFsharp). /// - public string Producer => Elements.GetString(Keys.Producer); + public string Producer + { + get => Elements.GetString(Keys.Producer); +#if PDFSHARP_SET_PRODUCER + set => Elements.SetString(Keys.Producer, value); +#else + internal init => Elements.SetString(Keys.Producer, value); +#endif + } /// - /// Gets or sets the creation date of the document. - /// Breaking Change: If the date is not set in a PDF file DateTime.MinValue is returned. + /// Gets or sets the creation date of the document or null, if no entry is set. /// - public DateTime CreationDate + public DateTimeOffset? CreationDate { - get => Elements.GetDateTime(Keys.CreationDate, DateTime.MinValue); - set => Elements.SetDateTime(Keys.CreationDate, value); + get => Elements.GetDateTime(Keys.CreationDate, null); + set + { + if (value == null) + Elements.Remove(Keys.CreationDate); + else + Elements.SetDateTime(Keys.CreationDate, value.Value); + } } /// - /// Gets or sets the modification date of the document. - /// Breaking Change: If the date is not set in a PDF file DateTime.MinValue is returned. + /// Gets or sets the modification date of the document or null, if no entry is set. /// - public DateTime ModificationDate + public DateTimeOffset? ModificationDate { - get => Elements.GetDateTime(Keys.ModDate, DateTime.MinValue); - set => Elements.SetDateTime(Keys.ModDate, value); + get => Elements.GetDateTime(Keys.ModDate, null); + set + { + if (value == null) + Elements.Remove(Keys.ModDate); + else + Elements.SetDateTime(Keys.ModDate, value.Value); + } } - // TODO_OLD CustomProperties and metadata + // TODO CustomProperties and metadata /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { + // Reference 2.0: Table 349 — Entries in the document information dictionary / Page 716 + /// - /// (Optional; PDF 1.1) The document’s title. + /// (Optional; PDF 1.1) The document’s title.
+ /// NOTE 1
+ /// The dc:title entry in the document’s metadata stream can be used to represent the + /// document’s title. ///
- [KeyInfo(KeyType.String | KeyType.Optional)] + [KeyInfo(KeyType.TextString | KeyType.Optional)] public const string Title = "/Title"; /// - /// (Optional) The name of the person who created the document. + /// (Optional) The name of the person who created the document.
+ /// NOTE 2
+ /// The dc:creator entry in the document’s metadata stream can be used to represent the + /// person or persons who created the document. This note was corrected in this + /// document (2020). ///
- [KeyInfo(KeyType.String | KeyType.Optional)] + [KeyInfo(KeyType.TextString | KeyType.Optional)] public const string Author = "/Author"; /// - /// (Optional; PDF 1.1) The subject of the document. + /// (Optional; PDF 1.1) The subject of the document.
+ /// NOTE 3
+ /// The dc:description entry in the document’s metadata stream can be used to represent + /// the subject the document. ///
- [KeyInfo(KeyType.String | KeyType.Optional)] + [KeyInfo(KeyType.TextString | KeyType.Optional)] public const string Subject = "/Subject"; /// - /// (Optional; PDF 1.1) Keywords associated with the document. + /// (Optional; PDF 1.1) Keywords associated with the document.
+ /// NOTE 4
+ /// The pdf:Keywords entry in the document’s metadata stream can be used to represent the + /// keywords for the document. ///
- [KeyInfo(KeyType.String | KeyType.Optional)] + [KeyInfo(KeyType.TextString | KeyType.Optional)] public const string Keywords = "/Keywords"; /// - /// (Optional) If the document was converted to PDF from another format, - /// the name of the application (for example, empira MigraDoc) that created the - /// original document from which it was converted. + /// (Optional) If the document was converted to PDF from another format, the name of the + /// application (for example, Adobe FrameMaker®) that created the original document from + /// which it was converted.
+ /// NOTE 5
+ /// The xmp:CreatorTool entry in the document’s metadata stream can be used to represent + /// the creation tool of the document. ///
- [KeyInfo(KeyType.String | KeyType.Optional)] + [KeyInfo(KeyType.TextString | KeyType.Optional)] public const string Creator = "/Creator"; /// /// (Optional) If the document was converted to PDF from another format, - /// the name of the application (for example, this library) that converted it to PDF. + /// the name of the application (for example, this library) that converted it to PDF.
+ /// NOTE 6
+ /// The pdf:Producer entry in the document’s metadata stream can be used to represent the + /// tool that saved the document as a PDF. ///
- [KeyInfo(KeyType.String | KeyType.Optional)] + [KeyInfo(KeyType.TextString | KeyType.Optional)] public const string Producer = "/Producer"; /// - /// (Optional) The date and time the document was created, in human-readable form. + /// (Optional) The date and time the document was created, in human-readable form.
+ /// NOTE 7
+ /// The xmp:CreateDate entry in the document’s metadata stream can be used to represent + /// document’s creation date and time. ///
[KeyInfo(KeyType.Date | KeyType.Optional)] public const string CreationDate = "/CreationDate"; /// /// (Required if PieceInfo is present in the document catalog; otherwise optional; PDF 1.1) - /// The date and time the document was most recently modified, in human-readable form. + /// The date and time the document was most recently modified, in human-readable form.
+ /// NOTE 8
+ /// The xmp:ModifyDate entry in the document’s metadata stream can be used to represent the + /// date and time the document was most recently modified. ///
[KeyInfo(KeyType.String | KeyType.Optional)] public const string ModDate = "/ModDate"; /// - /// (Optional; PDF 1.3) A name object indicating whether the document has been modified - /// to include trapping information. + /// (Optional; PDF 1.3; deprecated in PDF 2.0) A name object indicating whether the document + /// has been modified to include trapping information:
+ /// True
+ /// The document has been fully trapped; no further trapping is needed. (This is the name True, + /// not the boolean value true.)
+ /// False
+ /// The document has not yet been trapped; any desired trapping must still be done. + /// (This is the name False, not the boolean value false.)
+ /// Unknown
+ /// Either it is unknown whether the document has been trapped or it has been partly but not + /// yet fully trapped; some additional trapping may still be needed.
+ /// Default value: Unknown.
+ /// NOTE 9
+ /// The value of this entry can be set automatically by the software creating the document’s + /// trapping information, or it can be known only to a human operator and entered manually.
+ /// NOTE 10
+ /// The pdf:Trapped entry in the document’s metadata stream can be used to represent the + /// trapping information for the document. ///
[KeyInfo("1.3", KeyType.Name | KeyType.Optional)] public const string Trapped = "/Trapped"; @@ -158,7 +232,7 @@ internal sealed class Keys : KeysBase /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs index fe95413a..f1287462 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs @@ -92,10 +92,12 @@ public PdfWriterLayout Layout get => _writerLayout; set => _writerLayout = value; } + + PdfWriterLayout _writerLayout = #if DEBUG - PdfWriterLayout _writerLayout = PdfWriterLayout.Verbose; + PdfWriterLayout.Verbose; #else - PdfWriterLayout _writerLayout = PdfWriterLayout.Compact; + PdfWriterLayout.Compact; #endif } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs index 687e98b7..12159322 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfInteger.cs @@ -15,20 +15,34 @@ public sealed class PdfInteger : PdfNumber, IConvertible /// Initializes a new instance of the class. ///
public PdfInteger() - { - IsInteger = true; - } + => IsInteger = true; /// /// Initializes a new instance of the class. /// /// The value. - public PdfInteger(int value) + public PdfInteger(int value) : this() + => Value = value; + + internal PdfInteger(int value, bool isFlag) : this() { - IsInteger = true; Value = value; + IsFlag = isFlag; } +#if PRESERVE_PARSED_VALUES + internal PdfInteger(int value, string parsedValue, bool isFlag) : this() + { + Value = value; + ParsedValue = parsedValue; + IsFlag = isFlag; + } +#endif + /// + /// Gets or sets a value indicating whether this instance is used as a 32-bt flag. + /// + public bool IsFlag { get; set; } + /// /// Gets the value as integer. /// @@ -38,7 +52,11 @@ public PdfInteger(int value) /// Returns the integer as string. ///
public override string ToString() +#if PRESERVE_PARSED_VALUES + => ParsedValue ?? Value.ToString(CultureInfo.InvariantCulture); +#else => Value.ToString(CultureInfo.InvariantCulture); +#endif /// /// Writes the integer as string. @@ -58,8 +76,6 @@ double IConvertible.ToDouble(IFormatProvider? provider) => Value; DateTime IConvertible.ToDateTime(IFormatProvider? provider) - //// TO-DO: Add PdfInteger.ToDateTime implementation - // => new DateTime(); => throw new InvalidCastException(); float IConvertible.ToSingle(IFormatProvider? provider) @@ -99,10 +115,7 @@ decimal IConvertible.ToDecimal(IFormatProvider? provider) => Value; object IConvertible.ToType(Type conversionType, IFormatProvider? provider) - { - // TODO_OLD: Add PdfInteger.ToType implementation - return null!; - } + => throw new NotImplementedException("Conversion not implemented."); uint IConvertible.ToUInt32(IFormatProvider? provider) => Convert.ToUInt32(Value); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfIntegerObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfIntegerObject.cs index c2f6f6fa..0a727b28 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfIntegerObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfIntegerObject.cs @@ -6,8 +6,8 @@ namespace PdfSharp.Pdf { /// - /// Represents an indirect 32-bit signed integer value. This type is not used by PDFsharp. If it is imported from - /// an external PDF file, the value is converted into a direct object. + /// Represents an indirect 32-bit signed integer value. This type is not created by PDFsharp. + /// If it is imported from an external PDF file, the value is converted into a direct object. /// [DebuggerDisplay("({" + nameof(Value) + "})")] public sealed class PdfIntegerObject : PdfNumberObject @@ -16,22 +16,36 @@ public sealed class PdfIntegerObject : PdfNumberObject /// Initializes a new instance of the class. /// public PdfIntegerObject() - { } + => IsInteger = true; /// /// Initializes a new instance of the class. /// - public PdfIntegerObject(int value) + public PdfIntegerObject(int value) : this() + => Value = value; + + /// + /// Initializes a new instance of the class. + /// + public PdfIntegerObject(PdfDocument document, int value) + : base(document, true) { + IsInteger = true; Value = value; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// without making it indirect. + /// Used in PDF parser only. /// - public PdfIntegerObject(PdfDocument document, int value) - : base(document) + /// The document. + /// The initial value. + /// If true creates an indirect object. + internal PdfIntegerObject(PdfDocument document, int value, bool createIndirect) + : base(document, createIndirect) { + IsInteger = true; Value = value; } @@ -43,7 +57,7 @@ public PdfIntegerObject(PdfDocument document, int value) /// /// Returns the integer as string. /// - public override string ToString() + public override string ToString() => Value.ToString(CultureInfo.InvariantCulture); /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs index 0a4e3f90..b6684d27 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItem.cs @@ -2,37 +2,194 @@ // See the LICENSE file in the solution root for more information. using PdfSharp.Pdf.IO; +using System.Runtime.CompilerServices; + +#if PDFSHARP_DEBUG +using static PdfSharp.Diagnostics.DebugBreakHelper; +#endif namespace PdfSharp.Pdf { /// - /// The base class of all PDF objects and simple PDF types. + /// The base class of all PDF objects and primitive PDF types. /// public abstract class PdfItem : ICloneable { - // All simple types (i.e. derived from PdfItem but not from PdfObject) must be immutable. + /// + /// Initialized a new instance of this class. + /// + protected PdfItem() + { +#if PDFSHARP_DEBUG + InitItemNumber(); +#endif + ItemFlags = ItemFlags.IsPrimitiveItem; + } - object ICloneable.Clone() + /// + /// Initialized a new instance of this class. + /// + protected PdfItem(PdfItem item) { - return Copy(); +#if PDFSHARP_DEBUG + InitItemNumber(); +#endif + ItemFlags = item.ItemFlags; } + object ICloneable.Clone() => Copy(); + /// /// Creates a copy of this object. /// - public PdfItem Clone() - => (PdfItem)Copy(); + public PdfItem Clone() => (PdfItem)Copy(); /// /// Implements the copy mechanism. Must be overridden in derived classes. /// - protected virtual object Copy() - => MemberwiseClone(); +#if !PDFSHARP_DEBUG + protected virtual object Copy() => MemberwiseClone(); +#else + protected virtual object Copy() + { + var item = (PdfItem)MemberwiseClone(); + InitItemNumber(); + return item; + } +#endif /// /// When overridden in a derived class, appends a raw string representation of this object /// to the specified PdfWriter. /// internal abstract void WriteObject(PdfWriter writer); + + /// + /// Some low-level flags for making the code more efficient. + /// + internal ItemFlags ItemFlags; + + /// + /// Is PdfItem but not PdfObject. + /// + internal bool IsPureItem => (ItemFlags & ItemFlags.IsPrimitiveItem) is not 0; + + /// + /// Is PdfObject, but not PdfArray or PdfDictionary. + /// + internal bool IsPureObject => (ItemFlags & ItemFlags.IsCompoundObject) is not 0; + + /// + /// Is PdfArray. + /// + internal bool IsArray => (ItemFlags & ItemFlags.IsArray) is not 0; + + /// + /// Is PdfDictionary. + /// + internal bool IsDictionary => (ItemFlags & ItemFlags.IsDictionary) is not 0; + + /// + /// Is PdfArray or PdfDictionary. + /// + internal bool IsArrayOrDictionary => (ItemFlags & ItemFlags.IsArrayOrDictionary) is not 0; + + internal bool ShouldTryTransformation => (ItemFlags & ItemFlags.TransformationMask) is 0; + + internal bool IsTransformed => (ItemFlags & ItemFlags.IsTransformed) is not 0; + + internal void SetTransformed() + { +#if PDFSHARP_DEBUG + if (ShouldBreak5) + Debugger.Break(); +#endif + ItemFlags |= ItemFlags.IsTransformed; + } + + internal void SetTransformationTried() + { + //ItemFlags &= ItemFlags.ShTransformationWasTried; + ItemFlags |= ItemFlags.TransformationWasTried; + } + + internal void SetMustBeIndirect() + { +#if PDFSHARP_DEBUG + if (ShouldBreak1) + Debugger.Break(); +#endif + ItemFlags |= ItemFlags.MustBeIndirect; + } + + internal bool MustBeIndirect() + { +#if PDFSHARP_DEBUG + if (ShouldBreak1) + Debugger.Break(); +#endif + return (ItemFlags & ItemFlags.MustBeIndirect) is not 0; + } + + /// + /// Gets a value indicating whether a PDF object dead after an + /// object type transformation. + /// + internal bool IsDead + { + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + get { return (ItemFlags & ItemFlags.IsDead) is not 0; } + } + + /// + /// Marks a PDF object as dead. + /// + internal void SetDead() + { + Debug.Assert(this is PdfContainer); +#if DEBUG_ + //if (this is PdfObject { ObjectNumber: 96049 }) + // _ = typeof(int); + + //if (ShouldBreak1) + // Debugger.Break(); + if (this is PdfArray array) + { + if (array.Count() == 4) + Debugger.Break(); + } + +#endif +#if PDFSHARP_DEBUG + PdfSharpDebug.Instance.AddDeadContainer((PdfContainer)this); +#endif + ItemFlags |= ItemFlags.IsDead; + } + + [MethodImpl(MethodImplOptions.AggressiveInlining)] + internal void EnsureAlive() + { + if (IsDead) + { + throw new InvalidOperationException( + $"This instance of {GetType().FullName} cannot be used anymore, because it is dead. " + + "This happens when a PDF object is transformed to a derived class and your code still holds " + + "a reference to the old instance. This was wrong all along but is detected now."); + } + } + +#if PDFSHARP_DEBUG + void InitItemNumber() + { + ItemNumber = ++_itemCounter; + } + + /// + /// Gets the unique item count used for debugging purposes. + /// + public int ItemNumber { get; private set; } = 0; + + static int _itemCounter = 0; +#endif } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItemExtensions/PdfItemExtensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItemExtensions/PdfItemExtensions.cs new file mode 100644 index 00000000..6677e8b2 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfItemExtensions/PdfItemExtensions.cs @@ -0,0 +1,141 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Advanced; + +namespace PdfSharp.Pdf.PdfItemExtensions +{ + /// + /// Extension methods for PDF items. + /// + public static class PdfItemExtensions + { + + /// + /// Casts a PDF item into a PDF array, or throws an exception if this is not possible. + /// + public static PdfArray AsArray(this PdfItem? value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (value is PdfReference reference) + value = reference.Value; + + if (value is PdfArray arr) + return arr; + + throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to PdfArray."); + } + + /// + /// Casts a PDF item into a PDF array of type T, or throws an exception if this is not possible. + /// + public static T AsArray(this PdfItem? value) where T : PdfArray + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (value is PdfReference reference) + value = reference.Value; + + if (value is T arr) + return arr; + + throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to {typeof(T).FullName}."); + } + + //public static PdfArray AsArray(this PdfReference? value) + //{ + // if (value == null) + // throw new ArgumentNullException(nameof(value)); + + // if (value.Value is PdfArray arr) + // return arr; + + // throw new InvalidCastException("value is not a PdfArray."); + //} + + //public static T AsArray(this PdfReference? value) where T : PdfArray + //{ + // if (value == null) + // throw new ArgumentNullException(nameof(value)); + + // if (value.Value is T arr) + // return arr; + + // throw new InvalidCastException($"value is not a {typeof(T).Name}."); + //} + + /// + /// Casts a PDF item into a PDF dictionary of type T, or throws an exception if this is not possible. + /// + public static PdfDictionary AsDictionary(this PdfItem? value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (value is PdfReference reference) + value = reference.Value; + + if (value is PdfDictionary dict) + return dict; + + throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to PdfDictionary."); + } + + /// + /// Casts a PDF item into a PDF dictionary of type T, or throws an exception if this is not possible. + /// + public static T AsDictionary(this PdfItem? value) where T : PdfDictionary + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (value is PdfReference reference) + value = reference.Value; + + if (value is T dict) + return dict; + + throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to '{typeof(T).FullName}'."); + } + + //public static PdfDictionary AsDictionary(this PdfReference? value) + //{ + // if (value == null) + // throw new ArgumentNullException(nameof(value)); + + // if (value.Value is PdfDictionary dict) + // return dict; + + // throw new InvalidCastException("value is not a PdfDictionary."); + //} + + //public static T AsDictionary(this PdfReference? value) where T : PdfDictionary + //{ + // if (value == null) + // throw new ArgumentNullException(nameof(value)); + + // if (value.Value is T dict) + // return dict; + + // throw new InvalidCastException($"value is not a {typeof(T).Name}."); + //} + + /// + /// Casts a PDF item into a PDF reference, or throws an exception if this is not possible. + /// + public static PdfReference AsReference(this PdfItem? value) + { + if (value == null) + throw new ArgumentNullException(nameof(value)); + + if (value is PdfReference reference) + return reference; + + throw new InvalidCastException($"PdfItem of type '{value.GetType().FullName}' cannot be casted to PdfReference."); + } + } +} + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs index 0526c96a..6f675533 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLiteral.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using PdfSharp.Drawing; @@ -8,19 +8,21 @@ namespace PdfSharp.Pdf { /// - /// Represents text that is written 'as it is' into the PDF stream. This class can lead to invalid PDF files. + /// Represents text that is written ‘as it is’ into the PDF stream. + /// Using this class can lead to invalid PDF files. /// E.g. strings in a literal are not encrypted when the document is saved with a password. /// - public sealed class PdfLiteral : PdfItem + public sealed class PdfLiteral : PdfPrimitive { - /// - /// Initializes a new instance of the class. - /// - public PdfLiteral() - { } + // Note that PdfLiteral is used by PDFsharp to have an easy way to write e.g. a matrix or a + // page destination by creating just a single item instead of creating arrays with items. + // By contrast, PdfDebugItem and PdfDebugObject are used only in unit tests to create + // illegal PDF content. /// /// Initializes a new instance with the specified string. + /// The string is (as always) interpreted as an UTF16 .NET string and written + /// as a raw string. /// public PdfLiteral(string value) { @@ -38,6 +40,7 @@ public PdfLiteral(string format, params object[] args) /// /// Creates a literal from an XMatrix /// + [Obsolete] public static PdfLiteral FromMatrix(XMatrix matrix) { return new PdfLiteral($"[{PdfEncoders.ToString(matrix)}]"); @@ -46,14 +49,14 @@ public static PdfLiteral FromMatrix(XMatrix matrix) /// /// Gets the value as literal string. /// - public string Value { get; } = ""; + public string Value { get; } /// /// Returns a string that represents the current value. /// public override string ToString() => Value; - internal override void WriteObject(PdfWriter writer) + internal override void WriteObject(PdfWriter writer) => writer.Write(this); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongInteger.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongInteger.cs index 59f86a62..faf6a32b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongInteger.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongInteger.cs @@ -14,20 +14,15 @@ public sealed class PdfLongInteger : PdfNumber, IConvertible /// /// Initializes a new instance of the class. /// - public PdfLongInteger() - { - IsLongInteger = true; - } + public PdfLongInteger() + => IsLongInteger = true; /// /// Initializes a new instance of the class. /// /// The value. - public PdfLongInteger(long value) - { - IsLongInteger = true; - Value = value; - } + public PdfLongInteger(long value):this() + => Value = value; /// /// Gets the value as 64-bit integer. @@ -58,8 +53,6 @@ double IConvertible.ToDouble(IFormatProvider? provider) => Value; DateTime IConvertible.ToDateTime(IFormatProvider? provider) - //// TO-DO: Add PdfInteger.ToDateTime implementation - // => new DateTime(); => throw new InvalidCastException(); float IConvertible.ToSingle(IFormatProvider? provider) @@ -98,11 +91,8 @@ public TypeCode GetTypeCode() decimal IConvertible.ToDecimal(IFormatProvider? provider) => Value; - object IConvertible.ToType(Type conversionType, IFormatProvider? provider) - { - // TODO_OLD: Add PdfInteger.ToType implementation - return null!; - } + object IConvertible.ToType(Type conversionType, IFormatProvider? provider) + => throw new NotImplementedException("Conversion not implemented."); uint IConvertible.ToUInt32(IFormatProvider? provider) => Convert.ToUInt32(Value); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongIntegerObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongIntegerObject.cs index 028c9ed7..a34f3f35 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongIntegerObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfLongIntegerObject.cs @@ -6,8 +6,8 @@ namespace PdfSharp.Pdf { /// - /// Represents an indirect 64-bit signed integer value. This type is not used by PDFsharp. If it is imported from - /// an external PDF file, the value is converted into a direct object. + /// Represents an indirect 64-bit signed integer value. This type is not created by PDFsharp. + /// If it is imported from an external PDF file, the value is converted into a direct object. /// [DebuggerDisplay("({" + nameof(Value) + "})")] public sealed class PdfLongIntegerObject : PdfNumberObject @@ -16,22 +16,36 @@ public sealed class PdfLongIntegerObject : PdfNumberObject /// Initializes a new instance of the class. /// public PdfLongIntegerObject() - { } + => IsLongInteger = true; /// /// Initializes a new instance of the class. /// - public PdfLongIntegerObject(long value) + public PdfLongIntegerObject(long value) : this() + => Value = value; + + /// + /// Initializes a new instance of the class. + /// + public PdfLongIntegerObject(PdfDocument document, long value) + : base(document, true) { + IsLongInteger = true; Value = value; } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class + /// without making it indirect. + /// Used in PDF parser only. /// - public PdfLongIntegerObject(PdfDocument document, long value) - : base(document) + /// The document. + /// The initial value. + /// If true creates an indirect object. + internal PdfLongIntegerObject(PdfDocument document, long value, bool createIndirect) + : base(document, createIndirect) { + IsLongInteger = true; Value = value; } @@ -43,7 +57,7 @@ public PdfLongIntegerObject(PdfDocument document, long value) /// /// Returns the integer as string. /// - public override string ToString() + public override string ToString() => Value.ToString(CultureInfo.InvariantCulture); /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs index 9bbef518..88d65d08 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs @@ -1,9 +1,51 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; -using System.Text; using PdfSharp.Pdf.Internal; +using System.Text; +using Microsoft.Extensions.Logging; +using PdfSharp.Logging; +using PdfSharp.Pdf.Metadata; + +// v7.0.0 TODO review and sync with document information, DateTimeOffset review +// TODO DocumentID, InstanceID + +/// +/// Superfluous implementation. XMP in PDF files must use only UTF-8 encoding. +/// We keep it for internal tests. +/// +enum MetadataEncodingType +{ + // ReSharper disable InconsistentNaming + + /// + /// Encodes metadata stream using UTF-8 encoding. + /// This is the default and the only recommended option. + /// + UTF8, + + /// + /// Encodes metadata stream using UTF-16 little-endian encoding. + /// + UTF16LE, + + /// + /// Encodes metadata stream using UTF-16 big-endian encoding. + /// + UTF16BE, + + /// + /// Encodes metadata stream using UTF-32 little-endian encoding. + /// + UTF32LE, + + /// + /// Encodes metadata stream using UTF-32 big-endian encoding. + /// + UTF32BE, + + // ReSharper restore InconsistentNaming +} namespace PdfSharp.Pdf { @@ -12,14 +54,14 @@ namespace PdfSharp.Pdf /// public sealed class PdfMetadata : PdfDictionary { + // Reference 2.0: 14.3.2 Metadata streams / Page 714 + /// /// Initializes a new instance of the class. /// public PdfMetadata() { - Elements.SetName(Keys.Type, "/Metadata"); - Elements.SetName(Keys.Subtype, "/XML"); - SetupStream(); + Initialize(); } /// @@ -27,209 +69,185 @@ public PdfMetadata() /// /// The document that owns this object. public PdfMetadata(PdfDocument document) - : base(document) + : base(document, true) { - document.Internals.AddObject(this); - Elements.SetName(Keys.Type, "/Metadata"); - Elements.SetName(Keys.Subtype, "/XML"); - SetupStream(); + Initialize(); } - void SetupStream() - { - const string begin = @"begin="""; - - var stream = GenerateXmp(); - - // Preserve "" if text is UTF8 encoded. - var i = stream.IndexOf(begin, StringComparison.Ordinal); - var pos = i + begin.Length; - stream = stream[..pos] + "xxx" + stream[(pos + 3)..]; - - byte[] bytes = Encoding.UTF8.GetBytes(stream); - bytes[pos++] = (byte)'ï'; - bytes[pos++] = (byte)'»'; - bytes[pos] = (byte)'¿'; + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfMetadata(PdfDictionary dictionary) + : base(dictionary) + { } - CreateStream(bytes); - } - string GenerateXmp() + void Initialize() { - var instanceId = Guid.NewGuid().ToString(); - var documentId = Guid.NewGuid().ToString(); - - static DateTime SpecifyLocalDateTimeKindIfUnspecified(DateTime value) - => value.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(value, DateTimeKind.Local) : value; + Elements.SetName(Keys.Type, "/Metadata"); + Elements.SetName(Keys.Subtype, "/XML"); + } - var creationDate = SpecifyLocalDateTimeKindIfUnspecified(_document.Info.CreationDate).ToString("yyyy-MM-ddTHH:mm:ssK"); - var modificationDate = creationDate; + /// + /// Obsolete, use ToString. + /// + [Obsolete("Use ToString.")] + public string Xml + => Stream == null ? "" : Stream.ToString(); - var author = _document.Info.Author; - var creator = _document.Info.Creator; - var producer = _document.Info.Producer; - var title = _document.Info.Title; - var subject = _document.Info.Subject; - var keywords = _document.Info.Keywords; + /// + /// Creates the XMP metadata for the PDF document. + /// Creating XMP metadata is not the business of PDFsharp. + /// This is a suggestion how you can do it. + /// + // + // .NET contains classes to create XML content. We use simple text substitution here. + // + public string CreateDefaultMetadata() => CreateDefaultMetadata(MetadataEncodingType.UTF8); - // #PDF-A Tag PDF as PDF/A-1A conform. - string? pdfA = null; - if (_document.IsPdfA) - { - // #PDF-A - pdfA = $""" - - 1 - A - - """; - } -#if true - // Created based on a PDF created with Microsoft Word. - var str = $""" - - - - - {producer}{keywords} - - - {title} - {author} - {subject} - - - {creator} - {creationDate} - {modificationDate} - - - uuid:{documentId} - uuid:{instanceId} - - {pdfA} - - - - """; -#else - // Does not exist anymore. - // XMP Documentation: http://wwwimages.adobe.com/content/dam/Adobe/en/devnet/xmp/pdfs/XMP%20SDK%20Release%20cc-2016-08/XMPSpecificationPart1.pdf - - var str = - // UTF-8 Byte order mark "" and GUID (like in Reference) to avoid accidental usage in data stream. - "\n" + - - " \n" + - " \n" + - " \n" + - - " uuid:" + instanceId + "\n" + - " uuid:" + documentId + "\n" + - - " \n" + - " \n" + - " 1\n" + - " \n" + - " \n" + - - " " + creationDate + "\n" + - " " + modificationDate + "\n" + - " " + creator + "\n" + - " " + modificationDate + "\n" + - - " \n" + - " \n" + - - " " + producer + "\n" + - - " \n" + - " \n" + - " \n" + - " \n" + - - " " + title + "\n" + - - " \n" + - " \n" + - " \n" + - " \n" + - " \n" + - "\n"; + // I wrote the code, and we will keep it for reference. + internal string CreateDefaultMetadata(MetadataEncodingType encodingType) + { + // See XMP SPECIFICATION PART 1 - 3 for details. + // The files names are “XMPSpecificationPart1.pdf”, “XMPSpecificationPart2.pdf”, and “XMPSpecificationPart3.pdf”. + // They are created by Adobe in 2008 and can still be found by Google. + // + // I just scanned the specs und overlooked the fact that XMP data can be written in 5 encodings, + // BUT for PDF files only UTF-8 is allowed. Acrobat also accepts UFT-16 little and big endian, + // but not UTF-32. I wasted hours to make it work for UTF-32 + // + // Now we have the code created and PDFsharp would accept all encodings, but writes only UTF-8. + + var metadataInfo = MetadataManager.GetMetadataInfo(); + // ReSharper disable StringLiteralTypo because we deal with XML elements here. + bool isPdfA = metadataInfo.PdfAFormat.HasValue; + + var xml = + // XMP header. + // Note that in a .NET string the BOM is represented as a human-readable string. + // PDFsharp converts this string to correct BOM when encoded. + $""" + + + + """ + "\r\n" + + // PDF/A section. + (isPdfA ? + $""" + + {metadataInfo.PdfAFormat?.Part} + {metadataInfo.PdfAFormat?.ConformanceLevel} + + """ + "\r\n" + : "" + ) + + // xmlns:pdf: Producer, Keywords + $""" + + {metadataInfo.Producer} + {metadataInfo.Keywords} + + """ + "\r\n" + + // xmlns:xap: CreatorTool, CreateDate, ModifyDate + $""" + + {metadataInfo.Creator} + {metadataInfo.CreationDate} + {metadataInfo.ModificationDate} + {(!String.IsNullOrWhiteSpace(metadataInfo.ModificationDate) ? metadataInfo.ModificationDate : metadataInfo.CreationDate)} + + """ + "\r\n" + + // xmlns:dc: Title, Creator (author), Description + $""" + + + + {metadataInfo.Title} + + + + + {metadataInfo.Author} + + + + + {metadataInfo.Subject} + + + + """ + "\r\n" + +#if true_ + // TODO: what if string from trailer is not a UUID??? + // xmlns:xapMM: DocumentID, InstanceID + $""" + + uuid:{metadataInfo.DocumentID} + uuid:{metadataInfo.InstanceID} + + """ + "\r\n" + #endif + // XMP trailer. + """ + + + + """; + // ReSharper restore StringLiteralTypo + return xml; + } + + /// + /// Converts the stream content to a string.
+ /// The BOM in “<?xpacket begin="{BOM}"” is set to “UTF-8” because a UTF-8 BOM cannot be a + /// part of a Unicode string. + ///
+ public override String ToString() => StreamToString(); - return str; + /// + /// Replaces the stream content with the specified byte array. + /// + /// + public void SetMetadata(byte[] xml) + { + //var bytes = PdfEncoders.RawEncoding.GetBytes(xml); + Stream = null; + CreateStream(xml); } - void Foo() + /// + /// Sets the metadata stream.
+ /// The BOM tag of the “<?xpacket begin="{BOM}"” attribute must either be empty or 'UTF-8'. + ///
+ public void SetMetadata(string xml) { - var documentId = Guid.NewGuid().ToString(); - var instanceId = Guid.NewGuid().ToString(); - - static DateTime SpecifyLocalDateTimeKindIfUnspecified(DateTime value) - => value.Kind == DateTimeKind.Unspecified ? DateTime.SpecifyKind(value, DateTimeKind.Local) : value; - - var creationDate = SpecifyLocalDateTimeKindIfUnspecified(_document.Info.CreationDate).ToString("yyyy-MM-ddTHH:mm:ssK"); - var modificationDate = creationDate; - - var author = _document.Info.Author; - var creator = _document.Info.Creator; - var producer = _document.Info.Producer; - var title = _document.Info.Title; - var subject = _document.Info.Subject; - - string s2 = $""" - - - - - - {producer} - Tag1 Tag 2 Tag3 - - - - - - {title} - - - - - {author} - - - - - {subject} - - - - - - {creator} - {creationDate} - {modificationDate} - - - - uuid:{documentId} - uuid:{instanceId} - - - - - - """; + var bytes = MetadataEncoder.GetBytes(xml); + SetMetadata(bytes); } + /// + /// Converts the byte array of the stream’s value to a regular Unicode .NET string and + /// replaces the BOM by a string that shows the original in hexadecimal notation. + /// + string StreamToString() => MetadataEncoder.GetString(Stream?.UnfilteredValue ?? []); + + MetadataManager MetadataManager => _metadataManager ??= MetadataManager.ForDocument(Document + ?? throw new InvalidOperationException("PdfMetadata must belong to a document to create metadata.")); + MetadataManager? _metadataManager; + + // TODO: Table 348 — Additional entry for components having metadata + /// /// Predefined keys of this dictionary. /// - internal class Keys : KeysBase + public class Keys : KeysBase { + // Reference 2.0: Table 347 — Additional entries in a metadata stream dictionary / Page 713 + /// - /// (Required) The type of PDF object that this dictionary describes; must be Metadata for a metadata stream. + /// (Required) The type of PDF object that this dictionary describes; must be Metadata + /// for a metadata stream. /// [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Metadata")] public const string Type = "/Type"; @@ -240,5 +258,314 @@ internal class Keys : KeysBase [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "XML")] public const string Subtype = "/Subtype"; } + + internal static class MetadataPreparer // TODO StL Eliminate this class. + { + public static void PrepareDocument(PdfDocument doc) + { + //doc.Catalog.GetMetadata(); + } + } + + static class MetadataEncoder + { + /* For reference see XMP SPECIFICATION PART 3 page 14 + * + * Search for “ “” // Only valid BOM for XMP metadata in a PDF file. + const string utf16BomLE = "\u00FF\u00FE"; // <=> “ÿþ” // \ + const string utf16BomBE = "\u00FE\u00FF"; // <=> “þÿ” // \ Valid BOMs for XMP metadata, but not in a PDF file. + const string utf32BomLE = "\u00FF\u00FE\0\0"; // <=> “ÿþ\0\0” // / + const string utf32BomBE = "\0\0\u00FE\u00FF"; // <=> “\0\0þÿ” // / + + // ReSharper restore InconsistentNaming + // ReSharper restore StringLiteralTypo + + public static string GetString(byte[] bytes) + { + // Convert stream bytes into raw string for easier coding. + var rawString = PdfEncoders.RawEncoding.GetString(bytes); + if (String.IsNullOrEmpty(rawString)) + return ""; + + string result; + int pos; + if ((pos = rawString.IndexOf(utf8Header, StringComparison.Ordinal)) != -1) + { + if (pos > 0) + rawString = rawString[pos..]; + + // ReSharper disable once CommentTypo for better readability + // “ 0) + rawString = rawString[pos..]; + + const int leftPartLength = 2 * 17; + const int rightPartStart = leftPartLength + 2; + char leftQuot = rawString[leftPartLength - 2]; + char rightQuot = rawString[rightPartStart]; + if (leftQuot is '\"' or '\'' && rawString[leftPartLength..rightPartStart] == utf16BomLE && rightQuot is '\"' or '\'') + { + // Remove UTF-16LE BOM. + rawString = rawString[..leftPartLength] + rawString[rightPartStart..]; + } + else + throw new InvalidOperationException("An UTF-16 encoded metadata stream has an invalid BOM entry."); + + // Get bytes from stream without BOM. + bytes = PdfEncoders.RawEncoding.GetBytes(rawString); + result = Encoding.Unicode.GetString(bytes); + // Insert a readable encoding name. + result = result[..17] + MetadataEncodingType.UTF16LE + result[17..]; + } + else if ((pos = rawString.IndexOf(utf16HeaderBE, StringComparison.Ordinal)) != -1) + { + PdfSharpLogHost.Logger.LogWarning("XMP metadata is encoded using UTF-16 big endian, which is not allowed in PDF files, but accepted by PDFsharp."); + + if (pos > 0) + rawString = rawString[pos..]; + + const int leftPartLength = 2 * 17 + 1; + const int rightPartStart = leftPartLength + 2; + char leftQuot = rawString[leftPartLength - 2]; + char rightQuot = rawString[rightPartStart]; + if (leftQuot is '\"' or '\'' && rawString[(leftPartLength - 1)..(rightPartStart - 1)] == utf16BomBE && rightQuot is '\"' or '\'') + { + // Remove UTF-16BE BOM. + rawString = rawString[..(leftPartLength - 1)] + rawString[(rightPartStart - 1)..]; + } + else + throw new InvalidOperationException("An UTF-16 big-endian encoded metadata stream has an invalid BOM entry."); + + // Get bytes from stream without BOM. + bytes = PdfEncoders.RawEncoding.GetBytes(rawString); + result = Encoding.BigEndianUnicode.GetString(bytes); + // Insert a readable encoding name. + result = result[..17] + MetadataEncodingType.UTF16BE + result[17..]; + } + else if ((pos = rawString.IndexOf(utf32HeaderLE, StringComparison.Ordinal)) is not (-1 or 3)) // 3 because utf32HeaderBE also matches here. + { + PdfSharpLogHost.Logger.LogWarning("XMP metadata is encoded using UTF-32 little endian, which is not allowed in PDF files, but accepted by PDFsharp."); + + if (pos > 0) + rawString = rawString[pos..]; + + const int leftPartLength = 4 * 17; + const int rightPartStart = leftPartLength + 4; + char leftQuot = rawString[leftPartLength - 4]; + char rightQuot = rawString[rightPartStart]; + if (leftQuot is '\"' or '\'' && rawString[leftPartLength..rightPartStart] == utf32BomLE && rightQuot is '\"' or '\'') + { + // Remove UTF-32LE BOM. + rawString = rawString[..leftPartLength] + rawString[rightPartStart..]; + } + else + throw new InvalidOperationException("An UTF-32 encoded metadata stream has an invalid BOM entry."); + + // Get bytes from stream without BOM. + bytes = PdfEncoders.RawEncoding.GetBytes(rawString); + result = Encoding.UTF32.GetString(bytes); + // Insert a readable encoding name. + result = result[..17] + MetadataEncodingType.UTF32LE + result[17..]; + } + else if ((pos = rawString.IndexOf(utf32HeaderBE, StringComparison.Ordinal)) != -1) + { + PdfSharpLogHost.Logger.LogWarning( + "XMP metadata is encoded using UTF-32 big endian, which is not allowed in PDF files, but accepted by PDFsharp."); + + if (pos > 0) + rawString = rawString[pos..]; + + const int leftPartLength = 4 * 17 + 3; + const int rightPartStart = leftPartLength + 4; + char leftQuot = rawString[leftPartLength - 4]; + char rightQuot = rawString[rightPartStart]; + if (leftQuot is '\"' or '\'' && + rawString[(leftPartLength - 3)..(rightPartStart - 3)] == utf32BomBE && + rightQuot is '\"' or '\'') + { + // Remove UTF-32BE BOM. + rawString = rawString[..(leftPartLength - 3)] + rawString[(rightPartStart - 3)..]; + } + else + throw new InvalidOperationException( + "An UTF-32 big-endian encoded metadata stream has an invalid BOM entry."); + + // Get bytes from stream without BOM. + bytes = PdfEncoders.RawEncoding.GetBytes(rawString); + result = Utf32BigEndianEncoding.GetString(bytes); + // Insert a readable encoding name. + result = result[..17] + MetadataEncodingType.UTF32BE + result[17..]; + } + else + { + // We cannot determine the encoding and leave it as is. + result = rawString; + } + + return result; + } + + public static byte[] GetBytes(string xml) + { + if (!xml.StartsWith(header)) + throw new InvalidOperationException("An XMP metadata string must start with '" + header + "'."); + + const int leftPartLength = 17; + var rightPartStart = xml.IndexOf("\"", leftPartLength, StringComparison.Ordinal); + if (rightPartStart == -1) + rightPartStart = xml.IndexOf("'", leftPartLength + 1, StringComparison.Ordinal); + if (xml[leftPartLength - 1] is not ('\"' or '\'') || xml[rightPartStart] is not ('\"' or '\'') || rightPartStart < leftPartLength) + throw new InvalidOperationException("An XMP metadata string has an invalid header."); + + // Get BOM tag and remove it. + var bomTag = xml[leftPartLength..rightPartStart]; + xml = xml[..leftPartLength] + xml[rightPartStart..]; + MetadataEncodingType encoding = MetadataEncodingType.UTF8; + if (bomTag.Length > 0) + { + if (!Enum.TryParse(bomTag, false, out encoding)) + throw new InvalidOperationException($"Unknown metadata encoding tag '{bomTag}'."); + } + + if (encoding != MetadataEncodingType.UTF8) + { + PdfSharpLogHost.Logger.LogError("XMP metadata must not be encoded using {Encoding} in a PDF file.", encoding); + } + byte[] bytes; + switch (encoding) + { + case MetadataEncodingType.UTF8: + bytes = Encoding.UTF8.GetBytes(xml); + xml = PdfEncoders.RawEncoding.GetString(bytes); + xml = xml[..leftPartLength] + utf8Bom + xml[leftPartLength..]; + break; + + case MetadataEncodingType.UTF16LE: + bytes = Encoding.Unicode.GetBytes(xml); + xml = PdfEncoders.RawEncoding.GetString(bytes); + var pos = 2 * 17; + xml = xml[..pos] + utf16BomLE + xml[pos..]; + break; + + case MetadataEncodingType.UTF16BE: + bytes = Encoding.BigEndianUnicode.GetBytes(xml); + xml = PdfEncoders.RawEncoding.GetString(bytes); + xml = xml[..(2 * 17)] + utf16BomBE + xml[(2 * 17)..]; + break; + + case MetadataEncodingType.UTF32LE: + bytes = Encoding.UTF32.GetBytes(xml); + xml = PdfEncoders.RawEncoding.GetString(bytes); + xml = xml[..(4 * 17)] + utf32BomLE + xml[(4 * 17)..]; + break; + + case MetadataEncodingType.UTF32BE: + bytes = Utf32BigEndianEncoding.GetBytes(xml); + xml = PdfEncoders.RawEncoding.GetString(bytes); + xml = xml[..(4 * 17)] + utf32BomBE + xml[(4 * 17)..]; + break; + + default: + throw new InvalidOperationException($"Unknown metadata encoding tag '{bomTag}'."); + } + bytes = PdfEncoders.RawEncoding.GetBytes(xml); + return bytes; + } + + // Is only instantiated in some unit tests. + static UTF32Encoding Utf32BigEndianEncoding => _utf32BigEndianEncoding ??= new(true, false); + static UTF32Encoding? _utf32BigEndianEncoding; + } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs index 9cbc9cd2..2d749616 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfName.cs @@ -1,23 +1,34 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using PdfSharp.Internal; using PdfSharp.Pdf.IO; +// v7.0.0 Ready + namespace PdfSharp.Pdf { /// - /// Represents a PDF name value. + /// Represents a direct PDF name object. /// [DebuggerDisplay("({" + nameof(Value) + "})")] - public sealed class PdfName : PdfItem + public sealed class PdfName : PdfPrimitive { + // Reference 2.0: 7.3.5 Name objects / Page 27 + /// /// Initializes a new instance of the class. /// public PdfName() { - Value = "/"; // Empty name. + Name = Name.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + public PdfName(Name name) + { + Name = name; } /// @@ -26,12 +37,7 @@ public PdfName() /// public PdfName(string value) { - if (value == null) - throw new ArgumentNullException(nameof(value)); - if (value.Length == 0 || value[0] != '/') - throw new ArgumentException(PsMsgs.NameMustStartWithSlash); - - Value = value; + Name = Name.FromCanonicalName(value); } /// @@ -40,19 +46,24 @@ public PdfName(string value) public override bool Equals(object? obj) { if (obj is PdfName pdfName) - return Value.Equals(pdfName.Value); + return Name.Equals(pdfName.Name); return Value.Equals(obj); } /// /// Returns the hash code for this instance. /// - public override int GetHashCode() => Value.GetHashCode(); + public override int GetHashCode() => Name.GetHashCode(); + + /// + /// Gets the name as a canonical name string. + /// + public string Value => Name.Value; /// - /// Gets the name as a string. + /// Gets the underlying Name object. /// - public string Value { get; } + public Name Name { get; } /// /// Returns the name. The string always begins with a slash. @@ -62,7 +73,7 @@ public override bool Equals(object? obj) /// /// Determines whether the specified name and string are equal. /// - public static bool operator ==(PdfName? name, string? str) // BUG_OLD TODO_OLD check all operator == + public static bool operator ==(PdfName? name, string? str) { if (name is null) return str is null; @@ -79,32 +90,26 @@ public override bool Equals(object? obj) /// /// Gets an empty name. /// - public static PdfName Empty => new("/"); + public static PdfName Empty => _empty ??= new(); + + static PdfName? _empty; /// - /// Adds the slash to a string, that is needed at the beginning of a PDFName string. + /// Adds the slash that is needed at the beginning of a PDFName. /// - public static string AddSlash(string value) // TODO_OLD PDFsharp6: Naming. StL: WithSlash? - { - if (value.Length == 0) - return "/"; - - return value[0] != '/' ? $"/{value}" : value; - } + [Obsolete("Use Name.MakeName")] + public static string AddSlash(string value) + => Name.MakeName(value); /// - /// Removes the slash from a string, that is needed at the beginning of a PDFName string. + /// Removes the slash that is needed at the beginning of a PDFName. /// - public static string RemoveSlash(string value) // TODO_OLD PDFsharp6: Naming. StL: WithoutSlash? - { - if (value.Length == 0 || value[0] != '/') - return value; - - return value[1..]; - } + [Obsolete("Use Name.RemoveSlash")] + public static string RemoveSlash(string value) + => Name.RemoveSlash(value); /// - /// Gets a PdfName form a string. The string must not start with a slash. + /// Gets a PdfName from a string. The string must not start with a slash. /// public static PdfName FromString(string value) { @@ -120,26 +125,20 @@ public static PdfName FromString(string value) /// /// Writes the name including the leading slash. /// - internal override void WriteObject(PdfWriter writer) - { - // TODO_OLD: what if Unicode character are part of the name? - // TODO_OLD: 7.3.5 Name objects: "In such situations, the sequence of bytes making up the name - // object should be interpreted according to UTF-8, a variable-length byte-encoded - // representation of Unicode in which the printable ASCII characters have the same - // representations as in ASCII. This enables a name object to represent text virtually - // in any natural language, subject to the implementation limit on the length of a name." - writer.Write(this); - } + internal override void WriteObject(PdfWriter writer) + => writer.Write(this); /// /// Gets the comparer for this type. /// - public static PdfXNameComparer Comparer => new PdfXNameComparer(); + public static PdfNameComparer Comparer => _nameComparer ??= new(); + + static PdfNameComparer? _nameComparer; /// /// Implements a comparer that compares PdfName objects. /// - public class PdfXNameComparer : IComparer + public class PdfNameComparer : IComparer { /// /// Compares two objects and returns a value indicating whether one is less than, equal to, or greater than the other. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameObject.cs index ba9c51c1..25827633 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameObject.cs @@ -1,24 +1,42 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; using PdfSharp.Pdf.IO; namespace PdfSharp.Pdf { /// /// Represents an indirect name value. This type is not used by PDFsharp. If it is imported from - /// an external PDF file, the value is converted into a direct object. Acrobat sometime uses indirect + /// an external PDF file, the value is converted into a direct object. Acrobat sometimes uses indirect /// names to save space, because an indirect reference to a name may be shorter than a long name. /// [DebuggerDisplay("({" + nameof(Value) + "})")] - public sealed class PdfNameObject : PdfObject + public sealed class PdfNameObject : PdfPrimitiveObject { /// /// Initializes a new instance of the class. /// public PdfNameObject() { - Value = "/"; // Empty name. + Name = Name.Empty; + } + + /// + /// Initializes a new instance of the class. + /// + public PdfNameObject(Name name) + { + Name = name; + } + + /// + /// Initializes a new instance of the class. + /// + /// The value. + public PdfNameObject(string value) + { + Name = value; } /// @@ -27,41 +45,54 @@ public PdfNameObject() /// The document. /// The value. public PdfNameObject(PdfDocument document, string value) - : base(document) + : base(document, true) { - if (value == null) - throw new ArgumentNullException(nameof(value)); - if (value.Length == 0 || value[0] != '/') - throw new ArgumentException(PsMsgs.NameMustStartWithSlash); + Name = value; + } - Value = value; + /// + /// Initializes a new instance of the class + /// without making it indirect. + /// Used in PDF parser only. + /// + /// The document. + /// The initial value. + /// If true creates an indirect object. + internal PdfNameObject(PdfDocument document, Name name, bool createIndirect) + : base(document, createIndirect) + { + Name = name; } /// /// Determines whether the specified object is equal to the current object. /// - public override bool Equals(object? obj) - => Value.Equals(obj); + public override bool Equals(object? obj) + { + if (obj is PdfName pdfName) + return Name.Equals(pdfName.Name); + return Value.Equals(obj); + } /// /// Serves as a hash function for this type. /// - public override int GetHashCode() - => Value.GetHashCode(); + public override int GetHashCode() => Name.GetHashCode(); + + /// + /// Gets the name as a canonical name string. + /// + public string Value => Name.Value; /// - /// Gets or sets the name value. + /// Gets the underlying Name object. /// - public string Value { get; set; } + public Name Name { get; } /// /// Returns the name. The string always begins with a slash. /// - public override string ToString() - { - // TODO_OLD: Encode characters. - return Value; - } + public override string ToString() => Value; /// /// Determines whether a name is equal to a string. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameTreeNode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameTreeNode.cs index 3b131c1d..0bf5e5fe 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameTreeNode.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNameTreeNode.cs @@ -1,18 +1,26 @@ // 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.Advanced; using PdfSharp.Pdf.IO; +using System; +using System.Reflection; + +// ReSharper disable UnusedMember.Global // TODO +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member namespace PdfSharp.Pdf { /// - /// Represents a name tree node. + /// Represents a node in a name tree. /// [DebuggerDisplay("({" + nameof(DebuggerDisplay) + "})")] - public sealed class PdfNameTreeNode : PdfDictionary + public class PdfNameTreeNode : PdfDictionary { - // Reference: 3.8.5 Name Trees / Page 161 + // Reference 1.7: 3.8.5 Name Trees / Page 161 + // Reference 2.0: 7.9.6 Name trees / Page 119 /// /// Initializes a new instance of the class. @@ -21,9 +29,10 @@ public PdfNameTreeNode() { } /// - /// Initializes a new instance of the class. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// - public PdfNameTreeNode(PdfDictionary dict) + internal PdfNameTreeNode(PdfDictionary dict) : base(dict) { Initialize(); @@ -37,9 +46,37 @@ public PdfNameTreeNode(PdfDictionary dict) /// /// Gets a value indicating whether this instance is a root node. /// - public bool IsRoot + public bool IsRoot => Parent == null; + + public bool IsLeaf => Parent != null && Kids == null; + + public bool IsIntermediate => Parent != null && Kids != null; + + public PdfNameTreeKids? Kids + { + get + { + var kids = Elements.GetValue(Keys.Kids); + return kids; + } + } + + public PdfNameTreeNames? Names { - get => Parent == null; + get + { + var names = Elements.GetValue(Keys.Names); + return names; + } + } + + public PdfNameTreeLimits? Limits + { + get + { + var limits = Elements.GetValue(Keys.Limits); + return limits; + } } /// @@ -62,7 +99,7 @@ public int NamesCount get { var names = Elements.GetArray(Keys.Names); - // Entries are key / value pairs, so divide by 2. + // Entries are key-value pairs, so divide by 2. return names != null ? names.Elements.Count / 2 : 0; } } @@ -72,15 +109,60 @@ public int NamesCount /// public int NamesCountTotal => GetNames(true).Count; - /// - /// Gets the kids of this item. - /// - public IEnumerable Kids => _kids; + /////// + /////// Gets the kids of this item. + /////// + ////public IEnumerable Kids => _kids; - private readonly List _kids = new(); + ////private readonly List _kids = new(); - private void Initialize() + void Initialize() { +#if true + var kids = Elements.GetValue(Keys.Kids); + if (kids != null) + { + if (kids is PdfArray array) + { + kids = new PdfNameTreeKids(array); + Elements[Keys.Kids] = kids; + } + else + { + throw new InvalidOperationException("Value of name tree key 'Kids' is not of type PdfArray."); + } + } + + var names = Elements.GetValue(Keys.Names); + if (names != null) + { + if (names is PdfArray array) + { + if (names is not PdfNameTreeNames) + names = new PdfNameTreeNames(array); + Elements[Keys.Names] = names; + } + else + { + throw new InvalidOperationException("Value of name tree key 'Names' is not of type PdfArray."); + } + } + + var limits = Elements.GetValue(Keys.Limits); + if (limits != null) + { + if (limits is PdfArray array) + { + if (limits is not PdfNameTreeNames) + limits = new PdfNameTreeNames(array); + Elements[Keys.Limits] = limits; + } + else + { + throw new InvalidOperationException("Value of name tree key 'Limits' is not of type PdfArray."); + } + } +#else var kids = Elements.GetArray(Keys.Kids); if (kids != null) { @@ -96,13 +178,15 @@ private void Initialize() } _updateRequired = true; UpdateLimits(); +#endif } + /// /// Gets the list of names this node contains /// /// Specifies whether the names of the kids should also be returned /// The list of names this node contains - /// Note: When kids are included, the names are not guaranteed to be sorted + /// Note that if kids are included, the names are not guaranteed to be sorted public IReadOnlyList GetNames(bool includeKids = false) { var result = new List(); @@ -116,9 +200,14 @@ public IReadOnlyList GetNames(bool includeKids = false) } if (includeKids) { - foreach (var kid in _kids) + var item = Elements.GetValue(Keys.Kids); + if (item is PdfNumberTreeKids kids) { - result.AddRange(kid.GetNames(true)); + foreach (var kid in kids) + { + // result.AddRange(kid.GetNames(true)); + //TODO + } } } return result; @@ -142,11 +231,11 @@ public bool ContainsName(string name, bool includeKids = false) } if (includeKids) { - foreach (var kid in _kids) - { - if (!kid.ContainsName(name, true)) - return true; - } + //foreach (var kid in _kids) + //{ + // if (!kid.ContainsName(name, true)) + // return true; + //} } return false; } @@ -156,7 +245,7 @@ public bool ContainsName(string name, bool includeKids = false) /// /// The name whose value should be retrieved /// Specifies whether the kids should also be searched - /// The value for when found, otwerwise null + /// The value for when found, otherwise null public PdfItem? GetValue(string name, bool includeKids = false) { var names = Elements.GetArray(Keys.Names); @@ -173,12 +262,12 @@ public bool ContainsName(string name, bool includeKids = false) } if (includeKids) { - foreach (var kid in _kids) - { - var value = kid.GetValue(name, true); - if (value != null) - return value; - } + //foreach (var kid in _kids) + //{ + // var value = kid.GetValue(name, true); + // if (value != null) + // return value; + //} } return null; } @@ -200,7 +289,7 @@ public void AddKid(PdfNameTreeNode kidNode) } /// - /// Adds a key/value pair to the Names array of this node. + /// Adds a key-value pair to the Names array of this node. /// public void AddName(string key, PdfItem value) { @@ -214,12 +303,13 @@ public void AddName(string key, PdfItem value) // Insert names sorted by key. int i = 0; while (i < names.Elements.Count && string.CompareOrdinal(names.Elements.GetString(i), key) < 0) - // Entries are key / value pairs, so add 2. + { + // Entries are key-value pairs, so add 2. i += 2; - + } names.Elements.Insert(i, new PdfString(key)); names.Elements.Insert(i + 1, value); - _updateRequired = true; + _updateRequired = true; // TODO } /// @@ -230,10 +320,10 @@ public string LeastKey get { UpdateLimits(); - return _leastKey; + return _leastKey_; } } - private string _leastKey = "?"; + string _leastKey_ = "?"; /// /// Gets the greatest key. @@ -246,7 +336,7 @@ public string GreatestKey return _greatestKey; } } - private string _greatestKey = "?"; + string _greatestKey = "?"; /// /// Updates the limits by inspecting Kids and Names. @@ -259,15 +349,14 @@ void UpdateLimits() names.Sort(StringComparer.Ordinal); if (names.Count > 0) { - _leastKey = names[0]; + _leastKey_ = names[0]; _greatestKey = names[^1]; Elements[Keys.Limits] = new PdfArray(Owner, - new PdfString(_leastKey), new PdfString(_greatestKey)); + new PdfString(_leastKey_), new PdfString(_greatestKey)); } _updateRequired = false; } } - bool _updateRequired; internal override void PrepareForSave() @@ -283,19 +372,10 @@ internal override void WriteObject(PdfWriter writer) base.WriteObject(writer); } - ///// - ///// Returns the value in the PDF date format. - ///// - //public override string ToString() - //{ - // string delta = _value.ToString("zzz").Replace(':', '\''); - // return String.Format("D:{0:yyyyMMddHHmmss}{1}'", _value, delta); - //} - /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { // Reference: TABLE 3.33 Entries in a name tree node dictionary / Page 162 @@ -331,7 +411,7 @@ internal sealed class Keys : KeysBase /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; @@ -350,7 +430,208 @@ internal sealed class Keys : KeysBase // ReSharper disable UnusedMember.Local string DebuggerDisplay // ReSharper restore UnusedMember.Local - => - String.Format("root:{0}", IsRoot); + => $"root:{IsRoot}"; + } + + // TODO + static class PdfNameTreeNodeHelper + { + public static void BuildNameTree(PdfNameTreeNode node) + { + var sp = node.ParentInfo; + + //PdfEmbeddedFileStream + } } + + #region Name tree stuff / #NewFile + + public class PdfNameTreeKids : PdfArray + { + public PdfNameTreeKids() + { } + + public PdfNameTreeKids(PdfArray array) + : base(array) + { } + + public void Insert(PdfNameTreeNode node) + { + // TODO + } + } + + public class PdfNameTreeNames : PdfArray + { + public PdfNameTreeNames() + { } + + public PdfNameTreeNames(PdfArray array) + : base(array) + { + //foreach (var item in array) + //{ } + } + + public int Count => Elements.Count / 2; + + public NameTreeNameEntry this[int index] + { + get + { + index *= 2; + var key = Elements.GetString(index); + var item = Elements[index + 1]; + var result = new NameTreeNameEntry(key, item); + return result; + } + } + + public PdfItem? this[string key] + { + get + { + var count = Elements.Count; + for (int idx = 0; idx < count - 1; idx += 2) + { + var k = Elements.GetString(idx); + if (key == k) + return Elements[idx + 1]; + } + return null; + } + } + + public string[] NamesKeys + { + get + { + var count = Elements.Count; + count >>= 1; // Suppress odd numbers. + if (count == 0) + return []; + + var keys = new string[count]; + for (int idx = 0; idx < count; idx++) + { + keys[idx] = Elements.GetString(idx * 2); + } + return keys; + } + } + + internal void TransformItems() + { + int count = Elements.Count; + if (count == 0) + return; + if (count % 2 != 0) + throw new InvalidOperationException("Number of elements in a name tree /Names array must be even."); + + for (int idx = 1; idx < count; idx++) + { + var item = Elements[idx]; + //Debug.Assert(item.GetType()==typeof()); + } + } + + public T TransformType(PdfItem item) + { + return default(T)!; + } + + public void Insert(NameTreeNameEntry nameEntry) + { + string key = nameEntry.Key.Value; + + Elements.Insert(0, nameEntry.Key); + Elements.Insert(1, nameEntry.Value); + UpdateLimits(); + } + + void UpdateLimits() + { + if (ParentInfo != null) + { + } + } + } + + // Pair of PDF string and PDF item. + public class NameTreeNameEntry + { + public NameTreeNameEntry() + { } + + public NameTreeNameEntry(PdfString key, PdfItem item) + { + Key = key; + Value = item; + } + + public NameTreeNameEntry(string key, PdfItem item) + { + Key = new(key); + Value = item; + } + + public PdfString Key { get; set; } = PdfString.Empty; + + public PdfItem Value { get; set; } = PdfNull.Value; + + + } + + public class PdfNameTreeLimits : PdfArray + { + public PdfNameTreeLimits() + { + Elements.Add(new PdfString()); + Elements.Add(new PdfString()); + } + + protected PdfNameTreeLimits(PdfArray array) + : base(array) + { + EnsureSize(); + } + + public string First + { + get => Elements.GetString(0); + set => Elements[0] = new PdfString(value); + } + + public string Last + { + get => Elements.GetString(1); + set => Elements[1] = new PdfString(value); + } + + void EnsureSize() + { + if (Elements.Count != 2) + { + PdfSharpLogHost.Logger.LogError("NameTreeLimits must have 2 elements."); + } + } + + } + + public class PdfNumberTreeKids : PdfArray + { + // TODO + } + + public class PdfNumberTreeNames : PdfArray + { + // TODO + } + + public class PdfNumberTreeLimits : PdfArray + { + // TODO + } + + #endregion } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNull.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNull.cs index f401cd4a..127e3ce7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNull.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNull.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using PdfSharp.Pdf.IO; @@ -6,12 +6,15 @@ namespace PdfSharp.Pdf { /// - /// Represents an indirect reference that is not in the cross-reference table. + /// Represents a direct reference that is not in the cross-reference table. /// - public sealed class PdfNull : PdfItem + public sealed class PdfNull : PdfPrimitive { // Reference: 3.2.8 Null Object / Page 63 + /// + /// Use PdfNull.Value to get an instance of this class. + /// PdfNull() { } @@ -23,11 +26,12 @@ public sealed class PdfNull : PdfItem /// public override string ToString() => "null"; + /// + /// Writes ‘null’. + /// + internal override void WriteObject(PdfWriter writer) - { - // Implemented because it must be overridden. - writer.WriteRaw(" null "); - } + => writer.Write(this); /// /// The only instance of this class. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNullObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNullObject.cs index 6993e8d6..107569aa 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNullObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNullObject.cs @@ -9,7 +9,8 @@ namespace PdfSharp.Pdf /// Represents an indirect null value. This type is not used by PDFsharp, but at least /// one tool from Adobe creates PDF files with a null object. /// - public sealed class PdfNullObject : PdfObject + [DebuggerDisplay("({NullObject})")] + public sealed class PdfNullObject : PdfPrimitiveObject { // Reference: 3.2.8 Null Object / Page 63 @@ -24,7 +25,18 @@ public PdfNullObject() /// /// The document. public PdfNullObject(PdfDocument document) - : base(document) + : base(document, true) + { } + + /// + /// Initializes a new instance of the class + /// without making it indirect. + /// Used in PDF parser only. + /// + /// The document. + /// If true creates an indirect object. + internal PdfNullObject(PdfDocument document, bool createIndirect) + : base(document, createIndirect) { } /// @@ -33,12 +45,12 @@ public PdfNullObject(PdfDocument document) public override string ToString() => "null"; /// - /// Writes the keyword «null». + /// Writes the keyword ‘null’. /// internal override void WriteObject(PdfWriter writer) { writer.WriteBeginObject(this); - writer.WriteRaw(" null "); + writer.Write(PdfNull.Value); writer.WriteEndObject(); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumber.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumber.cs index d76c6321..87199b24 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumber.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumber.cs @@ -4,9 +4,9 @@ namespace PdfSharp.Pdf { /// - /// Base class for direct number values (not yet used, maybe superfluous). + /// Base class for direct number values. /// - public abstract class PdfNumber : PdfItem + public abstract class PdfNumber : PdfPrimitive { /// /// Gets or sets a value indicating whether this instance is a 32-bit signed integer. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumberObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumberObject.cs index cbec98a9..070e6ebd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumberObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumberObject.cs @@ -1,12 +1,12 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. namespace PdfSharp.Pdf { /// - /// Base class for indirect number values (not yet used, maybe superfluous). + /// Base class for indirect number values. /// - public abstract class PdfNumberObject : PdfObject + public abstract class PdfNumberObject : PdfPrimitiveObject { /// /// Initializes a new instance of the class. @@ -19,7 +19,33 @@ protected PdfNumberObject() /// /// The document. protected PdfNumberObject(PdfDocument document) - : base(document) + : base(document, true) { } + + /// + /// Initializes a new instance of the class + /// without making it indirect. + /// Used in PDF parser only. + /// + /// The document. + /// If true creates an indirect object. + protected internal PdfNumberObject(PdfDocument document, bool createIndirect) + : base(document, createIndirect) + { } + + /// + /// Gets a value indicating whether this instance is a 32-bit signed integer. + /// + public bool IsInteger { get; protected set; } + + /// + /// Gets a value indicating whether this instance is a 64-bit signed integer. + /// + public bool IsLongInteger { get; protected set; } + + /// + /// Gets a value indicating whether this instance is a floating point number. + /// + public bool IsReal { get; protected set; } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumberTreeNode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumberTreeNode.cs index a6252b48..70631f3d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumberTreeNode.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfNumberTreeNode.cs @@ -24,6 +24,14 @@ public PdfNumberTreeNode(bool isRoot = false) IsRoot = isRoot; } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfNumberTreeNode(PdfDictionary dict) + : base(dict) + { } + /// /// Gets a value indicating whether this instance is a root node. /// @@ -49,7 +57,7 @@ public int NumsCount get { var names = Elements.GetArray(Keys.Nums); - // Entries are key / value pairs, so divide by 2. + // Entries are key-value pairs, so divide by 2. return names != null ? names.Elements.Count / 2 : 0; } } @@ -70,7 +78,7 @@ public void AddKid(PdfNumberTreeNode kidNode) } /// - /// Adds a key/value pair to the Nums array of this node. + /// Adds a key-value pair to the Nums array of this node. /// public void AddNumber(int key, PdfObject value) { @@ -134,7 +142,7 @@ internal override void WriteObject(PdfWriter writer) /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { // Reference: TABLE 3.34 Entries in a number tree node dictionary / Page 166 @@ -170,7 +178,7 @@ internal sealed class Keys : KeysBase /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObject.cs index a7e80ba6..b4835fbe 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObject.cs @@ -1,13 +1,14 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.IO; namespace PdfSharp.Pdf { /// - /// Base class of all composite PDF objects. + /// Base class of all PDF objects that can be used indirectly. /// public abstract class PdfObject : PdfItem { @@ -15,37 +16,57 @@ public abstract class PdfObject : PdfItem /// Initializes a new instance of the class. /// protected PdfObject() - { } + { + ItemFlags = ItemFlags.IsPrimitiveItem; + } /// /// Initializes a new instance of the class. /// - protected PdfObject(PdfDocument document) + protected PdfObject(PdfDocument document, bool createIndirect = false) { // Calling a virtual member in a constructor is dangerous. - // In PDFsharp Document is overridden in PdfPage and the code is checked to be save + // In PDFsharp Document is overridden in PdfPage and the code is checked to be safe // when called for a not completely initialized object. // ReSharper disable once VirtualMemberCallInConstructor Document = document; + ItemFlags = ItemFlags.IsCompoundObject; + if (createIndirect) + document.IrefTable.Add(this); } /// /// Initializes a new instance from an existing object. Used for object type transformation. /// protected PdfObject(PdfObject obj) - : this(obj.Owner) + : base(obj) { + // ReSharper disable once VirtualMemberCallInConstructor + Document = obj.Owner; + // If the object that was transformed to an instance of a derived class was an indirect object // set the value of the reference to this. if (obj._iref != null) + { + Debug.Assert(obj.ParentInfo == null); + // Case: Indirect object is transformed. obj._iref.Value = this; -#if DEBUG_ // BUG_OLD + //TODO: obj._iref = null; ???? Write tests for this case. + } +#if DEBUG // TODO: Really debug? A transformed direct object must conserve the structure parent - test this. else { - // If this occurs it is an internal error - Debug.Assert(false, "Object type transformation must not be done with direct objects"); + // Case: A direct object like a PdfDictionary is transformed to a derived type. + _ = typeof(int); + + // This old text is wrong: + //// If this occurs it is an internal error + //Debug.Assert(false, "Object type transformation must not be done with direct objects"); } #endif + // ParentInfo keeps null and is set in SetValueInternal. + + ItemFlags = ItemFlags.IsCompoundObject; } /// @@ -58,9 +79,19 @@ protected PdfObject(PdfObject obj) /// protected override object Copy() { + // Create a shallow copy. var obj = (PdfObject)base.Copy(); + + // Here we do not know the new owner document. obj._document = null!; + + // If we are an indirect object, the caller must set the correct PdfReference + // for this object. obj._iref = null; + + // If we are a direct object, the caller must set the correct new ParentInfo. + obj._parentInfo = null; + return obj; } @@ -104,16 +135,13 @@ internal void SetObjectID(int objectNumber, int generationNumber) { var objectID = new PdfObjectID(objectNumber, generationNumber); - // TODO_OLD: check imported _iref ??= _document.IrefTable[objectID]; if (_iref == null) { - // ReSharper disable once ObjectCreationAsStatement because the new object is set to this object + // Re/Sharper disable once ObjectCreationAsStatement because the new object is set to this object. // in the constructor of PdfReference. - //new PdfReference(this); PdfReference.CreateFromObject(this, objectID, 0); Debug.Assert(_iref != null); - //_iref.ObjectID = objectID; } _iref.Value = this; _iref.Document = _document; @@ -124,24 +152,84 @@ internal void SetObjectID(int objectNumber, int generationNumber) /// public virtual PdfDocument Owner => _document; + internal PdfDocument OwningDocument + { + get + { + PdfDocument owner; + if (_document != null!) + owner = _document; + else if (ParentInfo != null) + owner = ParentInfo.OwningElements.OwningContainer.Owner; + else + throw new InvalidOperationException(SyMsgs.ObjectWithoutOwner.Message); + return owner; + } + } + /// /// Sets the PdfDocument this object belongs to. /// - internal virtual PdfDocument Document + public virtual PdfDocument Document { - set + get => _document; + internal set { if (!ReferenceEquals(_document, value)) { if (_document != null) - throw new InvalidOperationException("Cannot change document if it was set."); + throw new InvalidOperationException("Cannot change document if it was once set."); _document = value; + _document2 = value; if (_iref != null) _iref.Document = value; } } } - internal PdfDocument _document = default!; + PdfDocument _document = null!; + internal PdfDocument _document2 = null!; // TODO: Remove after testing + + /// + /// Gets the ParentInfo if this object is a direct PDF object. + /// + internal ParentInfo? ParentInfo + { + get => _parentInfo; + } + ParentInfo? _parentInfo; + + /// + /// Sets the ParentInfo if the owning container is a PDF array. + /// + /// The array elements that owns this PDF object. + /// The index within the owning PDF array. + /// + internal void SetStructureParent(PdfArray.ArrayElements elements, int index) + { + if (_parentInfo != null) + throw new InvalidOperationException("StructureParent already set."); + _parentInfo = new(elements, index); + } + + /// + /// Sets the ParentInfo if the owning container is a PDF dictionary. + /// + /// The dictionary elements that owns this PDF object. + /// The key within the owning PDF dictionary. + /// + internal void SetStructureParent(PdfDictionary.DictionaryElements elements, string key) + { + if (_parentInfo != null) + throw new InvalidOperationException("StructureParent already set."); + _parentInfo = new(elements, key); + } + + internal void SetStructureParentNull() + { + if (_parentInfo == null) + throw new InvalidOperationException("StructureParent already null."); + _parentInfo = null; + } /// /// Gets or sets the comment for debugging purposes. @@ -151,7 +239,7 @@ internal virtual PdfDocument Document /// /// Indicates whether the object is an indirect object. /// - public bool IsIndirect => _iref != null; + public bool IsIndirect => _iref is not null; /// /// Gets the PdfInternals object of this document, that grants access to some internal structures @@ -171,7 +259,7 @@ internal virtual void PrepareForSave() /// Saves the stream position. 2nd Edition. /// internal override void WriteObject(PdfWriter writer) - => Debug.Assert(false, "Must not come here, WriteObject must be overridden in derived class."); + => Debug.Assert(false, "Must not come here, WriteObject must be overridden in all derived classes."); /// /// Gets the object identifier. Returns PdfObjectID.Empty for direct objects, @@ -199,52 +287,72 @@ internal override void WriteObject(PdfWriter writer) internal static PdfObject DeepCopyClosure(PdfDocument owner, PdfObject externalObject) { // Get transitive closure. - PdfObject[] elements = externalObject.Owner.Internals.GetClosure(externalObject); - int count = elements.Length; -#if DEBUG_ + var objects = externalObject.Owner.Internals.GetClosure(externalObject); + int count = objects.Length; +#if DEBUG for (int idx = 0; idx < count; idx++) { - Debug.Assert(elements[idx].XRef != null); - Debug.Assert(elements[idx].XRef.Document != null); - Debug.Assert(elements[idx].Document != null); - if (elements[idx].ObjectID.ObjectNumber == 12) + var obj = objects[idx]; + if (obj.Reference == null) + { _ = typeof(int); + continue; // HACK + } + + // TODO: Assertion fails with + //const string Pdf = @"D:/repos/empira/PDFsharp.Tests/assets/user/23-11-01-marionojp-{6FBD7268-26BC-4D71-AE5A-6B3144505CAF}/i-130.pdf"; + Debug.Assert(obj.Reference != null); + Debug.Assert(obj.Reference!.Document != null); + Debug.Assert(obj.Owner != null); + Debug.Assert(obj.ParentInfo != null); + //if (objects[idx].ObjectID.ObjectNumber == 12) + // _ = typeof(int); } #endif // 1st loop. Replace all objects by their clones. - var iot = new PdfImportedObjectTable(owner, externalObject.Owner); + var importedObjectTable = new PdfImportedObjectTable(owner, externalObject.Owner); for (int idx = 0; idx < count; idx++) { - var obj = elements[idx]; + var obj = objects[idx]; var clone = obj.Clone(); Debug.Assert(clone.Reference == null); clone.Document = owner; if (obj.Reference != null) { // Case: The cloned object was an indirect object. + // Add clone to new owner document. owner.IrefTable.Add(clone); + // The clone gets an iref by adding it to its new owner. Debug.Assert(clone.Reference != null); + // Save an association from old object identifier to new iref. - iot.Add(obj.ObjectID, clone.Reference); + importedObjectTable.Add(obj.ObjectID, clone.Reference); } else { - // Case: The cloned object was an direct object. + // Case: The cloned object was a direct object. // Only the root object can be a direct object. Debug.Assert(idx == 0); } // Replace external object by its clone. - elements[idx] = clone; + objects[idx] = clone; } -#if DEBUG_ +#if DEBUG for (int idx = 0; idx < count; idx++) { - Debug.Assert(elements[idx]._iref != null); - Debug.Assert(elements[idx]._iref.Document != null); - Debug.Assert(resources[idx].Document != null); - if (elements[idx].ObjectID.ObjectNumber == 12) + var obj = objects[idx]; + if (obj.Reference == null) + { + _ = typeof(int); + continue; // HACK + } + + Debug.Assert(obj.Reference != null); + Debug.Assert(obj.Reference!.Document != null); + Debug.Assert(obj.Owner != null); + if (obj.ObjectID.ObjectNumber == 12) _ = typeof(int); } #endif @@ -252,13 +360,13 @@ internal static PdfObject DeepCopyClosure(PdfDocument owner, PdfObject externalO // 2nd loop. Fix up all indirect references that still refer to the import document. for (int idx = 0; idx < count; idx++) { - var obj = elements[idx]; + var obj = objects[idx]; Debug.Assert(obj.Owner == owner); - FixUpObject(iot, owner, obj); + FixUpObject(importedObjectTable, owner, obj); } // Return the clone of the former root object. - return elements[0]; + return objects[0]; } ///// @@ -275,22 +383,29 @@ internal static PdfObject ImportClosure(PdfImportedObjectTable importedObjectTab "The ExternalDocument of the importedObjectTable does not belong to the owner of object to be imported."); // Get transitive closure of external object. - PdfObject[] elements = externalObject.Owner.Internals.GetClosure(externalObject); - int count = elements.Length; -#if DEBUG_ + PdfObject[] objects = externalObject.Owner.Internals.GetClosure(externalObject); + int count = objects.Length; +#if DEBUG for (int idx = 0; idx < count; idx++) { - Debug.Assert(elements[idx].XRef != null); - Debug.Assert(elements[idx].XRef.Document != null); - Debug.Assert(elements[idx].Document != null); - if (elements[idx].ObjectID.ObjectNumber == 12) + if (idx != 0) + { + //Debug.Assert(objects[idx].Reference != null); + //Debug.Assert(objects[idx].Reference!.Document != null); + var iref = objects[idx].Reference; + Debug.Assert(iref != null); + Debug.Assert(iref!.Document != null); + } + + Debug.Assert(objects[idx].Owner != null); + if (objects[idx].ObjectID.ObjectNumber == 12) _ = typeof(int); } #endif // 1st loop. Already imported objects are reused and new ones are cloned. for (int idx = 0; idx < count; idx++) { - PdfObject obj = elements[idx]; + var obj = objects[idx]; Debug.Assert(!ReferenceEquals(obj.Owner, owner)); if (importedObjectTable.Contains(obj.ObjectID)) @@ -300,22 +415,29 @@ internal static PdfObject ImportClosure(PdfImportedObjectTable importedObjectTab _ = typeof(int); #endif // Case: External object was already imported. - PdfReference iref = importedObjectTable[obj.ObjectID]; - Debug.Assert(iref != null); - Debug.Assert(iref.Value != null); - Debug.Assert(iref.Document == owner); + + PdfReference reference = importedObjectTable[obj.ObjectID]; + Debug.Assert(reference != null); + Debug.Assert(reference.Value != null); + Debug.Assert(reference.Document == owner); // Replace external object by the already cloned counterpart. - elements[idx] = iref.Value; + objects[idx] = reference.Value; } else { // Case: External object was not yet imported earlier and must be cloned. + +#if DEBUG_ + if (idx == 6) + _ = typeof(int); +#endif var clone = obj.Clone(); Debug.Assert(clone.Reference == null); clone.Document = owner; if (obj.Reference != null) { // Case: The cloned object was an indirect object. + // Add clone to new owner document. owner.IrefTable.Add(clone); Debug.Assert(clone.Reference != null); @@ -325,20 +447,22 @@ internal static PdfObject ImportClosure(PdfImportedObjectTable importedObjectTab else { // Case: The cloned object was a direct object. + // Only the root object can be a direct object. Debug.Assert(idx == 0); } // Replace external object by its clone. - elements[idx] = clone; + objects[idx] = clone; } } -#if DEBUG_ +#if DEBUG for (int idx = 0; idx < count; idx++) { - //Debug.Assert(elements[idx].Reference != null); - //Debug.Assert(elements[idx].Reference.Document != null); - Debug.Assert(elements[idx].IsIndirect == false); - Debug.Assert(elements[idx].Owner != null); + if (idx != 0) + { + Debug.Assert(objects[idx].IsIndirect == true); + } + Debug.Assert(objects[idx].Owner != null); //if (elements[idx].ObjectID.ObjectNumber == 12) // _ = typeof(int); } @@ -346,13 +470,13 @@ internal static PdfObject ImportClosure(PdfImportedObjectTable importedObjectTab // 2nd loop. Fix up indirect references that still refers to the external document. for (int idx = 0; idx < count; idx++) { - var obj = elements[idx]; + var obj = objects[idx]; Debug.Assert(owner != null); FixUpObject(importedObjectTable, importedObjectTable.Owner, obj); } // Return the imported root object. - return elements[0]; + return objects[0]; } /// @@ -363,11 +487,13 @@ static void FixUpObject(PdfImportedObjectTable iot, PdfDocument owner, PdfObject { Debug.Assert(ReferenceEquals(iot.Owner, owner)); - PdfDictionary? dict; - PdfArray? array; - if ((dict = value as PdfDictionary) is not null) + //PdfDictionary? dict; + //PdfArray? array; + //if ((dict = value as PdfDictionary) is not null) + if (value is PdfDictionary dict) { // Case: The object is a dictionary. + // Set document for cloned direct objects. if (dict.Owner == null!) { @@ -384,13 +510,14 @@ static void FixUpObject(PdfImportedObjectTable iot, PdfDocument owner, PdfObject var names = dict.Elements.KeyNames; foreach (var name in names) { - var item = dict.Elements[name]; + var item = dict.Elements[name]; // Special treatment for References below. // TODO #US373 Debug.Assert(item != null, "A dictionary element cannot be null."); // Is item an iref? if (item is PdfReference iref) { // Case: The item is a reference. + // Does the iref already belong to the new owner? if (iref.Document == owner) { @@ -400,7 +527,8 @@ static void FixUpObject(PdfImportedObjectTable iot, PdfDocument owner, PdfObject //Debug.Assert(iref.Document == iot.Document); // No: Replace with iref of cloned object. - var newXRef = iot[iref.ObjectID]; // TODO_OLD: Explain this line of code in all details. + // The iot maps the external ID to its internal PdfReference. + var newXRef = iot[iref.ObjectID]; Debug.Assert(newXRef != null); Debug.Assert(newXRef.Document == owner); dict.Elements[name] = newXRef; @@ -408,6 +536,7 @@ static void FixUpObject(PdfImportedObjectTable iot, PdfDocument owner, PdfObject else { // Case: The item is not a reference. + // If item is an object recursively fix its inner items. if (item is PdfObject pdfObject) { @@ -425,7 +554,8 @@ static void FixUpObject(PdfImportedObjectTable iot, PdfDocument owner, PdfObject } } } - else if ((array = value as PdfArray) is not null) + //else if ((array = value as PdfArray) is not null) + else if (value is PdfArray array) { // Case: The object is an array. // Set document for cloned direct objects. @@ -458,9 +588,10 @@ static void FixUpObject(PdfImportedObjectTable iot, PdfDocument owner, PdfObject continue; } + //Debug.Assert(iref.Document == iot.ExternalDocument); // No: replace with iref of cloned object. - Debug.Assert(iref.Document == iot.ExternalDocument); - PdfReference newXRef = iot[iref.ObjectID]; + // The iot maps the external ID to its internal PdfReference. + var newXRef = iot[iref.ObjectID]; Debug.Assert(newXRef != null); Debug.Assert(newXRef.Document == owner); array.Elements[idx] = newXRef; @@ -491,7 +622,7 @@ static void FixUpObject(PdfImportedObjectTable iot, PdfDocument owner, PdfObject // Indirect integers, booleans, etc. are allowed, but PDFsharp do not create them. // If such objects occur in imported PDF files from other producers, nothing more is to do. // The owner was already set, which is double-checked by the assertions below. - if (value is PdfNameObject or PdfStringObject or PdfBooleanObject or PdfNumberObject) + if (value is PdfNumberObject or PdfNameObject or PdfStringObject or PdfBooleanObject or PdfNumberObject or PdfNullObject) { Debug.Assert(value.IsIndirect); Debug.Assert(value.Owner == owner); @@ -510,11 +641,9 @@ static void DebugCheckNonObjects(PdfItem item) { switch (item) { + case PdfNumber: // Includes PdfInteger and PdfLongInteger. case PdfName: case PdfBoolean: - //case PdfInteger: - //case PdfLongInteger: - case PdfNumber: case PdfString: case PdfRectangle: case PdfNull: @@ -547,7 +676,21 @@ public PdfReference? Reference /// Gets the indirect reference of this object. Throws if it is null. /// /// The indirect reference must be not null here. - public PdfReference ReferenceNotNull // TODO_OLD: Name in need of improvement. + public PdfReference RequiredReference => _iref ?? throw new InvalidOperationException("The indirect reference must be not null here."); + + /// + /// Gets the indirect reference of this object. Throws if it is null. + /// + /// The indirect reference must be not null here. + [Obsolete("Use RequiredReference.")] + public PdfReference ReferenceNotNull + => RequiredReference; + + /// + /// Gets the actual reference. + /// This function exists only for PdfWidgetAnnotations that are a part of an AcroField. + /// + internal virtual PdfReference? ActualReference => _iref; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs index ba975027..717c5bb2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfObjectID.cs @@ -13,21 +13,8 @@ namespace PdfSharp.Pdf // ReSharper disable once InconsistentNaming public readonly struct PdfObjectID : IComparable { - //// /// - //// /// Initializes a new instance of the class. - //// /// - //// /// The object number. - //// public PdfObjectID(int objectNumber) - //// { - //// Debug.Assert(objectNumber >= 1, "Object number out of range."); - //// _objectNumber = objectNumber; - //// _generationNumber = 0; - ////#if DEBUG_ - //// // Just a place for a breakpoint during debugging. - //// if (objectNumber == 5894) - //// _ = typeof(int); - ////#endif - //// } + internal const int MaxObjectNumber = 0x_7F_FF_FF; // 23 binary digits. + internal const int MaxGenerationNumber = 0x_FF_FF; // 16 binary digits. /// /// Initializes a new instance of the class. @@ -39,14 +26,14 @@ public PdfObjectID(int objectNumber, int generationNumber = 0) Debug.Assert(objectNumber >= 1, "Object number out of range."); //Debug.Assert(generationNumber >= 0 && generationNumber <= 65535, "Generation number out of range."); - if (objectNumber is < 1 or > 0x_7F_FF_FF) + if (objectNumber is < 1 or > MaxObjectNumber) { // We do not break existing code. - PdfSharpLogHost.PdfReadingLogger.LogError("Object number '{ObjectNumber}' is out of range [1..8388608].", objectNumber); + PdfSharpLogHost.PdfReadingLogger.LogError("Object number '{ObjectNumber}' is out of range [1..8388607].", objectNumber); // No high-performance logging because it is a rare case. } - if (generationNumber is <0 or > 0x_FF_FF) + if (generationNumber is < 0 or > MaxGenerationNumber) { // We do not break existing code. // We found an iText document with generation numbers with a value of 65536... @@ -55,14 +42,12 @@ public PdfObjectID(int objectNumber, int generationNumber = 0) } _objectNumber = objectNumber; - _generationNumber = (ushort)generationNumber; + _generationNumber = generationNumber; + // Make number easy to read. + UnifiedNumber = (ulong)objectNumber * 100000 + (uint)generationNumber; + //UnifiedNumber = ((ulong)objectNumber << 32) + (uint)generationNumber; } - /// - /// Calculates a 64-bit unsigned integer from object and generation number. - /// - internal ulong UniqueNumber => ((ulong)_objectNumber << 32) + _generationNumber; - /// /// Gets or sets the object number. /// @@ -75,10 +60,16 @@ public PdfObjectID(int objectNumber, int generationNumber = 0) /// public int GenerationNumber => _generationNumber; - readonly ushort _generationNumber; + readonly int _generationNumber; // Not ushort anymore because there are PDF files with large generation numbers. + + /// + /// Calculates a single 64-bit unsigned integer from object and generation number. + /// + public ulong UnifiedNumber { get; } /// /// Indicates whether this object is an empty object identifier. + /// This is the case if the object number is 0. /// public bool IsEmpty => _objectNumber == 0; @@ -89,8 +80,9 @@ public override bool Equals(object? obj) { if (obj is PdfObjectID id) { - if (_objectNumber == id._objectNumber) - return _generationNumber == id._generationNumber; + //if (_objectNumber == id._objectNumber) + // return _generationNumber == id._generationNumber; + return UnifiedNumber == id.UnifiedNumber; } return false; } @@ -99,7 +91,7 @@ public override bool Equals(object? obj) /// Returns the hash code for this instance. /// public override int GetHashCode() - => _objectNumber ^ _generationNumber; + => _objectNumber ^ (_generationNumber << 23); /// /// Determines whether the two objects are equal. @@ -117,11 +109,11 @@ public override int GetHashCode() /// Returns the object and generation numbers as a string. /// public override string ToString() - //return _objectNumber.ToString(CultureInfo.InvariantCulture) + " " + _generationNumber.ToString(CultureInfo.InvariantCulture); => Invariant($"{_objectNumber} {_generationNumber}"); /// /// Creates an empty object identifier. + /// Direct PDF objects have an empty ID, i.e. both numbers are zero. /// public static PdfObjectID Empty => new(); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs index 2518ea38..9f7ba767 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs @@ -1,9 +1,6 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -// Review: Under construction - StL/14-10-05 - -using System.Diagnostics.CodeAnalysis; using System.Text; using PdfSharp.Drawing; using PdfSharp.Pdf.Actions; @@ -11,9 +8,6 @@ using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Internal; -#pragma warning disable IDE0056 -#pragma warning disable IDE0057 - namespace PdfSharp.Pdf { /// @@ -44,9 +38,10 @@ internal PdfOutline(PdfDocument document) } /// - /// Initializes a new instance from an existing dictionary. Used for object type transformation. + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. /// - public PdfOutline(PdfDictionary dict) + internal PdfOutline(PdfDictionary dict) : base(dict) { Initialize(); @@ -139,7 +134,7 @@ public PdfOutline Parent get => _parent!; // ?? NRT.ThrowOnNull(); Can be null. internal set => _parent = value; } - PdfOutline? _parent; + PdfOutline? _parent; // TODO: No more caching fields. /// /// Gets or sets the title. @@ -158,43 +153,42 @@ public string Title /// Gets or sets the destination page. /// Can be null if destination page is not given directly. /// - [MaybeNull] - public PdfPage DestinationPage + public PdfPage? DestinationPage { - get => _destinationPage; + get => _destinationPage; /*?? NRT.ThrowOnNull(); // BUG*/ set => _destinationPage = value; } PdfPage? _destinationPage; /// - /// Gets or sets the left position of the page positioned at the left side of the window. + /// Gets or sets the left position of the page positioned on the left side of the window. /// Applies only if PageDestinationType is Xyz, FitV, FitR, or FitBV. /// public double? Left { get; set; } /// - /// Gets or sets the top position of the page positioned at the top side of the window. + /// Gets or sets the top position of the page positioned on the top side of the window. /// Applies only if PageDestinationType is Xyz, FitH, FitR, ob FitBH. /// public double? Top { get; set; } /// - /// Gets or sets the right position of the page positioned at the right side of the window. + /// Gets or sets the right position of the page positioned on the right side of the window. /// Applies only if PageDestinationType is FitR. /// public double Right // Cannot be null in a valid PDF. { get; set; - } = double.NaN; + } = Double.NaN; /// - /// Gets or sets the bottom position of the page positioned at the bottom side of the window. + /// Gets or sets the bottom position of the page positioned on the bottom side of the window. /// Applies only if PageDestinationType is FitR. /// public double Bottom // Cannot be null in a valid PDF. { get; set; - } = double.NaN; + } = Double.NaN; /// /// Gets or sets the zoom faction of the page. @@ -213,28 +207,7 @@ public double? Zoom public bool Opened { get => _opened; -#if true set => _opened = value; -#else - // TODO_OLD: adjust openCount of ascendant... - set - { - if (_opened != value) - { - _opened = value; - int sign = value ? 1 : -1; - PdfOutline parent = _parent; - if (_opened) - { - while (parent != null) - parent.openCount += 1 + _openCount; - } - else - { - } - } - } -#endif } bool _opened; @@ -296,7 +269,7 @@ void Initialize() Count = Elements.GetInteger(Keys.Count); var colors = Elements.GetArray(Keys.C); - if (colors != null && colors.Elements.Count == 3) + if (colors is { Elements.Count: 3 }) { double r = colors.Elements.GetReal(0); double g = colors.Elements.GetReal(1); @@ -304,44 +277,71 @@ void Initialize() TextColor = XColor.FromArgb((int)(r * 255), (int)(g * 255), (int)(b * 255)); } - // Style directly works on dictionary element. - + // /Dest and /A should be mutual exclusive. + // But it is valid that they are not. This was updated in PDF 2.0. var dest = Elements.GetValue(Keys.Dest); var a = Elements.GetValue(Keys.A); - PdfArray? destArray; + //PdfArray? destArray; if (dest != null) { +#if true + if (dest is PdfArray destArray) + { + ParsePageDestination(destArray); + } + else if (dest is PdfString str) + { + // Destination may be in a different PDF file, so we cannot determine the target page. + } + else if (dest is PdfName name) + { + } + else + { + Debug.Assert(false, "See what to do when this happened."); + } +#else destArray = dest as PdfArray; if (destArray != null) { - SplitDestinationPage(destArray); + ParsePageDestination(destArray); goto Done; } else { - Debug.Assert(false, "See what to do when this happened."); + // Do nothing here. + // Debug.Assert(false, "See what to do when this happened."); // TODO } +#endif } if (a != null) { + // Case: If we found an /A entry we use it if it is a /GoTo action to get the destination. + // The dictionary should be a GoTo action. if (a is PdfDictionary action && action.Elements.GetName(PdfAction.Keys.S) == "/GoTo") { - dest = action.Elements[PdfGoToAction.Keys.D]; - destArray = dest as PdfArray; - if (destArray != null) + //dest = action.Elements[PdfGoToAction.Keys.D]; // #US373: Should we expect references here? + dest = action.Elements.GetValue(PdfGoToAction.Keys.D); // #US373 + if (dest is PdfArray array) { - SplitDestinationPage(destArray); + //// Replace Action with /Dest entry. + //Elements.Remove(Keys.A); + //Elements.Add(Keys.Dest, array); + ParsePageDestination(array); goto Done; } if (dest is PdfString namedDestination) { - // look in Destinations and name-tree - if (Owner.Catalog.Destinations.Contains(namedDestination.Value)) + PdfArray? destArray = null; + // Look in Destinations and name-tree. + // GetDestinations() can be null. + if (Owner.Catalog.GetDestinations()?.Contains(namedDestination.Value) ?? false) { - destArray = Owner.Catalog.Destinations.GetDestination(namedDestination.Value); + // IMPROVE: Avoid "Contains()" and call "GetDestination()" only once. + destArray = Owner.Catalog.GetDestinations()!.GetDestination(namedDestination.Value); } else if (Owner.Catalog.Names.NameTree != null) { @@ -358,12 +358,13 @@ void Initialize() } if (destArray != null) { - SplitDestinationPage(destArray); + ParsePageDestination(destArray); + goto Done; } } else { - //throw new Exception("Destination Array or Name expected."); + // throw new Exception("Destination Array or Name expected."); // #KeepA } } else @@ -380,7 +381,7 @@ void Initialize() InitializeChildren(); } - void SplitDestinationPage(PdfArray destination) // Reference: 8.2 Destination syntax / Page 582 + void ParsePageDestination(PdfArray destination) // Reference: 8.2 Destination syntax / Page 582 { // The destination page may not yet have been transformed to PdfPage. var destPage = (PdfDictionary)((PdfReference)destination.Elements[0]).Value; @@ -388,14 +389,14 @@ void SplitDestinationPage(PdfArray destination) // Reference: 8.2 Destination page = new PdfPage(destPage); DestinationPage = page; + var itemCount = destination.Elements.Count; if (destination.Elements[1] is PdfName type) { -#pragma warning disable CA1846 PageDestinationType = (PdfPageDestinationType)Enum.Parse(typeof(PdfPageDestinationType), type.Value.Substring(1), true); -#pragma warning restore CA1846 switch (PageDestinationType) { // [page /XYZ left top zoom] -- left, top, and zoom can be null. + // TODO Crashes with entries like "/Dest [ 5 0 R /XYZ ]". case PdfPageDestinationType.Xyz: Left = destination.Elements.GetNullableReal(2); Top = destination.Elements.GetNullableReal(3); @@ -486,8 +487,8 @@ internal override void PrepareForSave() // Case: This is the outline dictionary (the root). // Reference: TABLE 8.3 Entries in the outline dictionary / Page 585 Debug.Assert(_outlines is { Count: > 0 } && _outlines[0] != null); - Elements[Keys.First] = _outlines[0].Reference; - Elements[Keys.Last] = _outlines[_outlines.Count - 1].Reference; + Elements[Keys.First] = _outlines[0].RequiredReference; + Elements[Keys.Last] = _outlines[_outlines.Count - 1].RequiredReference; // TODO_OLD: /Count - the meaning is not completely clear to me. // Get PDFs created with Acrobat and analyze what to implement. @@ -498,7 +499,7 @@ internal override void PrepareForSave() { // Case: This is an outline item dictionary. // Reference: TABLE 8.4 Entries in the outline item dictionary / Page 585 - Elements[Keys.Parent] = _parent.Reference; + Elements[Keys.Parent] = _parent.RequiredReference; int count = _parent!._outlines!.Count; int index = _parent._outlines.IndexOf(this); @@ -510,23 +511,23 @@ internal override void PrepareForSave() // Not the first element? if (index > 0) - Elements[Keys.Prev] = _parent._outlines[index - 1].Reference; + Elements[Keys.Prev] = _parent._outlines[index - 1].RequiredReference; // Not the last element? if (index < count - 1) - Elements[Keys.Next] = _parent._outlines[index + 1].Reference; + Elements[Keys.Next] = _parent._outlines[index + 1].RequiredReference; if (hasKids && _outlines != null) { - Elements[Keys.First] = _outlines[0].Reference; - Elements[Keys.Last] = _outlines[_outlines.Count - 1].Reference; + Elements[Keys.First] = _outlines[0].RequiredReference; + Elements[Keys.Last] = _outlines[_outlines.Count - 1].RequiredReference; } // TODO_OLD: /Count - the meaning is not completely clear to me if (OpenCount > 0) Elements[Keys.Count] = new PdfInteger((_opened ? 1 : -1) * OpenCount); if (TextColor != XColor.Empty && Owner.HasVersion("1.4")) - Elements[Keys.C] = new PdfLiteral("[{0}]", PdfEncoders.ToString(TextColor, PdfColorMode.Rgb)); + Elements[Keys.C] = new PdfLiteral("[{0}]", PdfEncoders.ToString(TextColor, PdfColorMode.Rgb)); // #US309 // if (Style != PdfOutlineStyle.Regular && Document.HasVersion("1.4")) // //pdf.AppendFormat("/F {0}\n", (int)_style); @@ -544,71 +545,81 @@ internal override void PrepareForSave() PdfArray CreateDestArray() { + // Here I learned that “0.ToString("#.##")” evaluates to the empty string. + + var pageRef = DestinationPage?.Reference; + if (pageRef == null) + throw new InvalidOperationException("Cannot create PdfArray because this Outline has no destination page."); + // Only called if DestinationPage is not null. - PdfArray? dest = PageDestinationType switch + PdfArray? dest = PageDestinationType switch // TODO: Do not use PdfLiteral here. // #US309 { // [page /XYZ left top zoom] - PdfPageDestinationType.Xyz => new PdfArray(Owner, DestinationPage!.ReferenceNotNull, - new PdfLiteral($"/XYZ {Fd(Left)} {Fd(Top)} {Fd(Zoom)}")), + PdfPageDestinationType.Xyz => new(Owner, pageRef, + new PdfLiteral(Invariant($"/XYZ {Fd2(Left)} {Fd2(Top)} {Fd2(Zoom)}"))), // [page /Fit] - PdfPageDestinationType.Fit => new PdfArray(Owner, DestinationPage!.ReferenceNotNull, + PdfPageDestinationType.Fit => new PdfArray(Owner, DestinationPage!.RequiredReference, new PdfLiteral("/Fit")), // [page /FitH top] - PdfPageDestinationType.FitH => new PdfArray(Owner, DestinationPage!.ReferenceNotNull, - new PdfLiteral($"/FitH {Fd(Top)}")), + PdfPageDestinationType.FitH => new PdfArray(Owner, DestinationPage!.RequiredReference, + new PdfLiteral(Invariant($"/FitH {Fd2(Top)}"))), // [page /FitV left] - PdfPageDestinationType.FitV => new PdfArray(Owner, DestinationPage!.ReferenceNotNull, - new PdfLiteral($"/FitV {Fd(Left)}")), + PdfPageDestinationType.FitV => new PdfArray(Owner, DestinationPage!.RequiredReference, + new PdfLiteral(Invariant($"/FitV {Fd2(Left)}"))), // [page /FitR left bottom right top] - PdfPageDestinationType.FitR => new PdfArray(Owner, DestinationPage!.ReferenceNotNull, - new PdfLiteral($"/FitR {Fd(Left)} {Fd(Bottom)} {Fd(Right)} {Fd(Top)}")), + PdfPageDestinationType.FitR => new PdfArray(Owner, DestinationPage!.RequiredReference, + new PdfLiteral(Invariant($"/FitR {Fd2(Left)} {Fd1(Bottom)} {Fd1(Right)} {Fd2(Top)}"))), // [page /FitB] - PdfPageDestinationType.FitB => new PdfArray(Owner, DestinationPage!.ReferenceNotNull, + PdfPageDestinationType.FitB => new PdfArray(Owner, DestinationPage!.RequiredReference, new PdfLiteral("/FitB")), // [page /FitBH top] - PdfPageDestinationType.FitBH => new PdfArray(Owner, DestinationPage!.ReferenceNotNull, - new PdfLiteral($"/FitBH {Fd(Top)}")), + PdfPageDestinationType.FitBH => new PdfArray(Owner, DestinationPage!.RequiredReference, + new PdfLiteral(Invariant($"/FitBH {Fd2(Top)}"))), // [page /FitBV left] - PdfPageDestinationType.FitBV => new PdfArray(Owner, DestinationPage!.ReferenceNotNull, - new PdfLiteral($"/FitBV {Fd(Left)}")), + PdfPageDestinationType.FitBV => new PdfArray(Owner, DestinationPage!.RequiredReference, + new PdfLiteral(Invariant($"/FitBV {Fd2(Left)}"))), _ => throw new ArgumentOutOfRangeException() }; return dest; - } - - /// - /// Format double. - /// - static string Fd(double value) - { - if (Double.IsNaN(value)) - throw new InvalidOperationException("Value is not a valid Double."); - return value.ToString("0.##", CultureInfo.InvariantCulture); - //return Double.IsNaN(value) ? "null" : value.ToString("0.##", CultureInfo.InvariantCulture); - } + // /// + // /// Format double. + // /// + static string Fd1(double value) + { + if (Double.IsNaN(value)) + throw new InvalidOperationException("Value is not a valid Double."); + return value.ToString("0.###", CultureInfo.InvariantCulture); + } - /// - /// Format nullable double. - /// - static string Fd(double? value) - { - return value.HasValue ? value.Value.ToString("0.##", CultureInfo.InvariantCulture) : "null"; + // /// + // /// Format nullable double. + // /// + static string Fd2(double? value) + { + return value.HasValue ? value.Value.ToString("0.###", CultureInfo.InvariantCulture) : "null"; + } } internal override void WriteObject(PdfWriter writer) { -#if DEBUG - writer.WriteRaw("% Title = " + FilterUnicode(Title) + "\n"); -#endif + if (writer.IsVerboseLayout) + { + var title = Title; + if (String.IsNullOrEmpty(title)) + writer.WriteRaw("% Title: \u008bempty\u009b\n"); + else + writer.WriteRaw("% Title: \u0093" + FilterUnicode(Title) + "\u0094\n"); + } + // TODO_OLD: Proof that there is nothing to do here. bool hasKids = HasChildren; if (_parent != null || hasKids) @@ -624,22 +635,20 @@ internal override void WriteObject(PdfWriter writer) ////} base.WriteObject(writer); } - } -#if DEBUG - static string FilterUnicode(string text) - { - var result = new StringBuilder(); - foreach (char ch in text) - result.Append((uint)ch < 256 ? (ch != '\r' && ch != '\n' ? ch : ' ') : '?'); - return result.ToString(); + static string FilterUnicode(string text) + { + var result = new StringBuilder(); + foreach (char ch in text) + result.Append((uint)ch < 256 ? (ch != '\r' && ch != '\n' ? ch : ' ') : '?'); + return result.ToString(); + } } -#endif /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { // ReSharper disable InconsistentNaming @@ -739,7 +748,7 @@ internal sealed class Keys : KeysBase /// /// (Optional; PDF 1.3; must be an indirect reference) The structure element to which the item /// refers. - /// Note: The ability to associate an outline item with a structure element (such as the beginning + /// Note the ability to associate an outline item with a structure element (such as the beginning /// of a chapter) is a PDF 1.3 feature. For backward compatibility with earlier PDF versions, such /// an item should also specify a destination (Dest) corresponding to an area of a page where the /// contents of the designated structure element are displayed. @@ -765,7 +774,7 @@ internal sealed class Keys : KeysBase /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutlineCollection.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutlineCollection.cs index 8bac640a..c171dafe 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutlineCollection.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutlineCollection.cs @@ -1,9 +1,8 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; -using System.Collections.Generic; using System.Collections; +using PdfSharp.Internal; using PdfSharp.Drawing; // Review: CountOpen does not work. - StL/14-10-05 @@ -15,6 +14,7 @@ namespace PdfSharp.Pdf /// public class PdfOutlineCollection : PdfObject, ICollection, IList { + // TODO: Derive from PdfArray? /// /// Can only be created as part of PdfOutline. /// @@ -293,6 +293,6 @@ void RemoveFromOutlinesTree(PdfOutline outline) /// readonly PdfOutline _parent; - readonly List _outlines = new List(); + readonly List _outlines = []; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs index 9d451f45..023a0d4b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs @@ -1,22 +1,27 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.ComponentModel; using Microsoft.Extensions.Logging; -using PdfSharp.Pdf.IO; using PdfSharp.Drawing; +#if CORE +using PdfSharp.Internal.Imaging; +#endif using PdfSharp.Logging; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Annotations; -using InvalidOperationException = System.InvalidOperationException; +using PdfSharp.Pdf.IO; +using System.ComponentModel; + +// v7.0.0 TODO review, TODO Keys 2.0 namespace PdfSharp.Pdf { /// /// Represents a page in a PDF document. /// - public sealed class PdfPage : PdfDictionary, IContentStream + public sealed class PdfPage : PdfPageTreeBase, IContentStream { + // 7.7.3.3 Page objects /// /// Initializes a new page. The page must be added to a document before it can be used. /// Depending on the IsMetric property of the current region, the page size is set to @@ -37,10 +42,14 @@ public PdfPage(PdfDocument document) : base(document) { Elements.SetName(Keys.Type, "/Page"); - Elements[Keys.Parent] = document.Pages.Reference; + Elements[Keys.Parent] = document.Pages.RequiredReference; Initialize(false); } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfPage(PdfDictionary dict) : base(dict) { @@ -53,7 +62,7 @@ internal void Initialize(bool setupSizeFromMediaBox) { // Setup page size from MediaBox. var rectangle = Elements.GetRectangle(InheritablePageKeys.MediaBox, false); - if (rectangle.IsZero) + if (rectangle == null) throw new InvalidOperationException("Page has no MediaBox."); _width = XUnit.FromPoint(rectangle.X2 - rectangle.X1); @@ -92,18 +101,14 @@ public void Close() /// /// Gets or sets the PdfDocument this page belongs to. /// - internal override PdfDocument Document + public override PdfDocument Document { - set + internal set { - if (!ReferenceEquals(_document, value)) + if (!ReferenceEquals(base.Document, value)) { - if (_document != null) - throw new InvalidOperationException("Cannot change document."); - _document = value; - if (Reference != null) - Reference.Document = value; - Elements[Keys.Parent] = _document.Pages.Reference; + base.Document = value; + Elements[Keys.Parent] = value.Pages.RequiredReference; } } } @@ -173,7 +178,7 @@ void UpdateOrientation() public PageSize Size { [Obsolete("Use Width and Height to get the absolute size of the page.")] - get => throw new InvalidOperationException("Cannot get PageSize. Use Width or Height instead."); + get => throw new InvalidOperationException("Cannot get PageSize. Use Width and Height instead."); set { if (!Enum.IsDefined(typeof(PageSize), value)) @@ -191,12 +196,12 @@ public TrimMargins TrimMargins { get { - _trimMargins ??= new TrimMargins(); + _trimMargins ??= new(); return _trimMargins; } set { - _trimMargins ??= new TrimMargins(); + _trimMargins ??= new(); if (value != null!) { _trimMargins.Left = value.Left; @@ -216,13 +221,10 @@ public TrimMargins TrimMargins /// public PdfRectangle MediaBox { - get => Elements.GetRectangle(InheritablePageKeys.MediaBox, true); + get => Elements.GetRequiredRectangle(InheritablePageKeys.MediaBox); set { // Can be different from 0 in case of imported PDF. - //if (value is not { X1: 0, Y1: 0 }) - // throw new ArgumentException("MediaBox origin cannot be other than (0,0)."); - _width = XUnit.FromPoint(value.X2 - value.X1); _height = XUnit.FromPoint(value.Y2 - value.Y1); UpdateOrientation(); @@ -233,43 +235,40 @@ public PdfRectangle MediaBox /// /// Gets a value indicating whether a media box is set. /// - public bool HasMediaBox => Elements[InheritablePageKeys.MediaBox] != null; + public bool HasMediaBox => Elements.HasValue(InheritablePageKeys.MediaBox); /// - /// Gets a copy of the media box if it exists, or PdfRectangle.Empty if no media box is set. + /// Gets a copy of the media box if it exists, or an empty PdfRectangle if no media box is set. /// - public PdfRectangle MediaBoxReadOnly => Elements.GetRectangle(InheritablePageKeys.MediaBox, false); + [Obsolete("Use properties HasMediaBox and MediaBox.")] + public PdfRectangle MediaBoxReadOnly => Elements.GetRequiredRectangle(InheritablePageKeys.MediaBox, false, new()); /// /// Gets or sets the crop box. /// public PdfRectangle CropBox { - get => Elements.GetRectangle(InheritablePageKeys.CropBox, true); + get => Elements.GetRequiredRectangle(InheritablePageKeys.CropBox); set => Elements.SetRectangle(InheritablePageKeys.CropBox, value); } /// /// Gets a value indicating whether a crop box is set. /// - public bool HasCropBox => Elements[InheritablePageKeys.CropBox] != null; + public bool HasCropBox => Elements.HasValue(InheritablePageKeys.CropBox); /// - /// Gets a copy of the crop box if it exists, or PdfRectangle.Empty if no crop box is set. + /// Gets a copy of the crop box if it exists, or an empty PdfRectangle if no crop box is set. /// - public PdfRectangle CropBoxReadOnly => Elements.GetRectangle(InheritablePageKeys.CropBox, false); + [Obsolete("Use properties HasCropBox and CropBox.")] + public PdfRectangle CropBoxReadOnly => Elements.GetRequiredRectangle(InheritablePageKeys.CropBox, false, new()); /// - /// Gets a copy of the effective crop box if it exists, or PdfRectangle.Empty if neither crop box nor media box are set. + /// Gets a copy of the effective crop box if it exists, or an empty PdfRectangle if neither crop box nor media box are set. /// public PdfRectangle EffectiveCropBoxReadOnly { - get - { - if (HasCropBox) - return CropBox; - return MediaBoxReadOnly; - } + get => HasCropBox ? CropBox : MediaBox; } /// @@ -277,22 +276,24 @@ public PdfRectangle EffectiveCropBoxReadOnly /// public PdfRectangle BleedBox { - get => Elements.GetRectangle(Keys.BleedBox, true); + get => Elements.GetRequiredRectangle(Keys.BleedBox); set => Elements.SetRectangle(Keys.BleedBox, value); } /// /// Gets a value indicating whether a bleed box is set. /// - public bool HasBleedBox => Elements[Keys.BleedBox] != null; + public bool HasBleedBox => Elements.HasValue(Keys.BleedBox); /// - /// Gets a copy of the bleed box if it exists, or PdfRectangle.Empty if no bleed box is set. + /// Gets a copy of the bleed box if it exists, or an empty PdfRectangle if no bleed box is set. /// - public PdfRectangle BleedBoxReadOnly => Elements.GetRectangle(Keys.BleedBox, false); + [Obsolete("Use properties HasBleedBox and BleedBox.")] + + public PdfRectangle BleedBoxReadOnly => Elements.GetRequiredRectangle(Keys.BleedBox, false, new()); /// - /// Gets a copy of the effective bleed box if it exists, or PdfRectangle.Empty if neither bleed box nor crop box nor media box are set. + /// Gets a copy of the effective bleed box if it exists, or an empty PdfRectangle if neither bleed box nor crop box nor media box are set. /// public PdfRectangle EffectiveBleedBoxReadOnly { @@ -309,22 +310,23 @@ public PdfRectangle EffectiveBleedBoxReadOnly /// public PdfRectangle ArtBox { - get => Elements.GetRectangle(Keys.ArtBox, true); + get => Elements.GetRequiredRectangle(Keys.ArtBox); set => Elements.SetRectangle(Keys.ArtBox, value); } /// /// Gets a value indicating whether an art box is set. /// - public bool HasArtBox => Elements[Keys.ArtBox] != null; + public bool HasArtBox => Elements.HasValue(Keys.ArtBox); /// - /// Gets a copy of the art box if it exists, or PdfRectangle.Empty if no art box is set. + /// Gets a copy of the art box if it exists, or an empty PdfRectangle if no art box is set. /// - public PdfRectangle ArtBoxReadOnly => Elements.GetRectangle(Keys.ArtBox, false); + [Obsolete("Use properties HasArtBox and ArtBox.")] + public PdfRectangle ArtBoxReadOnly => Elements.GetRequiredRectangle(Keys.ArtBox, false, new()); /// - /// Gets a copy of the effective art box if it exists, or PdfRectangle.Empty if neither art box nor crop box nor media box are set. + /// Gets a copy of the effective art box if it exists, or an empty PdfRectangle if neither art box nor crop box nor media box are set. /// public PdfRectangle EffectiveArtBoxReadOnly { @@ -341,22 +343,24 @@ public PdfRectangle EffectiveArtBoxReadOnly /// public PdfRectangle TrimBox { - get => Elements.GetRectangle(Keys.TrimBox, true); + get => Elements.GetRequiredRectangle(Keys.TrimBox); set => Elements.SetRectangle(Keys.TrimBox, value); } /// /// Gets a value indicating whether a trim box is set. /// - public bool HasTrimBox => Elements[Keys.TrimBox] != null; + public bool HasTrimBox => Elements.HasValue(Keys.TrimBox); /// - /// Gets a copy of the trim box if it exists, or PdfRectangle.Empty if no trim box is set. + /// Gets a copy of the trim box if it exists, or an empty PdfRectangle if no trim box is set. /// - public PdfRectangle TrimBoxReadOnly => Elements.GetRectangle(Keys.TrimBox, false); + [Obsolete("Use properties HasTrimBox and TrimBox.")] + + public PdfRectangle TrimBoxReadOnly => Elements.GetRequiredRectangle(Keys.TrimBox, false, new()); /// - /// Gets a copy of the effective trim box if it exists, or PdfRectangle.Empty if neither trim box nor crop box nor media box are set. + /// Gets a copy of the effective trim box if it exists, or an empty PdfRectangle if neither trim box nor crop box nor media box are set. /// public PdfRectangle EffectiveTrimBoxReadOnly { @@ -378,7 +382,7 @@ public XUnit Height #if DEBUG get { - var rectangle = Elements.GetRectangle(InheritablePageKeys.MediaBox, true); + var rectangle = Elements.GetRequiredRectangle(InheritablePageKeys.MediaBox); var height = XUnit.FromPoint(rectangle.Y2 - rectangle.Y1); Debug.Assert(height == _height); return _height; @@ -407,7 +411,7 @@ public XUnit Width #if DEBUG get { - var rectangle = Elements.GetRectangle(InheritablePageKeys.MediaBox, true); + var rectangle = Elements.GetRequiredRectangle(InheritablePageKeys.MediaBox); var width = XUnit.FromPoint(rectangle.X2 - rectangle.X1); Debug.Assert(width == _width); return _width; @@ -426,6 +430,27 @@ public XUnit Width } XUnit _width; + /// + /// Gets or sets the height of the page in point. + /// If the page width is less than or equal to page height, the orientation is Portrait; + /// otherwise Landscape. + /// + public double PointHeight + { + get => _height.Point; + set => Height = XUnit.FromPoint(value); // Use property to update orientation. + } + + /// + /// Gets or sets the width of the page in point. + /// If the page width is less than or equal to page height, the orientation is Portrait; + /// otherwise Landscape. + /// + public double PointWidth + { + get => _width.Point; + set => Width = XUnit.FromPoint(value); // Use property to update orientation. + } /// /// Gets or sets the /Rotate entry of the PDF page. The value is the number of degrees by which the page @@ -500,7 +525,8 @@ public PdfContents Contents { if (true) // || Document.IsImported) { - var item = Elements[Keys.Contents]; + //var item = Elements[Keys.Contents]; + var item = Elements.GetValue(Keys.Contents); // #US373 if (item == null) { _contents = new PdfContents(Owner); @@ -508,8 +534,8 @@ public PdfContents Contents } else { - if (item is PdfReference reference) - item = reference.Value; + //if (item is PdfReference reference) // TODO #US373 + // item = reference.Value; if (item is PdfArray array) { @@ -538,13 +564,11 @@ public PdfContents Contents // _content = new PdfContent(Document); // Document.xrefTable.Add(_content); //} - Debug.Assert(_contents.Reference == null); Elements[Keys.Contents] = _contents; } return _contents; } } - PdfContents? _contents; #region Annotations @@ -556,11 +580,7 @@ public bool HasAnnotations { get { - if (_hasAnnotations == null) - { - // Get annotations array if it exists. - _hasAnnotations = (PdfAnnotations?)Elements.GetValue(Keys.Annots) != null; - } + _hasAnnotations ??= (PdfAnnotations?)Elements.GetValue(Keys.Annots) != null; return _hasAnnotations.Value; } } @@ -573,17 +593,21 @@ public PdfAnnotations Annotations { get { - if (_annotations == null) - { - // Get or create annotations array. - _annotations = (PdfAnnotations?)Elements.GetValue(Keys.Annots, VCF.Create) ?? NRT.ThrowOnNull(); - _annotations.Page = this; - _hasAnnotations = false; - } - return _annotations; + // Get or create annotations array. + var annots = Elements.GetRequiredArray(Keys.Annots, VCF.Create); + annots.Page = this; + return annots; + //if (_annotations == null) + //{ + // // Get or create annotations array. + // _annotations = (PdfAnnotations?)Elements.GetValue(Keys.Annots, VCF.Create) ?? NRT.ThrowOnNull(); + // _annotations.Page = this; + // _hasAnnotations = false; + //} + //return _annotations; } } - PdfAnnotations? _annotations; + //PdfAnnotations? _annotations; /// /// Adds an internal document link. @@ -593,8 +617,7 @@ public PdfAnnotations Annotations /// The position in the destination page. public PdfLinkAnnotation AddDocumentLink(PdfRectangle rect, int destinationPage, XPoint? point = null) { - var annotation = PdfLinkAnnotation.CreateDocumentLink(rect, destinationPage, point); - Annotations.Add(annotation); + var annotation = PdfLinkAnnotation.CreateDocumentLink(this, rect, destinationPage, point); return annotation; } @@ -605,8 +628,7 @@ public PdfLinkAnnotation AddDocumentLink(PdfRectangle rect, int destinationPage, /// The Named Destination’s name. public PdfLinkAnnotation AddDocumentLink(PdfRectangle rect, string destinationName) { - var annotation = PdfLinkAnnotation.CreateDocumentLink(rect, destinationName); - Annotations.Add(annotation); + var annotation = PdfLinkAnnotation.CreateDocumentLink(this, rect, destinationName); return annotation; } @@ -619,8 +641,7 @@ public PdfLinkAnnotation AddDocumentLink(PdfRectangle rect, string destinationNa /// True, if the destination document shall be opened in a new window. If not set, the viewer application should behave in accordance with the current user preference. public PdfLinkAnnotation AddDocumentLink(PdfRectangle rect, string documentPath, string destinationName, bool? newWindow = null) { - var annotation = PdfLinkAnnotation.CreateDocumentLink(rect, documentPath, destinationName, newWindow); - Annotations.Add(annotation); + var annotation = PdfLinkAnnotation.CreateDocumentLink(this, rect, documentPath, destinationName, newWindow); return annotation; } @@ -636,8 +657,7 @@ public PdfLinkAnnotation AddDocumentLink(PdfRectangle rect, string documentPath, /// If not set, the viewer application should behave in accordance with the current user preference. public PdfLinkAnnotation AddEmbeddedDocumentLink(PdfRectangle rect, string destinationPath, bool? newWindow = null) { - var annotation = PdfLinkAnnotation.CreateEmbeddedDocumentLink(rect, destinationPath, newWindow); - Annotations.Add(annotation); + var annotation = PdfLinkAnnotation.CreateEmbeddedDocumentLink(this, rect, destinationPath, newWindow); return annotation; } @@ -654,8 +674,7 @@ public PdfLinkAnnotation AddEmbeddedDocumentLink(PdfRectangle rect, string desti /// If not set, the viewer application should behave in accordance with the current user preference. public PdfLinkAnnotation AddEmbeddedDocumentLink(PdfRectangle rect, string documentPath, string destinationPath, bool? newWindow = null) { - var annotation = PdfLinkAnnotation.CreateEmbeddedDocumentLink(rect, documentPath, destinationPath, newWindow); - Annotations.Add(annotation); + var annotation = PdfLinkAnnotation.CreateEmbeddedDocumentLink(this, rect, documentPath, destinationPath, newWindow); return annotation; } @@ -666,8 +685,7 @@ public PdfLinkAnnotation AddEmbeddedDocumentLink(PdfRectangle rect, string docum /// The URL. public PdfLinkAnnotation AddWebLink(PdfRectangle rect, string url) { - var annotation = PdfLinkAnnotation.CreateWebLink(rect, url); - Annotations.Add(annotation); + var annotation = PdfLinkAnnotation.CreateWebLink(this, rect, url); return annotation; } @@ -678,8 +696,7 @@ public PdfLinkAnnotation AddWebLink(PdfRectangle rect, string url) /// Name of the file. public PdfLinkAnnotation AddFileLink(PdfRectangle rect, string fileName) { - var annotation = PdfLinkAnnotation.CreateFileLink(rect, fileName); - Annotations.Add(annotation); + var annotation = PdfLinkAnnotation.CreateFileLink(this, rect, fileName); return annotation; } @@ -689,7 +706,6 @@ public PdfLinkAnnotation AddFileLink(PdfRectangle rect, string fileName) // TODO_OLD: PdfPageTransition - /// /// Gets or sets the custom values. /// @@ -716,10 +732,16 @@ public PdfCustomValues CustomValues /// public PdfResources Resources { - get => _resources ??= (PdfResources?)Elements.GetValue(InheritablePageKeys.Resources, VCF.Create) - ?? NRT.ThrowOnNull(); + get + { +#if true + return field ??= Elements.GetRequiredDictionary(InheritablePageKeys.Resources, VCF.Create); +#else + return _resources ??= (PdfResources?)Elements.GetValue(InheritablePageKeys.Resources, VCF.Create) + ?? NRT.ThrowOnNull(); +#endif + } } - PdfResources? _resources; /// /// Implements the interface because the primary function is internal. @@ -731,7 +753,7 @@ public PdfResources Resources /// internal string GetFontName(XGlyphTypeface glyphTypeface, FontType fontType, out PdfFont pdfFont) { - pdfFont = _document.FontTable.GetOrCreateFont(glyphTypeface, fontType); + pdfFont = Document.FontTable.GetOrCreateFont(glyphTypeface, fontType); Debug.Assert(pdfFont != null); string name = Resources.AddFont(pdfFont); return name; @@ -746,7 +768,7 @@ string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontTyp /// internal string? TryGetFontName(string idName, out PdfFont? pdfFont) { - pdfFont = _document.FontTable.TryGetFont(idName); + pdfFont = Document.FontTable.TryGetFont(idName); string? name = null; if (pdfFont != null) name = Resources.AddFont(pdfFont); @@ -758,7 +780,7 @@ string IContentStream.GetFontName(XGlyphTypeface glyphTypeface, FontType fontTyp /// internal string GetFontName(string idName, byte[] fontData, out PdfFont pdfFont) { - pdfFont = _document.FontTable.GetFont(idName, fontData); + pdfFont = Document.FontTable.GetFont(idName, fontData); //pdfFont = new PdfType0Font(Owner, idName, fontData); //pdfFont.Document = _document; Debug.Assert(pdfFont != null); @@ -774,11 +796,21 @@ string IContentStream.GetFontName(string idName, byte[] fontData, out PdfFont pd /// internal string GetImageName(XImage image) { - var pdfImage = _document.ImageTable.GetImage(image); + var pdfImage = Document.ImageTable.GetImage(image); + Debug.Assert(pdfImage != null); + string name = Resources.AddImage(pdfImage); + return name; + } + +#if CORE + internal string GetImageName(PdfSharp.Internal.Imaging.ImportedImage importedImage) + { + var pdfImage = Document.ImageTable.GetImage(importedImage); Debug.Assert(pdfImage != null); string name = Resources.AddImage(pdfImage); return name; } +#endif /// /// Implements the interface because the primary function is internal. @@ -791,7 +823,7 @@ string IContentStream.GetImageName(XImage image) /// internal string GetFormName(XForm form) { - var pdfForm = _document.FormTable.GetForm(form); + var pdfForm = Document.FormTable.GetForm(form); Debug.Assert(pdfForm != null); string name = Resources.AddForm(pdfForm); return name; @@ -807,7 +839,7 @@ internal override void WriteObject(PdfWriter writer) { // #PDF-A // Suppress transparency group if PDF-A is required. - if (!_document.IsPdfA) + if (!Document.IsPdfA) { // Add transparency group to prevent rendering problems of Adobe viewer. // Update (PDFsharp 1.50 beta 3): Add transparency group only if ColorMode is defined. @@ -815,11 +847,11 @@ internal override void WriteObject(PdfWriter writer) // we respect this and skip the transparency group. TransparencyUsed = true; // TODO_OLD: check XObjects if (TransparencyUsed && !Elements.ContainsKey(Keys.Group) && - _document.Options.ColorMode != PdfColorMode.Undefined) + Document.Options.ColorMode != PdfColorMode.Undefined) { var group = new PdfDictionary(); Elements["/Group"] = group; - if (_document.Options.ColorMode != PdfColorMode.Cmyk) + if (Document.Options.ColorMode != PdfColorMode.Cmyk) group.Elements.SetName("/CS", "/DeviceRGB"); else group.Elements.SetName("/CS", "/DeviceCMYK"); @@ -837,20 +869,68 @@ internal override void WriteObject(PdfWriter writer) /// /// HACK_OLD to indicate that a page-level transparency group must be created. /// - internal bool TransparencyUsed; + internal bool TransparencyUsed { get; set; } /// - /// Inherit values from parent node. + /// Applies inherited values from the parent nodes to this page. /// - internal static void InheritValues(PdfDictionary page, InheritedValues values) + internal void ApplyInheritedValues(ref readonly PdfPageTreeNode.InheritedValues inheritedValues) // TODO review { +#if true + if (inheritedValues.Resources != null) + { + var resources = Elements.GetDictionary(InheritablePageKeys.Resources); + //if (res is PdfReference pdfReference) + //{ + // resources = (PdfDictionary)pdfReference.Value.Clone(); + // resources.Document = Owner; + //} + //else + // resources = (PdfDictionary?)res; + + // The PDF 2.0 specifications states: + // “All values shall be inherited as-is, without merging, even for composite data types such as arrays and dictionaries.” + + if (resources == null) + { + //resources = values.Resources.Clone(); + //resources.Document = Owner; + Elements.Add(InheritablePageKeys.Resources, inheritedValues.Resources); + } + else + { + + //foreach (var name in values.Resources.Elements.KeyNames) + //{ + // if (!resources.Elements.ContainsKey(name.Value)) + // { + // var item = values.Resources.Elements[name]; + // Debug.Assert(item != null); + // if (item is PdfObject) + // item = item.Clone(); + // resources.Elements.Add(name.ToString(), item); + // } + //} + } + } + + if (inheritedValues.MediaBox != null && Elements.GetValue(InheritablePageKeys.MediaBox) == null) + Elements[InheritablePageKeys.MediaBox] = inheritedValues.MediaBox; + + if (inheritedValues.CropBox != null && Elements.GetValue(InheritablePageKeys.CropBox) == null) + Elements[InheritablePageKeys.CropBox] = inheritedValues.CropBox; + + if (inheritedValues.Rotate != null && Elements.GetValue(InheritablePageKeys.Rotate) == null) + Elements[InheritablePageKeys.Rotate] = inheritedValues.Rotate; +#else + // Old code - DELETE if (values.Resources != null!) { PdfDictionary? resources; var res = page.Elements[InheritablePageKeys.Resources]; - if (res is PdfReference) + if (res is PdfReference pdfReference) { - resources = (PdfDictionary)((PdfReference)res).Value.Clone(); + resources = (PdfDictionary)pdfReference.Value.Clone(); resources.Document = page.Owner; } else @@ -868,7 +948,8 @@ internal static void InheritValues(PdfDictionary page, InheritedValues values) { if (!resources.Elements.ContainsKey(name.Value)) { - PdfItem? item = values.Resources.Elements[name]; + var item = values.Resources.Elements[name]; + Debug.Assert(item != null); if (item is PdfObject) item = item.Clone(); resources.Elements.Add(name.ToString(), item); @@ -885,37 +966,7 @@ internal static void InheritValues(PdfDictionary page, InheritedValues values) if (values.Rotate != null! && page.Elements[InheritablePageKeys.Rotate] == null) page.Elements[InheritablePageKeys.Rotate] = values.Rotate; - } - - /// - /// Add all inheritable values from the specified page to the specified values structure. - /// - internal static void InheritValues(PdfDictionary page, ref InheritedValues values) - { - var item = page.Elements[InheritablePageKeys.Resources]; - if (item != null) - { - if (item is PdfReference reference) - values.Resources = (PdfDictionary)(reference.Value); - else - values.Resources = (PdfDictionary)item; - } - - item = page.Elements[InheritablePageKeys.MediaBox]; - if (item != null) - values.MediaBox = new PdfRectangle(item); - - item = page.Elements[InheritablePageKeys.CropBox]; - if (item != null) - values.CropBox = new PdfRectangle(item); - - item = page.Elements[InheritablePageKeys.Rotate]; - if (item != null) - { - if (item is PdfReference) - item = ((PdfReference)item).Value; - values.Rotate = (PdfInteger)item; - } +#endif } internal override void PrepareForSave() @@ -935,22 +986,25 @@ internal override void PrepareForSave() double width = _trimMargins.Left.Point + Width.Point + _trimMargins.Right.Point; double height = _trimMargins.Top.Point + Height.Point + _trimMargins.Bottom.Point; - MediaBox = new PdfRectangle(0, 0, width, height); - CropBox = new PdfRectangle(0, 0, width, height); - BleedBox = new PdfRectangle(0, 0, width, height); + MediaBox = new(0, 0, width, height); + CropBox = new(0, 0, width, height); + BleedBox = new(0, 0, width, height); var rect = new PdfRectangle(_trimMargins.Left.Point, _trimMargins.Top.Point, width - _trimMargins.Right.Point, height - _trimMargins.Bottom.Point); TrimBox = rect; - ArtBox = rect.Clone(); + ArtBox = rect; } } /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : InheritablePageKeys + public sealed class Keys : InheritablePageKeys { + // IMPROVE Reference 2.0: Table 31 + // All entries except the inheritable page keys. + /// /// (Required) The type of PDF object that this dictionary describes; /// must be Page for a page object. @@ -993,7 +1047,7 @@ internal sealed class Keys : InheritablePageKeys /// /// (Optional; PDF 1.3) A rectangle, expressed in default user space units, defining the - /// extent of the page’s meaningful content (including potential white space) as intended + /// extent of the page’s meaningful content (including potential white-space) as intended /// by the page’s creator. Default value: the value of CropBox. /// [KeyInfo("1.3", KeyType.Rectangle | KeyType.Optional)] @@ -1018,7 +1072,8 @@ internal sealed class Keys : InheritablePageKeys /// to the page’s logical content or organization. Applications that consume or produce PDF /// files are not required to preserve the existing structure of the Contents array. /// - [KeyInfo(KeyType.Array | KeyType.Stream | KeyType.Optional)] + //[KeyInfo(KeyType.Array | KeyType.Stream | KeyType.Optional)] // #US373 Note that you cannot use "|" with types. + [KeyInfo(KeyType.StreamOrArray | KeyType.Optional)] // #US373 public const string Contents = "/Contents"; /// @@ -1152,7 +1207,6 @@ internal sealed class Keys : InheritablePageKeys /// Gets the KeysMeta for these keys. /// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); - static DictionaryMeta? _meta; } @@ -1164,8 +1218,11 @@ internal sealed class Keys : InheritablePageKeys /// /// Predefined keys common to PdfPage and PdfPages. /// - internal class InheritablePageKeys : KeysBase + public class InheritablePageKeys : KeysBase { + // TODO Reference 2.0: Table 31 + // These 4 entries are the inheritable page keys. + /// /// (Required; inheritable) A dictionary containing any resources required by the page. /// If the page requires no resources, the value of this entry should be an empty dictionary. @@ -1198,16 +1255,5 @@ internal class InheritablePageKeys : KeysBase [KeyInfo(KeyType.Integer | KeyType.Optional)] public const string Rotate = "/Rotate"; } - - /// - /// Values inherited from a parent in the parent chain of a page tree. - /// - internal struct InheritedValues - { - public PdfDictionary Resources; - public PdfRectangle MediaBox; - public PdfRectangle CropBox; - public PdfInteger Rotate; - } } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeBase.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeBase.cs new file mode 100644 index 00000000..5e278639 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeBase.cs @@ -0,0 +1,32 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +// v7.0.0 Ready + +namespace PdfSharp.Pdf +{ + /// + /// Base class of all page tree entries. + /// Allows PdfPageTreeNode as well as PdfPage objects to be elements in + /// a PdfTreeNodes array. + /// + public abstract class PdfPageTreeBase : PdfDictionary + { + protected PdfPageTreeBase() + { } + + internal PdfPageTreeBase(PdfDocument document) + : base(document, true) + { } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfPageTreeBase(PdfDictionary dictionary) + : base(dictionary) + { } + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeNode.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeNode.cs new file mode 100644 index 00000000..b5e1c7f4 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeNode.cs @@ -0,0 +1,187 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf +{ + /// + /// Represents an entry in the documents page tree that is not a leaf. + /// + //[DebuggerDisplay("(PageCount={" + nameof(Count) + "})")] // TODO #US279 + public class PdfPageTreeNode : PdfDictionary + { + // Reference 2.0: 7.7.3.2 Page tree nodes / Page 102 + + internal PdfPageTreeNode(PdfDocument document) + : base(document) + { + Elements.SetName(Keys.Type, "/Pages"); + Elements[Keys.Count] = new PdfInteger(0); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfPageTreeNode(PdfDictionary dictionary) + : base(dictionary) + { } + + /// + /// Gets the parent of this tree node or null for the root node. + /// The root node is PdfPages. + /// + public PdfPageTreeNode? Parent + { + get // TODO => + { + var result = Elements.GetDictionary(Keys.Parent); + return result; + } + } + + /// + /// Gets the /kids array of this node. + /// + public PdfPageTreeNodes Kids + { + get // TODO => + { + var result = Elements.GetRequiredArray(Keys.Kids); + return result; + } + } + + public int Count + { + get // TODO => + { + var result = Elements.GetInteger(Keys.Count); + return result; + } + } + + /// + /// Add or overrides all inheritable values from this node to the specified values structure. + /// + internal void GetInheritableValues(ref InheritedValues inheritedValues) + { +#if true // @@@ + //var res = Elements.GetDictionary(InheritablePageKeys.Resources); + //if (res != null) + // inheritedValues.Resources = res; + + inheritedValues.Resources = Elements.GetDictionary(PdfPage.InheritablePageKeys.Resources); + inheritedValues.MediaBox = Elements.GetRectangle(PdfPage.InheritablePageKeys.MediaBox); + inheritedValues.CropBox = Elements.GetRectangle(PdfPage.InheritablePageKeys.CropBox); + + // TODO Should be written simpler. + var rotate = Elements.GetValue(PdfPage.InheritablePageKeys.Rotate); + if (rotate is PdfInteger integer) + inheritedValues.Rotate = integer; + + //inheritedValues.CropBox = Elements.GetValue(InheritablePageKeys.CropBox); + //inheritedValues.Rotate = Elements.GetValue(InheritablePageKeys.Rotate); +#else + // Old code from PdfPage - DELETE + var item = page.Elements[InheritablePageKeys.Resources]; + if (item != null) + { + if (item is PdfReference reference) + values.Resources = (PdfDictionary)(reference.Value); + else + values.Resources = (PdfDictionary)item; + } + + item = page.Elements.GetValue(InheritablePageKeys.MediaBox); + if (item != null) + values.MediaBox = new PdfRectangle(item); + + item = page.Elements.GetValue(InheritablePageKeys.CropBox); + if (item != null) + values.CropBox = new PdfRectangle(item); + + item = page.Elements.GetValue(InheritablePageKeys.Rotate); + if (item != null) + { + //if (item is PdfReference) + // item = ((PdfReference)item).Value; + values.Rotate = (PdfInteger)item; + } +#endif + } + + /// + /// Predefined keys of this dictionary. + /// + public sealed class Keys : KeysBase + { + // Reference 2.0: Table 30 — Required entries in a page tree node / Page 103 + + /// + /// (Required) The type of PDF object that this dictionary describes; shall be Pages for a + /// page tree node. + /// + [KeyInfo(KeyType.Name | KeyType.Required, FixedValue = "Pages")] + public const string Type = "/Type"; + + /// + /// (Required except in root node; not permitted in the root node; shall be an indirect reference) + /// The page tree node that is the immediate parent of this one. + /// + [KeyInfo(KeyType.Dictionary | KeyType.Required)] + public const string Parent = "/Parent"; + + /// + /// (Required) An array of indirect references to the immediate children of this node. + /// The children shall only be page objects or other page tree nodes. + /// + [KeyInfo(KeyType.Array | KeyType.Required)] + public const string Kids = "/Kids"; + + /// + /// (Required) The number of leaf nodes (page objects) that are descendants of this node + /// within the page tree. + /// NOTE + /// Since the number of pages descendant from a Pages dictionary can be accurately determined + /// by examining the tree itself using the Kids arrays, the Count entry is redundant. + /// A PDF writer shall ensure that the value of the Count key is consistent with the number + /// of entries in the Kids array and its descendants which definitively determines the number + /// of descendant pages. + /// + [KeyInfo(KeyType.Integer | KeyType.Required)] + public const string Count = "/Count"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + + static DictionaryMeta? _meta; + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + + /// + /// Represents the inheritable entries from a parent node in a page tree. + /// Used to displace them to the pages in the leafs of the tree. + /// + internal struct InheritedValues + { + // Reference 2.0: 7.7.3.4 Inheritance of page attributes / Page 108 + + // Note that it must be a struct because the use based on value type semantic. + + public PdfDictionary? Resources; + public PdfRectangle? MediaBox; + public PdfRectangle? CropBox; + public PdfInteger? Rotate; + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeNodes.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeNodes.cs new file mode 100644 index 00000000..f83e76ad --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPageTreeNodes.cs @@ -0,0 +1,31 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// v7.0.0 TODO review + +namespace PdfSharp.Pdf +{ + /// + /// Represents the children of a PdfPageTreeNode. + /// + //[DebuggerDisplay("(PageCount={" + nameof(Count) + "})")] + public sealed class PdfPageTreeNodes : PdfArray + { + // Reference 2.0: 7.7.3.2 Page tree nodes / Page 102 + + internal PdfPageTreeNodes() + { } + + internal PdfPageTreeNodes(IEnumerable pages) + { + foreach (var page in pages) + { + Elements.Add(page); + } + } + + internal PdfPageTreeNodes(PdfArray array) + : base(array) + { } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPages.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPages.cs index 2ce880af..0b594218 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPages.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPages.cs @@ -2,27 +2,34 @@ // See the LICENSE file in the solution root for more information. using System.Collections; +using PdfSharp.Internal; using PdfSharp.Events; using PdfSharp.Logging; -using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.IO; + +// v7.0.0 TODO review, delete test code. namespace PdfSharp.Pdf { /// - /// Represents the pages of the document. + /// Represents the root of the page tree of the document. /// - [DebuggerDisplay("(PageCount={" + nameof(Count) + "})")] - public sealed class PdfPages : PdfDictionary, IEnumerable + //[DebuggerDisplay("(PageCount={" + nameof(Count) + "})")] + public sealed class PdfPages : PdfPageTreeNode, IEnumerable { + // Reference 2.0: 7.7.3.2 Page tree nodes / Page 102 + // PdfPages is the root of the page tree. + internal PdfPages(PdfDocument document) : base(document) - { - Elements.SetName(Keys.Type, "/Pages"); - Elements[Keys.Count] = new PdfInteger(0); - } + { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// internal PdfPages(PdfDictionary dictionary) : base(dictionary) { } @@ -31,11 +38,24 @@ internal PdfPages(PdfDictionary dictionary) /// Gets the number of pages. /// //public int Count => PagesArray.Elements.Count; // This can be wrong in Import mode. - //public int Count => _document.PageCount; // Slower, but also works in Import mode. - public int Count => - _document.CanModify ? - PagesArray.Elements.Count : // Only valid in Modify mode. - _document.PageCount; // Valid in Import mode. + //public int Count => Document.PageCount; // Slower, but also works in Import mode. + public new int Count // TODO Remove and use Count from base class. + { + get + { + // TODO Delete test code. + //var count1 = Document.CanModify + // ? PagesArray.Elements.Count // Only valid in Modify mode. + // : Document.PageCount; // Valid in Import mode. + + var count2 = base.Count; + + + var result = PagesArray.Elements.Count; + Debug.Assert(count2 == result); + return result; + } + } /// /// Gets the page with the specified index. @@ -47,10 +67,12 @@ public PdfPage this[int index] if (index < 0 || index >= Count) throw new ArgumentOutOfRangeException(nameof(index), index, PsMsgs.PageIndexOutOfRange); - var dict = (PdfDictionary)((PdfReference)PagesArray.Elements[index]).Value; - if (dict is not PdfPage) - dict = new PdfPage(dict); - return (PdfPage)dict; + // Is document modifiable and page tree flattened? + if (_flattenedPages == null) + return PagesArray.Elements.GetRequiredDictionary(index); + + Debug.Assert(!Document.CanModify); + return _flattenedPages.Elements.GetRequiredDictionary(index); } } @@ -89,9 +111,7 @@ public PdfPage Add() /// The value returned is a new object if the added page comes from a foreign document. /// public PdfPage Add(PdfPage page) - { - return Insert(Count, page); - } + => Insert(Count, page); /// /// Creates a new PdfPage, inserts it at the specified position into this document, and returns it. @@ -116,6 +136,7 @@ public PdfPage Insert(int index, PdfPage page) if (page.Owner == Owner) { // Case: Page is first removed and then inserted again, maybe at another position. + int count = Count; // Check if page is not already part of the document. for (int idx = 0; idx < count; idx++) @@ -124,22 +145,23 @@ public PdfPage Insert(int index, PdfPage page) throw new InvalidOperationException(PsMsgs.MultiplePageInsert); } - // Because the owner of the inserted page is this document we assume that the page was former part of it, - // and is therefore well-defined. + // Because the owner of the inserted page is this document we assume that the page was + // former already a part of it, and is therefore well-defined. Owner.IrefTable.Add(page); Debug.Assert(page.Owner == Owner); // Insert page in array. - PagesArray.Elements.Insert(index, page.ReferenceNotNull); // Page is always indirect. + //PagesArray.Elements.Insert(index, page.RequiredReference); // Page is always indirect. + PagesArray.Elements.Insert(index, page); // Update page count. Elements.SetInteger(Keys.Count, PagesArray.Elements.Count); // #PDF-UA: Pages must not be moved. - if (_document._uaManager != null) - _document.Events.OnPageAdded(_document, new PageEventArgs(_document) { Page = page, PageIndex = index, EventType = PageEventType.Moved }); + if (Document.UAManager != null) + Document.Events.OnPageAdded(Document, new PageEventArgs(Document) { Page = page, PageIndex = index, EventType = PageEventType.Moved }); - PdfSharpLogHost.Logger.ExistingPdfPageAdded(_document?.Name); + PdfSharpLogHost.Logger.ExistingPdfPageAdded(Document?.Name); return page; } @@ -152,34 +174,34 @@ public PdfPage Insert(int index, PdfPage page) Owner.IrefTable.Add(page); Debug.Assert(page.Owner == Owner); - PagesArray.Elements.Insert(index, page.ReferenceNotNull); + PagesArray.Elements.Insert(index, page.RequiredReference); Elements.SetInteger(Keys.Count, PagesArray.Elements.Count); // #PDF-UA: Page was created. - if (_document._uaManager != null) - _document.Events.OnPageAdded(_document, new PageEventArgs(_document) { Page = page, PageIndex = index, EventType = PageEventType.Created }); + if (Document.UAManager != null) + Document.Events.OnPageAdded(Document, new PageEventArgs(Document) { Page = page, PageIndex = index, EventType = PageEventType.Created }); } else { // Case: Page is from an external document -> import it. PdfPage importPage = page; page = ImportExternalPage(importPage); - Owner.IrefTable.Add(page); + //Owner.IrefTable.Add(page); // DELETE Pages are now always indirect // Add page substitute to importedObjectTable. PdfImportedObjectTable importedObjectTable = Owner.FormTable.GetImportedObjectTable(importPage); - importedObjectTable.Add(importPage.ObjectID, page.ReferenceNotNull); + importedObjectTable.Add(importPage.ObjectID, page.RequiredReference); - PagesArray.Elements.Insert(index, page.ReferenceNotNull); + PagesArray.Elements.Insert(index, page.RequiredReference); Elements.SetInteger(Keys.Count, PagesArray.Elements.Count); PdfAnnotations.FixImportedAnnotation(page); // #PDF-UA: Page was imported. - if (_document._uaManager != null) - _document.Events.OnPageAdded(_document, new PageEventArgs(_document) { Page = page, PageIndex = index, EventType = PageEventType.Imported }); + if (Document.UAManager != null) + Document.Events.OnPageAdded(Document, new PageEventArgs(Document) { Page = page, PageIndex = index, EventType = PageEventType.Imported }); } - PdfSharpLogHost.Logger.NewPdfPageCreated(_document?.Name); + PdfSharpLogHost.Logger.NewPdfPageCreated(Document?.Name); if (Owner.Settings.TrimMargins.AreSet) page.TrimMargins = Owner.Settings.TrimMargins; @@ -211,26 +233,24 @@ public void InsertRange(int index, PdfDocument document, int startIndex, int pag if (pageCount > importDocumentPageCount) throw new ArgumentOutOfRangeException(nameof(pageCount), "Argument 'pageCount' out of range."); - PdfPage[] insertPages = new PdfPage[pageCount]; - PdfPage[] importPages = new PdfPage[pageCount]; + var insertPages = new PdfPage[pageCount]; + var importPages = new PdfPage[pageCount]; // 1st create all new pages. for (int idx = 0, insertIndex = index, importIndex = startIndex; importIndex < startIndex + pageCount; idx++, insertIndex++, importIndex++) { - PdfPage importPage = document.Pages[importIndex]; - PdfPage page = ImportExternalPage(importPage); + var importPage = document.Pages[importIndex]; + var page = ImportExternalPage(importPage); insertPages[idx] = page; importPages[idx] = importPage; - Owner.IrefTable.Add(page); - // Add page substitute to importedObjectTable. - PdfImportedObjectTable importedObjectTable = Owner.FormTable.GetImportedObjectTable(importPage); - importedObjectTable.Add(importPage.ObjectID, page.ReferenceNotNull); + var importedObjectTable = Owner.FormTable.GetImportedObjectTable(importPage); + importedObjectTable.Add(importPage.ObjectID, page.RequiredReference); - PagesArray.Elements.Insert(insertIndex, page.ReferenceNotNull); + PagesArray.Elements.Insert(insertIndex, page/*.RequiredReference*/); if (Owner.Settings.TrimMargins.AreSet) page.TrimMargins = Owner.Settings.TrimMargins; @@ -242,14 +262,14 @@ public void InsertRange(int index, PdfDocument document, int startIndex, int pag importIndex < startIndex + pageCount; idx++, importIndex++) { - PdfPage importPage = document.Pages[importIndex]; - PdfPage page = insertPages[idx]; + var importPage = document.Pages[importIndex]; + var page = insertPages[idx]; // Get annotations. - PdfArray? annots = importPage.Elements.GetArray(PdfPage.Keys.Annots); + var annots = importPage.Elements.GetArray(PdfPage.Keys.Annots); if (annots != null) { - PdfAnnotations annotations = new PdfAnnotations(Owner); + var annotations = page.Annotations; // Loop through annotations. int count = annots.Elements.Count; @@ -262,7 +282,7 @@ public void InsertRange(int index, PdfDocument document, int startIndex, int pag if (subtype == "/Link") { bool addAnnotation = false; - PdfLinkAnnotation newAnnotation = new PdfLinkAnnotation(Owner); + var newAnnotation = new PdfLinkAnnotation(Owner); PdfName[] importAnnotationKeyNames = annot.Elements.KeyNames; foreach (var pdfItem in importAnnotationKeyNames) @@ -270,33 +290,33 @@ public void InsertRange(int index, PdfDocument document, int startIndex, int pag PdfItem? impItem; switch (pdfItem.Value) { - case "/BS": - newAnnotation.Elements.Add("/BS", new PdfLiteral("<>")); + case PdfLinkAnnotation.Keys.BS: + newAnnotation.Elements.Add(PdfLinkAnnotation.Keys.BS, new PdfLiteral("<>")); break; - case "/F": // /F 4 - impItem = annot.Elements.GetValue("/F"); + case PdfAnnotation.Keys.F: // /F 4 + impItem = annot.Elements.GetValue(PdfAnnotation.Keys.F); Debug.Assert(impItem is PdfInteger); - newAnnotation.Elements.Add("/F", impItem.Clone()); + newAnnotation.Elements.Add(PdfAnnotation.Keys.F, impItem.Clone()); break; - case "/Rect": // /Rect [68.6 681.08 145.71 702.53] - impItem = annot.Elements.GetValue("/Rect"); - Debug.Assert(impItem is PdfArray); - newAnnotation.Elements.Add("/Rect", impItem.Clone()); + case PdfAnnotation.Keys.Rect: // /Rect [68.6 681.08 145.71 702.53] + impItem = annot.Elements.GetValue(PdfAnnotation.Keys.Rect); + Debug.Assert(impItem is PdfRectangle); + newAnnotation.Elements.Add(PdfAnnotation.Keys.Rect, impItem.Clone()); break; - case "/StructParent": // /StructParent 3 - impItem = annot.Elements.GetValue("/StructParent"); + case PdfAnnotation.Keys.StructParent: // /StructParent 3 + impItem = annot.Elements.GetValue(PdfAnnotation.Keys.StructParent); Debug.Assert(impItem is PdfInteger); - newAnnotation.Elements.Add("/StructParent", impItem.Clone()); + newAnnotation.Elements.Add(PdfAnnotation.Keys.StructParent, impItem.Clone()); break; - case "/Subtype": // Already set. + case PdfAnnotation.Keys.Subtype: // Already set. break; - case "/Dest": // /Dest [30 0 R /XYZ 68 771 0] - impItem = annot.Elements.GetValue("/Dest"); + case PdfLinkAnnotation.Keys.Dest: // /Dest [30 0 R /XYZ 68 771 0] + impItem = annot.Elements.GetValue(PdfLinkAnnotation.Keys.Dest); impItem = impItem!.Clone(); // NRT // Is value an array with 5 elements where the first one is an iref? @@ -353,8 +373,8 @@ public void InsertRange(int index, PdfDocument document, int startIndex, int pag } // #PDF-UA: Pages were imported. - if (_document._uaManager != null) - _document.Events.OnPageAdded(_document, new PageEventArgs(_document) { EventType = PageEventType.Imported }); + if (Document.UAManager != null) + Document.Events.OnPageAdded(Document, new PageEventArgs(Document) { EventType = PageEventType.Imported }); } /// @@ -389,12 +409,12 @@ public void InsertRange(int index, PdfDocument document, int startIndex) /// public void Remove(PdfPage page) { - PagesArray.Elements.Remove(page.ReferenceNotNull); + PagesArray.Elements.Remove(page/*.RequiredReference*/); Elements.SetInteger(Keys.Count, PagesArray.Elements.Count); // #PDF-UA: Page was removed. - if (_document._uaManager != null) - _document.Events.OnPageRemoved(_document, new PageEventArgs(_document) { Page = page, PageIndex = -1, EventType = PageEventType.Removed }); + if (Document.UAManager != null) + Document.Events.OnPageRemoved(Document, new PageEventArgs(Document) { Page = page, PageIndex = -1, EventType = PageEventType.Removed }); } /// @@ -407,8 +427,8 @@ public void RemoveAt(int index) Elements.SetInteger(Keys.Count, PagesArray.Elements.Count); // #PDF-UA - if (_document._uaManager != null && page != null) - _document.Events.OnPageRemoved(_document, new PageEventArgs(_document) { Page = page, PageIndex = index }); + if (Document.UAManager != null && page != null) + Document.Events.OnPageRemoved(Document, new PageEventArgs(Document) { Page = page, PageIndex = index }); } /// @@ -419,7 +439,7 @@ public void RemoveAt(int index) public void MovePage(int oldIndex, int newIndex) { // #PDF-UA: Not implemented. - if (_document._uaManager != null) + if (Document.UAManager != null) throw new InvalidOperationException("Cannot move a page in a PDF/UA document."); if (oldIndex < 0 || oldIndex >= Count) @@ -443,10 +463,10 @@ public void MovePage(int oldIndex, int newIndex) /// PdfPage ImportExternalPage(PdfPage importPage) { - if (importPage.Owner._openMode != PdfDocumentOpenMode.Import) + if (importPage.Owner.OpenMode != PdfDocumentOpenMode.Import) throw new InvalidOperationException("A PDF document must be opened with PdfDocumentOpenMode.Import to import pages from it."); - var page = new PdfPage(_document); + var page = new PdfPage(Document); // ReSharper disable AccessToStaticMemberViaDerivedType for a better code readability. CloneElement(page, importPage, PdfPage.Keys.Resources, false); @@ -457,13 +477,10 @@ PdfPage ImportExternalPage(PdfPage importPage) CloneElement(page, importPage, PdfPage.Keys.BleedBox, true); CloneElement(page, importPage, PdfPage.Keys.TrimBox, true); CloneElement(page, importPage, PdfPage.Keys.ArtBox, true); -#if true + // Do not deep copy annotations. CloneElement(page, importPage, PdfPage.Keys.Annots, false); -#else - // Deep copy annotations. - CloneElement(page, importPage, PdfPage.Keys.Annots, true); -#endif + page.Initialize(true); // ReSharper restore AccessToStaticMemberViaDerivedType @@ -476,26 +493,31 @@ PdfPage ImportExternalPage(PdfPage importPage) void CloneElement(PdfPage page, PdfPage importPage, string key, bool deepCopy) { Debug.Assert(page != null); - Debug.Assert(page.Owner == _document); + Debug.Assert(page.Owner == Document); Debug.Assert(importPage.Owner != null); - Debug.Assert(importPage.Owner != _document); + Debug.Assert(importPage.Owner != Document); - PdfItem? item = importPage.Elements[key]; + //PdfItem? item = importPage.Elements[key]; + PdfItem? item = importPage.Elements.GetValue(key); // #US373 if (item != null) { PdfImportedObjectTable? importedObjectTable = null; if (!deepCopy) importedObjectTable = Owner.FormTable.GetImportedObjectTable(importPage); - // The item can be indirect. If so, replace it by its value. - if (item is PdfReference reference) - item = reference.Value; + // TODO #US373 begin + //// The item can be indirect. If so, replace it by its value. + ////if (item is PdfReference reference) + //// item = reference.Value; + //PdfReference.Dereference(ref item); + // TODO #US373 end + if (item is PdfObject root) { if (deepCopy) { Debug.Assert(root.Owner != null, "See 'else' case for details"); - root = DeepCopyClosure(_document, root); + root = DeepCopyClosure(Document, root); } else { @@ -518,7 +540,7 @@ void CloneElement(PdfPage page, PdfPage importPage, string key, bool deepCopy) } } - static PdfReference? RemapReference(PdfPage[] newPages, PdfPage[] impPages, PdfReference iref) + static PdfReference? RemapReference(PdfPage[] newPages, PdfPage[] impPages, PdfReference iref) // TODO review { // Directs the iref to a one of the imported pages? for (int idx = 0; idx < newPages.Length; idx++) @@ -532,9 +554,17 @@ void CloneElement(PdfPage page, PdfPage importPage, string key, bool deepCopy) /// /// Gets a PdfArray containing all pages of this document. The array must not be modified. /// - public PdfArray PagesArray - => _pagesArray ??= (PdfArray?)Elements.GetValue(Keys.Kids, VCF.Create) ?? NRT.ThrowOnNull(); - PdfArray? _pagesArray; + //public PdfArray PagesArray + public PdfPageTreeNodes PagesArray + { + get + { + //return _pagesArray ??= (PdfArray?)Elements.GetValue(Keys.Kids, VCF.Create) ?? NRT.ThrowOnNull(); + return _pageTreeNodes ??= _flattenedPages ?? Elements.GetRequiredArray(Keys.Kids, VCF.Create); + } + } + //PdfArray? _pagesArray; + PdfPageTreeNodes? _pageTreeNodes; /// /// Replaces the page tree by a flat array of indirect references to the pages objects. @@ -542,95 +572,239 @@ public PdfArray PagesArray internal void FlattenPageTree() { // Acrobat creates a balanced tree if the number of pages is roughly more than ten. This is - // not difficult but obviously also not necessary. I created a document with 50000 pages with + // not difficult but obviously also not necessary. I created a document with 50,000 pages with // PDF4NET and Acrobat opened it in less than 2 seconds. - //PdfReference xrefRoot = Document.Catalog.Elements[PdfCatalog.Keys.Pages] as PdfReference; - //PdfDictionary[] pages = GetKids(xrefRoot, null); + // Promote inheritable values down the page tree to each single page. + var inheritedValues = new PdfPageTreeNode.InheritedValues(); + // Get the root inheritable values. + GetInheritableValues(ref inheritedValues); - // Promote inheritable values down the page tree - PdfPage.InheritedValues values = new PdfPage.InheritedValues(); - PdfPage.InheritValues(this, ref values); - PdfDictionary[] pages = GetKids(ReferenceNotNull, values, null); + // Flat list of pages. + // Used to replace the page tree (if one exists). + List pages = []; - // Replace /Pages in catalog by this object. - // xrefRoot.Value = this; + // Iterate the page tree in pre-order. + TraversePageTree(pages, this, inheritedValues); - var array = new PdfArray(Owner); + // Put a flat list to the root node. + var array = Elements.GetRequiredArray(Keys.Kids); + array.Elements.Clear(); foreach (var page in pages) { // Fix the parent. - page.Elements[PdfPage.Keys.Parent] = Reference; - array.Elements.Add(page.ReferenceNotNull); + page.Elements[PdfPage.Keys.Parent] = this; + array.Elements.Add(page); } - Elements.SetName(Keys.Type, "/Pages"); -#if true + Debug.Assert(Elements.GetName(Keys.Type) == "/Pages"); + Elements.SetName(Keys.Type, "/Pages"); // TODO DELETE + // Direct array. - Elements.SetValue(Keys.Kids, array); -#else - // Indirect array. - Document.xrefTable.Add(array); - Elements.SetValue(Keys.Kids, array.XRef); -#endif + Debug.Assert(ReferenceEquals(Elements.GetRequiredObject(Keys.Kids).Reference, array.Reference)); + Elements.SetValue(Keys.Kids, array); // TODO DELETE + + var count = Elements.GetInteger(Keys.Count); + Debug.Assert(count == array.Elements.Count); Elements.SetInteger(Keys.Count, array.Elements.Count); + + return; + + // DELETE + void CollectKids__(PdfDictionary treeNode, PdfPageTreeNode.InheritedValues values) + { + // TODO_OLD: inherit inheritable keys... + //var kid = (PdfDictionary)iref.Value; + + //string type = dict.Elements.GetName(Keys.Type); + //if (type == "/Page") + //{ + // PdfPage.InheritValues(dict, values); + // pages.Add(dict); + // return; + //} + //Debug.Assert(type == "/Pages"); + + var kids = treeNode.Elements.GetArray(Keys.Kids); + if (kids != null) + { + foreach (var item in kids) + { + if (((PdfReference)item).Value is PdfDictionary kid) + { + string type = kid.Elements.GetName(Keys.Type); + if (type == "/Page") + { + var oldKid = kid; + var oldRef = kid.RequiredReference; + var page = (PdfPage)kid.Elements.CreateContainer(typeof(PdfPage), kid, true); + + Debug.Assert(oldKid.IsDead); + Debug.Assert(ReferenceEquals(oldRef, page.RequiredReference)); + + pages.Add(page); + } + else + { + Debug.Assert(type == "/Pages"); + + //var oldKid = kid; + //var oldRef = kid.RequiredReference; + //var node = (PdfPageTreeNode)kid.Elements.CreateContainer(typeof(PdfPageTreeNode), kid, true); + + //Debug.Assert(oldKid.IsDead); + //Debug.Assert(ReferenceEquals(oldRef, node.RequiredReference)); + + CollectKids__(kid, values); + } + } + } + return; + } + // A page tree node with no kids? + Debug.Assert(false, $"Page tree node () has no /Kids entry."); + } } /// - /// Recursively converts the page tree into a flat array. + /// Preserves the page tree of an imported document. /// - static PdfDictionary[] GetKids(PdfReference iref, PdfPage.InheritedValues values, PdfDictionary? parentNotUsed) + internal void PreservePageTree() { - // TODO_OLD: inherit inheritable keys... - var kid = (PdfDictionary)iref.Value; + // Although the page tree is not changed, we promote inheritable values down the page tree + // to each single page. This makes it much more simple to import a page because every page + // already knows it closure. + var inheritedValues = new PdfPageTreeNode.InheritedValues(); + // Get the root inheritable values. + GetInheritableValues(ref inheritedValues); -#if true - string type = kid.Elements.GetName(Keys.Type); - if (type == "/Page") - { - PdfPage.InheritValues(kid, values); - return [kid]; - } + // Flat list of pages. + List pages = []; + + TraversePageTree(pages, this, inheritedValues); + + // Save the pages array. + _flattenedPages = new PdfPageTreeNodes(pages); + + var count = Elements.GetInteger(Keys.Count); + Debug.Assert(count == pages.Count); + Elements.SetInteger(Keys.Count, pages.Count); + + return; - // If it has kids, it’s logically not going to be type page. - if (String.IsNullOrEmpty(type) && !kid.Elements.ContainsKey("/Kids")) + // DELETE + void TraverseKids__(PdfPageTreeNode treeNode, PdfPageTreeNode.InheritedValues values) { - // Type is required. If type is missing, assume it is "/Page" and hope it will work. - // TODO_OLD Implement a "Strict" mode in PDFsharp and don’t do this in "Strict" mode. - PdfPage.InheritValues(kid, values); - return [kid]; + // TODO_OLD: inherit inheritable keys... + //var kid = (PdfDictionary)iref.Value; + + var kids = treeNode.Elements.GetArray(Keys.Kids); + if (kids != null) + { + foreach (var item in kids /*.Elements.AsEnumerable()*/) + { + if (((PdfReference)item).Value is PdfDictionary kid) + { + string type = kid.Elements.GetName(Keys.Type); + if (type == "/Page") + { + var oldKid = kid; + var oldRef = kid.RequiredReference; + var page = (PdfPage)kid.Elements.CreateContainer(typeof(PdfPage), kid, true); + + Debug.Assert(oldKid.IsDead); + Debug.Assert(ReferenceEquals(oldRef, page.RequiredReference)); + + pages.Add(page); + } + else + { + Debug.Assert(type == "/Pages"); + + var oldKid = kid; + var oldRef = kid.RequiredReference; + var node = (PdfPageTreeNode)kid.Elements.CreateContainer(typeof(PdfPageTreeNode), kid, true); + + Debug.Assert(oldKid.IsDead); + Debug.Assert(ReferenceEquals(oldRef, node.RequiredReference)); + + TraverseKids__(node, values); + } + } + } + return; + } + // A page tree node with no kids? + Debug.Assert(false, $"Page tree node () has no /Kids entry."); } + } -#else - if (kid.Elements.GetName(Keys.Type) == "/Page") + /// + /// TODO + /// Traverses the page tree in pre-order. + /// Recursively converts the page tree into a flat array. + /// + /// The pages. + /// The tree node. + /// The values. + void TraversePageTree(List pages, PdfPageTreeNode treeNode, PdfPageTreeNode.InheritedValues inheritedValues) + { + // TODO inherit inheritable keys... + _ = typeof(int); + var kids = treeNode.Elements.GetArray(Keys.Kids); + if (kids != null) { - PdfPage.InheritValues(kid, values); - return new PdfDictionary[] { kid }; - } + foreach (var item in kids /*.Elements.AsEnumerable()*/) + { + if (((PdfReference)item).Value is PdfDictionary kid) + { + string type = kid.Elements.GetName(Keys.Type); + if (type == "/Page") + { +#if DEBUG + var oldKid = kid; + var oldRef = kid.RequiredReference; #endif + var page = (PdfPage)kid.Elements.CreateContainer(typeof(PdfPage), kid, true); + page.ApplyInheritedValues(ref inheritedValues); - Debug.Assert(kid.Elements.GetName(Keys.Type) == "/Pages"); - PdfPage.InheritValues(kid, ref values); - List list = []; - var kids = kid.Elements["/Kids"] as PdfArray; + pages.Add(page); - if (kids == null) - { - if (kid.Elements["/Kids"] is PdfReference xref3) - kids = xref3.Value as PdfArray; +#if DEBUG + Debug.Assert(oldKid.IsDead); + Debug.Assert(ReferenceEquals(oldRef, page.RequiredReference)); +#endif + } + else if (type == "/Pages") + { + //Debug.Assert(type == "/Pages"); +#if DEBUG + var oldKid = kid; + var oldRef = kid.RequiredReference; +#endif + var node = (PdfPageTreeNode)kid.Elements.CreateContainer(typeof(PdfPageTreeNode), kid, true); + node.GetInheritableValues(ref inheritedValues); +#if DEBUG + Debug.Assert(oldKid.IsDead); + Debug.Assert(ReferenceEquals(oldRef, node.RequiredReference)); +#endif + // Note that inheritedValues are not changed by the recursive invocation + // because it is a value type that it is passed by value. + TraversePageTree(pages, node, inheritedValues); + } + else + { + // A page tree node with no valid /Type? + Debug.Assert(false, $"Page tree node ({ObjectID.ToString()}) has the unknown /Type entry '{type}'."); + } + } + } } - - Debug.Assert(kids != null, "kids should not be null here anymore. Check PDF file what happened."); - - foreach (var pdfItem in kids!) + else { - var xref2 = (PdfReference)pdfItem; - list.AddRange(GetKids(xref2, values, kid)); + // A page tree node with no kids? + Debug.Assert(false, $"Page tree node ({ObjectID.ToString()}) has no /Kids entry."); } - - int count = list.Count; - Debug.Assert(count == kid.Elements.GetInteger("/Count")); - return list.ToArray(); } /// @@ -656,9 +830,7 @@ internal override void PrepareForSave() /// Gets the enumerator. /// public new IEnumerator GetEnumerator() - { - return new PdfPagesEnumerator(this); - } + => new PdfPagesEnumerator(this); class PdfPagesEnumerator : IEnumerator { @@ -708,10 +880,21 @@ public void Dispose() readonly PdfPages _list; } + /// + /// If the page tree is not flattened, it contains all pages of an imported document; + /// null otherwise. + /// + PdfPageTreeNodes? _flattenedPages; + + void EnsureNotFrozen() + { + // TODO + } + /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : PdfPage.InheritablePageKeys + public new sealed class Keys : PdfPage.InheritablePageKeys { /// /// (Required) The type of PDF object that this dictionary describes; @@ -744,7 +927,7 @@ internal sealed class Keys : PdfPage.InheritablePageKeys /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPrimitive.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPrimitive.cs new file mode 100644 index 00000000..1af836cf --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPrimitive.cs @@ -0,0 +1,35 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf +{ + /// + /// Base class for all types that cannot be indirect PDF objects. + /// All primitive types are immutable and can be used multiple times. + /// Therefore, e.g. for strings exists PdfString and PdfStringObject. + /// + public abstract class PdfPrimitive : PdfItem + { + // All derived primitive types are immutable. + + /// + /// Initialized a new instance of this class. + /// + protected PdfPrimitive() + { } + + /// + /// Initialized a new instance of this class. + /// + protected PdfPrimitive(PdfItem item) : base(item) + { } + + +#if PRESERVE_PARSED_VALUES + /// + /// Gets or sets the byte string that was originally read from the lexer. + /// + internal string? ParsedValue { get; set; } +#endif + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPrimitiveObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPrimitiveObject.cs new file mode 100644 index 00000000..bd091362 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPrimitiveObject.cs @@ -0,0 +1,46 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace PdfSharp.Pdf +{ + /// + /// Common base class for PDF objects that are neither PdfArray nor PdfDictionary. + /// ‘Primitive objects’ are PdfStringObject or PdfNumberObject, which is base class of PdfIntegerObject, + /// PdfLongIntegerObject, and PdfRealObject. + /// A none-compound object must not be used as a direct object. + /// For technical use only, e.g. as marker class. There is no counterpart of + /// this class in the PDF specification. + /// + public abstract class PdfPrimitiveObject : PdfObject + { + /// + /// Initializes a new instance of this class. + /// + protected PdfPrimitiveObject() + { } + + /// + /// Initializes a new instance of this class + /// without making it indirect. + /// + protected internal PdfPrimitiveObject(PdfDocument document, bool createIndirect) + : base(document, createIndirect) + { + // The PDFsharp parser internally needs a way to create primitive objects initially as direct object. + // Developers must not do that. + } + +#if PRESERVE_PARSED_VALUES + /// + /// Gets or sets the string that was originally read from the lexer. + /// + internal string? ParsedValue { get; set; } +#endif + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfReal.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfReal.cs index 4e6b0e34..3f450ea1 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfReal.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfReal.cs @@ -15,19 +15,18 @@ public sealed class PdfReal : PdfNumber, IConvertible /// Initializes a new instance of the class. /// public PdfReal() - { - IsReal = true; - } + => IsReal = true; /// /// Initializes a new instance of the class. /// /// The value. - public PdfReal(double value) + public PdfReal(double value) : this() { + if (value is < Single.MinValue or > Single.MaxValue) - Debug.Assert(false); - IsReal = true; + throw new ArgumentException($"The value must fit in the range of type Single."); + Value = value; } @@ -43,7 +42,7 @@ public override string ToString() => Value.ToString(Config.SignificantDecimalPlaces3, CultureInfo.InvariantCulture); /// - /// Writes the real value with up to three digits. + /// Writes the real value with up to three digits. /// internal override void WriteObject(PdfWriter writer) => writer.Write(this); @@ -88,7 +87,7 @@ char IConvertible.ToChar(IFormatProvider? provider) long IConvertible.ToInt64(IFormatProvider? provider) => Convert.ToInt64(Value); - + /// /// Returns TypeCode for 32-bit integers. /// @@ -99,10 +98,7 @@ decimal IConvertible.ToDecimal(IFormatProvider? provider) => Convert.ToDecimal(Value); object IConvertible.ToType(Type conversionType, IFormatProvider? provider) - { - // TODO_OLD: Add PdfInteger.ToType implementation - return null!; - } + => throw new NotImplementedException("Conversion not implemented."); uint IConvertible.ToUInt32(IFormatProvider? provider) => Convert.ToUInt32(Value); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfRealObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfRealObject.cs index 4e9eb0be..3b5bc8db 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfRealObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfRealObject.cs @@ -15,16 +15,14 @@ public sealed class PdfRealObject : PdfNumberObject /// Initializes a new instance of the class. /// public PdfRealObject() - { } + => IsReal = true; /// /// Initializes a new instance of the class. /// /// The value. - public PdfRealObject(double value) - { - Value = value; - } + public PdfRealObject(double value) : this() + => Value = value; /// /// Initializes a new instance of the class. @@ -32,8 +30,24 @@ public PdfRealObject(double value) /// The document. /// The value. public PdfRealObject(PdfDocument document, double value) - : base(document) + : base(document, true) { + IsReal = true; + Value = value; + } + + /// + /// Initializes a new instance of the class + /// without making it indirect. + /// Used in PDF parser only. + /// + /// The document. + /// The initial value. + /// If true creates an indirect object. + internal PdfRealObject(PdfDocument document, double value, bool createIndirect) + : base(document, createIndirect) + { + IsReal = true; Value = value; } @@ -43,10 +57,15 @@ public PdfRealObject(PdfDocument document, double value) public double Value { get; set; } /// - /// Returns the real as a culture invariant string. + /// Returns the real as a culture invariant floating point string. /// - public override string ToString() - => Value.ToString(CultureInfo.InvariantCulture); + public override string ToString() + { + //=> Value.ToString(CultureInfo.InvariantCulture); + var f = (float)Value; // See unit test Test_Single_Write_and_Read for explanation. + return f.ToString(Config.SignificantDecimalPlaces7, CultureInfo.InvariantCulture); + } + /// /// Writes the real literal. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfRectangle.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfRectangle.cs index 1e6ef8fd..e09839b3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfRectangle.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfRectangle.cs @@ -1,40 +1,42 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; +using PdfSharp.Drawing; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.Internal; #if GDI using System.Drawing; #endif #if WPF using System.Windows; #endif -using System.CodeDom; -using PdfSharp.Drawing; -using PdfSharp.Pdf.Advanced; -using PdfSharp.Pdf.IO; -using PdfSharp.Pdf.Internal; + +// v7.0 Ready namespace PdfSharp.Pdf { /// - /// Represents an immutable PDF rectangle value. + /// Represents an immutable PDF rectangle object. /// In a PDF file it is represented by an array of four real values. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - public sealed class PdfRectangle : PdfItem + public sealed class PdfRectangle : PdfPrimitive { - // This reference type must behave like a value type. Therefore, it cannot be changed (like System.String). + // Reference 2.0: 3.8.4 Rectangles / Page 161 /// - /// Initializes a new instance of the PdfRectangle class with all values set to zero. + /// Initializes a new instance of the class with all values set to zero. /// public PdfRectangle() { } /// - /// Initializes a new instance of the PdfRectangle class with two points specifying - /// two diagonally opposite corners. Notice that in contrast to GDI+ convention the - /// 3rd and the 4th parameter specify a point and not a width. This is so much confusing - /// that this function is for internal use only. + /// Initializes a new instance of the class with two points specifying + /// two diagonally opposite corners. Notice that in contrast to GDI+, WPF, WinUI etc. convention + /// the 3rd and the 4th parameter specify a point and not a width. This is so much confusing that + /// this function is for internal use only. /// internal PdfRectangle(double x1, double y1, double x2, double y2) { @@ -46,7 +48,7 @@ internal PdfRectangle(double x1, double y1, double x2, double y2) #if GDI /// - /// Initializes a new instance of the PdfRectangle class with two points specifying + /// Initializes a new instance of the class with two points specifying /// two diagonally opposite corners. /// public PdfRectangle(PointF pt1, PointF pt2) @@ -60,7 +62,7 @@ public PdfRectangle(PointF pt1, PointF pt2) #if WPF /// - /// Initializes a new instance of the PdfRectangle class with two points specifying + /// Initializes a new instance of the class with two points specifying /// two diagonally opposite corners. /// public PdfRectangle(Point pt1, Point pt2) @@ -73,7 +75,7 @@ public PdfRectangle(Point pt1, Point pt2) #endif /// - /// Initializes a new instance of the PdfRectangle class with two points specifying + /// Initializes a new instance of the class with two points specifying /// two diagonally opposite corners. /// public PdfRectangle(XPoint pt1, XPoint pt2) @@ -86,7 +88,8 @@ public PdfRectangle(XPoint pt1, XPoint pt2) #if GDI /// - /// Initializes a new instance of the PdfRectangle class with the specified location and size. + /// Initializes a new instance of the class with the specified location + /// and size. /// public PdfRectangle(PointF pt, SizeF size) { @@ -98,7 +101,8 @@ public PdfRectangle(PointF pt, SizeF size) #endif /// - /// Initializes a new instance of the PdfRectangle class with the specified location and size. + /// Initializes a new instance of the class with the specified location + /// and size. /// public PdfRectangle(XPoint pt, XSize size) { @@ -114,7 +118,7 @@ public PdfRectangle(XPoint pt, XSize size) public PdfRectangle(XRect rect) { if (rect.IsEmpty) - throw new InvalidOperationException("Cannot create PdfRectangle from an empty XRect."); + throw new InvalidOperationException("Cannot create a PdfRectangle from an empty XRect."); X1 = rect.X; Y1 = rect.Y; @@ -123,19 +127,26 @@ public PdfRectangle(XRect rect) } /// - /// Initializes a new instance of the PdfRectangle class with the specified PdfArray. + /// Initializes a new instance of the class with the specified PdfArray. /// internal PdfRectangle(PdfItem item) { if (item is null or PdfNull) return; - if (item is PdfReference reference) - item = reference.Value; + PdfReference.Dereference(ref item); - var array = item as PdfArray; - if (array == null) - throw new InvalidOperationException(PsMsgs.UnexpectedTokenInPdfFile); + if (item is not PdfArray { Elements.Count: 4 } array) + { + // Handle special case PDF rectangle. + if (item is not PdfRectangle rect) + throw new InvalidOperationException(PsMsgs.UnexpectedTokenInPdfFile); // TODO Better message + X1 = rect.X1; + Y1 = rect.Y1; + X2 = rect.X2; + Y2 = rect.Y2; + return; + } X1 = array.Elements.GetReal(0); Y1 = array.Elements.GetReal(1); @@ -179,50 +190,49 @@ public override int GetHashCode() { // This code is from System.Drawing... return (int)(((((uint)X1) ^ ((((uint)Y1) << 13) | - (((uint)Y1) >> 0x13))) ^ ((((uint)X2) << 0x1a) | + (((uint)Y1) >> 19))) ^ ((((uint)X2) << 26) | (((uint)X2) >> 6))) ^ ((((uint)Y2) << 7) | - (((uint)Y2) >> 0x19))); + (((uint)Y2) >> 25))); } /// - /// Tests whether two structures have equal coordinates. + /// Tests whether two instances have equal coordinates. /// public static bool operator ==(PdfRectangle? left, PdfRectangle? right) { // ReSharper disable CompareOfFloatsByEqualityOperator - // use: if (Object.ReferenceEquals(left, null)) - if ((object?)left != null) + if (left is not null) { - if ((object?)right != null) + if (right is not null) return left.X1 == right.X1 && left.Y1 == right.Y1 && left.X2 == right.X2 && left.Y2 == right.Y2; return false; } - return (object?)right == null; + return right is null; // ReSharper restore CompareOfFloatsByEqualityOperator } /// - /// Tests whether two structures differ in one or more coordinates. + /// Tests whether two instances differ in one or more coordinates. /// public static bool operator !=(PdfRectangle? left, PdfRectangle? right) => !(left == right); /// - /// Gets or sets the x-coordinate of the first corner of this PdfRectangle. + /// Gets the x-coordinate of the first corner of this PdfRectangle. /// public double X1 { get; } /// - /// Gets or sets the y-coordinate of the first corner of this PdfRectangle. + /// Gets the y-coordinate of the first corner of this PdfRectangle. /// public double Y1 { get; } /// - /// Gets or sets the x-coordinate of the second corner of this PdfRectangle. + /// Gets the x-coordinate of the second corner of this PdfRectangle. /// public double X2 { get; } /// - /// Gets or sets the y-coordinate of the second corner of this PdfRectangle. + /// Gets the y-coordinate of the second corner of this PdfRectangle. /// public double Y2 { get; } @@ -237,12 +247,12 @@ public override int GetHashCode() public double Height => Y2 - Y1; /// - /// Gets or sets the coordinates of the first point of this PdfRectangle. + /// Gets the coordinates of the first point of this PdfRectangle. /// public XPoint Location => new(X1, Y1); /// - /// Gets or sets the size of this PdfRectangle. + /// Gets the size of this PdfRectangle. /// public XSize Size => new(X2 - X1, Y2 - Y1); @@ -302,15 +312,20 @@ public override string ToString() return PdfEncoders.Format("[{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "}]", X1, Y1, X2, Y2); } + internal PdfArray GetAsArrayOfValues() + // Always create a new copy of PdfArray because the returned array is not immutable. + => new(new PdfReal(X1), new PdfReal(Y1), new PdfReal(X2), new PdfReal(Y2)); + /// /// Writes the rectangle. /// - internal override void WriteObject(PdfWriter writer) => writer.Write(this); + internal override void WriteObject(PdfWriter writer) + => writer.Write(this); /// /// Represents an empty PdfRectangle. /// - [Obsolete("A rectangle defined by two points cannot be meaningfully defined as empty. Do not use this property.")] + [Obsolete("A rectangle defined by two points cannot be well-defined as empty. Do not use this property.")] public static PdfRectangle Empty => throw new InvalidOperationException("Use 'new PdfRectangle()' instead of 'PdfRectangle.Empty'"); /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfString.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfString.cs index a05fbdff..84a08d26 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfString.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfString.cs @@ -3,7 +3,6 @@ using System.Text; using Microsoft.Extensions.Logging; -using PdfSharp.Internal; using PdfSharp.Logging; using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Internal; @@ -85,16 +84,13 @@ enum PdfStringFlags /// Represents a direct text string value. /// [DebuggerDisplay("({" + nameof(Value) + "})")] - public sealed class PdfString : PdfItem + public sealed class PdfString : PdfPrimitive // #HEX_STRING_FIX DELETE { /// /// Initializes a new instance of the class. /// public PdfString() - { - // Redundant assignment. - //_flags = PdfStringFlags.RawEncoding; - } + { } /// /// Initializes a new instance of the class. @@ -102,22 +98,26 @@ public PdfString() /// The value. public PdfString(string value) { -#if true if (!IsRawEncoding(value)) _flags = PdfStringFlags.Unicode; _value = value; -#else - CheckRawEncoding(value); - _value = value; - //_flags = PdfStringFlags.RawEncoding; -#endif + } + + /// + /// Initializes a new instance of the class. + /// + /// The string value. + /// If set to true the string is written as hexadecimal string. + public PdfString(string value, bool hexString) : this(value) + { + _flags |= PdfStringFlags.HexLiteral; } /// /// Initializes a new instance of the class. /// /// The value. - /// The encoding. + /// The string value. public PdfString(string value, PdfStringEncoding encoding) { switch (encoding) @@ -151,6 +151,17 @@ public PdfString(string value, PdfStringEncoding encoding) _flags = (PdfStringFlags)encoding; } + /// + /// Initializes a new instance of the class. + /// + /// The string value. + /// The encoding. + /// If set to true the string is written as hexadecimal string. + public PdfString(string value, PdfStringEncoding encoding, bool hexString) : this(value) + { + _flags |= PdfStringFlags.HexLiteral; + } + internal PdfString(string? value, PdfStringFlags flags) { _value = value ?? ""; @@ -274,7 +285,7 @@ static bool TryRereadAsUnicode(ref string? value) return true; } - // UTF-16LE Unicode strings start with U+FFE ("ÿþ"). + // UTF-16LE Unicode strings start with U+FFFE ("ÿþ"). // Adobe Reader also supports UTF-16LE with BOM, so we do. if (value is ['\xFF', '\xFE', ..]) { @@ -328,6 +339,12 @@ public override string ToString() #endif } + /// + /// Get an empty PdfString. + /// + public static PdfString Empty => _empty ??= new PdfString(); + static PdfString? _empty; + #if true_ /// /// H/ack for document encoded bookmarks. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfStringObject.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfStringObject.cs index 3bda593b..16a70464 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfStringObject.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfStringObject.cs @@ -7,11 +7,11 @@ namespace PdfSharp.Pdf { /// - /// Represents an indirect text string value. This type is not used by PDFsharp. If it is imported from - /// an external PDF file, the value is converted into a direct object. + /// Represents an indirect text string value. This type is not created by PDFsharp. + /// If it is imported from an external PDF file, the value is converted into a direct object. /// [DebuggerDisplay("({" + nameof(Value) + "})")] - public sealed class PdfStringObject : PdfObject + public sealed class PdfStringObject : PdfPrimitiveObject // #HEX_STRING_FIX DELETE { /// /// Initializes a new instance of the class. @@ -21,13 +21,23 @@ public PdfStringObject() _flags = PdfStringFlags.RawEncoding; } + /// + /// Initializes a new instance of the class. + /// + /// The value. + public PdfStringObject(string value) + { + _value = value; + _flags = PdfStringFlags.RawEncoding; + } + /// /// Initializes a new instance of the class. /// /// The document. /// The value. public PdfStringObject(PdfDocument document, string value) - : base(document) + : base(document, true) { _value = value; _flags = PdfStringFlags.RawEncoding; @@ -41,19 +51,37 @@ public PdfStringObject(PdfDocument document, string value) public PdfStringObject(string value, PdfStringEncoding encoding) { _value = value; - //if ((flags & PdfStringFlags.EncodingMask) == 0) - // flags |= PdfStringFlags.PDFDocEncoding; _flags = (PdfStringFlags)encoding; } internal PdfStringObject(string value, PdfStringFlags flags) { _value = value; - //if ((flags & PdfStringFlags.EncodingMask) == 0) - // flags |= PdfStringFlags.PDFDocEncoding; _flags = flags; } +#if PRESERVE_PARSED_VALUES_ + /// + /// Gets or sets the string that was read from the lexer. + /// + internal string? ParsedValue { get; set; } +#endif + + /// + /// Initializes a new instance of the class + /// without making it indirect. + /// Used in PDF parser only. + /// + /// The document. + /// The initial value. + /// + internal PdfStringObject(PdfDocument document, string value, bool createIndirect) + : base(document, createIndirect) + { + _value = value; + _flags = PdfStringFlags.RawEncoding; + } + /// /// Gets the number of characters in this string. /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfUInteger.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfUInteger.cs index 86dfcc40..f2b216db 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfUInteger.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfUInteger.cs @@ -73,12 +73,12 @@ public sbyte ToSByte(IFormatProvider? provider) public double ToDouble(IFormatProvider? provider) => Value; /// - /// Returns an undefined DateTime structure. + /// Returns an undefined Date/Time structure. /// - public DateTime ToDateTime(IFormatProvider? provider) + public Date/Time ToDateTime(IFormatProvider? provider) { // TODO_OLD: Add PdfUInteger.ToDateTime implementation - return new DateTime(); + return new Date/Time(); } /// @@ -87,7 +87,7 @@ public DateTime ToDateTime(IFormatProvider? provider) public float ToSingle(IFormatProvider? provider) => Value; /// - /// Converts the value of this instance to an equivalent Boolean value. + /// Converts the value of this instance to an equivalent boolean value. /// public bool ToBoolean(IFormatProvider? provider) => Convert.ToBoolean(Value); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfViewerPreferences.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfViewerPreferences.cs index 427d6e4f..6877b3a7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfViewerPreferences.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfViewerPreferences.cs @@ -12,6 +12,14 @@ internal PdfViewerPreferences(PdfDocument document) : base(document) { } + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal PdfViewerPreferences(PdfDictionary dict) + : base(dict) + { } + ///// ///// Initializes a new instance of the class. ///// @@ -21,7 +29,7 @@ internal PdfViewerPreferences(PdfDocument document) /// /// Gets or sets a value indicating whether to hide the viewer application’s - /// tool bars when the document is active. + /// toolbars when the document is active. /// public bool HideToolbar { @@ -123,7 +131,7 @@ public PdfReadingDirection? Direction /// /// Predefined keys of this dictionary. /// - internal sealed class Keys : KeysBase + public sealed class Keys : KeysBase { /// /// (Optional) A flag specifying whether to hide the viewer application’s tool @@ -261,7 +269,7 @@ internal sealed class Keys : KeysBase /// /// Gets the KeysMeta for these keys. /// - public static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); static DictionaryMeta? _meta; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/TrimMargins.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/TrimMargins.cs index 6ca749f4..7d3d8eaf 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/TrimMargins.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/TrimMargins.cs @@ -1,7 +1,6 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Diagnostics; using PdfSharp.Drawing; namespace PdfSharp.Pdf @@ -13,7 +12,7 @@ namespace PdfSharp.Pdf public sealed class TrimMargins { /// - /// Sets all four crop margins simultaneously. + /// Sets all four crop margins simultaneously to the specified value. /// public XUnit All { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/ItemFlags.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/ItemFlags.cs new file mode 100644 index 00000000..369fdfbb --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/ItemFlags.cs @@ -0,0 +1,82 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf // #FOLDER /Pdf/enums +{ + /// + /// Internal flags for fast type and state testing. + /// + [Flags] + enum ItemFlags + { + // Note that these flags are used only internally in PDFsharp and therefore can + // safely be changed if needed. + + /// + /// No bit is set. The default value. + /// + Null = 0, + + // Type flags + + /// + /// Is PdfItem but not PdfObject. + /// + IsPrimitiveItem = 0b_00000000_0000_0001, + + /// + /// Is PdfObject, but not PdfArray or PdfDictionary. + /// + IsCompoundObject = 0b_00000000_0000_0010, + + /// + /// Is PdfArray. + /// + IsArray = 0b_00000000_0000_0100, + + /// + /// Is PdfDictionary. + /// + IsDictionary = 0b_0000_0000_0000_1000, + + /// + /// Is PdfArray or PdfDictionary. + /// + IsArrayOrDictionary = 0b_0000_1100, + + /// + /// + /// + //TypeMask____ = 0x0000_00FF, + //TypeMask = 0b00000000_00000000_00000000_11111111, + + // Object type transformation + + IsTransformed = 0b_1000_0000, // 0x0080 + TransformationWasTried = 0b_0100_0000, // 0x0040 + + /// + /// Bits used for transformation flags. + /// + TransformationMask = 0b_1100_0000, // 0xC0 + + /// + /// After an indirect container (PdfArray or PdfDictionary) is transformed into a derived class, + /// the object is considered to be dead. If user code contains C# references to such an object + /// PDFsharp can detect such objects. + /// + IsDead = 0b_0000_0001_0000_0000, + + /// + /// Marks a PdfReference during reading a PDF document. + /// + IsTempRef = 0b_0000_0010_0000_0000, + + /// + /// If you set a stream to a direct PdfDictionary, it must become an indirect object. + /// + MustBeIndirect = 0b_0000_0100_0000_0000, + + // IsProxyReference = 0b_0000_1000_0000_0000, // not used anymore + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/PdfFontColoredGlyphs.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/PdfFontColoredGlyphs.cs index ca151c6f..2fe2b3aa 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/PdfFontColoredGlyphs.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/PdfFontColoredGlyphs.cs @@ -21,7 +21,7 @@ public enum PdfFontColoredGlyphs /// Version0 = 1, - // Note: Version1 is also possible, but with significantly more code. Version 1 + // Note that Version1 is also possible, but with significantly more code. Version 1 // uses extended graphical capabilities like gradient brushes and much more. } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/PdfVersion.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/PdfVersion.cs new file mode 100644 index 00000000..4a9a7721 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/PdfVersion.cs @@ -0,0 +1,65 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf +{ + /// + /// Identifies the PDF version of a document. + /// NOT YET USED. + /// + [Flags] + // ReSharper disable InconsistentNaming because we want to use underscores in identifiers. + public enum PdfVersion + { + /// + /// PDF version is undefined. + /// + PDF_None = 0, + + /// + /// PDF version 1.0 (%PDF–1.0). + /// + PDF_1_0 = 10, + + /// + /// PDF version 1.1 (%PDF–1.1). + /// + PDF_1_1 = 11, + + /// + /// PDF version 1.2 (%PDF–1.2). + /// + PDF_1_2 = 12, + + /// + /// PDF version 1.3 (%PDF–1.3). + /// + PDF_1_3 = 13, + + /// + /// PDF version 1.4 (%PDF–1.4). + /// + PDF_1_4 = 14, + + /// + /// PDF version 1.5 (%PDF–1.5). + /// + PDF_1_5 = 15, + + /// + /// PDF version 1.6 (%PDF–1.6. + /// + PDF_1_6 = 16, + + /// + /// PDF version 1.7 (%PDF–1.7). + /// + PDF_1_7 = 17, + + /// + /// PDF version 2.0 (%PDF–2.0). + /// + PDF_2_0 = 20 + } + // ReSharper restore InconsistentNaming +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/VCF.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/VCF.cs new file mode 100644 index 00000000..e6cd4276 --- /dev/null +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/enums/VCF.cs @@ -0,0 +1,35 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Pdf +{ + /// + /// Value creation flags. Specifies whether and how a value that does not exist is created. + /// + // ReSharper disable InconsistentNaming + public enum VCF + // ReSharper restore InconsistentNaming + { + /// + /// Don’t create the value if it does not yet exist. + /// If the value exists try a type transformation. + /// + None, + + /// + /// Create the value as direct object if it does not yet exist. + /// + Create, + + /// + /// Create the value as indirect object if it does not yet exist. + /// + CreateIndirect, + + /// + /// If the value exists return it immediately. + /// Do not try a type transformation. + /// + NoTransform + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj b/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj index dd12565d..f7ff7380 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj +++ b/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj @@ -1,18 +1,20 @@  library - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp CORE + True ..\..\..\..\..\StrongnameKey.snk true true + $(NoWarn);2070 - true + True @@ -44,6 +46,8 @@ + + @@ -51,20 +55,32 @@ - - + For now we use the whole public key instead. />--> + + + + + + + + + + + + - - + + + + + + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj.DotSettings b/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj.DotSettings index 9f8b5819..a45d2bfc 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj.DotSettings +++ b/src/foundation/src/PDFsharp/src/PdfSharp/PdfSharp.csproj.DotSettings @@ -1,11 +1,25 @@  True + True True True True + False + True + True + True + False + True True True + True + True + True + True + True + True + True True True True diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Properties/GlobalDeclarations.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Properties/GlobalDeclarations.cs index 3b6d8a7d..ebd28509 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Properties/GlobalDeclarations.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Properties/GlobalDeclarations.cs @@ -1,9 +1,13 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +global using System.Diagnostics; +global using System.Diagnostics.CodeAnalysis; global using System.IO; -global using PdfSharp.Internal; - +global using static System.FormattableString; +#if !NET8_0_OR_GREATER +global using PdfSharp.DotNetFrameworkExtensions; +#endif #if USE_LONG_SIZE global using SizeType = System.Int64; @@ -11,17 +15,10 @@ global using SizeType = System.Int32; #endif -global using static System.FormattableString; - -using System.Diagnostics.CodeAnalysis; -//using System.Runtime.CompilerServices; -using System.Runtime.InteropServices; -[assembly: ComVisible(false)] -[assembly: SuppressMessage("LoggingGenerator", "SYSLIB1006:Multiple logging methods cannot use the same event ID within a class", - Justification = "We use logging event IDs as documented, i.e. multiple times", Scope = "member"/*, Target = "~M:PdfSharp.Internal.Logging.LogMessages.XGraphicsCreated(Microsoft.Extensions.Logging.ILogger,System.String)"*/)] +////using System.Runtime.CompilerServices; +//using System.Runtime.InteropServices; -// TODO_OLD We should add a WPF Preview panel -//#if WPF -//[assembly: XmlnsDefinition("http://schemas.empira.com/pdfsharp/2010/xaml/presentation", "PdfSharp.Windows")] -//#endif +//[assembly: ComVisible(false)] +//[assembly: SuppressMessage("LoggingGenerator", "SYSLIB1006:Multiple logging methods cannot use the same event ID within a class", +// Justification = "We use logging event IDs as documented, i.e. multiple times", Scope = "member"/*, Target = "~M:PdfSharp.Internal.Logging.LogMessages.XGraphicsCreated(Microsoft.Extensions.Logging.ILogger,System.String)"*/)] diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs index 1eb063b4..956e3494 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Properties/PdfSharpProductVersionInformation.cs @@ -3,6 +3,8 @@ //#pragma warning disable 0436 +using PdfSharp.Internal; + namespace PdfSharp { /// @@ -11,8 +13,8 @@ namespace PdfSharp // TODO_OLD: These literals are not in sync with the NuGet metadata. Should be fixed. public static class PdfSharpProductVersionInformation { - // Cannot use const string anymore because GitVersionInformation used static string. // The fields are reordered to take initialization chronology into account. + // Today this may be not necessary anymore. /// /// The title of the product. @@ -27,43 +29,48 @@ public static class PdfSharpProductVersionInformation /// /// The major version number of the product. /// - public static readonly string VersionMajor = PdfSharpGitVersionInformation.Major; + public static readonly string VersionMajor = SemVersionInformation.Major; /// /// The minor version number of the product. /// - public static readonly string VersionMinor = PdfSharpGitVersionInformation.Minor; + public static readonly string VersionMinor = SemVersionInformation.Minor; /// /// The patch number of the product. /// - public static readonly string VersionPatch = PdfSharpGitVersionInformation.Patch; + public static readonly string VersionPatch = SemVersionInformation.Patch; /// /// The Version PreRelease string for NuGet. /// - public static readonly string VersionPreRelease = PdfSharpGitVersionInformation.PreReleaseLabel; + public static readonly string VersionPreRelease = SemVersionInformation.PreReleaseLabel; /// /// The PDF creator application information string. /// - public static readonly string Creator = $"{Title} {PdfSharpGitVersionInformation.InformationalVersion}{Technology}"; + public static readonly string Creator = $"{Title} {SemVersionInformation.InformationalVersion}{Technology}"; /// /// The PDF producer (created by) information string. /// TODO_OLD: Called Creator in MigraDoc??? /// - public static readonly string Producer = $"{Title} {PdfSharpGitVersionInformation.InformationalVersion} ({Url})"; + public static readonly string Producer = $"{Title} {SemVersionInformation.InformationalVersion} ({Url})"; /// /// The full version number. /// - public static readonly string Version = PdfSharpGitVersionInformation.MajorMinorPatch; + public static readonly string Version = SemVersionInformation.Version; + + /// + /// The full file version number. + /// + public static readonly string FileVersion = SemVersionInformation.FileVersion; /// - /// The full semantic version number created by GitVersion. + /// The full semantic version number. /// - public static readonly string SemanticVersion = PdfSharpGitVersionInformation.SemVer; + public static readonly string SemanticVersion = SemVersionInformation.InformationalVersion; /// /// The home page of this product. @@ -88,7 +95,7 @@ public static class PdfSharpProductVersionInformation /// /// The copyright information. /// - public const string Copyright = "Copyright © 2005-2026 empira Software GmbH."; + public const string Copyright = "Copyright © 2005-2026 empira Software GmbH."; // Not used as NuGet Copyright. See Directory.Build.Props. /// /// The trademark of the product. @@ -105,99 +112,99 @@ public static class PdfSharpProductVersionInformation /// The calculated build number. /// // ReSharper disable RedundantNameQualifier - public static int BuildNumber = (DateTime.Now - new DateTime(2005, 1, 1)).Days; + public static int BuildNumber = (DateTimeOffset.Now - new DateTimeOffset(2005, 1, 1)).Days; // ReSharper restore RedundantNameQualifier #endif -//#if Not_used_anymore -// /// -// /// E.g. "2005-01-01", for use in NuGet Script. -// /// -// public const string VersionReferenceDate = "2005-01-01"; - -// /// -// /// Use _ instead of blanks and special characters. Can be complemented with a suffix in the NuGet Script. -// /// Nuspec Doc: The unique identifier for the package. This is the package name that is shown when packages -// /// are listed using the Package Manager Console. These are also used when installing a package using the -// /// Install-Package command within the Package Manager Console. Package IDs may not contain any spaces -// /// or characters that are invalid in a URL. In general, they follow the same rules as .NET namespaces do. -// /// So Foo.Bar is a valid ID, Foo! and Foo Bar are not. -// /// -// public const string NuGetID = "PDFsharp"; - -// /// -// /// Nuspec Doc: The human-friendly title of the package displayed in the Manage NuGet Packages dialog. -// /// If none is specified, the ID is used instead. -// /// -// public const string NuGetTitle = "PDFsharp"; - -// /// -// /// Nuspec Doc: A comma-separated list of authors of the package code. -// /// -// public const string NuGetAuthors = "empira Software GmbH"; - -// /// -// /// Nuspec Doc: A comma-separated list of the package creators. This is often the same list as in authors. -// /// This is ignored when uploading the package to the NuGet.org Gallery. -// /// -// public const string NuGetOwners = "empira Software GmbH"; - -// /// -// /// Nuspec Doc: A long description of the package. This shows up in the right pane of the Add Package Dialog -// /// as well as in the Package Manager Console when listing packages using the Get-Package command. -// /// -// // This assignment must be written in one line because it will be parsed from a PS1 file. -// public const string NuGetDescription = "PDFsharp is the Open Source .NET library that easily creates and processes PDF documents on the fly from any .NET language. The same drawing routines can be used to create PDF documents, draw on the screen, or send output to any printer."; - -// /// -// /// Nuspec Doc: A description of the changes made in each release of the package. This field only shows up -// /// when the _Updates_ tab is selected and the package is an update to a previously installed package. -// /// It is displayed where the Description would normally be displayed. -// /// -// public const string NuGetReleaseNotes = ""; - -// /// -// /// Nuspec Doc: A short description of the package. If specified, this shows up in the middle pane of the -// /// Add Package Dialog. If not specified, a truncated version of the description is used instead. -// /// -// public const string NuGetSummary = "A .NET library for processing PDF."; - -// /// -// /// Nuspec Doc: The locale ID for the package, such as en-us. -// /// -// public const string NuGetLanguage = ""; - -// /// -// /// Nuspec Doc: A URL for the home page of the package. -// /// -// /// -// /// http://www.pdfsharp.net/NuGetPackage_PDF/sharp-GDI.ashx -// /// http://www.pdfsharp.net/NuGetPackage_PDF/sharp-WPF.ashx -// /// -// public const string NuGetProjectUrl = "www.pdfsharp.com"; - -// /// -// /// Nuspec Doc: A URL for the image to use as the icon for the package in the Manage NuGet Packages -// /// dialog box. This should be a 32x32-pixel .png file that has a transparent background. -// /// -// public const string NuGetIconUrl = "http://www.pdf/sharp.net/resources/PDF/sharp-Logo-32x32.png"; - -// /// -// /// Nuspec Doc: A link to the license that the package is under. -// /// -// public const string NuGetLicenseUrl = "http://www.pdfsharp.net/PDF/sharp_License.ashx"; - -// /// -// /// Nuspec Doc: A Boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed. -// /// -// public const bool NuGetRequireLicenseAcceptance = false; - -// /// -// /// Nuspec Doc: A space-delimited list of tags and keywords that describe the package. This information is used to help make sure users can find the package using -// /// searches in the Add Package Reference dialog box or filtering in the Package Manager Console window. -// /// -// public const string NuGetTags = "PDFsharp PDF creation"; -//#endif +#if Not_used_anymore + /// + /// E.g. "2005-01-01", for use in NuGet Script. + /// + public const string VersionReferenceDate = "2005-01-01"; + + /// + /// Use _ instead of blanks and special characters. Can be complemented with a suffix in the NuGet Script. + /// Nuspec Doc: The unique identifier for the package. This is the package name that is shown when packages + /// are listed using the Package Manager Console. These are also used when installing a package using the + /// Install-Package command within the Package Manager Console. Package IDs may not contain any spaces + /// or characters that are invalid in an URL. In general, they follow the same rules as .NET namespaces do. + /// So Foo.Bar is a valid ID, Foo! and Foo Bar are not. + /// + public const string NuGetID = "PDFsharp"; + + /// + /// Nuspec Doc: The human-friendly title of the package displayed in the Manage NuGet Packages dialog. + /// If none is specified, the ID is used instead. + /// + public const string NuGetTitle = "PDFsharp"; + + /// + /// Nuspec Doc: A comma-separated list of authors of the package code. + /// + public const string NuGetAuthors = "empira Software GmbH"; + + /// + /// Nuspec Doc: A comma-separated list of the package creators. This is often the same list as in authors. + /// This is ignored when uploading the package to the NuGet.org Gallery. + /// + public const string NuGetOwners = "empira Software GmbH"; + + /// + /// Nuspec Doc: A long description of the package. This shows up in the right pane of the Add Package Dialog + /// as well as in the Package Manager Console when listing packages using the Get-Package command. + /// + // This assignment must be written in one line because it will be parsed from a PS1 file. + public const string NuGetDescription = "PDFsharp is the Open Source .NET library that easily creates and processes PDF documents on the fly from any .NET language. The same drawing routines can be used to create PDF documents, draw on the screen, or send output to any printer."; + + /// + /// Nuspec Doc: A description of the changes made in each release of the package. This field only shows up + /// when the _Updates_ tab is selected and the package is an update to a previously installed package. + /// It is displayed where the Description would normally be displayed. + /// + public const string NuGetReleaseNotes = ""; + + /// + /// Nuspec Doc: A short description of the package. If specified, this shows up in the middle pane of the + /// Add Package Dialog. If not specified, a truncated version of the description is used instead. + /// + public const string NuGetSummary = "A .NET library for processing PDF."; + + /// + /// Nuspec Doc: The locale ID for the package, such as en-us. + /// + public const string NuGetLanguage = ""; + + /// + /// Nuspec Doc: A URL for the home page of the package. + /// + /// + /// http://www.pdfsharp.net/NuGetPackage_PDFsharp-GDI.ashx + /// http://www.pdfsharp.net/NuGetPackage_PDFsharp-WPF.ashx + /// + public const string NuGetProjectUrl = "www.pdfsharp.net"; + + /// + /// Nuspec Doc: A URL for the image to use as the icon for the package in the Manage NuGet Packages + /// dialog box. This should be a 32x32-pixel .png file that has a transparent background. + /// + public const string NuGetIconUrl = "http://www.pdfsharp.net/resources/PDFsharp-Logo-32x32.png"; + + /// + /// Nuspec Doc: A link to the license that the package is under. + /// + public const string NuGetLicenseUrl = "http://www.pdfsharp.net/PDFsharp_License.ashx"; + + /// + /// Nuspec Doc: A Boolean value that specifies whether the client needs to ensure that the package license (described by licenseUrl) is accepted before the package is installed. + /// + public const bool NuGetRequireLicenseAcceptance = false; + + /// + /// Nuspec Doc: A space-delimited list of tags and keywords that describe the package. This information is used to help make sure users can find the package using + /// searches in the Add Package Reference dialog box or filtering in the Package Manager Console window. + /// + public const string NuGetTags = "PDFsharp PDF creation"; +#endif /// /// The technology tag of the product: diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility.Drawing/XGraphicsExtensions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility.Drawing/XGraphicsExtensions.cs index 767d196f..6ac2751e 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility.Drawing/XGraphicsExtensions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility.Drawing/XGraphicsExtensions.cs @@ -348,7 +348,7 @@ static UAManager GetUAManager(XGraphics gfx) if (page == null) throw new InvalidOperationException("Graphics object must belong to a PDF document page."); - var uaManager = page.Owner._uaManager; // HACK_OLD: Should be a property + var uaManager = page.Owner.UAManager; // HACK_OLD: Should be a property if (uaManager == null) throw new InvalidOperationException("Document is not a PDF/UA document."); return uaManager; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/StructureBuilder.cs b/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/StructureBuilder.cs index edc7b03c..33da4edf 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/StructureBuilder.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/StructureBuilder.cs @@ -252,7 +252,7 @@ PdfStructureElement CreateStructureElement(string tag) var ste = UaManager.Owner.Internals.CreateIndirectObject(); // Set parent ... - ste.Elements.SetReference(PdfStructureElement.Keys.P, parent); + ste.Elements.SetObject(PdfStructureElement.Keys.P, parent); // ...and structure type. ste.Elements.SetName(PdfStructureElement.Keys.S, tag); @@ -290,7 +290,7 @@ void AddMarkedContentToStructureElement(PdfStructureElement ste, int mcid) // Set the Page of this StructureElement, if not yet set. var stePage = ste.Elements.GetReference(PdfStructureElement.Keys.Pg); if (stePage == null) - ste.Elements.SetReference(PdfStructureElement.Keys.Pg, UaManager.CurrentPage); + ste.Elements.SetObject(PdfStructureElement.Keys.Pg, UaManager.CurrentPage); // Is the added Marked Content on the Page, the StructureElement began? if (stePage == null || stePage.Value == UaManager.CurrentPage) @@ -302,7 +302,7 @@ void AddMarkedContentToStructureElement(PdfStructureElement ste, int mcid) { // No. Add a MarkedContentReference that contains the Page of this Marked Content and the MCID. var mcr = new PdfMarkedContentReference(); - mcr.Elements.SetReference(PdfMarkedContentReference.Keys.Pg, UaManager.CurrentPage); + mcr.Elements.SetObject(PdfMarkedContentReference.Keys.Pg, UaManager.CurrentPage); mcr.Elements.SetInteger(PdfMarkedContentReference.Keys.MCID, mcid); steKids.Elements.Add(mcr); } @@ -378,7 +378,7 @@ void AddToParentTree(PdfStructureElement ste, int mcid) /// /// Adds the structure element to the ParentTree. - /// Sets the annotation’s "/StructParent" key to the index of the structure element in the ParentTree. + /// Sets the annotation’s /StructParent key to the index of the structure element in the ParentTree. /// /// The structure element to be added to the parent tree. /// The annotation to be added. @@ -421,7 +421,7 @@ void AddToParentTree(PdfStructureElement ste, PdfAnnotation annotation) var count = parentTreeRootNums.Elements.Count; var lastKey = count > 0 ? parentTreeRootNums.Elements[count - 2] as PdfInteger : new PdfInteger(-1); - Debug.Assert(lastKey != null && lastKey.Value + 1 == structParents.Value, "The values should be continous."); + Debug.Assert(lastKey != null && lastKey.Value + 1 == structParents.Value, "The values should be continuous."); parentTreeRoot.AddNumber(structParents.Value, ste); } @@ -455,8 +455,8 @@ void AddAnnotationToStructureElement(PdfStructureElement ste, PdfAnnotation anno } var objr = new PdfObjectReference(); - objr.Elements.SetReference(PdfObjectReference.Keys.Obj, annotation); - objr.Elements.SetReference(PdfObjectReference.Keys.Pg, page); + objr.Elements.SetObject(PdfObjectReference.Keys.Obj, annotation); + objr.Elements.SetObject(PdfObjectReference.Keys.Pg, page); steK.Elements.Add(objr); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/UAManager.cs b/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/UAManager.cs index 8dd4f1f2..092dcf67 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/UAManager.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/UAManager.cs @@ -5,6 +5,7 @@ using PdfSharp.Drawing; using PdfSharp.Events; using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Internal; using PdfSharp.Pdf.Structure; namespace PdfSharp.UniversalAccessibility @@ -14,58 +15,58 @@ namespace PdfSharp.UniversalAccessibility /// By using its StructureBuilder, you can easily build up the structure tree to give hints to screen readers about how to read the document. /// // ReSharper disable once InconsistentNaming - public class UAManager + public class UAManager : ManagerBase { /// /// Initializes a new instance of the class. /// /// The PDF document. - UAManager(PdfDocument document) + UAManager(PdfDocument document) : base(document) { //document._uaManager = this; done in ForDocument - _document = document; + //Document = document; // Set default language to English. SetDocumentLanguage("en"); // DisplayDocTitle must be true. - _document.ViewerPreferences.DisplayDocTitle = true; + Document.ViewerPreferences.DisplayDocTitle = true; - var internals = _document.Internals; + var internals = Document.Internals; - _document.Events.PageAdded += OnPageAdded; - _document.Events.PageRemoved += OnPageRemoved; - _document.Events.PageGraphicsCreated += OnPageGraphicsCreated; - _document.Events.PageGraphicsAction += OnPageGraphicsAction; + Document.Events.PageAdded += OnPageAdded; + Document.Events.PageRemoved += OnPageRemoved; + Document.Events.PageGraphicsCreated += OnPageGraphicsCreated; + Document.Events.PageGraphicsAction += OnPageGraphicsAction; // Marked must be true in MarkInfo. var markInfo = new PdfMarkInformation(); internals.AddObject(markInfo); markInfo.Elements.SetBoolean(PdfMarkInformation.Keys.Marked, true); - internals.Catalog.Elements.SetReference(PdfCatalog.Keys.MarkInfo, markInfo); + internals.Catalog.Elements.SetObject(PdfCatalog.Keys.MarkInfo, markInfo); // Build Structure Tree. StructureTreeRoot = new PdfStructureTreeRoot(); internals.AddObject(StructureTreeRoot); - internals.Catalog.Elements.SetReference(PdfCatalog.Keys.StructTreeRoot, StructureTreeRoot); + internals.Catalog.Elements.SetObject(PdfCatalog.Keys.StructTreeRoot, StructureTreeRoot); // Set parent tree root. var parentTreeRoot = new PdfNumberTreeNode(true); - _document.Internals.AddObject(parentTreeRoot); - StructureTreeRoot.Elements.SetReference(PdfStructureTreeRoot.Keys.ParentTree, parentTreeRoot); + Document.Internals.AddObject(parentTreeRoot); + StructureTreeRoot.Elements.SetObject(PdfStructureTreeRoot.Keys.ParentTree, parentTreeRoot); // Child node for Document is recommended. - StructureTreeElementDocument = new PdfStructureElement(_document); - _document.Internals.AddObject(StructureTreeElementDocument); + StructureTreeElementDocument = new PdfStructureElement(Document); + Document.Internals.AddObject(StructureTreeElementDocument); // Parent is root. - StructureTreeElementDocument.Elements.SetReference(PdfStructureElement.Keys.P, StructureTreeRoot); + StructureTreeElementDocument.Elements.SetObject(PdfStructureElement.Keys.P, StructureTreeRoot); // Type is document. StructureTreeElementDocument.Elements.SetName(PdfStructureElement.Keys.S, "/Document"); - StructureTreeRoot.Elements.SetReference(PdfStructureElement.Keys.K, StructureTreeElementDocument); + StructureTreeRoot.Elements.SetObject(PdfStructureElement.Keys.K, StructureTreeElementDocument); } /// @@ -81,8 +82,8 @@ public class UAManager /// /// Gets or creates the Universal Accessibility Manager for the specified document. /// - public static UAManager ForDocument(PdfDocument document) - => document._uaManager ??= new UAManager(document); + public static UAManager ForDocument(PdfDocument document) + => document.UAManager ??= new(document); /// /// Gets the structure builder. @@ -139,8 +140,7 @@ void OnPageGraphicsAction(object sender, PageGraphicsEventArgs e) /// /// Gets the owning document for this UAManager. /// - public PdfDocument Owner => _document; - readonly PdfDocument _document; + public PdfDocument Owner => Document; /// /// Gets the current page. @@ -155,25 +155,25 @@ void OnPageGraphicsAction(object sender, PageGraphicsEventArgs e) /// /// Sets the language of the document. /// - public void SetDocumentLanguage(string lang) - => _document.Internals.Catalog.Language = lang; + public void SetDocumentLanguage(string lang) + => Document.Internals.Catalog.Language = lang; /// /// Sets the text mode. /// - public void BeginTextMode() + public void BeginTextMode() => PdfRendererExtensions.BeginTextMode(CurrentGraphics); /// /// Sets the graphic mode. /// - public void BeginGraphicMode() + public void BeginGraphicMode() => PdfRendererExtensions.BeginGraphicMode(CurrentGraphics); /// /// Determine if renderer is in Text mode or Graphic mode. /// - public bool IsInTextMode() + public bool IsInTextMode() => PdfRendererExtensions.IsInTextMode(CurrentGraphics); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Windows/enums/Zoom.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Windows/enums/Zoom.cs index 9af55946..f7b45ca0 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Windows/enums/Zoom.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Windows/enums/Zoom.cs @@ -11,7 +11,7 @@ public enum Zoom /// /// The smallest possible zoom factor. /// - Mininum = 10, + Minimum = 10, /// /// The largest possible zoom factor. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/root/Capabilities.cs b/src/foundation/src/PDFsharp/src/PdfSharp/root/Capabilities.cs index cc187547..6b875f9d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/root/Capabilities.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/root/Capabilities.cs @@ -156,7 +156,7 @@ public static string Framework => "8.0"; #elif NET7_0_OR_GREATER => "7.0"; -#elif NET6_0_OR_GREATER +#elif NET8_0_OR_GREATER => "6.0"; #elif NET481 => "4.8"; @@ -185,29 +185,29 @@ public static class OperatingSystem /// public static bool IsWindows => _isWindows ??= RuntimeInformation.IsOSPlatform(OSPlatform.Windows); - private static bool? _isWindows; + static bool? _isWindows; /// /// Indicates whether the current application is running on Linux. /// public static bool IsLinux => _isLinux ??= RuntimeInformation.IsOSPlatform(OSPlatform.Linux); - private static bool? _isLinux; + static bool? _isLinux; /// /// Indicates whether the current application is running on OSX. /// public static bool IsOSX => _isOSX ??= RuntimeInformation.IsOSPlatform(OSPlatform.OSX); - private static bool? _isOSX; + static bool? _isOSX; -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER /// /// Indicates whether the current application is running on FreeBSD. /// public static bool IsFreeBSD => _isFreeBSD ??= RuntimeInformation.IsOSPlatform(OSPlatform.FreeBSD); - private static bool? _isFreeBSD; + static bool? _isFreeBSD; #endif /// @@ -218,7 +218,7 @@ public static class OperatingSystem public static bool IsWsl2 => _isWsl2 ??= IsLinux && Environment.GetEnvironmentVariable("WSL_DISTRO_NAME") != null; - private static bool? _isWsl2; + static bool? _isWsl2; // New implementation checking Environment.GetEnvironmentVariable("WSL_DISTRO_NAME"). // Checking WSL_DISTRO_NAME seems to be the best way to check whether we are under WSL2. #else @@ -256,7 +256,7 @@ public static string OSAbbreviation return "LNX"; if (IsOSX) return "OSX"; -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER if (IsFreeBSD) return "BSD"; #endif diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/root/PageSizeConverter.cs b/src/foundation/src/PDFsharp/src/PdfSharp/root/PageSizeConverter.cs index df00af40..b77a8cfd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/root/PageSizeConverter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/root/PageSizeConverter.cs @@ -15,7 +15,7 @@ namespace PdfSharp /// /// Converter from to . /// - public static class PageSizeConverter + public static class PageSizeConverter // TODO: See class Calc. { /// /// Converts the specified page size enumeration to a pair of values in point. diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests-gdi/PdfSharp.Tests-gdi.csproj b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests-gdi/PdfSharp.Tests-gdi.csproj index 52fc24c9..70fa12b8 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests-gdi/PdfSharp.Tests-gdi.csproj +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests-gdi/PdfSharp.Tests-gdi.csproj @@ -1,14 +1,14 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Tests_Windows) true true - GDI + $(DefineConstants);GDI - CS1685,CS0436 + $(NoWarn);1685;0436 True ..\..\..\..\..\StrongnameKey.snk @@ -28,8 +28,12 @@ - + + + + + @@ -49,14 +53,7 @@ - - - - - - - @@ -76,8 +73,4 @@ - - - - diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Basics/SmokeTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Basics/SmokeTests.cs new file mode 100644 index 00000000..fb94d417 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Basics/SmokeTests.cs @@ -0,0 +1,171 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Diagnostics; +using PdfSharp.Drawing; +using PdfSharp.Drawing.Layout; +using PdfSharp.Fonts; +using PdfSharp.Pdf; +using PdfSharp.Quality; +using System.Drawing; +using Xunit; + +namespace PdfSharp.Tests.Basics +{ + [Collection("PDFsharp")] + public class SmokeTests : IDisposable + { + readonly string _tempRoot = "unit-tests/" + typeof(SmokeTests).Namespace + "/"; + + public SmokeTests() + { + PdfSharpCore.ResetAll(); +#if CORE + GlobalFontSettings.FontResolver = new UnitTestFontResolver(); +#endif + } + + public void Dispose() + { + PdfSharpCore.ResetAll(); + } + + [Fact] + public void Create_Hello_World_PDF() + { + // Create a new PDF document. + var document = new PdfDocument(); + document.Info.Title = "Created with PDFsharp"; + + // Create an empty page in this document. + var page = document.AddPage(); + + // Get an XGraphics object for drawing on this page. + var gfx = XGraphics.FromPdfPage(page); + + // Draw two lines with a red default pen. + var width = page.Width.Point; + var height = page.Height.Point; + gfx.DrawLine(XPens.Red, 0, 0, width, height); + gfx.DrawLine(XPens.Red, width, 0, 0, height); + + // Draw a circle with a red pen which is 1.5 point thick. + var r = width / 5; + gfx.DrawEllipse(new XPen(XColors.Red, 1.5), XBrushes.White, + new XRect(width / 2 - r, height / 2 - r, 2 * r, 2 * r)); + + // Create a font. + var font = new XFont("Times New Roman", 20, XFontStyleEx.BoldItalic); + + // Draw the text. + gfx.DrawString("Hello, World!", font, XBrushes.Black, + new XRect(0, 0, width, height), XStringFormats.Center); + + // Save the document… + string filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/PDF/creation/HelloWorld"); + document.Save(filename); + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(filename); + } + + [Fact] + public void Check_PDF_Options() + { + const string text = """ + Facin exeraessisit la consenim iureet dignibh eu facilluptat vercil dunt autpat. + Ecte magna faccum dolor sequisc iliquat, quat, quipiss equipit accummy niate magna + facil iure eraesequis am velit, quat atis dolore dolent luptat nulla adio odipissectet + lan venis do essequatio conulla facillandrem zzriusci bla ad minim inis nim velit eugait + aut aut lor at ilit ut nulla ate te eugait alit augiamet ad magnim iurem il eu feuissi. + + Guer sequis duis eu feugait luptat lum adiamet, si tate dolore mod eu facidunt adignisl in + henim dolorem nulla faccum vel inis dolutpatum iusto od min ex euis adio exer sed del + dolor ing enit veniamcon vullutat praestrud molenis ciduisim doloborem ipit nulla consequisi. + + Nos adit pratetu eriurem delestie del ut lumsandreet nis exerilisit wis nos alit venit praestrud + dolor sum volore facidui blaor erillaortis ad ea augue corem dunt nis iustinciduis euisi. + Ut ulputate volore min ut nulpute dolobor sequism olorperilit autatie modit wisl illuptat dolore + min ut in ute doloboreet ip ex et am dunt at. + """; + + int[] results = new int[3]; + + for (int run = 0; run < 3; ++run) + { + // Create a new PDF document. + var document = new PdfDocument(); + document.Info.Title = "Created with PDFsharp"; + + switch (run) + { + case 0: + document.Options.CompressContentStreams = false; + break; + case 1: + document.Options.CompressContentStreams = true; + break; + case 2: + document.Options.CompressContentStreams = true; + document.Options.FlateEncodeMode = PdfFlateEncodeMode.BestCompression; + break; + + default: + throw new NotSupportedException("Not yet implemented."); + } + + // Create an empty page in this document. + var page = document.AddPage(); + + // Get an XGraphics object for drawing on this page. + var gfx = XGraphics.FromPdfPage(page); + + var font = new XFont("Times New Roman", 10, XFontStyleEx.Bold); + var tf = new XTextFormatter(gfx); + + var rect = new XRect(40, 100, 250, 232); + gfx.DrawRectangle(XBrushes.SeaShell, rect); + //tf.Alignment = ParagraphAlignment.Left; + tf.DrawString(text, font, XBrushes.Black, rect, XStringFormats.TopLeft); + + rect = new XRect(310, 100, 250, 232); + gfx.DrawRectangle(XBrushes.SeaShell, rect); + tf.Alignment = XParagraphAlignment.Right; + tf.DrawString(text, font, XBrushes.Black, rect, XStringFormats.TopLeft); + + rect = new XRect(40, 400, 250, 232); + gfx.DrawRectangle(XBrushes.SeaShell, rect); + tf.Alignment = XParagraphAlignment.Center; + tf.DrawString(text, font, XBrushes.Black, rect, XStringFormats.TopLeft); + + rect = new XRect(310, 400, 250, 232); + gfx.DrawRectangle(XBrushes.SeaShell, rect); + tf.Alignment = XParagraphAlignment.Justify; + tf.DrawString(text, font, XBrushes.Black, rect, XStringFormats.TopLeft); + + using (var stream = new MemoryStream()) + { + document.Save(stream); + results[run] = (int)stream.Length; + } +#if false + // Save the document… + string filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/PDF/creation/HelloWorld"); + document.Save(filename); + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(filename); +#endif + } + + results[0].Should().BeGreaterThan(results[1], "File with compressed content streams must be smaller."); + results[0].Should().BeGreaterThan(results[2], "File with compressed content streams must be smaller."); +#if NET6_0_OR_GREATER + // Smaller files with .NET 6 or greater. + results[1].Should().BeGreaterThan(results[2], "File with best compression must be smaller than file with standard compression."); +#else + // Same results with .NET Framework 4. + results[1].Should().BeGreaterThanOrEqualTo(results[2], "File with best compression must not be larger than file with standard compression."); +#endif + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CSharpFeaturesTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CSharpFeaturesTests.cs index b25944ea..a9ecc25c 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CSharpFeaturesTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CSharpFeaturesTests.cs @@ -1,13 +1,15 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using FluentAssertions; +using System.Globalization; using PdfSharp.Drawing; using PdfSharp.Fonts; using PdfSharp.Pdf; +using PdfSharp; using PdfSharp.Snippets.Font; using PdfSharp.TestHelper; using Xunit; +using FluentAssertions; #if true @@ -23,6 +25,14 @@ namespace PdfSharp.Tests.Build [Collection("PDFsharp")] public class CSharpFeaturesTests { + + [Fact] + public void Test_String_Extensions() // C# 14 + { + "Hello".NullOrEmpty.Should().BeFalse(); + ((String)null!).NullOrEmpty.Should().BeTrue(); + } + [Fact] public void Range_and_Index() // C# 8 { @@ -81,12 +91,228 @@ public void Collection_expressions() // C# 12 twoDFromVariables.Length.Should().Be(3); twoDFromVariables[1].Length.Should().Be(3); } + + //[System.Runtime.CompilerServices.InlineArray(10)] ??? does not compile ??? + //public struct Buffer + //{ + // int _element0; + //} + + [Fact] + public void Everything_you_always_wanted_to_know_about_type_compatibility() + { + // Works like expected… + + // ReSharper disable CanSimplifyIsInstanceOfType + + // Note that “typeof(A).IsInstanceOfType(new B())” <=> “new B() is A” + + var result11 = typeof(A).IsInstanceOfType(new A()); + result11.Should().BeTrue(); + var result12 = typeof(A).IsInstanceOfType(new B()); + result12.Should().BeTrue(); + var result13 = typeof(B).IsInstanceOfType(new A()); + result13.Should().BeFalse(); + var result14 = typeof(A).IsInstanceOfType((object)new C()); // Expression is always false. + result14.Should().BeFalse(); + var result15 = typeof(C).IsInstanceOfType((object)new A()); // Expression is always false. + result15.Should().BeFalse(); + + // ReSharper restore CanSimplifyIsInstanceOfType + + var result21 = typeof(A).IsSubclassOf(typeof(A)); + result21.Should().BeFalse(); + var result22 = typeof(A).IsSubclassOf(typeof(B)); + result22.Should().BeFalse(); + var result23 = typeof(B).IsSubclassOf(typeof(A)); + result23.Should().BeTrue(); + var result24 = typeof(A).IsSubclassOf(typeof(C)); + result24.Should().BeFalse(); + var result25 = typeof(C).IsSubclassOf(typeof(A)); + result25.Should().BeFalse(); + + var result31 = typeof(A).IsAssignableFrom(typeof(A)); + result31.Should().BeTrue(); + var result32 = typeof(A).IsAssignableFrom(typeof(B)); + result32.Should().BeTrue(); + var result33 = typeof(B).IsAssignableFrom(typeof(A)); + result33.Should().BeFalse(); + var result34 = typeof(A).IsAssignableFrom(typeof(C)); + result34.Should().BeFalse(); + var result35 = typeof(C).IsAssignableFrom(typeof(A)); + result35.Should().BeFalse(); + +#if NET8_0_OR_GREATER + var result41 = typeof(A).IsAssignableTo(typeof(A)); + result41.Should().BeTrue(); + var result42 = typeof(A).IsAssignableTo(typeof(B)); + result42.Should().BeFalse(); + var result43 = typeof(B).IsAssignableTo(typeof(A)); + result43.Should().BeTrue(); + var result44 = typeof(A).IsAssignableTo(typeof(C)); + result44.Should().BeFalse(); + var result45 = typeof(C).IsAssignableTo(typeof(A)); + result45.Should().BeFalse(); +#endif + } + + [Fact] + public void Test_format_strings() + { + var zero = 0d; + var a = String.Format(CultureInfo.InvariantCulture, "{0}", 0d); + var b = String.Format(CultureInfo.InvariantCulture, "{0}", -0d); + var c = String.Format(CultureInfo.InvariantCulture, "{0:0;0;0}", -zero); + var d = String.Format(CultureInfo.InvariantCulture, "{0:0;0}", -(-7 - (-7))); + var e = String.Format(CultureInfo.InvariantCulture, "{0}", -(-7d - -7d)); + var f = String.Format(CultureInfo.InvariantCulture, "{0:0;0}", -(-7d - -7d)); + var g = String.Format(CultureInfo.InvariantCulture, "{0:0;0;0}", -(-7d - -7d)); + + bool b1 = zero == -zero; + bool b2 = zero.Equals(-zero); + } + + [Fact] + public void Test_new_field_keyword() + { + var x = Hours; + var y = PropA; + } + + public A PropA + { + get => field; //??= new A(); + set + { + field = value; + } + } = new A(); + + public double Hours + { + get; + set + { + field = (value >= 0) + ? value + : throw new ArgumentOutOfRangeException(nameof(value), "The value must not be negative"); + } + } + + public class A + { } + + class B : A + { } + + class C + { } + +#if true_ + [Fact] + public void Generic_function_with_nullable_enum_values() + { + MyEnum e1 = default; + MyEnum e2 = MyEnum.Foo; + MyEnum? e3 = null; + MyEnum? e4 = default; + MyEnum? e5 = MyEnum.Foo; + + MyEnum r11 = GetEnum1(e1); + //MyEnum? r12 = GetEnum1(e3); // does not compile, OK + MyEnum? r12 = GetEnum1((MyEnum)e3); // does not compile, OK + //var r13 = GetEnum1(42); // does not compile, OK + + MyEnum? r21 = GetEnum2(e1); + MyEnum? r22 = GetEnum2(e3); + var r23 = GetEnum2(42); + var r24 = GetEnum2(null); + } + + enum MyEnum { Foo = 42 } + + TEnum? GetEnum1(TEnum? value) where TEnum : System.Enum + { + //_ = value.HasValue; // Works, because v is a value type. + //_ = value!.Value; // Works, because v is a value type. + //return null; + //return (TEnum?)(object?)null; // compiles, but crashes at runtime + return default; // return always 0 + return value; // return never null because value cannot be null. + } + + TEnum? GetEnum2(TEnum? value) where TEnum : struct, Enum + { + _ = value.HasValue; // Works, because v is a value type. + //_ = value!.Value; // Works, because v is a value type. + return null; + } +#endif + + [Fact] + public void Test_extension_properties() + { + var s = "Hello, World!"; + var len = s.GetTheLength; + len.Should().Be(13); + + var s2 = "Hello, World" + "!" * 3; + s2.Should().Be("Hello, World!!!"); + + double inches = 3; + var points = inches.InchToPoint; + points.Should().Be(3 * 72); + + inches.InchToPoint = 144; + inches.Should().Be(2); + + char[] chars = ['a', 'b', 'c', 'd']; + var c = chars.LastItem; + c.Should().Be('d'); + chars.LastItem = 'z'; + var c2 = chars.LastItem; + c2.Should().Be('z'); + + chars.LastItem -= (char)1; + var c3 = chars.LastItem; + c3.Should().Be('y'); + } } - //[System.Runtime.CompilerServices.InlineArray(10)] ??? does not compile ??? - //public struct Buffer - //{ - // private int _element0; - //} + /// + /// Extension property for tests. + /// + public static class TestExtensions + { + extension(String str) + { + public int GetTheLength => + str.Length; + + public static string operator *(string source, int count) + { + return string.Concat(Enumerable.Repeat(source, count)); + } + } + + extension(ref double d) + { + // Simple unit conversion helper. Needs "ref" above. + public double InchToPoint + { + get { return d * 72; } + set { d = value / 72; } + } + } + + extension(char[] chars) + { + public char LastItem + { + get { return chars[^1]; } + set { chars[^1] = value; } + } + } + } } #endif diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CheckPdfDictionaryClassesTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CheckPdfDictionaryClassesTests.cs new file mode 100644 index 00000000..446e8ad2 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/CheckPdfDictionaryClassesTests.cs @@ -0,0 +1,113 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#if WPF +using System.IO; +#endif +using FluentAssertions; +using PdfSharp.Drawing; +using PdfSharp.Fonts; +using PdfSharp.Internal; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Internal; +using PdfSharp.Quality; +using PdfSharp.Snippets.Font; +using PdfSharp.TestHelper; +using System.Diagnostics; +using System.Reflection; +using System.Text; +using PdfSharp.Pdf.Advanced; +using Xunit; + +namespace PdfSharp.Tests.Build +{ + [Collection("PDFsharp")] + public class CheckPdfDictionaryClassesTests + + { + //[Fact(Skip = "Skip for now.")] + [Fact] + public void Check_classes_derived_from_PdfDictionary() + { + var classList = FindAllDerivedTypes(); + + var sbOK = new StringBuilder(); + var sbPublic = new StringBuilder(); + var sbNotFound = new StringBuilder(); + var sbClassNotPublic = new StringBuilder(); + + int notFound = 0; + int notNotPublic = 0; + int classNotPublic = 0; + + foreach (var type in classList) + { + if (!type.IsPublic) + { + sbClassNotPublic.AppendLine(type.Name); + ++classNotPublic; + } + + // Try to get constructor with signature 'Ctor(PdfDictionary)'. + var ctorInfo = type.GetConstructor(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [typeof(PdfDictionary)], null); + + var ctors = type.GetConstructors(BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic); + var found = false; + + if (ctorInfo != null) + { + found = true; + if (ctorInfo.IsPublic) + { + sbPublic.AppendLine(type.Name); + ++notNotPublic; + } + else + sbOK.AppendLine(type.Name); + } + + foreach (var ctor in ctors) + { + var param = ctor.GetParameters(); + if (param.Length == 1 && param[0].ParameterType == typeof(PdfDictionary)) + { + found = true; + if (ctor.IsPublic) + { + sbPublic.AppendLine(type.Name); + ++notNotPublic; + } + else + sbOK.AppendLine(type.Name); + } + } + + if (!found) + { + sbNotFound.AppendLine(type.Name); + ++notFound; + } + } + + var list1 = sbOK.ToString(); + var list2 = sbPublic.ToString(); + var list3 = sbNotFound.ToString(); + var list4 = sbClassNotPublic.ToString(); + notFound.Should().Be(0, "All classes derived from PdfDictionary should have a c'tor that takes a PdfDictionary."); + notNotPublic.Should().Be(0, "All classes derived from PdfDictionary should have an internal c'tor that takes a PdfDictionary."); + classNotPublic.Should().Be(0, "All classes derived from PdfDictionary should be public."); + } + + public static List FindAllDerivedTypes() + { + return FindAllDerivedTypes(Assembly.GetAssembly(typeof(T))!); + } + + public static List FindAllDerivedTypes(Assembly assembly) + { + var baseType = typeof(T); + return assembly.GetTypes().Where(t => t != baseType && baseType.IsAssignableFrom(t)).ToList(); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/NumericsTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/NumericsTests.cs new file mode 100644 index 00000000..fdb65344 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/NumericsTests.cs @@ -0,0 +1,78 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Globalization; +using System.Numerics; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.Build +{ + /// + /// Is Vector2 and Matrix3x2 available in all builds? + /// + [Collection("PDFsharp")] + public class NumericsTests + { + [Fact] + public void Test_String_Extensions() + { + Vector2 vector = new(); + Matrix3x2 matrix = new(); + + _ = vector; + _ = matrix; + } + + [Fact] + public void Numeric_formats() + { + const string format = ".##;-.##;0"; + + const double number0001 = 0.0001d; + const double number001 = 0.001d; + const double number01 = 0.01d; + const double number1 = 0.1d; + + string str = String.Format(CultureInfo.InvariantCulture, "{0:" + format + "}", number0001); + str.Should().Be("0"); + str = string.Format(CultureInfo.InvariantCulture, "{0:" + format + "}", -number0001); + str.Should().Be("0"); + + str = String.Format(CultureInfo.InvariantCulture, "{0:" + format + "}", number001); + str.Should().Be("0"); + str = string.Format(CultureInfo.InvariantCulture, "{0:" + format + "}", -number001); + str.Should().Be("0"); + + str = String.Format(CultureInfo.InvariantCulture, "{0:" + format + "}", number01); + str.Should().Be(".01"); + str = string.Format(CultureInfo.InvariantCulture, "{0:" + format + "}", -number01); + str.Should().Be("-.01"); + + str = String.Format(CultureInfo.InvariantCulture, "{0:" + format + "}", number1); + str.Should().Be(".1"); + str = string.Format(CultureInfo.InvariantCulture, "{0:" + format + "}", -number1); + str.Should().Be("-.1"); + + str = Invariant($"{number0001:.##;-.##;0}"); + str.Should().Be("0"); + str = Invariant($"{-number0001:.##;-.##;0}"); + str.Should().Be("0"); + + str = Invariant($"{number001:.##;-.##;0}"); + str.Should().Be("0"); + str = Invariant($"{-number001:.##;-.##;0}"); + str.Should().Be("0"); + + str = Invariant($"{number01:.##;-.##;0}"); + str.Should().Be(".01"); + str = Invariant($"{-number01:.##;-.##;0}"); + str.Should().Be("-.01"); + + str = Invariant($"{number1:.##;-.##;0}"); + str.Should().Be(".1"); + str = Invariant($"{-number1:.##;-.##;0}"); + str.Should().Be("-.1"); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/ReleaseBuildTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/ReleaseBuildTests.cs index 95a0e67d..4774937f 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/ReleaseBuildTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Build/ReleaseBuildTests.cs @@ -1,10 +1,11 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. #if WPF using System.IO; #endif using System.Diagnostics; +using System.Reflection; using System.Text; using FluentAssertions; using PdfSharp.Drawing; @@ -20,7 +21,7 @@ namespace PdfSharp.Tests.Build { [Collection("PDFsharp")] - public class ReleaseBuildTests + public class ReleaseBuildTests // CHECK_BEFORE_RELEASE Run before release. { #if !DEBUG [Fact] @@ -33,15 +34,17 @@ public void Check_renamed_identifiers() } #endif - [Fact(Skip = "Do not run this test always.")] - //[Fact] + //[Fact(Skip = "Do not run this test always.")] + [Fact] public void Check_CS_files_for_non_ASCII_characters() { - // Tests runs only under .NET Framework and GDI. -#if NET6_0_OR_GREATER || CORE || WPF - // Exit here if not GDI under .NET Framework. + // This tests only runs in GDI build under .NET 4.6.2. +#if NET8_0_OR_GREATER || CORE || WPF return; #else + // Set dryRun to true to just check the files. + // Set dryRun to false and files will be updated. + const bool dryRun = true; #if DEBUG _ = BuildInformation.BuildVersionNumber; #endif @@ -51,20 +54,19 @@ public void Check_CS_files_for_non_ASCII_characters() folder = Path.Combine(folder, "../src/"); -#if true_ - folder = @"D:\repos\emp\PDFsharp-COPY\"; -#endif - var list = new List(); GetFiles(folder, ref list); + int updateNecessary = 0; foreach (var file in list) { - CheckFile(file); + var result = CheckFile(file, dryRun); + updateNecessary += result; } + updateNecessary.Should().Be(0); #endif } - static void CheckFile(string file) + static int CheckFile(string file, bool dryRun) { var bytes = File.ReadAllBytes(file); @@ -125,7 +127,7 @@ static void CheckFile(string file) // Resave ANSI as UTF-8 with BOM. _ = typeof(int); // Assume it is ANSI and save with BOM. - Resave(bytes, true, file, true); + return Resave(bytes, true, file, true, dryRun); } if (utf8Bom && ascii) @@ -133,7 +135,7 @@ static void CheckFile(string file) // Resave as UTF-8 with no BOM. _ = typeof(int); // Assume it is UTF-8 and save without BOM. - Resave(bytes, false, file, false); + return Resave(bytes, false, file, false, dryRun); } if (utf8Bom && !ascii) @@ -155,9 +157,11 @@ static void CheckFile(string file) // (German) Visual Studio Code assumes UTF-8. _ = typeof(int); // Assume it is ANSI and save with BOM. - Resave(bytes, true, file, true); + return Resave(bytes, true, file, true, dryRun); } + return 0; + //throw new InvalidOperationException( // $"File '{file}' contains non-ASCII characters but has no byte-order mark"); } @@ -178,7 +182,7 @@ void GetFiles(string path, ref List files) } } - static void Resave(byte[] bytes, bool isAnsi, string fileName, bool withBom) + static int Resave(byte[] bytes, bool isAnsi, string fileName, bool withBom, bool dryRun) { if (isAnsi) { @@ -189,12 +193,12 @@ static void Resave(byte[] bytes, bool isAnsi, string fileName, bool withBom) if (withBom) { // Save as UTF-8 with BOM. - WriteFile(fileName, utf, true); + return WriteFile(fileName, utf, true, dryRun); } else { // Save as UTF-8 without BOM. - WriteFile(fileName, utf, false); + return WriteFile(fileName, utf, false, dryRun); } } else @@ -205,12 +209,12 @@ static void Resave(byte[] bytes, bool isAnsi, string fileName, bool withBom) if (withBom) { // Save as UTF-8 with BOM. - WriteFile(fileName, bytes, true); + return WriteFile(fileName, bytes, true, dryRun); } else { // Save as UTF-8 without BOM. - WriteFile(fileName, bytes, false); + return WriteFile(fileName, bytes, false, dryRun); } } } @@ -219,12 +223,26 @@ static void Resave(byte[] bytes, bool isAnsi, string fileName, bool withBom) /// Writes the file. /// Must be called only if the file requires an update (add a non-existing BOM or remove an existing BOM). /// - static void WriteFile(String fileName, Byte[] bytes, Boolean addBom) + static int WriteFile(string fileName, byte[] bytes, bool addBom, bool dryRun) { bool utf8Bom = bytes is [0xEF, 0xBB, 0xBF, ..]; (addBom == utf8Bom).Should().BeFalse("Should not come here."); + if (dryRun) + { + if (addBom && !utf8Bom) + { + // Write new BOM. + return 1; + } + else + { + // Write bytes without existing BOM. + return 1; + } + } + using (FileStream fs = new FileStream(fileName, FileMode.Create, FileAccess.ReadWrite, FileShare.Read)) { if (addBom && !utf8Bom) @@ -234,13 +252,54 @@ static void WriteFile(String fileName, Byte[] bytes, Boolean addBom) // Write bytes as they are. fs.Write(bytes, 0, bytes.Length); + return 1; } else { // Write bytes without existing BOM. fs.Write(bytes, 3, bytes.Length - 3); + return 1; } } } + + // ---------- + + // TODO: Check for classes or functions ending with underscore. + + //[Fact] + static void Check_renamed_classes_or_functions() + { + + } + + // [Fact] + public static void Ensure_DEBUG_stuff_is_excluded() + { + const string aaa = "PdfSharp.Diagnostics.DebugBreakHelper"; +#if DEBUG_ + true.Should().BeTrue(); +#else + var assembly = Assembly.GetAssembly(typeof(PdfDocument)) ?? throw new SystemException("What?"); + var type = assembly.GetType(aaa); + type.Should().BeNull($"{aaa} must not be part of a release build."); +#endif + } + + [Fact] + public static void Ensure_developer_tags_are_removed() + { + string[] filesToExclude = ["blah.md", "blub.md"]; + string[] dirsToExclude = ["bin", "obj", "publish"]; // TODO: TestResults etc. + + var root = Directory.GetCurrentDirectory(); +#if DEBUG_ + true.Should().BeTrue(); +#else + //var assembly = Assembly.GetAssembly(typeof(PdfDocument)) ?? throw new SystemException("What?"); + //var type = assembly.GetType(aaa); + //type.Should().BeNull($"{aaa} should not be part of a release build."); +#endif + } } } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/images/ImageTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/images/ImageTests.cs index 8988fbd0..23bb7013 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/images/ImageTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/images/ImageTests.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. #if WPF @@ -18,6 +18,7 @@ using PdfSharp.Snippets.Font; using PdfSharp.TestHelper; #if CORE +using PdfSharp.Internal.Imaging; #endif using Xunit; @@ -123,10 +124,10 @@ public void PDF_with_Images() gfx.DrawImage(image, 100, 100, 100, 100); - // Save the document... - string filename = PdfFileUtility.GetTempPdfFileName("ImageTests"); + // Save the document… + string filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/images/ImageTests"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -188,9 +189,10 @@ public void WriteAndRead_PDF_with_FlateDecode() GC.WaitForFullGCComplete(); int offset = 0; - int imageHeight = 800 / imagePaths.Length; + int imageHeight = 800 / imagePaths.Length / 2; foreach (var imagePath in imagePaths) { + // Read from file. var fullName = IOUtility.GetAssetsPath(imagePath)!; var image = XImage.FromFile(fullName); @@ -198,9 +200,21 @@ public void WriteAndRead_PDF_with_FlateDecode() offset += imageHeight; } + foreach (var imagePath in imagePaths) + { + // Read from stream. + var fullName = IOUtility.GetAssetsPath(imagePath)!; + using (var stream = new FileStream(fullName, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var image = XImage.FromStream(stream); + gfx.DrawImage(image, 10, 10 + offset, imageHeight, imageHeight); + } + + offset += imageHeight; + } // Save the document. - string filename = PdfFileUtility.GetTempPdfFileName("FlateDecodeImageTest"); + string filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/images/FlateDecodeImageTest"); document.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); @@ -237,7 +251,7 @@ void ExportJpeg(PdfDictionary image) // TODO_OLD Check filter types. This works for "/Filter [/FlateDecode /DCTDecode]" only. var imageFilename = Path.Combine(dir, $"image-{Guid.NewGuid():N}.jpg"); - var stream = image.Stream.Value; + var stream = image.Stream!.Value; var fd = new FlateDecode(); var decoded = fd.Decode(stream); using var fs = new FileStream(imageFilename, FileMode.Create, FileAccess.Write); @@ -261,15 +275,18 @@ public void PDF_with_Image_from_stream() var imagePath = IOUtility.GetAssetsPath("PDFsharp/images/samples/jpeg/truecolorA.jpg")!; - var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read); + // WPF stores a reference to the stream internally. + // We must not destroy the stream before the PDF has been written, otherwise rendering the PDF will fail. + // Same for GDI. + using var stream = new FileStream(imagePath, FileMode.Open, FileAccess.Read, FileShare.Read); using var xImage = XImage.FromStream(stream); gfx.DrawImage(xImage, 100, 100, 100, 100); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("ImageFromStream"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/images/ImageFromStream"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -294,15 +311,15 @@ public void PDF_with_Image_from_private_memorystream() var pngBytes = File.ReadAllBytes(imagePath); // Create a MemoryStream that does not allow GetBuffer. - var stream = new MemoryStream(pngBytes); + using var stream = new MemoryStream(pngBytes); using var xImage = XImage.FromStream(stream); gfx.DrawImage(xImage, 100, 100, 100, 100); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("ImageFromStream"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/images/ImageFromStream"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -327,15 +344,15 @@ public void PDF_with_Image_from_public_memorystream() var pngBytes = File.ReadAllBytes(imagePath); // Create a MemoryStream that allows GetBuffer. - var stream = new MemoryStream(pngBytes, 0, pngBytes.Length, false, true); + using var stream = new MemoryStream(pngBytes, 0, pngBytes.Length, false, true); using var xImage = XImage.FromStream(stream); gfx.DrawImage(xImage, 100, 100, 100, 100); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("ImageFromStream"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/images/ImageFromStream"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -376,16 +393,16 @@ public void PDF_with_image_from_GDI() xImage = XImage.FromGdiPlusImage(gdiImage); gfx.DrawImage(xImage, new RectangleF(0f, 144f, 128f, 128f)); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("ImageFromStream"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/images/ImageFromGDI"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } #endif -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER [Fact] public async Task PDF_with_Image_from_http_stream() { @@ -409,10 +426,10 @@ public async Task PDF_with_Image_from_http_stream() var xImage = XImage.FromBitmapImageStreamThatCannotSeek(imageStream); gfx.DrawImage(xImage, 100, 100, 100, 100); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("ImageFromStream"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/images/ImageFromStream"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); #endif diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/text/TextTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/text/TextTests.cs index 283ddd5d..2955b23e 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/text/TextTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/text/TextTests.cs @@ -1,14 +1,14 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using FluentAssertions; +using PdfSharp.Internal.OpenType; using PdfSharp.Drawing; using PdfSharp.Fonts; -using PdfSharp.Fonts.Internal; using PdfSharp.Pdf; using PdfSharp.Quality; using PdfSharp.TestHelper; using Xunit; +using FluentAssertions; namespace PdfSharp.Tests.Drawing { @@ -76,10 +76,10 @@ public void PDF_with_Emojis() gfx.DrawString("glyphs \ud83d\udca9\ud83d\udc1b\ud83e\udd84\u2615\ud83d\ude82\ud83d\udef8\u2714", font, XBrushes.Black, new XRect(0, 20, width, height), XStringFormats.Center); gfx.DrawString("\ud83d\udca9\ud83d\udca9\ud83d\udca9\u2713\u2714\u2705\ud83d\udc1b\ud83d\udc4c\ud83c\udd97\ud83d\udd95 \ud83e\udd84 \ud83e\udd82 \ud83c\udf47 \ud83c\udf46 \u2615 \ud83d\ude82 \ud83d\udef8 \u2601 \u2622 \u264c \u264f \u2705 \u2611 \u2714 \u2122 \ud83c\udd92 \u25fb", font, XBrushes.Black, new XRect(0, 100, width, height), XStringFormats.Center); - // Save the document... + // Save the document… string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTests/Drawing/text/HelloEmoji"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -114,10 +114,10 @@ public void PDF_with_No_Break_Hyphen() var font = new XFont("Arial", 12, XFontStyleEx.Bold, options); gfx.DrawString("No\u2011break\u2011hyphen-Test", font, XBrushes.Black, new XRect(0, 50, page.Width.Point, page.Height.Point), XStringFormats.Center); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("PdfWithNoBreakHyphen"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/text/PdfWithNoBreakHyphen"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); // Analyze the drawn text in the PDF’s content stream. @@ -170,10 +170,10 @@ public void PDF_with_Wingdings() gfx.DrawString("1 þ", font, XBrushes.Black, new XRect(50, 100, 20, 20), XStringFormats.Center); } - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("PdfWithWingdings"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/text/PdfWithWingdings"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); containsNotFoundGlyphs.Should().BeFalse(); @@ -350,10 +350,10 @@ public void PDF_with_ligatures_text_event() pos += 30; #endif - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("PdfLigatureTest-TextEvent"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/text/PdfLigatureTest-TextEvent"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); // Analyze the drawn text in the PDF’s content stream. @@ -471,10 +471,10 @@ public void PDF_with_ligatures_render_event() gfx.DrawString(text, font, XBrushes.Black, new XRect(50, 62, page.Width.Point, page.Height.Point), XStringFormats.TopLeft); - // Save the document... - var filename = PdfFileUtility.GetTempPdfFileName("PdfLigatureTest-RenderEvent"); + // Save the document… + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/drawing/text/PdfLigatureTest-RenderEvent"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); // Analyze the drawn text in the PDF’s content stream. diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/text/UnicodeHelperTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/text/UnicodeHelperTests.cs index 0c85f7a3..7f6ddbbd 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/text/UnicodeHelperTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Drawing/text/UnicodeHelperTests.cs @@ -1,11 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using FluentAssertions; -using PdfSharp.Fonts; -using PdfSharp.Fonts.Internal; -using PdfSharp.Snippets.Font; +using PdfSharp.Internal.OpenType; using Xunit; +using FluentAssertions; namespace PdfSharp.Tests.Drawing { diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Encodings/EncodingTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Encodings/EncodingTests.cs index bc4aab7f..24d8b026 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Encodings/EncodingTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Encodings/EncodingTests.cs @@ -175,7 +175,7 @@ public void AnsiEncoding_Unicode_to_Unicode_test_implementation() // Used test PDFsharp AnsiEncoding against Microsoft code page 1252. Encoding? GetDotNetAnsiEncoding() { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER return CodePagesEncodingProvider.Instance.GetEncoding(1252); #else return Encoding.GetEncoding(1252); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Filters/Ascii85Tests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Filters/Ascii85Tests.cs index 50d3e703..8310b21b 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Filters/Ascii85Tests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Filters/Ascii85Tests.cs @@ -60,7 +60,7 @@ public void Check_no_padding() var filter = new Ascii85Decode(); var bytes = new byte[4]; - TestRange(0, 500); + //TestRange(0, 500); //TestRange(0, 0xffffffff); // Takes long, but succeeds. TestRange(0, 700); TestRange(0xfffff000, 0xffffffff); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Fonts/FontResolverTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Fonts/FontResolverTests.cs index 56c78353..b5bf0c8d 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Fonts/FontResolverTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Fonts/FontResolverTests.cs @@ -64,30 +64,31 @@ public void No_FontResolver_set() SegoeUIShouldFail(); var arial = new XFont(ArialName, 10, XFontStyleEx.Regular); + var name = arial.Name; ArialShouldSucceed(); - arial.GlyphTypeface.FaceName.Should().Be("Arial"); + arial.GlyphTypeface.FamilyName.Should().Be("Arial"); var times = new XFont(TimesName, 10, XFontStyleEx.Regular); - times.GlyphTypeface.FaceName.Should().Be("Times New Roman"); + times.GlyphTypeface.FamilyName.Should().Be("Times New Roman"); var courier = new XFont(CourierName, 10, XFontStyleEx.Regular); - courier.GlyphTypeface.FaceName.Should().Be("Courier New"); + courier.GlyphTypeface.FamilyName.Should().Be("Courier New"); var verdana = new XFont(VerdanaName, 10, XFontStyleEx.Regular); - verdana.GlyphTypeface.FaceName.Should().Be("Verdana"); + verdana.GlyphTypeface.FamilyName.Should().Be("Verdana"); var lucida = new XFont(LucidaName, 10, XFontStyleEx.Regular); - lucida.GlyphTypeface.FaceName.Should().Be("Lucida Console"); + lucida.GlyphTypeface.FamilyName.Should().Be("Lucida Console"); // Test Symbol font because it is a symbol font. var symbol = new XFont(SymbolName, 10, XFontStyleEx.Regular); var symbolB = new XFont(SymbolName, 10, XFontStyleEx.Bold, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.BoldSimulation); var symbolI = new XFont(SymbolName, 10, XFontStyleEx.Italic, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.ItalicSimulation); var symbolBI = new XFont(SymbolName, 10, XFontStyleEx.BoldItalic, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.BoldItalicSimulation); - symbol.GlyphTypeface.FaceName.Should().Be("Symbol"); - symbolB.GlyphTypeface.FaceName.Should().Be("Symbol"); - symbolI.GlyphTypeface.FaceName.Should().Be("Symbol"); - symbolBI.GlyphTypeface.FaceName.Should().Be("Symbol"); + symbol.GlyphTypeface.FamilyName.Should().Be("Symbol"); + symbolB.GlyphTypeface.FamilyName.Should().Be("Symbol"); + symbolI.GlyphTypeface.FamilyName.Should().Be("Symbol"); + symbolBI.GlyphTypeface.FamilyName.Should().Be("Symbol"); symbol.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.None); symbolB.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.BoldSimulation); symbolI.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.ItalicSimulation); @@ -98,10 +99,10 @@ public void No_FontResolver_set() var winDingsB = new XFont(WingdingsName, 10, XFontStyleEx.Bold, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.BoldSimulation); var winDingsI = new XFont(WingdingsName, 10, XFontStyleEx.Italic, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.ItalicSimulation); var winDingsBI = new XFont(WingdingsName, 10, XFontStyleEx.BoldItalic, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.BoldItalicSimulation); - winDings.GlyphTypeface.FaceName.Should().Be("Wingdings"); - winDingsB.GlyphTypeface.FaceName.Should().Be("Wingdings"); - winDingsI.GlyphTypeface.FaceName.Should().Be("Wingdings"); - winDingsBI.GlyphTypeface.FaceName.Should().Be("Wingdings"); + winDings.GlyphTypeface.FamilyName.Should().Be("Wingdings"); + winDingsB.GlyphTypeface.FamilyName.Should().Be("Wingdings"); + winDingsI.GlyphTypeface.FamilyName.Should().Be("Wingdings"); + winDingsBI.GlyphTypeface.FamilyName.Should().Be("Wingdings"); winDings.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.None); winDingsB.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.BoldSimulation); winDingsI.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.ItalicSimulation); @@ -211,47 +212,47 @@ public void No_FontResolver_set_and_multiple_creations2() var arial0 = new XFont(ArialName, 10, XFontStyleEx.Regular); var arial1 = new XFont(ArialName, 10, XFontStyleEx.Italic); var arial = new XFont(ArialName, 10, XFontStyleEx.Bold); - arial0.GlyphTypeface.FaceName.Should().Be("Arial"); - arial1.GlyphTypeface.FaceName.Should().Be("Arial Italic"); - arial.GlyphTypeface.FaceName.Should().Be("Arial Bold"); + arial0.GlyphTypeface.FontName.Should().Be("Arial"); + arial1.GlyphTypeface.FontName.Should().Be("Arial Italic"); + arial.GlyphTypeface.FontName.Should().Be("Arial Bold"); var times0 = new XFont(TimesName, 10, XFontStyleEx.Regular); var times1 = new XFont(TimesName, 10, XFontStyleEx.Bold); var times = new XFont(TimesName, 10, XFontStyleEx.Italic); - times0.GlyphTypeface.FaceName.Should().Be("Times New Roman"); - times1.GlyphTypeface.FaceName.Should().Be("Times New Roman Bold"); + times0.GlyphTypeface.FontName.Should().Be("Times New Roman"); + times1.GlyphTypeface.FontName.Should().Be("Times New Roman Bold"); times.GlyphTypeface.DisplayName.Should().Be("Times New Roman Italic"); var courier0 = new XFont(CourierName, 10, XFontStyleEx.Bold); var courier1 = new XFont(CourierName, 10, XFontStyleEx.Bold); var courier = new XFont(CourierName, 10, XFontStyleEx.BoldItalic); - courier0.GlyphTypeface.FaceName.Should().Be("Courier New Bold"); - courier1.GlyphTypeface.FaceName.Should().Be("Courier New Bold"); - courier.GlyphTypeface.FaceName.Should().Be("Courier New Bold Italic"); + courier0.GlyphTypeface.FontName.Should().Be("Courier New Bold"); + courier1.GlyphTypeface.FontName.Should().Be("Courier New Bold"); + courier.GlyphTypeface.FontName.Should().Be("Courier New Bold Italic"); var verdana0 = new XFont(VerdanaName, 10, XFontStyleEx.Bold); var verdana1 = new XFont(VerdanaName, 10, XFontStyleEx.Bold); var verdana = new XFont(VerdanaName, 10, XFontStyleEx.BoldItalic); - verdana0.GlyphTypeface.FaceName.Should().Be("Verdana Bold"); - verdana1.GlyphTypeface.FaceName.Should().Be("Verdana Bold"); - verdana.GlyphTypeface.FaceName.Should().Be("Verdana Bold Italic"); + verdana0.GlyphTypeface.FontName.Should().Be("Verdana Bold"); + verdana1.GlyphTypeface.FontName.Should().Be("Verdana Bold"); + verdana.GlyphTypeface.FontName.Should().Be("Verdana Bold Italic"); var lucida0 = new XFont(LucidaName, 10, XFontStyleEx.Regular); var lucida1 = new XFont(LucidaName, 10, XFontStyleEx.Italic); var lucida = new XFont(LucidaName, 10, XFontStyleEx.Italic); - lucida0.GlyphTypeface.FaceName.Should().Be("Lucida Console"); - lucida1.GlyphTypeface.FaceName.Should().Be("Lucida Console"); - lucida.GlyphTypeface.FaceName.Should().Be("Lucida Console"); + lucida0.GlyphTypeface.FontName.Should().Be("Lucida Console"); + lucida1.GlyphTypeface.FontName.Should().Be("Lucida Console"); + lucida.GlyphTypeface.FontName.Should().Be("Lucida Console"); var symbol0 = new XFont(SymbolName, 10, XFontStyleEx.Italic, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.ItalicSimulation); var symbol1 = new XFont(SymbolName, 10, XFontStyleEx.Bold, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.BoldSimulation); var symbol = new XFont(SymbolName, 10, XFontStyleEx.BoldItalic, XPdfFontOptions.AutomaticEncoding, XStyleSimulations.BoldItalicSimulation); - symbol0.GlyphTypeface.FaceName.Should().Be("Symbol"); + symbol0.GlyphTypeface.FontName.Should().Be("Symbol"); symbol0.IsSymbolFont.Should().BeTrue(); symbol0.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.ItalicSimulation); - symbol1.GlyphTypeface.FaceName.Should().Be("Symbol"); + symbol1.GlyphTypeface.FontName.Should().Be("Symbol"); symbol1.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.BoldSimulation); - symbol.GlyphTypeface.FaceName.Should().Be("Symbol"); + symbol.GlyphTypeface.FontName.Should().Be("Symbol"); symbol.GlyphTypeface.StyleSimulations.Should().Be(XStyleSimulations.BoldItalicSimulation); var arialCount = FontHelper.CountGlyphs(arial); @@ -294,21 +295,21 @@ public void Test_FailsafeFontResolver_as_fallback_font_resolver() #endif // Platform font resolver creates Arial. var arial = new XFont(ArialName, 10, XFontStyleEx.Regular); - arial.GlyphTypeface.FaceName.Should().Be("Arial"); + arial.GlyphTypeface.FontName.Should().Be("Arial"); #if CORE // Fallback font resolver creates Segoe WP. var dummy = new XFont("Dummy", 10, XFontStyleEx.Regular); - dummy.GlyphTypeface.FaceName.Should().Be("Segoe WP"); + dummy.GlyphTypeface.FamilyName.Should().Be("Segoe WP"); #endif #if GDI // Fallback font resolver creates Segoe WP. var dummy = new XFont("Dummy", 10, XFontStyleEx.Regular); - dummy.GlyphTypeface.FaceName.Should().Be("Segoe WP"); + dummy.GlyphTypeface.FontName.Should().Be("Segoe WP"); #endif #if WPF // Fallback font resolver creates Segoe WP. var dummy = new XFont("Dummy", 10, XFontStyleEx.Regular); - dummy.GlyphTypeface.FaceName.Should().Be("Segoe WP"); + dummy.GlyphTypeface.FamilyName.Should().Be("Segoe WP"); #endif } @@ -335,7 +336,7 @@ public void TestXFilesFontResolver1_used_as_main_font_resolver() var xfilesB = new XFont(XFilesName, 10, XFontStyleEx.Bold); var xfilesI = new XFont(XFilesName, 10, XFontStyleEx.Italic); var xfilesBI = new XFont(XFilesName, 10, XFontStyleEx.BoldItalic); - xfilesR.GlyphTypeface.FaceName.Should().Be("X-Files"); + xfilesR.GlyphTypeface.FontName.Should().Be("X-Files"); var xfilesCount = FontHelper.CountGlyphs(xfilesR); @@ -363,11 +364,11 @@ public void TestXFilesFontResolver1_used_as_main_font_resolver_with_fallback() // Arial fails not no because of fallback font resolver // but was resolved as Segoe WP. var arial = new XFont(ArialName, 10, XFontStyleEx.Regular); - arial.GlyphTypeface.FaceName.Should().Be("Segoe WP"); + arial.GlyphTypeface.FontName.Should().Be("Segoe WP"); // X-Files resolved by font resolver. var xfiles = new XFont(XFilesName, 10, XFontStyleEx.Regular); - xfiles.GlyphTypeface.FaceName.Should().Be("X-Files"); + xfiles.GlyphTypeface.FontName.Should().Be("X-Files"); } [Fact] @@ -397,11 +398,11 @@ public void TestXFilesFontResolver2_used_as_main_font_resolver() // Arial fails not because custom font resolver calls // platform font resolver. var arial = new XFont(ArialName, 10, XFontStyleEx.Regular); - arial.GlyphTypeface.FaceName.Should().Be("Arial"); + arial.GlyphTypeface.FontName.Should().Be("Arial"); // X-Files resolved by font resolver. var xfiles = new XFont(XFilesName, 10, XFontStyleEx.Regular); - xfiles.GlyphTypeface.FaceName.Should().Be("X-Files"); + xfiles.GlyphTypeface.FontName.Should().Be("X-Files"); #if CORE // Dummy fails because it cannot be resolved. @@ -411,14 +412,14 @@ public void TestXFilesFontResolver2_used_as_main_font_resolver() #if GDI // Dummy resolved by platform font resolver and creates Microsoft Sans Serif. //var dummy = new XFont("Dummy", 10, XFontStyleEx.Regular); - //dummy.GlyphTypeface.FaceName.Should().Be("Microsoft Sans Serif"); + //dummy.GlyphTypeface.FontFamily.Should().Be("Microsoft Sans Serif"); Func createDummy = () => new XFont("Dummy", 10, XFontStyleEx.Regular); createDummy.Should().Throw(); #endif #if WPF //// Dummy resolved by platform font resolver and creates Microsoft Sans Serif. //var dummy = new XFont("Dummy", 10, XFontStyleEx.Regular); - //dummy.GlyphTypeface.FaceName.Should().Be("Microsoft Sans Serif"); + //dummy.GlyphTypeface.FontFamily.Should().Be("Microsoft Sans Serif"); // Dummy fails because it cannot be resolved. Func createDummy = () => new XFont("Dummy", 10, XFontStyleEx.Regular); createDummy.Should().Throw(); @@ -449,25 +450,25 @@ public void TestXFilesFontResolver2_used_as_main_font_resolver_with_fallback() // Arial fails not because custom font resolver calls // platform font resolver. var arial = new XFont(ArialName, 10, XFontStyleEx.Regular); - arial.GlyphTypeface.FaceName.Should().Be("Arial"); + arial.GlyphTypeface.FontName.Should().Be("Arial"); #endif // X-Files resolved by font resolver. var xfiles = new XFont(XFilesName, 10, XFontStyleEx.Regular); - xfiles.GlyphTypeface.FaceName.Should().Be("X-Files"); + xfiles.GlyphTypeface.FamilyName.Should().Be("X-Files"); // Dummy resolved by fallback font resolver and creates Segoe WP. var dummy = new XFont("Dummy", 10, XFontStyleEx.Regular); #if CORE - dummy.GlyphTypeface.FaceName.Should().Be("Segoe WP"); + dummy.GlyphTypeface.FamilyName.Should().Be("Segoe WP"); #endif #if GDI // PDFsharp ignores the fact that GDI resolve every unknown font with 'Microsoft Sans Serif'. - //dummy.GlyphTypeface.FaceName.Should().Be("Microsoft Sans Serif"); - dummy.GlyphTypeface.FaceName.Should().Be("Segoe WP"); + //dummy.GlyphTypeface.FontFamily.Should().Be("Microsoft Sans Serif"); + dummy.GlyphTypeface.FamilyName.Should().Be("Segoe WP"); #endif #if WPF - dummy.GlyphTypeface.FaceName.Should().Be("Segoe WP"); + dummy.GlyphTypeface.FamilyName.Should().Be("Segoe WP"); #endif } @@ -480,7 +481,7 @@ public void TestXFilesFontResolver2_used_as_fallback() // X-Files resolved by font resolver. var xfiles = new XFont(XFilesName, 10, XFontStyleEx.Regular); - xfiles.GlyphTypeface.FaceName.Should().Be("X-Files"); + xfiles.GlyphTypeface.FamilyName.Should().Be("X-Files"); // Arial fails because fallback font resolver calls platform font resolver, // which is illegal. @@ -548,9 +549,9 @@ void ArialShouldFail(XFontStyleEx style = XFontStyleEx.Regular) EnsureFail(ArialName, style); } - void ArialShouldBeReplacedBy(string faceName, XFontStyleEx style = XFontStyleEx.Regular) + void ArialShouldBeReplacedBy(string fontName, XFontStyleEx style = XFontStyleEx.Regular) { - EnsureSuccess(ArialName, faceName, style); + EnsureSuccess(ArialName, fontName, style); } void DummyShouldFail(XFontStyleEx style = XFontStyleEx.Regular) @@ -558,9 +559,9 @@ void DummyShouldFail(XFontStyleEx style = XFontStyleEx.Regular) EnsureFail("Dummy", style); } - void DummyShouldBeReplacedBy(string faceName, XFontStyleEx style = XFontStyleEx.Regular) + void DummyShouldBeReplacedBy(string fontName, XFontStyleEx style = XFontStyleEx.Regular) { - EnsureSuccess("Dummy", faceName, style); + EnsureSuccess("Dummy", fontName, style); } // ReSharper disable once InconsistentNaming @@ -576,9 +577,9 @@ void SegoeUIShouldFail(XFontStyleEx style = XFontStyleEx.Regular) } // ReSharper disable once InconsistentNaming - void SegoeUIShouldBeReplacedBy(string faceName, XFontStyleEx style = XFontStyleEx.Regular) + void SegoeUIShouldBeReplacedBy(string fontName, XFontStyleEx style = XFontStyleEx.Regular) { - EnsureSuccess(SegoeUIName, faceName, style); + EnsureSuccess(SegoeUIName, fontName, style); } void XFilesShouldSucceed(XFontStyleEx style = XFontStyleEx.Regular) @@ -590,20 +591,20 @@ void XFilesShouldFail(XFontStyleEx style = XFontStyleEx.Regular) { EnsureFail(XFilesName, style); } - void XFilesShouldBeReplacedBy(string faceName, XFontStyleEx style = XFontStyleEx.Regular) + void XFilesShouldBeReplacedBy(string fontName, XFontStyleEx style = XFontStyleEx.Regular) { - EnsureSuccess(XFilesName, faceName, style); + EnsureSuccess(XFilesName, fontName, style); } - void EnsureSuccess(string inputFaceName, string resolvedFaceName, XFontStyleEx style = XFontStyleEx.Regular) + void EnsureSuccess(string inputFontName, string resolvedFontName, XFontStyleEx style = XFontStyleEx.Regular) { - var font = new XFont(inputFaceName, 10, style); - font.GlyphTypeface.FaceName.Should().Be(resolvedFaceName); + var font = new XFont(inputFontName, 10, style); + font.GlyphTypeface.FontName.Should().Be(resolvedFontName); } - void EnsureFail(string inputFaceName, XFontStyleEx style = XFontStyleEx.Regular) + void EnsureFail(string inputFontName, XFontStyleEx style = XFontStyleEx.Regular) { - var font = () => new XFont(inputFaceName, 10, style); + var font = () => new XFont(inputFontName, 10, style); font.Should().Throw(); } } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Helper/XunitHelper.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Helper/XunitHelper.cs index 8db8becf..505c84cc 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Helper/XunitHelper.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Helper/XunitHelper.cs @@ -1,12 +1,6 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - // ReSharper disable once CheckNamespace namespace Xunit { @@ -21,8 +15,12 @@ public static class SkippableTests /// True if slow tests should be skipped. public static bool SkipSlowTests() { +#if RUN_SLOW_TESTS + return false; +#else var env = Environment.GetEnvironmentVariable("PDFsharpTests"); return String.IsNullOrEmpty(env); +#endif } // /// @@ -32,7 +30,7 @@ public static bool SkipSlowTests() // /// True if slow tests should be skipped. // public static bool SkipSlowTestsUnderDotNetFramework() // { - //#if NET6_0_OR_GREATER + //#if NET8_0_OR_GREATER // return false; //#else // return SkipSlowTests(); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/CLexerTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/CLexerTests.cs index 5e48f374..de8957b2 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/CLexerTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/CLexerTests.cs @@ -1,21 +1,14 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.IO; using System.Text; using FluentAssertions; -using PdfSharp.Diagnostics; -using PdfSharp.Drawing; -using PdfSharp.Fonts; -using PdfSharp.Pdf; -using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Content; using PdfSharp.Pdf.Content.Objects; -using PdfSharp.Quality; -using PdfSharp.Snippets.Font; -using PdfSharp.TestHelper; using Xunit; +// TODO REMOVED +#if false namespace PdfSharp.Tests.IO { [Collection("PDFsharp")] @@ -29,20 +22,17 @@ public void Content_Can_Be_Parsed_And_Reconstructed(string contentString) var contentBytes = Encoding.UTF8.GetBytes(contentString); var sequence = ContentReader.ReadContent(contentBytes); - using var ms = new MemoryStream(); - var cw = new ContentWriter(ms); + var cw = new ContentWriter(new ContentWriterOptions()); foreach (var obj in sequence) { obj.WriteObject(cw); } - var newContent = new PdfContent(new PdfDictionary()); - newContent.CreateStream(ms.ToArray()); - var content = newContent.Stream.ToString(); // ContentWriter adds a newline after each operator - newContent.Stream.ToString().Should().Be("q\n(text)Tj\nQ\n"); - // is this intended ? ToString() writes only operator-names but not the operands... - var s = sequence.ToString(); // result: "qTjQ" + cw.ToString().Should().Be("q\n(text)Tj\nQ\n"); + + // Is this intended? ToString() writes only operator-names but not the operands... + var s = sequence.ToString(); // result: "q Tj Q" } [Fact] @@ -52,49 +42,42 @@ public void Content_Can_Be_Manually_Constructed() var op = OpCodes.OperatorFromName("q"); sequence.Add(op); op = OpCodes.OperatorFromName("Tj"); - op.Operands.Add(new CString { CStringType = CStringType.String, Value = "text" }); + op.Operands.Add(new CString("text")); sequence.Add(op); op = OpCodes.OperatorFromName("Q"); sequence.Add(op); - byte[] text = null!; - using (var ms = new MemoryStream()) + var cw = new ContentWriter(new ContentWriterOptions()); + foreach (var obj in sequence) { - var cw = new ContentWriter(ms); - foreach (var obj in sequence) - { - obj.WriteObject(cw); - } - cw.Close(); - text = ms.ToArray(); + obj.WriteObject(cw); } - var newContent = new PdfContent(new PdfDictionary()); - newContent.CreateStream(text); - var text2 = newContent.Stream.ToString(); // ContentWriter adds a newline after each operator. - text2.Should().Be("q\n(text)Tj\nQ\n"); + cw.ToString().Should().Be("q\n(text)Tj\nQ\n"); } [Theory] [InlineData("<7465787420> Tj")] // This works. - [InlineData("<746578742> Tj")] // This had to be fixed. + [InlineData("<746578742> Tj")] // This had to be fixed (if the final digit of a hex string is missing, + // it shall be assumed to be 0 according to the pdf reference). public void Can_Parse_Hex_String_With_Odd_Length(string contentString) { var contentBytes = Encoding.UTF8.GetBytes(contentString); var sequence = ContentReader.ReadContent(contentBytes); - using var ms = new MemoryStream(); - var cw = new ContentWriter(ms); + var cw = new ContentWriter(new ContentWriterOptions()); foreach (var obj in sequence) { - obj.WriteObject(cw); + obj.WriteObject(cw); // B/UG: + // A hex string read by ContentReader was converted to a CStringType.String CString before. + // Now, it remains a CStringType.HexString CString, to be able to write content as it was read. + // Is other code affected by this change? Provide a way to get the CStringType.String value? } - var newContent = new PdfContent(new PdfDictionary()); - newContent.CreateStream(ms.ToArray()); // ContentWriter adds a newline after each operator. - newContent.Stream.ToString().Should().Be("(text )Tj\n"); + cw.ToString().Should().Be("<7465787420>Tj\n"); } } } +#endif \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ContentReaderTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ContentReaderTests.cs new file mode 100644 index 00000000..918c1d10 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ContentReaderTests.cs @@ -0,0 +1,75 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Text; +using PdfSharp.Pdf.IO; +using PdfSharp.Diagnostics; +using PdfSharp.Pdf.Content; +using PdfSharp.Pdf.Content.Objects; +using PdfSharp.Quality; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.IO +{ + [Collection("PDFsharp")] + public class ContentReaderTests : IDisposable + { + public ContentReaderTests() + { + PdfSharpCore.ResetAll(); +#if CORE_ + GlobalFontSettings.FontResolver = new UnitTestFontResolver(); +#endif + } + + public void Dispose() + { + PdfSharpCore.ResetAll(); + } + + [Fact] + public void Read_HelloWorld() + { + const int requiredAssets = 1032; + IOUtility.EnsureAssetsVersion(requiredAssets); + + const string pdf = @"archives/samples-1.5/PDFs/HelloWorld.pdf"; + var testFile = IOUtility.GetAssetsPath(pdf)!; + + var doc = PdfReader.Open(testFile, PdfDocumentOpenMode.Import); + + doc.PageCount.Should().Be(1); + doc.Pages.Count.Should().Be(1); + var page = doc.Pages[0]; + var contents = ContentReader.ReadContent(page); + int comments = 0, names = 0, operators = 0, sequences = 0, strings = 0, arrays = 0, integers = 0, reals = 0; + var sb = new StringBuilder(); + foreach (var item in contents) + { + string info = item.GetType().Name switch + { + nameof(CComment) => $"Comment {++comments}", + nameof(CName) => $"Name {++names}", + nameof(COperator) => $"Operator {++operators}: {((COperator)item).Name} with {((COperator)item).Operands.Count} operands", + nameof(CSequence) => $"Sequence {++sequences}", + nameof(CString) => $"String {++strings}", + nameof(CArray) => $"Array {++arrays}", + nameof(CInteger) => $"Integer {++integers}", + nameof(CReal) => $"Real {++reals}", + _ => throw new NotImplementedException($"Type {item.GetType().Name} was unexpected.") + }; + sb.AppendLine(info); + } + var infos = sb.ToString(); + comments.Should().Be(0); + names.Should().Be(0); + operators.Should().Be(12); + sequences.Should().Be(0); + strings.Should().Be(0); + arrays.Should().Be(0); + integers.Should().Be(0); + reals.Should().Be(0); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ContentWriterTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ContentWriterTests.cs new file mode 100644 index 00000000..5d0368cb --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ContentWriterTests.cs @@ -0,0 +1,284 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Text; +using PdfSharp.Diagnostics; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Content; +using PdfSharp.Pdf.Content.Objects; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using PdfSharp.Drawing; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.IO +{ + [Collection("PDFsharp")] + public class ContentWriterTests : IDisposable + { + public ContentWriterTests() + { + PdfSharpCore.ResetAll(); +#if CORE_ + GlobalFontSettings.FontResolver = new UnitTestFontResolver(); +#endif + } + + public void Dispose() + { + PdfSharpCore.ResetAll(); + } + + [SkippableTheory] + // Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-Border.pdf")] + // Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-Color.pdf")] + // Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-LineAndFillFormat.pdf")] + // Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-Shading.pdf")] + // Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-Units.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-Border.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-Color.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-LineAndFillFormat.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-Shading.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-Units.pdf")] + public void Read_Write_Compare_Sample_Files(string file0) + { + const int requiredAssets = 1032; + IOUtility.EnsureAssetsVersion(requiredAssets); + + var assets = IOUtility.GetAssetsPath(); + var file = Environment.ExpandEnvironmentVariables(file0.Replace("$assets$", assets)); + if (!File.Exists(file)) + { + Skip.If(true); + } + + // 1. Read the file, parse contents, write contents. + var doc = PdfReader.Open(file, PdfDocumentOpenMode.Modify); + foreach (var page in doc.Pages) + { + var newContent = new CSequence(); + var contents = ContentReader.ReadContent(page); + foreach (var item in contents) + { + newContent.Add(item); + } + page.Contents.ReplaceContent(newContent); + } + + var filename = PdfFileUtility.GetTempPdfFullFileName("unittest/contentwritertests/read_write_compare"); + + // Save the document… + doc.Save(filename); + //// … and start a viewer. + //PdfFileUtility.ShowDocumentIfDebugging(filename); + + // 2. Read the file just written, parse contents, write contents. + var doc2 = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + foreach (var page in doc2.Pages) + { + var newContent = new CSequence(); + var contents = ContentReader.ReadContent(page); + foreach (var item in contents) + { + newContent.Add(item); + } + page.Contents.ReplaceContent(newContent); + } + + var filename2 = filename.Replace(".pdf", "_.pdf"); + // Save the document… + doc2.Save(filename2); + //// … and start a viewer. + //PdfFileUtility.ShowDocumentIfDebugging(filename2); + + doc.Pages.Count.Should().Be(doc2.Pages.Count); + int pages = doc.Pages.Count; + + var xformOri = XPdfForm.FromFile(file); + var xformWritten = XPdfForm.FromFile(filename2); + + // 3. Read the file just written, parse contents, write contents. + var docCompare = new PdfDocument(); + for (int idx = 0; idx < pages; ++idx) + { + // Add a new page to the output document + PdfPage pageCompare = docCompare.AddPage(); + pageCompare.Orientation = PageOrientation.Landscape; + double width = pageCompare.Width.Point; + double height = pageCompare.Height.Point; + + var gfx = XGraphics.FromPdfPage(pageCompare); + + xformOri.PageNumber = idx + 1; + var box = new XRect(0, 0, width / 2, height); + gfx.DrawImage(xformOri, box); + + xformWritten.PageNumber = idx + 1; + box = new XRect(width / 2, 0, width / 2, height); + gfx.DrawImage(xformWritten, box); + } + + var filename3 = filename.Replace(".pdf", "_compare.pdf"); + // Save the document… + docCompare.Save(filename3); + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(filename3); + } + + [Fact] + public void Create_simple_PDF() + { + var doc = new PdfDocument(); + var page = doc.AddPage(); + + var newContent = new CSequence(); + var comment = new CComment + { + Text = "Begin page content" + }; + newContent.Add(comment); + + // This is no real operator, just something that looks somewhat real. Like a "Lorem ipsum" text. + var @operator = OpCodes.OperatorFromName("Tj"); + var array = new CArray(); + var cint = new CInteger(1); + array.Add(cint); + var creal = new CReal(2); + array.Add(creal); + creal = new CReal(3); + array.Add(creal); + + // This line adds 3 operands: the array members, not the CArray itself. + @operator.Operands.Add(array); + + // This line adds one operand, the CArray. + //@operator.Operands.Add((CObject)array); + newContent.Add(@operator); + + comment = new CComment + { + Text = "End page content" + }; + newContent.Add(comment); + page.Contents.ReplaceContent(newContent); + + var filename = PdfFileUtility.GetTempPdfFullFileName("unittest/contentwritertests/create_simple_PDF"); + + // Save the document… + doc.Save(filename); + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(filename); + + { + doc = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + var pages = doc.Pages.Count; + pages.Should().Be(1); + + // Create a PDF with readable contents. + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Options.CompressContentStreams = false; + + foreach (var page2 in doc.Pages) + { + var content = new CSequence(); + // Clone page contents. + foreach (var item in ContentReader.ReadContent(page)) + content.Add(item); + // Force serializer to write contents. + page.Contents.ReplaceContent(content); + } + + var filename2 = filename.Replace(".pdf", "_.pdf"); + doc.Save(filename2); + } + } + + [Fact] + public void Modify_HelloWorld() + { + const int requiredAssets = 1032; + IOUtility.EnsureAssetsVersion(requiredAssets); + + const string pdf = @"archives/samples-1.5/PDFs/HelloWorld.pdf"; + var testFile = IOUtility.GetAssetsPath(pdf)!; + + var doc = PdfReader.Open(testFile, PdfDocumentOpenMode.Modify); + + doc.PageCount.Should().Be(1); + doc.Pages.Count.Should().Be(1); + var page = doc.Pages[0]; + var contents = ContentReader.ReadContent(page); + int comments = 0, names = 0, operators = 0, sequences = 0, strings = 0, arrays = 0, integers = 0, reals = 0; + var sb = new StringBuilder(); + //int text = 0; + var newContent = new CSequence(); + int idx = 0; + foreach (var item in contents) + { + string info = item.GetType().Name switch + { + nameof(CComment) => $"Comment {++comments}", + nameof(CName) => $"Name {++names}", + nameof(COperator) => $"Operator {++operators}: {((COperator)item).Name} with {((COperator)item).Operands.Count} operands", + nameof(CSequence) => $"Sequence {++sequences}", + nameof(CString) => $"String {++strings}", + nameof(CArray) => $"Array {++arrays}", + nameof(CInteger) => $"Integer {++integers}", + nameof(CReal) => $"Real {++reals}", + _ => throw new NotImplementedException($"Type {item.GetType().Name} was unexpected.") + }; + sb.AppendLine(info); + + if (item is COperator @operator) + { + //var newOp = @operator.Clone(); + if (@operator.OpCode.OpCodeName == OpCodeName.Tj) + { + //var newOps = new CSequence(); + foreach (var op in @operator.Operands) + { + if (op is CString @string) + { + if (idx == 0) + @string.Value = "Hero"; + if (idx == 1) + @string.Value = "Word!!! ABCXYZ abcxyz ÄÖÜäöüß"; + ++idx; + } + } + //@operator.Operands = newOps; + newContent.Add(item); + } + else + { + newContent.Add(item); + } + } + else + { + newContent.Add(item); + } + } + var infos = sb.ToString(); + comments.Should().Be(0); + names.Should().Be(0); + operators.Should().Be(12); + sequences.Should().Be(0); + strings.Should().Be(0); + arrays.Should().Be(0); + integers.Should().Be(0); + reals.Should().Be(0); + + page.Contents.ReplaceContent(newContent); + var filename = Path.GetFileName(pdf).Replace(".pdf", "_.pdf"); + var path = IOUtility.GetTempPath("unittest/contentwritertests")!; + path = Path.Combine(path, filename); + + // Save the document… + doc.Save(path); + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(path); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/LexerTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/LexerTests.cs index 99bba3c0..690aa917 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/LexerTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/LexerTests.cs @@ -61,7 +61,7 @@ public void ReverseSolidusTests() idx.Should().BeGreaterThan(8); // Manipulate text to get "\P" instead of "\\P". -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var replacementText = text.Replace(creatorWritten, creatorReplaced, StringComparison.InvariantCulture); #else var replacementText = text.Replace(creatorWritten, creatorReplaced); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ReaderTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ReaderTests.cs index 81e74418..f27c017a 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ReaderTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/ReaderTests.cs @@ -12,11 +12,15 @@ using PdfSharp.Pdf.IO; using PdfSharp.Quality; #if CORE +using PdfSharp.Fonts; #endif using Xunit; using FluentAssertions; using PdfSharp.Diagnostics; -using PdfSharp.Fonts; + +#if PDFSHARP_DEBUG +using static PdfSharp.Diagnostics.DebugBreakHelper; +#endif namespace PdfSharp.Tests.IO { @@ -106,7 +110,6 @@ public void Read_reals_and_integers_test() // Now add many different numbers for testing. var dict = new PdfDictionary(doc); var array = new PdfArray(doc); - dict.Elements["/empiraPlayground"] = array; array.Elements.Add(new PdfLiteral("-32768")); array.Elements.Add(new PdfLiteral("-2147483648")); // Int32 array.Elements.Add(new PdfLiteral("-2147483649")); // Int64 @@ -123,24 +126,16 @@ public void Read_reals_and_integers_test() // This line causes an error message in Adobe Reader. array.Elements.Add(new PdfLiteral("12345678901234567890.12345678901234567890")); array.Elements.Add(new PdfLiteral("-9223372036854775808")); // Int64 doc.Internals.AddObject(dict); - doc.Internals.Catalog.Elements["/empiraPlayground"] = PdfInternals.GetReference(dict); page.Resources.Elements["/NumberTest"] = array; - //array = new PdfArray(doc); - //array.Elements.Add(new PdfInteger(-32768)); - //array.Elements.Add(new PdfIntegerObject(-32768)); - //array.Elements.Add(new PdfReal(17)); - //array.Elements.Add(new PdfRealObject(17)); - //array.Elements.Add(new PdfReal(17.7)); - //array.Elements.Add(new PdfRealObject(17.7)); - //array.Elements.Add(new PdfString("Foo")); - //array.Elements.Add(new PdfStringObject("Foo_äöüß", PdfStringEncoding.PDFDocEncoding)); - //array.Elements.Add(new PdfStringObject("Foo_äöüß", PdfStringEncoding.WinAnsiEncoding)); - //array.Elements.Add(new PdfStringObject("Foo_äöüß", PdfStringEncoding.Unicode)); - //page.Resources.Elements["/ReferenceTest"] = array; - +#if true_ + // Create a file for debugging. + using var stream = new FileStream("$temp$$$.pdf", FileMode.Create); +#else + // Use a memory stream for speed. using var stream = new MemoryStream(); +#endif doc.Save(stream, false); stream.Position = 0; stream.Length.Should().BeGreaterThan(0); @@ -237,7 +232,7 @@ public void Custom_properties_test() doc3.Info.Title.Should().Be("Test"); var doc4 = PdfReader.Open(stream3, PdfDocumentOpenMode.Modify); - var filename = PdfFileUtility.GetTempPdfFileName("Custom_properties"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/IO/Custom_properties"); doc4.Save(filename); PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -269,7 +264,7 @@ public void Read_and_modify_PDF_file_PageOrientation_test() $"Page {i + 1} - Rotate {page.Rotate} - Orientation {page.Orientation}"); } - // Save the document... + // Save the document… string filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationTestStep1"); document.Save(filename); @@ -285,7 +280,7 @@ public void Read_and_modify_PDF_file_PageOrientation_test() DrawPageFrame(page, XColors.Yellow, 10); } - // Save the document... + // Save the document… filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationTestStep2"); document.Save(filename); @@ -297,7 +292,7 @@ public void Read_and_modify_PDF_file_PageOrientation_test() DrawPageFrame(page, XColors.Green, 5); } - // Save the document... + // Save the document… filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationTestStep3"); document.Save(filename); @@ -348,7 +343,7 @@ public void Read_and_modify_PDF_file_CustomPageSize_PageOrientation_test() DrawFooter(page, XBrushes.Red, box, "After creation " + i); } - // Save the document... + // Save the document… string filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationCustomTestStep1"); document.Save(filename); @@ -370,10 +365,11 @@ public void Read_and_modify_PDF_file_CustomPageSize_PageOrientation_test() DrawFooter(page, XBrushes.Yellow, box, "After import " + i); } - // Save the document... + // Save the document… filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationCustomTestStep2"); document.Save(filename); + //ShouldBreak1 = true; // Read the document from step 2. document = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); for (int i = 0; i < 8; ++i) @@ -386,9 +382,10 @@ public void Read_and_modify_PDF_file_CustomPageSize_PageOrientation_test() DrawFooter(page, XBrushes.Green, box, "After second import " + i); } - // Save the document... + // Save the document… filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationCustomTestStep3"); document.Save(filename); + //ShouldBreak1 = false; // Read the document from step 3. document = PdfReader.Open(filename, PdfDocumentOpenMode.Import); @@ -411,7 +408,7 @@ public void Read_and_modify_PDF_file_CustomPageSize_PageOrientation_test() DrawFooter(page, XBrushes.LimeGreen, box, "After page import " + i); } - // Save the document... + // Save the document… filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationCustomTestStep4"); doc2.Save(filename); static void DrawPageFrame(PdfPage page, XColor color, Int32 width, string? label = null) @@ -437,5 +434,138 @@ static void DrawFooter(PdfPage page, XBrush color, XRect box, string footer) gfx.DrawString(footer, font, color, box, format); } } + + [Fact] // Merge with test above + public void Read_and_modify_PDF_file_CustomPageSize_PageOrientation_test_NT() // PDFsharp/NT + { + // Simplified version of the test above to find dead object exception. + // StL / 25-02-02 + // This test creates all 8 possible variations of 14 cm by 16 cm pages. + // Step 1 draws a red frame around the page. + // Step 2 draws a narrower, yellow frame around the page. + // Step 3 draws a narrow, green frame around the page. + // All frames should be visible after step 3 and should be around the edges of the pages. + var document = new PdfDocument(); + + // There are 4 possible values for rotation and 2 options for orientation. + // We create 8 pages to cover all cases. + const int pageCount = 2; + for (int i = 0; i < pageCount; ++i) + { + var page = document.AddPage(); + page.Width = XUnit.FromCentimeter(14); + page.Height = XUnit.FromCentimeter(16); + page.Rotate = i % 4 * 90; + page.Orientation = (i / 4) switch + { + 0 => PageOrientation.Portrait, + 1 => PageOrientation.Landscape, + _ => throw new ArgumentOutOfRangeException() + }; + DrawPageFrame(page, XColors.Red, 20, + $"Page {i + 1} - Rotate {page.Rotate} - Orientation {page.Orientation}"); + + var box = new XRect(0, 0, page.Width.Point, page.Height.Point); + box.Inflate(0, -20); + DrawFooter(page, XBrushes.Red, box, "After creation " + i); + } + + // Save the document… + string filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationCustomTestNTStep1"); + document.Save(filename); + + // Read the document from step 1. + document = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + var a1 = document.Catalog.Elements["/Pages"]; + var a2 = a1.As().Value; + var a3 = a2.As().Elements["/Kids"]; + + //ShouldBreak5 = false; + for (int i = 0; i < pageCount; ++i) + { + var page = document.Pages[i]; + var width = page.MediaBox.Width; + var height = page.MediaBox.Height; + + width.Should().BeApproximately(page.Width.Point, 2); + height.Should().BeApproximately(page.Height.Point, 2); + + DrawPageFrame(page, XColors.Yellow, 10); + + var box = new XRect(0, 0, page.Width.Point, page.Height.Point); + box.Inflate(0, -30); + DrawFooter(page, XBrushes.Yellow, box, "After import " + i); + } + //ShouldBreak5 = false; + + // Save the document… + filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationCustomTestNTStep2"); + document.Save(filename); + + //ShouldBreak1 = false; + // Read the document from step 2. + document = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + for (int i = 0; i < pageCount; ++i) + { + var page = document.Pages[i]; + DrawPageFrame(page, XColors.Green, 5); + + var box = new XRect(0, 0, page.Width.Point, page.Height.Point); + box.Inflate(0, -40); + DrawFooter(page, XBrushes.Green, box, "After second import " + i); + } + + // Save the document… + filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationCustomTestNTStep3"); + document.Save(filename); + //ShouldBreak1 = false; + + // Read the document from step 3. + document = PdfReader.Open(filename, PdfDocumentOpenMode.Import); + var doc2 = new PdfDocument(); + for (int i = 0; i < pageCount; ++i) + { + var page0 = document.Pages[i]; + var page = doc2.AddPage(page0); + + var width = page.MediaBox.Width; + var height = page.MediaBox.Height; + + width.Should().BeApproximately(page.Width.Point, 2); + height.Should().BeApproximately(page.Height.Point, 2); + + DrawPageFrame(page, XColors.LimeGreen, 2); + + var box = new XRect(0, 0, page.Width.Point, page.Height.Point); + box.Inflate(0, -50); + DrawFooter(page, XBrushes.LimeGreen, box, "After page import " + i); + } + + // Save the document… + filename = PdfFileUtility.GetTempPdfFullFileName("unittests/IO/ReaderTests/OrientationCustomTestNTStep4"); + doc2.Save(filename); + static void DrawPageFrame(PdfPage page, XColor color, Int32 width, string? label = null) + { + // Draw a frame around the page. Add an optional label. + using var gfx = XGraphics.FromPdfPage(page); + var pen = new XPen(XColors.OrangeRed, 10) { LineCap = XLineCap.Round }; + gfx.DrawLine(pen, 0, 0, 200, 200); + gfx.DrawRectangle(new XPen(color, width), new XRect(0, 0, page.Width.Point, page.Height.Point)); + if (!String.IsNullOrWhiteSpace(label)) + { + var font = new XFont("Verdana", 10, XFontStyleEx.Regular); + gfx.DrawString(label ?? "", font, XBrushes.Firebrick, 20, 20); + } + } + + static void DrawFooter(PdfPage page, XBrush color, XRect box, string footer) + { + // Draw a frame around the page. Add an optional label. + using var gfx = XGraphics.FromPdfPage(page); + var font = new XFont("Verdana", 10, XFontStyleEx.Regular); + var format = XStringFormats.BottomCenter; + gfx.DrawString(footer, font, color, box, format); + } + } } } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs index 278b4b9c..3af04c71 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs @@ -6,7 +6,6 @@ using PdfSharp.Drawing; using PdfSharp.Fonts; using PdfSharp.Pdf; -using PdfSharp.Pdf.Internal; using PdfSharp.Pdf.IO; using PdfSharp.Quality; using PdfSharp.Snippets.Font; @@ -18,56 +17,12 @@ namespace PdfSharp.Tests.IO [Collection("PDFsharp")] public class WriterTests { - -#if true - [Fact] - public void Test_issues() - { - var document = CreateDocument("CompactLayout-IssuesXXX"); - //var dict = new PdfDictionary(); - document.Options.Layout = PdfWriterLayout.Compact; - - var catalog = document.Catalog; - - var dict = new PdfDictionary(document/*, true*/); - document.Internals.AddObject(dict); - var bytes = PdfEncoders.RawEncoding.GetBytes("ABC"); - dict.CreateStream(bytes); - - catalog.Elements.Add("/TestStream", dict); - - Save(document); - //Reload(); - //ReloadedDocument.Options.Layout = PdfWriterLayout.Compact; - //Resave(); - - //CreateDocument("CompactLayout-Issues"); - } - protected PdfDocument CreateDocument(string name) - { - var _document = PdfDocUtility.CreateNewPdfDocument(name); - _document.Info.Title = name; - var _page = _document.AddPage(); - - return _document; - } - protected string Save(PdfDocument document, string? filename = null) - { - filename ??= document.Info.Title; - var _fullFilename = PdfFileUtility.GetTempPdfFullFileName("Pdf-ObjectModel/" + filename); - //Document.Options.Layout = PdfWriterLayout.Compact; - document.Save(_fullFilename); - return _fullFilename; - } -#endif - - [Fact] public void Write_import_file() { var testFile = IOUtility.GetAssetsPath("archives/samples-1.5/PDFs/SomeLayout.pdf")!; - var filename = PdfFileUtility.GetTempPdfFileName("ImportTest"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/IO/ImportTest"); var doc = PdfReader.Open(testFile, PdfDocumentOpenMode.Import); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Internal/ErrorMessages.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Internal/ErrorMessages.cs new file mode 100644 index 00000000..b8c54b88 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Internal/ErrorMessages.cs @@ -0,0 +1,25 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Attachments; +using PdfSharp.Pdf.Signatures; +using Xunit; + +namespace PdfSharp.Tests.Internal +{ + [Collection("PDFsharp")] + public class ErrorMessages + { + [Fact] + public void Test_error_messages() + { + //EmbeddedFilesManager.AddFile2(); + + var msg1 = new PsCryptoMsg(PsCryptoMsgId.SampleMessage1, "some text"); + + var msg2 = new PsCryptoMsg2(PsCryptoMsgId.SampleMessage1, "some more text"); + PsCryptoMsgId id1 = msg2.Id; + int i2d = ((IErrorMessageInfo)msg2).Id; + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Parsers/pdf-files/StringParserTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Parsers/pdf-files/StringParserTests.cs new file mode 100644 index 00000000..91513d77 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Parsers/pdf-files/StringParserTests.cs @@ -0,0 +1,77 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Internal; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using Xunit; + +namespace PdfSharp.Tests.Pdf +{ + [Collection("PDFsharp")] + public class StringParserTests + { + [Fact] + public void Test_Panose_string() + { + var doc = new PdfDocument(); + doc.Pages.Add(); + + var catalog = doc.Catalog; + var dict = new PdfDictionary(doc, true); + catalog.Elements.Add("/TestStuff", dict); + + // Sample from the specs. + dict.Elements.Add("/Panose1", new PdfDebugItem("<01 05 02 02 03 00 00 00 00 00 00 00>")); + + // "< 0 0 2 b 6 6 3 8 4 2 2 4>" comes from a bug report. + // According to the specs (PDF 2.0: 7.3.4.3 Hexadecimal strings / Page 27) + // white space shall be ignored: + // “Each pair of hexadecimal digits defines one byte of the string. White-space characters + // shall be ignored.” + // "< 0 0 2 b 6 6 3 8 4 2 2 4>" => "<002b66384224>" + // is a string of 6 bytes. + // However, the string comes with a /Panose entry and is expected to be 12 bytes. + // That is: + // "< 0 0 2 b 6 6 3 8 4 2 2 4>" => "<00 00 02 0b 06 06 03 08 04 02 02 04>" or "<0000020b0606030804020204>" + // Therefore PDFsharp now interprets a single hex character "x" as "0x". + dict.Elements.Add("/Panose2", new PdfDebugItem("< 0 0 2 b 6 6 3 8 4 2 2 4>")); + + dict.Elements.Add("/Panose3", new PdfDebugItem("< 0 0 2 b 6 6 3 8 4 2 2 4 >")); + dict.Elements.Add("/Panose4", new PdfDebugItem("< 00 00 02 0b 06 06 03 08 04 02 02 04 >")); + + var filename = PdfFileUtility.GetTempPdfFullFileName("Parsers/" + nameof(Test_Panose_string)); + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Save(filename); + + var doc2 = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + var stuff = doc2.Catalog.Elements.GetRequiredDictionary("/TestStuff"); + var p1 = stuff.Elements.GetString("/Panose1"); + var p2 = stuff.Elements.GetString("/Panose2"); + var p3 = stuff.Elements.GetString("/Panose3"); + var p4 = stuff.Elements.GetString("/Panose4"); + + p1.Length.Should().Be(12); + p2.Length.Should().Be(12); + p2.Should().Be(p3); + p2.Should().Be(p4); + + filename = PdfFileUtility.GetTempPdfFullFileName("Parsers/" + nameof(Test_Panose_string) + "#2"); + doc2.Options.Layout = PdfWriterLayout.Verbose; + doc2.Save(filename); + + // PDFsharp wrote the following code in the first place: + // + // /Panose1 <010502020300000000000000> + // /Panose2 <0000020B0606030804020240> + // /Panose3 <0000020B0606030804020204> + // /Panose4 <0000020B0606030804020204> + // + // Note that the last "4" in /Panose2 goes to "40". + // To fix this I add a hack to ScanHexadecimalString in case + // the hex-string is presumable a 12 byte Panose style code. + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Actions/.gitkeep b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Actions/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/AnnotationTestBase.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/AnnotationTestBase.cs new file mode 100644 index 00000000..0d50d73b --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/AnnotationTestBase.cs @@ -0,0 +1,45 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.IO; +using FluentAssertions; +using PdfSharp.Diagnostics; +using PdfSharp.Drawing; +using PdfSharp.Fonts; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using PdfSharp.Snippets.Font; +using PdfSharp.TestHelper; +using Xunit; + +namespace PdfSharp.Tests.Pdf.Annotations +{ + [Collection("PDFsharp")] + public class AnnotationTestBase : IDisposable + { + public AnnotationTestBase() + { + // These tests only run under Windows or WSL2. + GlobalFontSettings.UseWindowsFontsUnderWindows = true; + GlobalFontSettings.UseWindowsFontsUnderWsl2 = true; + } + + public void Dispose() + { + GlobalFontSettings.ResetAll(); + } + + protected PdfDocument CreateDocument(string name) + { + var document = new PdfDocument(); + var page = document.AddPage(); + var gfx = XGraphics.FromPdfPage(page); + var font = new XFont("Arial", 18); + gfx.DrawString(name, font, XBrushes.DarkBlue, 50, 100); + + return document; + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/annotations/AnnotationTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/AnnotationTests_old.cs similarity index 50% rename from src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/annotations/AnnotationTests.cs rename to src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/AnnotationTests_old.cs index 16307cdc..1ad5b306 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/annotations/AnnotationTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/AnnotationTests_old.cs @@ -7,18 +7,68 @@ using PdfSharp.Drawing; using PdfSharp.Fonts; using PdfSharp.Pdf; +using PdfSharp.Pdf.Annotations; using PdfSharp.Pdf.IO; using PdfSharp.Quality; using PdfSharp.Snippets.Font; using PdfSharp.TestHelper; using Xunit; -namespace PdfSharp.Tests.PDF +namespace PdfSharp.Tests.Pdf { [Collection("PDFsharp")] public class AnnotationTests { - [Fact(Skip = "Disabled until /Annots bug is fixed")] + const string TempRoot = "unittests/annotations/"; + + /// + /// Tests the test. + /// + [SkippableFact] + public void Test_all_annotations() + { + string src = @"C:\Users\StLa\Desktop\PDFsharp\ISO_32000-2_2020(en)_.pdf"; + + if (!File.Exists(src)) + { + Skip.If(true); + } + + var doc = PdfReader.Open(src, PdfDocumentOpenMode.Modify); + + var pageCount = doc.PageCount; + int annotCount = 0; + for (int idx = 0; idx < pageCount; idx++) + { + var page = doc.Pages[idx]; + if (page.HasAnnotations) + { + var annots = page.Annotations; + var count = annots.Count; + + annotCount += count; + } + } + + ////C1.CreateAcroFormObjects(doc); + ////C1.CreateAnnotationObjects(doc); + + ////var fields = C2.CreateFieldList(doc); + + //var objs = doc.Internals.GetAllObjects(); + //for (int idx = 0; idx < objs.Length; idx++) + //{ + // if (objs[idx] is PdfDictionary { Stream: not null } dict) + // { + // dict.Stream.TryUncompress(); + // } + + var dest = PdfFileUtility.GetTempPdfFullFileName(TempRoot + nameof(Test_all_annotations)); + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Save(dest); + } + + [Fact] public void Exception_with_duplicate_annotations() { var document1 = new PdfDocument(); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/NameTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/CreateAnnotationTests_old_delete.cs similarity index 57% rename from src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/NameTests.cs rename to src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/CreateAnnotationTests_old_delete.cs index a9e01c07..caac01f5 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/NameTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Annotations/CreateAnnotationTests_old_delete.cs @@ -1,23 +1,27 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using System.Globalization; +using System.IO; +using System.Xml.Linq; using PdfSharp.Diagnostics; using PdfSharp.Drawing; using PdfSharp.Fonts; using PdfSharp.Pdf; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.IO; using PdfSharp.Quality; using PdfSharp.Snippets.Font; using PdfSharp.TestHelper; +using PdfSharp.Drawing.Layout; using Xunit; +using FluentAssertions; namespace PdfSharp.Tests.Pdf { [Collection("PDFsharp")] - public class NamesTests + public class CreateAnnotationTests { - [Fact] - public void ToDo() - { - } + const string TempRoot = "unittests/annotations/"; } } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Attachments/EmbeddedFilesTests-old.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Attachments/EmbeddedFilesTests-old.cs new file mode 100644 index 00000000..76bc658d --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Attachments/EmbeddedFilesTests-old.cs @@ -0,0 +1,37 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Diagnostics; +using PdfSharp.Fonts; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Quality; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.Pdf.Attachments +{ + [Collection("PDFsharp")] + public class EmbeddedFilesTests_old : IDisposable + { + public EmbeddedFilesTests_old() + { + PdfSharpCore.ResetAll(); +#if CORE + GlobalFontSettings.FontResolver = new UnitTestFontResolver(); +#endif + } + + public void Dispose() + { + PdfSharpCore.ResetAll(); + } + + [Fact(Skip = "xxx")] + public void AddEmbeddedFile() + { + var x = new PdfEmbeddedFileParameters((PdfDocument)null!); + var y = new PdfEmbeddedFileStream((PdfDictionary)null!); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Attachments/EmbeddedFilesTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Attachments/EmbeddedFilesTests.cs new file mode 100644 index 00000000..7a0cf542 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Attachments/EmbeddedFilesTests.cs @@ -0,0 +1,118 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Diagnostics; +using PdfSharp.Diagnostics; +using PdfSharp.Fonts; +using PdfSharp.Pdf.Attachments; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.Pdf.Attachments +{ + [Collection("PDFsharp")] + public class EmbeddedFilesTests : IDisposable + { + public EmbeddedFilesTests() + { + PdfSharpCore.ResetAll(); +#if CORE + GlobalFontSettings.FontResolver = new UnitTestFontResolver(); +#endif + } + + public void Dispose() + { + PdfSharpCore.ResetAll(); + } + + [Fact] + public void Get_embedded_files_low_level() + { + var root = IOUtility.GetAssetsPath("pdfsharp-6.x/pdfs/generated/attachments"); + Debug.Assert(root != null); + + var fileName = Path.Combine(root, "TestFile-3Attachments.pdf"); + var doc = PdfReader.Open(fileName); + + var nameDict = doc.Catalog.Names; + var has = nameDict.HasEmbeddedFiles; + var ef = nameDict.GetEmbeddedFiles(); + + var count = ef.Elements.Count; + count.Should().Be(1, @"there is a /Names entry."); + + var keys = ef.Names?.NamesKeys ?? throw new InvalidOperationException("No /Names entry."); + keys.Length.Should().Be(3); + keys[0].Should().Be("embedded_file_1"); + keys[1].Should().Be("embedded_file_2"); + keys[2].Should().Be("embedded_file_3"); + + count = ef.FileCount; + count.Should().Be(3); + + var kids = ef.Kids; + kids.Should().BeNull(); + + var names = ef.Names; + names.Should().NotBeNull(); + + var limits = ef.Limits; + limits.Should().BeNull(); + + PdfFileSpecification fileSpecification1 = ef.GetFileSpecification(0); + PdfFileSpecification fileSpecification2 = ef.GetFileSpecification(1); + PdfFileSpecification fileSpecification3 = ef.GetFileSpecification(2); + + var fileInfo1 = fileSpecification1.GetFileInfo(); + CheckFileInfo(fileInfo1, "embedded_file_1", "PDFsharp-128x128.png", "/image/png", 6666); + + + var fileInfo2 = fileSpecification2.GetFileInfo(); + CheckFileInfo(fileInfo2, "embedded_file_2", "2nd Dummy File", "/text/plain", 19); + + var fileInfo3 = fileSpecification3.GetFileInfo(); + CheckFileInfo(fileInfo3, "embedded_file_3", "3rd Dummy File", "/text/plain", 19); + } + + [Fact] + public void Get_embedded_files_using_EmbeddedFilesManager() + { + var root = IOUtility.GetAssetsPath("pdfsharp-6.x/pdfs/generated/attachments"); + Debug.Assert(root != null); + + var fileName = Path.Combine(root, "TestFile-3Attachments.pdf"); + var doc = PdfReader.Open(fileName); + + var efm = EmbeddedFilesManager.ForDocument(doc); + + var count = efm.FileCount; + count.Should().Be(3); + + var keys = efm.NamesKeys; + keys.Length.Should().Be(3); + keys[0].Should().Be("embedded_file_1"); + keys[1].Should().Be("embedded_file_2"); + keys[2].Should().Be("embedded_file_3"); + + var fileInfo1 = efm.GetEmbeddedFileInfo(0); + CheckFileInfo(fileInfo1, "embedded_file_1", "PDFsharp-128x128.png", "/image/png", 6666); + + var fileInfo2 = efm.GetEmbeddedFileInfo(1); + CheckFileInfo(fileInfo2, "embedded_file_2", "2nd Dummy File", "/text/plain", 19); + + var fileInfo3 = efm.GetEmbeddedFileInfo(2); + CheckFileInfo(fileInfo3, "embedded_file_3", "3rd Dummy File", "/text/plain", 19); + } + + void CheckFileInfo(EmbeddedFileInfo fileInfo, string namesKey, string fileName, string fileType, int dataLength) + { + fileInfo.NamesKey.Should().Be(namesKey); + fileInfo.FileName.Should().Be(fileName); + fileInfo.FileType.Should().Be(fileType); + fileInfo.Data.Length.Should().Be(dataLength); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Content/ContentReaderTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Content/ContentReaderTests.cs new file mode 100644 index 00000000..b77ff674 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Content/ContentReaderTests.cs @@ -0,0 +1,73 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Text; +using PdfSharp.Diagnostics; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.Content; +using PdfSharp.Pdf.Content.Objects; +using PdfSharp.Quality; +using Xunit; +using FluentAssertions; + + +namespace PdfSharp.Tests.Pdf.Content +{ + [Collection("PDFsharp")] + public class ContentReaderTests : IDisposable + { + public ContentReaderTests() + { + PdfSharpCore.ResetAll(); + } + + public void Dispose() + { + PdfSharpCore.ResetAll(); + } + + [Fact] + public void Read_HelloWorld() + { + const int requiredAssets = 1032; + IOUtility.EnsureAssetsVersion(requiredAssets); + + const string pdf = @"archives/samples-1.5/PDFs/HelloWorld.pdf"; + var testFile = IOUtility.GetAssetsPath(pdf)!; + + var doc = PdfReader.Open(testFile, PdfDocumentOpenMode.Import); + + doc.PageCount.Should().Be(1); + doc.Pages.Count.Should().Be(1); + var page = doc.Pages[0]; + var contents = ContentReader.ReadContent(page); + int comments = 0, names = 0, operators = 0, sequences = 0, strings = 0, arrays = 0, integers = 0, reals = 0; + var sb = new StringBuilder(); + foreach (var item in contents) + { + string info = item.GetType().Name switch + { + nameof(CComment) => $"Comment {++comments}", + nameof(CName) => $"Name {++names}", + nameof(COperator) => $"Operator {++operators}: {((COperator)item).Name} with {((COperator)item).Operands.Count} operands", + nameof(CSequence) => $"Sequence {++sequences}", + nameof(CString) => $"String {++strings}", + nameof(CArray) => $"Array {++arrays}", + nameof(CInteger) => $"Integer {++integers}", + nameof(CReal) => $"Real {++reals}", + _ => throw new NotImplementedException($"Type {item.GetType().Name} was unexpected.") + }; + sb.AppendLine(info); + } + var infos = sb.ToString(); + comments.Should().Be(0); + names.Should().Be(0); + operators.Should().Be(12); + sequences.Should().Be(0); + strings.Should().Be(0); + arrays.Should().Be(0); + integers.Should().Be(0); + reals.Should().Be(0); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Content/ContentWriterTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Content/ContentWriterTests.cs new file mode 100644 index 00000000..66c6371c --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Content/ContentWriterTests.cs @@ -0,0 +1,372 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Diagnostics; +using PdfSharp.Drawing; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Content; +using PdfSharp.Pdf.Content.Objects; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using System.Text; +using Xunit; + +namespace PdfSharp.Tests.Pdf.Content +{ + [Collection("PDFsharp")] + public class ContentWriterTests : IDisposable + { + readonly string _tempRoot = PdfFileUtility.GetUnitTestPath(typeof(ContentWriterTests)); + + public ContentWriterTests() + { + PdfSharpCore.ResetAll(); + } + + public void Dispose() + { + PdfSharpCore.ResetAll(); + } + + [SkippableFact] + public void Test_one_extracted_page() + { + var assets = IOUtility.GetAssetsPath(); + var file = @"D:\repos\empira\PDFsharp\assets\StLa\content\word\Hallo Welt_.pdf"; + Skip.If(!File.Exists(file)); + + var name = "PDF-Reference-1.7-Page-1"; + name = "Hallo-2"; + + // 1. Read the file, parse contents, write contents. + var original = PdfReader.Open(file, PdfDocumentOpenMode.Import); + var doc = new PdfDocument(); + doc.Pages.Add(original.Pages[0]); + + var p1 = doc.Pages[0]; + var cont = p1.Elements.GetDictionary(PdfPage.Keys.Contents); + cont?.Stream?.TryUncompress(); + + + var filename = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + name); + doc.Save(filename); + + //doc = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + doc = PdfReader.Open(file, PdfDocumentOpenMode.Modify); + + foreach (var page in doc.Pages) + { + var newContent = new CSequence(); + var contents = ContentReader.ReadContent(page); + foreach (var item in contents) + { + newContent.Add(item); + } + page.Contents.ReplaceContent(newContent); + } + + //var filename = PdfFileUtility.GetTempPdfFullFileName("unittest/contentwritertests/read_write_compare"); + + filename = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + name + "_2nd"); + + // Save the document… + doc.Save(filename); + + return; + + //// … and start a viewer. + //PdfFileUtility.ShowDocumentIfDebugging(filename); + + //// 2. Read the file just written, parse contents, write contents. + //var doc2 = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + //foreach (var page in doc2.Pages) + //{ + // var newContent = new CSequence(); + // var contents = ContentReader.ReadContent(page); + // foreach (var item in contents) + // { + // newContent.Add(item); + // } + // page.Contents.ReplaceContent(newContent); + //} + + //var filename2 = filename.Replace(name, name + "_2nd"); + //doc2.Save(filename2); + + //doc.Pages.Count.Should().Be(doc2.Pages.Count); + //int pages = doc.Pages.Count; + + //var xformOri = XPdfForm.FromFile(file); + //var xformWritten = XPdfForm.FromFile(filename2); + + //// 3. Read the file just written, parse contents, write contents. + //var docCompare = new PdfDocument(); + //for (int idx = 0; idx < pages; ++idx) + //{ + // // Add a new page to the output document + // PdfPage pageCompare = docCompare.AddPage(); + // pageCompare.Orientation = PageOrientation.Landscape; + // double width = pageCompare.Width.Point; + // double height = pageCompare.Height.Point; + + // var gfx = XGraphics.FromPdfPage(pageCompare); + + // xformOri.PageNumber = idx + 1; + // var box = new XRect(0, 0, width / 2, height); + // gfx.DrawImage(xformOri, box); + + // xformWritten.PageNumber = idx + 1; + // box = new XRect(width / 2, 0, width / 2, height); + // gfx.DrawImage(xformWritten, box); + //} + + //var filename3 = filename2.Replace("_2nd", "_compare"); + //docCompare.Save(filename3); + //PdfFileUtility.ShowDocumentIfDebugging(filename3); + } + + + [SkippableTheory] + ////// Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-Border.pdf")] + ////// Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-Color.pdf")] + ////// Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-LineAndFillFormat.pdf")] + ////// Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-Shading.pdf")] + ////// Crashes: [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\CPP 1.10\Attributes-Units.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-Border.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-Color.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-LineAndFillFormat.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-Shading.pdf")] + [InlineData(@"$assets$\archives\grammar-by-example\GBE\ReferencePDFs\GDI 1.30\Attributes-Units.pdf")] + [InlineData(@"C:\Users\StLa\Desktop\PDFsharp\PDF-References\pdfreference1.7.pdf")] + public void Read_Write_Compare_Sample_Files(string file0) + { + TestConfig.CheckManually(new(2025, 11, 9)); + + var assets = IOUtility.GetAssetsPath(); + var file = Environment.ExpandEnvironmentVariables(file0.Replace("$assets$", assets)); + if (!File.Exists(file)) + { + Skip.If(true); + } + var name = Path.GetFileNameWithoutExtension(file); + + // 1. Read the file, parse contents, write contents. + var doc = PdfReader.Open(file, PdfDocumentOpenMode.Modify); + foreach (var page in doc.Pages) + { + var newContent = new CSequence(); + var contents = ContentReader.ReadContent(page); + foreach (var item in contents) + { + newContent.Add(item); + } + page.Contents.ReplaceContent(newContent); + } + + //var filename = PdfFileUtility.GetTempPdfFullFileName("unittest/contentwritertests/read_write_compare"); + + var filename = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + name); + + // Save the document… + doc.Save(filename); + //// … and start a viewer. + //PdfFileUtility.ShowDocumentIfDebugging(filename); + + // 2. Read the file just written, parse contents, write contents. + var doc2 = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + foreach (var page in doc2.Pages) + { + var newContent = new CSequence(); + var contents = ContentReader.ReadContent(page); + foreach (var item in contents) + { + newContent.Add(item); + } + page.Contents.ReplaceContent(newContent); + } + + var filename2 = filename.Replace(name, name + "_2nd"); + doc2.Save(filename2); + + doc.Pages.Count.Should().Be(doc2.Pages.Count); + int pages = doc.Pages.Count; + + var xformOri = XPdfForm.FromFile(file); + var xformWritten = XPdfForm.FromFile(filename2); + + // 3. Read the file just written, parse contents, write contents. + var docCompare = new PdfDocument(); + for (int idx = 0; idx < pages; ++idx) + { + // Add a new page to the output document + PdfPage pageCompare = docCompare.AddPage(); + pageCompare.Orientation = PageOrientation.Landscape; + double width = pageCompare.Width.Point; + double height = pageCompare.Height.Point; + + var gfx = XGraphics.FromPdfPage(pageCompare); + + xformOri.PageNumber = idx + 1; + var box = new XRect(0, 0, width / 2, height); + gfx.DrawImage(xformOri, box); + + xformWritten.PageNumber = idx + 1; + box = new XRect(width / 2, 0, width / 2, height); + gfx.DrawImage(xformWritten, box); + } + + var filename3 = filename2.Replace("_2nd", "_compare"); + docCompare.Save(filename3); + PdfFileUtility.ShowDocumentIfDebugging(filename3); + } + + [Fact] + public void Create_simple_PDF() + { + var doc = new PdfDocument(); + var page = doc.AddPage(); + + var newContent = new CSequence(); + var comment = new CComment + { + Text = "Begin page content" + }; + newContent.Add(comment); + + // TODO Dies ist kein sinnvoller Befehl, sondern nur ein Writer-Test. + var @operator = OpCodes.OperatorFromName("Tj"); + var array = new CArray(); + var cint = new CInteger(1); + array.Add(cint); + var creal = new CReal(2); + array.Add(creal); + creal = new CReal(3); + array.Add(creal); + + // This line adds 3 operands: the array members, not the CArray itself. + @operator.Operands.Add(array); + + // This line adds one operand, the CArray. + //@operator.Operands.Add((CObject)array); + newContent.Add(@operator); + + comment = new CComment + { + Text = "End page content" + }; + newContent.Add(comment); + page.Contents.ReplaceContent(newContent); + + var filename = PdfFileUtility.GetTempPdfFullFileName("unittest/contentwritertests/create_simple_PDF"); + + // Save the document… + doc.Save(filename); + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(filename); + + { + doc = PdfReader.Open(filename, PdfDocumentOpenMode.Modify); + var pages = doc.Pages.Count; + pages.Should().Be(1); + + // Create a PDF with readable contents. + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Options.CompressContentStreams = false; + + foreach (var page2 in doc.Pages) + { + var content = new CSequence(); + // Clone page contents. + foreach (var item in ContentReader.ReadContent(page)) + content.Add(item); + // Force serializer to write contents. + page.Contents.ReplaceContent(content); + } + + var filename2 = filename.Replace(".pdf", "_.pdf"); + doc.Save(filename2); + } + } + + [Fact] + public void Modify_HelloWorld() + { + const int requiredAssets = 1032; + IOUtility.EnsureAssetsVersion(requiredAssets); + + const string pdf = @"archives/samples-1.5/PDFs/HelloWorld.pdf"; + var testFile = IOUtility.GetAssetsPath(pdf)!; + + var doc = PdfReader.Open(testFile, PdfDocumentOpenMode.Modify); + + doc.PageCount.Should().Be(1); + doc.Pages.Count.Should().Be(1); + var page = doc.Pages[0]; + var contents = ContentReader.ReadContent(page); + int comments = 0, names = 0, operators = 0, sequences = 0, strings = 0, arrays = 0, integers = 0, reals = 0; + var sb = new StringBuilder(); + //int text = 0; + var newContent = new CSequence(); + int idx = 0; + foreach (var item in contents) + { + string info = item.GetType().Name switch + { + nameof(CComment) => $"Comment {++comments}", + nameof(CName) => $"Name {++names}", + nameof(COperator) => $"Operator {++operators}: {((COperator)item).Name} with {((COperator)item).Operands.Count} operands", + nameof(CSequence) => $"Sequence {++sequences}", + nameof(CString) => $"String {++strings}", + nameof(CArray) => $"Array {++arrays}", + nameof(CInteger) => $"Integer {++integers}", + nameof(CReal) => $"Real {++reals}", + _ => throw new NotImplementedException($"Type {item.GetType().Name} was unexpected.") + }; + sb.AppendLine(info); + + if (item is COperator @operator) + { + if (@operator.OpCode.OpCodeName == OpCodeName.Tj) + { + foreach (var op in @operator.Operands) + { + if (op is CString @string) + { + ++idx; + } + } + newContent.Add(item); + } + else + { + newContent.Add(item); + } + } + else + { + newContent.Add(item); + } + } + var infos = sb.ToString(); + comments.Should().Be(0); + names.Should().Be(0); + operators.Should().Be(12); + sequences.Should().Be(0); + strings.Should().Be(0); + arrays.Should().Be(0); + integers.Should().Be(0); + reals.Should().Be(0); + + page.Contents.ReplaceContent(newContent); + var filename = Path.GetFileName(pdf).Replace(".pdf", "_.pdf"); + var path = IOUtility.GetTempPath("unittest/contentwritertests")!; + path = Path.Combine(path, filename); + + // Save the document… + doc.Save(path); + // … and start a viewer. + PdfFileUtility.ShowDocumentIfDebugging(path); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Forms/AcroFormStuff.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Forms/AcroFormStuff.cs new file mode 100644 index 00000000..edb9074c --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Forms/AcroFormStuff.cs @@ -0,0 +1,281 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#if NET8_0_OR_GREATER +using System.Diagnostics; +using System.Text; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.Forms; + +// TODO: DELETE before first release + +namespace PdfSharp.Tests.Pdf.Forms // TODO: FormsCleanUp: Folder Pdf.Forms can be removed as soon as empty. +{ + public static class C1 // TODO: FormsCleanUp: Delete - current code in AnnotationPreparer and AcroFieldPreparer + { + public static void CreateAcroFormObjects(PdfDocument doc) + { + var catalog = doc.Catalog; + var acroForm = catalog.GetAcroForm(); + + var fields = acroForm?.Elements.GetArray(PdfForm.Keys.Fields); + if (fields != null) + { + var fieldCount = fields.Elements.Count; + + for (int idx = 0; idx < fieldCount; idx++) + { + var field = fields.Elements.GetRequiredDictionary(idx); + var ft = field.Elements[PdfFormField.Keys.FT]; + var subFieldCount = field.Elements.GetArray(PdfForm.Keys.Fields)?.Count() ?? 0; + + if (field is not PdfFormField) + { + CreateDerivedField(fields, idx); + Debug.Assert(field.IsDead); + } + + var acroField = fields.Elements.GetRequiredDictionary(idx); + //var acroField = fields.Elements.GetRequiredDictionary(idx); + HandleAP(acroField); + HandleKids(acroField); + } + } + } + + public static void CreateAnnotationObjects(PdfDocument doc) + { + var pages = doc.Catalog.Pages; + foreach (var page in pages) + { + var annots = page.Elements.GetArray(PdfPage.Keys.Annots); + if (annots != null) + { + var count = annots.Elements.Count; + for (int idx = 0; idx < count; idx++) + { + var annot = annots.Elements.GetDictionary(idx); + if (annot is PdfWidgetAnnotation widget) + { + // throw new InvalidOperationException("WTF happened???"); + } + else if (annot is PdfFormField field) + { + var proxyWidget = field.GetAsWidgetAnnotation(); + if (proxyWidget != null) + { + annots.Elements[idx] = proxyWidget; + } + else + throw new InvalidOperationException("Should not happen."); + } + else if (annot != null) + { + var type = annot.GetType(); + if (type != typeof(PdfDictionary)) + throw new InvalidOperationException("Not a dictionary???"); + } + else + { + throw new InvalidOperationException("Not an annotation???"); + } + } + } + } + } + + static void CreateDerivedField(PdfArray fields, int index) + { + var field = fields.Elements.GetRequiredDictionary(index); + // Is field already transformed? + if (field is PdfFormField) + return; + + //var ft = field.Elements.GetName(PdfFormField.Keys.FT); + var (type, isWidget) = PdfFormField.GetAcroFieldType(field); + var newField = fields.Elements.GetRequiredDictionary(index, VCF.None); + + if (isWidget) + newField.GetAsWidgetAnnotation(); + + Debug.Assert(field.IsDead); + } + + // ReSharper disable once InconsistentNaming + static void HandleAP(PdfFormField field) + { + var ap = field.Elements.GetDictionary(PdfAnnotation.Keys.AP); + if (ap != null) + { + var valN = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.N); + HandleAPEntry(valN); + + var valR = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.R); + HandleAPEntry(valR); + + var valD = ap.Elements.GetDictionary(PdfAnnotationAppearance.Keys.D); + HandleAPEntry(valD); + } + + // ReSharper disable once InconsistentNaming + void HandleAPEntry(PdfDictionary? streamOrDict) + { + if (streamOrDict == null) + return; + + var stream = streamOrDict.Stream; + if (stream is null) + { + var keys = streamOrDict.Elements.Keys; + foreach (var key in keys) + { + var xo = streamOrDict.Elements.GetDictionary(key); + if (xo is PdfFormXObject) + continue; + + if (xo != null) + { + Debug.Assert(xo.IsIndirect); +#if true + var xo2 = streamOrDict.Elements.GetDictionary(key, VCF.None, typeof(PdfFormXObject)); +#else + TransformToXObject(xo); +#endif + Debug.Assert(xo.IsDead); + } + else + { + Debug.Assert(false); + } + } + } + else + { + Debug.Assert(streamOrDict.IsIndirect); + TransformToXObject(streamOrDict); + } + + void TransformToXObject(PdfDictionary dict) + { + if (dict is PdfFormXObject alreadyFormXObject) + { + _ = typeof(int); + } + else + { + dict.Elements.CreateContainer(typeof(PdfFormXObject), dict, true); + Debug.Assert(dict.IsDead); + } + } + } + } + + static void HandleKids(PdfFormField field) + { + var kids = field.Elements.GetArray(PdfFormField.Keys.Kids); + var hasKids = field.HasKids; + Debug.Assert(kids == null || (hasKids && kids.Elements.Count > 0) || (!hasKids && kids.Elements.Count == 0)); + if (kids != null) + { + // Transform kids to Acro fields. + var count = kids.Elements.Count; + for (int idx = 0; idx < count; idx++) + { + CreateDerivedField(kids, idx); + } + + for (int idx = 0; idx < count; idx++) + { + var acroField2 = kids.Elements.GetDictionary(idx); + if (acroField2 is not PdfFormField) + _ = typeof(int); + + var acroField = kids.Elements.GetRequiredDictionary(idx); + HandleAP(acroField); + HandleKids(acroField); + } + } + } + } + + public static class C2 + { + public static string CreateFieldListByFields(PdfDocument doc) // TODO: FormsCleanUp: Delete - FormsManager.FlattenForm() and ExportToJson() enumerate the pages and get the fields via its widgets. + { + var pageCount = doc.PageCount; + var sb = new StringBuilder(); + sb.Append("Fields by AcroForm\n"); + int indent = 1; + var acroForm = doc.Catalog.GetAcroForm(); + if (acroForm != null) + { + foreach (var field in acroForm.Fields) + { + HandleField(field); + } + } + return sb.ToString(); + + void HandleField(PdfFormField field) + { + bool containsWidgetPart = field.ContainsWidgetPart; + sb.Append(Invariant($"{Indent()}Type: {field.GetType().Name}, {(containsWidgetPart ? "Widget, " : "")}ID: [{field.RequiredReference.ObjectID.ToString()}]\n")); + indent++; + { + sb.Append($"{Indent()}Name: »{field.Name}«\n"); + sb.Append($"{Indent()}FullName: »{field.FullName}«\n"); + sb.Append($"{Indent()}Value: »{field.Value}«\n"); + if (field.ContainsWidgetPart) + { + sb.Append($"{Indent()}Widget part:\n"); + indent++; + { + var pageRef = field.Elements.GetReference(PdfAnnotation.Keys.P); + var page = pageRef != null + ? pageRef.ObjectID.ToString() + : "none"; + sb.Append($"{Indent()}Page: [{page}]\n"); + } + indent--; + } + if (field.HasKids) + { + sb.Append($"{Indent()}Kids: {field.Kids.Elements.Count}\n"); + indent++; + { + HandleKids(field.Kids); + } + indent--; + } + } + indent--; + } + +#pragma warning disable CS8321 // Local function is declared but never used + void HandleWidget(PdfFormFields fields) +#pragma warning restore CS8321 // Local function is declared but never used + { + foreach (var field in fields) + { + HandleField(field); + } + } + + void HandleKids(PdfFormFields fields) + { + foreach (var field in fields) + { + HandleField(field); + } + } + +#pragma warning disable CS8321 // Local function is declared but never used + void AppendIndent() => sb.Append(new String(' ', indent * 2)); +#pragma warning restore CS8321 // Local function is declared but never used + string Indent() => new String(' ', indent * 2); + } + } +} +#endif diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Metadata/MetadataManagerTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Metadata/MetadataManagerTests.cs new file mode 100644 index 00000000..58e617ad --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Metadata/MetadataManagerTests.cs @@ -0,0 +1,238 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; +using PdfSharp.Fonts; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.Metadata; +using PdfSharp.Quality; +using Xunit; +using FluentAssertions; +using PdfSharp.Pdf.PdfA; + +namespace PdfSharp.Tests.Pdf.Metadata +{ + [Collection("PDFsharp")] + public class MetadataManagerTests : IDisposable + { + readonly string _tempRoot = PdfFileUtility.GetUnitTestPath(typeof(MetadataManagerTests)); + + public MetadataManagerTests() + { + GlobalFontSettings.ResetFontManagement(); + GlobalFontSettings.FontResolver = new UnitTestFontResolver(); + } + + public void Dispose() + { } + + [Fact] + public void Create_document_with_no_metadata() + { + var doc = new PdfDocument(); + var page = doc.AddPage(); + + var gfx = XGraphics.FromPdfPage(page); + var font = new XFont("Arial", 10); + gfx.DrawString($"{nameof(Create_document_with_no_metadata)}", font, XBrushes.Black, 10, 20); + + var dest = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(Create_document_with_no_metadata)); + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Save(dest); + + var doc2 = PdfReader.Open(dest); + var mmd = doc2.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata); + var b = doc2.Catalog.HasMetadata; + doc2.Catalog.HasMetadata.Should().BeFalse(); + + var dest2 = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(Create_document_with_no_metadata) + "#2"); + doc2.Save(dest2); + + var doc3 = PdfReader.Open(dest2); + doc3.Catalog.HasMetadata.Should().BeFalse(); + + var xml = doc3.Catalog.GetOrCreateMetadata().ToString(); + xml.Length.Should().Be(0); + } + + [Fact] + public void Create_another_document_with_no_metadata() + { + var doc = new PdfDocument(); + var page = doc.AddPage(); + + var gfx = XGraphics.FromPdfPage(page); + var font = new XFont("Arial", 10); + gfx.DrawString($"{nameof(Create_document_with_no_metadata)}", font, XBrushes.Black, 10, 20); + + var dest = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(Create_document_with_no_metadata)); + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Save(dest); + doc.Catalog.HasMetadata.Should().BeFalse(); + + var doc2 = PdfReader.Open(dest); + doc2.Catalog.HasMetadata.Should().BeFalse(); + var mmd = doc2.Catalog.Elements.GetDictionary(PdfCatalog.Keys.Metadata); + var b = doc2.Catalog.HasMetadata; + doc2.Catalog.HasMetadata.Should().BeFalse(); + + // HasMeta should be true after accessing Metadata. Empty Metadata must not be saved to PDF. + var metadata2 = doc2.Catalog.GetOrCreateMetadata(); + doc2.Catalog.HasMetadata.Should().BeTrue(); + + var dest2 = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(Create_document_with_no_metadata) + "#2"); + doc2.Save(dest2); + + var doc3 = PdfReader.Open(dest2); + // HasMeta should be false here because empty Metadata was not saved. + doc3.Catalog.HasMetadata.Should().BeFalse(); + + var xml = doc3.Catalog.GetOrCreateMetadata().ToString(); + xml.Length.Should().Be(0); + doc3.Catalog.HasMetadata.Should().BeTrue(); + } + + [Fact] + public void Create_document_with_metadata() + { + var doc = new PdfDocument(); + var page = doc.AddPage(); + + var gfx = XGraphics.FromPdfPage(page); + var font = new XFont("Arial", 10); + gfx.DrawString($"{nameof(Create_document_with_metadata)}", font, XBrushes.Black, 10, 20); + + var metadataManager = MetadataManager.ForDocument(doc); + metadataManager.Strategy = DocumentMetadataStrategy.AutoGenerate; + + var dest = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(Create_document_with_metadata)); + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Save(dest); + + var doc2 = PdfReader.Open(dest); + doc2.Catalog.HasMetadata.Should().BeTrue(); + var metadata2 = doc2.Catalog.GetOrCreateMetadata(); + metadata2.ToString().Should().StartWith(@" + { + args.Metadata.SetMetadata( + // Note that keeping BOM empty is the best choice. + $""" + + + + """ + "\r\n" + + // Just test, no valid XMP + """ + + This is invalid XMP metadata + + """ + "\r\n" + + """ + + + + """); + }; + + var dest = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(Create_document_with_metadata)); + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Save(dest); + + var doc2 = PdfReader.Open(dest); + doc2.Catalog.HasMetadata.Should().BeTrue(); + var metadata2 = doc2.Catalog.GetOrCreateMetadata(); + metadata2.ToString().Should().StartWith(@""""); + // ReSharper restore StringLiteralTypo + } + + void SetSomeMetadata(PdfDocument doc) + { + var info = doc.Info; + + info.Title = "Test document (Title) おはよう സുപ്രഭാതം"; + info.Author = "Stefan Lange (Author)"; + info.Subject = "Test XMP metadata (Subject)"; + info.Keywords = "Some Keywords (Keywords)"; + info.Creator = "PDFsharp (Creator)"; + info.Elements.SetString(PdfDocumentInformation.Keys.Producer, "MigraDoc (Producer)"); +#if true_ + info.Title = "TTTT"; + info.Author = "AAAA"; + info.Subject = "SSSS"; + info.Keywords = "KKKK"; + info.Creator = "CCCC"; +#endif + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Metadata/MetadataTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Metadata/MetadataTests.cs new file mode 100644 index 00000000..13e7092f --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Metadata/MetadataTests.cs @@ -0,0 +1,105 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.Metadata; +using PdfSharp.Quality; +using Xunit; + +namespace PdfSharp.Tests.Pdf.Metadata +{ + [Collection("PDFsharp")] + public class MetadataTests : IDisposable + { + readonly string _tempRoot = PdfFileUtility.GetUnitTestPath(typeof(MetadataTests)); + + public MetadataTests() + { } + + public void Dispose() + { } + + [Fact] + public void Test_Catalog_entries() + { + var src = @"C:\Users\StLa\Desktop\PDFsharp\PDF-References/pdfreference1.7.pdf"; + //var src = "d:/pdfreference1.7.pdf"; + var dest = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + "PdfReference1.7.pdf"); + if (!File.Exists(src)) + return; + + //var doc = PdfReader.Open(file, "User", PdfDocumentOpenMode.Import); + var doc = PdfReader.Open(src, PdfDocumentOpenMode.Modify); + var mdManager = MetadataManager.ForDocument(doc); + //var metadata = mdManager.Metadata; + + var metadata = doc.Catalog.GetOrCreateMetadata(); + + var xml = metadata.ToString(); + xml = xml[..17] + "UTF8" + xml[21..]; + // XMP metadata will be used only if ModDate of document info is not newer than XMP. Manipulate XMP date to make it newer. + xml = xml.Replace("2006-11-09T14:01:16-03:30", $"{DateTime.Now.Year:4}-12-31T14:01:16-03:30"); + // Modify title in XMP to show that XMP is used for display. + xml = xml.Replace("PDF Reference, version 1.7", "PDF Reference, version 1.7 (modified)"); + metadata.SetMetadata(xml); + + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Save(dest); + } + + [Theory] + [InlineData(MetadataEncodingType.UTF8)] + //[InlineData(MetadataEncodingType.UTF16LE)] // Creates an invalid PDF file that is accepted by Acrobat. + //[InlineData(MetadataEncodingType.UTF16BE)] // Creates an invalid PDF file that is accepted by Acrobat. + //[InlineData(MetadataEncodingType.UTF32LE)] // Creates an invalid PDF file that is not accepted by Acrobat. + //[InlineData(MetadataEncodingType.UTF32BE)] // Creates an invalid PDF file that is not accepted by Acrobat. + void ReadWrite_AutoCreate_metadata(MetadataEncodingType type) + { + var doc = new PdfDocument(); + doc.AddPage(); + SetSomeMetadata(doc); + + var mdManager = MetadataManager.ForDocument(doc); + var metadata = doc.Catalog.GetOrCreateMetadata()!; + + var xml = metadata.ToString(); + xml.Should().BeEmpty(); + + xml = metadata.CreateDefaultMetadata(type); + metadata.SetMetadata(xml); + xml = metadata.ToString(); + xml.Should().NotBeEmpty(); + + var dest = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(ReadWrite_AutoCreate_metadata) + $"-with-{type}"); + doc.Options.Layout = PdfWriterLayout.Compact; + doc.Save(dest); + + var doc2 = PdfReader.Open(dest); + var xml2 = doc2.Catalog.GetOrCreateMetadata().ToString(); + // ReSharper disable StringLiteralTypo + xml2.Should().StartWith($""""""); + // ReSharper restore StringLiteralTypo + } + + void SetSomeMetadata(PdfDocument doc) + { + var info = doc.Info; + + info.Title = "Test document (Title) おはよう സുപ്രഭാതം"; + info.Author = "Stefan Lange (Author)"; + info.Subject = "Test XMP metadata (Subject)"; + info.Keywords = "Some Keywords (Keywords)"; + info.Creator = "PDFsharp (Creator)"; + info.Elements.SetString(PdfDocumentInformation.Keys.Producer, "MigraDoc (Producer)"); +#if true_ + info.Title = "TTTT"; + info.Author = "AAAA"; + info.Subject = "SSSS"; + info.Keywords = "KKKK"; + info.Creator = "CCCC"; +#endif + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/Array620Tests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/Array620Tests.cs new file mode 100644 index 00000000..f43d9cd9 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/Array620Tests.cs @@ -0,0 +1,98 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Diagnostics; +using PdfSharp.Drawing; +using PdfSharp.Fonts; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Quality; +using PdfSharp.Snippets.Font; +using PdfSharp.TestHelper; +using Xunit; +// ReSharper disable UnusedVariable + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + /// + /// Class ensures that 6.2 code compiles without any issues in 6.3 and later. + /// + [Collection("PDFsharp")] + public class Array620Tests + { + [Fact] + public void Ensure_PdfArray_620_API() + { + // Ensure that all functions from 6.2 still compile and later. + + PdfArray array = new PdfArray(); + if (TrueOrFalse.False) + { + // Just compile, but do not execute. + PdfArray.ArrayElements e = array.Elements; + + array = new PdfArray((PdfDocument)null!); + array = new PdfArray((PdfDocument)null!, new PdfItem[0]); + //protected PdfArray(PdfArray array) + array = array.Clone(); + using IEnumerator @enum = array.GetEnumerator(); + string str = array.ToString(); + + } + } + + [Fact] + public void Ensure_PdfArray_Elements_620_API() + { + // Ensure that all functions from 6.2 still compile and later. + + var array = new PdfArray(); + var elements = array.Elements; + if (TrueOrFalse.False) + { + // Just compile, but do not execute. + + PdfArray.ArrayElements e = array.Elements; + e = e.Clone(); + bool b = e.GetBoolean(0); + int i = e.GetInteger(0); + double d = e.GetReal(0); + double? nd = e.GetNullableReal(0); + string str = e.GetString(0); + str = e.GetName(0); + PdfObject? no = e.GetObject(0); + PdfDictionary? ndict = e.GetDictionary(0); + PdfArray? na = e.GetArray(0); + PdfReference? nr = e.GetReference(0); + PdfItem[] items = e.Items; + + #region IList Members + + b = e.IsReadOnly; + PdfItem item = e[0]; + e.RemoveAt(0); + e.Remove(item); + e.Insert(0, item); + b = e.Contains(item); + e.Clear(); + i = e.IndexOf(item); + e.Add(item); + b = e.IsFixedSize; + + #endregion + + #region ICollection Members + + b = e.IsSynchronized; + i = e.Count; + // ReSharper disable once UseCollectionExpression + e.CopyTo(new PdfItem[0], 0); + object obj = e.SyncRoot; + + #endregion + + using IEnumerator enum2 = e.GetEnumerator(); + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayElementsTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayElementsTests.cs new file mode 100644 index 00000000..9e3d11c2 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayElementsTests.cs @@ -0,0 +1,995 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class ArrayElementsTests : ObjectModelTestsBase + { + // Keep all tests in sync with DictionaryElementsTests. + + [Fact] + public void Indexer_Tests() + { + // === Test indexer === + { + var array = new TestArrayElements(); + + var array1 = array.Elements[(int)TestArrayElements.Index.TestArray1]; + + var dict1 = array.Elements[(int)TestArrayElements.Index.TestDict1]; + } + } + + [Fact] + public void GetValue_Tests() + { + // === GetValue with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = array.Elements.GetValue((int)TestArrayElements.Index.TestArray1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = array.Elements.GetValue((int)TestArrayElements.Index.TestDict1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = array.Elements.GetValue((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfArray)); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = array.Elements.GetValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetValue((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetValue with primitives === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? item; + + // Get PDF integer. + item = array.Elements.GetValue((int)TestArrayElements.Index.Integer); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(PdfInteger)); + } + + // === GetRequiredValue with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestArray1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestDict1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfArray)); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetRequiredValue with primitives === + // ... + + // === TryGetValue with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once InlineOutVariableDeclaration + PdfItem? item; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + + // Get PDF array. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestArray1, out item); + success.Should().BeTrue(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestDict1, out item); + success.Should().BeTrue(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestArray1, out item, typeof(PdfArray)); + success.Should().BeTrue(); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestDict1, out item, typeof(PdfDictionary)); + success.Should().BeTrue(); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestArray1, out item, typeof(PdfDictionary)); + success.Should().BeFalse(); + item.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestDict1, out item, typeof(PdfArray)); + success.Should().BeFalse(); + item.Should().BeNull(); + } + + // === GetValue with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = array.Elements.GetValue((int)TestArrayElements.Index.TestArray1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = array.Elements.GetValue((int)TestArrayElements.Index.TestDict1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = array.Elements.GetValue((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = array.Elements.GetValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetValue((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + } + + // === GetRequiredValue with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestArray1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestDict1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetRequiredValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + action.Should().Throw(); + } + + // === TryGetValue with containers === + { + var array = new TestArrayElements(); + //// ReSharper disable once InlineOutVariableDeclaration + //PdfItem item; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + + // Get PDF array. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestArray1, out var resultTestArray1); + success.Should().BeTrue(); + resultTestArray1!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestDict1, out var resultTestDict1); + success.Should().BeTrue(); + resultTestDict1!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestArray1, out resultTestArray1, typeof(PdfArray)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestArray1, out resultTestDict1); + success.Should().BeFalse(); + resultTestDict1.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestDict1, out resultTestArray1); + success.Should().BeFalse(); + resultTestArray1.Should().BeNull(); + } + } + + [Fact] + public void SetValue_Tests() + { + // === GetValue === + { + var array = new TestArrayElements(); + + var array1 = array.Elements.GetValue((int)TestArrayElements.Index.TestArray1); + + var dict1 = array.Elements.GetValue((int)TestArrayElements.Index.TestDict1); + } + + // === SetValue === + { + var array = new TestArrayElements(); + + var array1 = array.Elements.GetValue((int)TestArrayElements.Index.TestArray1); + + var dict1 = array.Elements.GetValue((int)TestArrayElements.Index.TestDict1); + } + + // === GetValue === + { + var array = new TestArrayElements(); + + var array1 = array.Elements[(int)TestArrayElements.Index.TestArray1]; + + var dict1 = array.Elements[(int)TestArrayElements.Index.TestDict1]; + } + } + + + [Fact] + public void GetObject_Tests() + { + // === GetObject with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfObject? result; + // ReSharper disable once JoinDeclarationAndInitializer + //Action action; + + // Get PDF array. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestArray1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestDict1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfArray)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfDictionary)); + result.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfArray)); + result.Should().BeNull(); + } + + // === GetObject with primitives === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfObject? result; + + // Get PDF integer. + result = array.Elements.GetObject((int)TestArrayElements.Index.Integer); + result.Should().BeNull(); + } + + // === GetRequiredObject with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfObject result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestArray1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestDict1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfArray)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetRequiredValue with primitives === + // ... + + // === TryGetObjects with containers === + // Not exits. + + // === GetObject with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfObject? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestArray1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestDict1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = array.Elements.GetObject((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetObject((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetObject((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + } + + // === GetRequiredObject with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfObject? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestArray1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestDict1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetRequiredObject((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + action.Should().Throw(); + } + + // === TryGetValue with containers === + // Not exits. + } + + [Fact] + public void GetArray_Tests() + { + // === GetArray with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = array.Elements.GetArray((int)TestArrayElements.Index.TestArray1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = array.Elements.GetArray((int)TestArrayElements.Index.TestDict1); + result.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = array.Elements.GetArray((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfArray)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + action = () => array.Elements.GetArray((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + //item.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetArray((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetArray((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetArray with primitives === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray? result; + + // Get PDF integer. + result = array.Elements.GetArray((int)TestArrayElements.Index.Integer); + result.Should().BeNull(); + //item!.GetType().Should().BeNull(); + } + + // === GetRequiredArray with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestArray1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + action = () => array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestDict1); + action.Should().Throw(); + //item.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfArray)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + action = () => array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + //item.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetRequiredArray with primitives === + // ... + + // === TryGetArray with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once InlineOutVariableDeclaration + PdfArray? result; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + success = array.Elements.TryGetArray((int)TestArrayElements.Index.TestArray1, out result); + success.Should().BeTrue(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + success = array.Elements.TryGetArray((int)TestArrayElements.Index.TestDict1, out result); + success.Should().BeFalse(); + result.Should().BeNull(); + + // Get PDF array with existing type. + success = array.Elements.TryGetArray((int)TestArrayElements.Index.TestArray1, out result, typeof(PdfArray)); + success.Should().BeTrue(); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + action = () => array.Elements.TryGetArray((int)TestArrayElements.Index.TestDict1, out result, typeof(PdfDictionary)); + action.Should().Throw(); + //success.Should().BeFalse(); + //result.Should().BeNull(); + + // Get PDF array with unsuitable type. + action = () => array.Elements.TryGetArray((int)TestArrayElements.Index.TestArray1, out result, typeof(PdfDictionary)); + action.Should().Throw(); + //success.Should().BeFalse(); + //result.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + success = array.Elements.TryGetArray((int)TestArrayElements.Index.TestDict1, out result, typeof(PdfArray)); + success.Should().BeFalse(); + result.Should().BeNull(); + } + + // === GetArray with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = array.Elements.GetArray((int)TestArrayElements.Index.TestArray1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary. + //item = array.Elements.GetArray((int)TestArrayElements.Index.TestDict1); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = array.Elements.GetArray((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //item = array.Elements.GetArray((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with unsuitable type. + //action = () => array.Elements.GetArray((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + ////item.Should().BeNull(); + //action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetArray((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + } + + // === GetRequiredArray with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestArray1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + //item = array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestDict1); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //item = array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with unsuitable type. + //action = () => array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + //action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetRequiredArray((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + action.Should().Throw(); + } + + // === TryGetArray with containers === + { + var array = new TestArrayElements(); + //// ReSharper disable once InlineOutVariableDeclaration + //PdfItem item; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + + // Get PDF array. + success = array.Elements.TryGetArray((int)TestArrayElements.Index.TestArray1, out var resultTestArray1); + success.Should().BeTrue(); + resultTestArray1!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary. + //success = array.Elements.TryGetArray((int)TestArrayElements.Index.TestDict1, out var resultTestDict1); + //success.Should().BeTrue(); + //resultTestDict1!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestArray1, out resultTestArray1, typeof(PdfArray)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with unsuitable type. + //success = array.Elements.TryGetArray((int)TestArrayElements.Index.TestArray1, out var resultTestDict1); + //success.Should().BeFalse(); + //resultTestDict1.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + success = array.Elements.TryGetArray((int)TestArrayElements.Index.TestDict1, out resultTestArray1); + success.Should().BeFalse(); + resultTestArray1.Should().BeNull(); + } + } + + [Fact] + public void GetDictionary_Tests() + { + // === GetDictionary with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = array.Elements.GetDictionary((int)TestArrayElements.Index.TestArray1); + result.Should().BeNull(); + + // Get PDF dictionary. + result = array.Elements.GetDictionary((int)TestArrayElements.Index.TestDict1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetDictionary((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = array.Elements.GetDictionary((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetDictionary((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetDictionary((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetArray with primitives === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary? result; + + // Get PDF integer. + result = array.Elements.GetDictionary((int)TestArrayElements.Index.Integer); + result.Should().BeNull(); + //item!.GetType().Should().Be(typeof(PdfInteger)); + } + + // === GetRequiredDictionary with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + action = () => array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestArray1); + action.Should().Throw(); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestDict1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF dictionary with existing type. + action = () => array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestArray1, VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetRequiredDictionary with primitives === + // ... + + // === TryGetDictionary with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once InlineOutVariableDeclaration + PdfDictionary? result; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + success = array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestArray1, out result); + success.Should().BeFalse(); + result.Should().BeNull(); + + // Get PDF dictionary. + success = array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestDict1, out result); + success.Should().BeTrue(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + action = () => array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestArray1, out result, typeof(PdfArray)); + action.Should().Throw(); + //success.Should().BeFalse(); + //result.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + success = array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestDict1, out result, typeof(PdfDictionary)); + action.Should().Throw(); + //success.Should().BeTrue(); + //result.Should().NotBeNull(); + //result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + success = array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestArray1, out result, typeof(PdfDictionary)); + success.Should().BeFalse(); + result.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + action = () => array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestDict1, out result, typeof(PdfArray)); + action.Should().Throw(); + //success.Should().BeFalse(); + //result.Should().BeNull(); + } + + // === GetDictionary with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + //// Get PDF array. + //item = array.Elements.GetDictionary((int)TestArrayElements.Index.TestArray1); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = array.Elements.GetDictionary((int)TestArrayElements.Index.TestDict1); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //item = array.Elements.GetDictionary((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = array.Elements.GetDictionary((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetDictionary((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + + //// Get PDF dictionary with unsuitable type. + //action = () => array.Elements.GetDictionary((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + ////item.Should().BeNull(); + //action.Should().Throw(); + } + + // === GetRequiredDictionary with containers === + { + var array = new TestArrayElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + //item = array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestArray1); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestDict1); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //item = array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestArray1, VCF.NoTransform); + action.Should().Throw(); + + //// Get PDF dictionary with unsuitable type. + //action = () => array.Elements.GetRequiredDictionary((int)TestArrayElements.Index.TestDict1, VCF.NoTransform); + //action.Should().Throw(); + } + + // === TryGetDictionary with containers === + { + var array = new TestArrayElements(); + //// ReSharper disable once InlineOutVariableDeclaration + //PdfDictionary item; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + + //// Get PDF array. + //success = array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestArray1, out var resultTestArray1); + //success.Should().BeTrue(); + //resultTestArray1!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + success = array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestDict1, out var resultTestDict1); + success.Should().BeTrue(); + resultTestDict1!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestArray1, out resultTestArray1, typeof(PdfArray)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //success = array.Elements.TryGetValue((int)TestArrayElements.Index.TestDict1, VCF.NoTransform, typeof(PdfDictionary)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + success = array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestArray1, out resultTestDict1); + success.Should().BeFalse(); + resultTestDict1.Should().BeNull(); + + //// Get PDF dictionary with unsuitable type. + //success = array.Elements.TryGetDictionary((int)TestArrayElements.Index.TestDict1, out var resultTestArray1); + //success.Should().BeFalse(); + //resultTestArray1.Should().BeNull(); + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/ArrayTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayItemTests.cs similarity index 50% rename from src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/ArrayTests.cs rename to src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayItemTests.cs index eaa5e143..5a441fbf 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/ArrayTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayItemTests.cs @@ -1,23 +1,18 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using PdfSharp.Diagnostics; -using PdfSharp.Drawing; -using PdfSharp.Fonts; -using PdfSharp.Pdf; -using PdfSharp.Quality; -using PdfSharp.Snippets.Font; -using PdfSharp.TestHelper; using Xunit; +using FluentAssertions; -namespace PdfSharp.Tests.Pdf +namespace PdfSharp.Tests.Pdf.ObjectModel { [Collection("PDFsharp")] - public class ArrayTests + public class ArrayItemTests : ObjectModelTestsBase { [Fact] public void ToDo() { + } } } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayPrimitivesTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayPrimitivesTests.cs new file mode 100644 index 00000000..07ada286 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayPrimitivesTests.cs @@ -0,0 +1,239 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Quality.Testing; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class ArrayPrimitivesTests : ObjectModelTestsBase + { + [Fact] + public void GetBoolean_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryTests), nameof(GetBoolean_Tests)); + + var dict = new PdfDictionary(); + var array1 = new PdfArray(); + new ObjectModelTestHelper(rwh.Document).PopulateArray(array1); + dict.Elements.Add("/DirectDictionary", array1); + + // Test cases that succeed for existing values. + + var b = array1.Elements.GetBoolean(1); + b.Should().BeTrue(); + + b = array1.Elements.GetBoolean(16); + b.Should().BeTrue(); + + // Test cases that throw. + + Action getValueFromIncompatibleItem = () => array1.Elements.GetBoolean(0); + getValueFromIncompatibleItem.Should().Throw(); + + getValueFromIncompatibleItem = () => array1.Elements.GetBoolean(2); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void GetInteger_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryTests), nameof(GetInteger_Tests)); + + + var dict = new PdfDictionary(); + var array1 = new PdfArray(); + new ObjectModelTestHelper(rwh.Document).PopulateArray(array1); + dict.Elements.Add("/DirectDictionary", array1); + + // Test cases that succeed for existing values. + + var i = array1.Elements.GetInteger(2); + i.Should().Be(42); + + i = array1.Elements.GetInteger(17); + i.Should().Be(42); + + // Test cases that throw. + + Action getIntegerFromString = () => i = array1.Elements.GetInteger(10); + getIntegerFromString.Should().Throw(); + + Action getIntegerFromLong = () => i = array1.Elements.GetInteger(6); + getIntegerFromLong.Should().Throw(); + + Action getIntegerFromNull = () => i = array1.Elements.GetInteger(0); + getIntegerFromNull.Should().Throw(); + + Action getValueFromIncompatibleItem = () => i = array1.Elements.GetInteger(11); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void GetLongInteger_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryTests), nameof(GetLongInteger_Tests)); + + var dict = new PdfDictionary(); + var array1 = new PdfArray(); + new ObjectModelTestHelper(rwh.Document).PopulateArray(array1); + dict.Elements.Add("/DirectDictionary", array1); + + // Test cases that succeed for existing values. + + var l = array1.Elements.GetLongInteger(2); + l.Should().Be(42); + + l = array1.Elements.GetLongInteger(17); + l.Should().Be(42); + + l = array1.Elements.GetLongInteger(6); + l.Should().Be(Int64.MaxValue); + + // Test cases that throw. + + Action getIntegerFromString = () => l = array1.Elements.GetLongInteger(10); + getIntegerFromString.Should().Throw(); + + Action getIntegerFromNull = () => l = array1.Elements.GetLongInteger(0); + getIntegerFromNull.Should().Throw(); + + Action getValueFromIncompatibleItem = () => l = array1.Elements.GetInteger(11); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void GetReal_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryTests), nameof(GetReal_Tests)); + + var dict = new PdfDictionary(); + var array1 = new PdfArray(); + new ObjectModelTestHelper(rwh.Document).PopulateArray(array1); + dict.Elements.Add("/DirectDictionary", array1); + + // Test cases that succeed for existing values. + + var r = array1.Elements.GetReal(7); + r.Should().Be(Math.PI); + + r = array1.Elements.GetReal(8); + r.Should().Be(Single.MinValue); + + r = array1.Elements.GetReal(9); + r.Should().Be(Single.MaxValue); + + r = array1.Elements.GetReal(14); + r.Should().Be(-1); + + r = array1.Elements.GetReal(22); + r.Should().Be(Math.PI); + + r = array1.Elements.GetReal(23); + r.Should().Be(Single.MinValue); + + r = array1.Elements.GetReal(24); + r.Should().Be(Single.MaxValue); + + // Test cases that throw. + + Action getValueFromIncompatibleItem = () => array1.Elements.GetReal(0); + getValueFromIncompatibleItem.Should().Throw(); + + getValueFromIncompatibleItem = () => array1.Elements.GetReal(11); + getValueFromIncompatibleItem.Should().Throw(); + + getValueFromIncompatibleItem = () => array1.Elements.GetReal(15); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void GetNullableReal_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryTests), nameof(GetNullableReal_Tests)); + + var dict = new PdfDictionary(); + var array1 = new PdfArray(); + new ObjectModelTestHelper(rwh.Document).PopulateArray(array1); + dict.Elements.Add("/DirectDictionary", array1); + + // Test cases that succeed for existing values. + + var r = array1.Elements.GetNullableReal(7); + r.Should().Be(Math.PI); + + r = array1.Elements.GetNullableReal(8); + r.Should().Be(Single.MinValue); + + r = array1.Elements.GetNullableReal(9); + r.Should().Be(Single.MaxValue); + + r = array1.Elements.GetNullableReal(14); + r.Should().Be(-1); + + r = array1.Elements.GetNullableReal(22); + r.Should().Be(Math.PI); + + r = array1.Elements.GetNullableReal(23); + r.Should().Be(Single.MinValue); + + r = array1.Elements.GetNullableReal(24); + r.Should().Be(Single.MaxValue); + + r = array1.Elements.GetNullableReal(0); + r.Should().BeNull(); + + r = array1.Elements.GetNullableReal(15); + r.Should().BeNull(); + + // Test cases that throw. + + Action getValueFromIncompatibleItem = () => array1.Elements.GetNullableReal(11); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void GetString_GetName_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryTests), nameof(GetString_GetName_Tests)); + + var dict = new PdfDictionary(); + var array1 = new PdfArray(); + new ObjectModelTestHelper(rwh.Document).PopulateArray(array1); + dict.Elements.Add("/DirectDictionary", array1); + + // Test cases that succeed for existing values. + + var s = array1.Elements.GetString(10); + s.Should().Be("Hello"); + + var n = array1.Elements.GetName(11); + n.Should().Be("/Mambo #5"); + + s = array1.Elements.GetString(25); + s.Should().Be("Hello"); + + n = array1.Elements.GetName(26); + n.Should().Be("/Mambo #5"); + + + // Test cases that throw. + + Action getValueFromIncompatibleItem = () => array1.Elements.GetString(2); + getValueFromIncompatibleItem.Should().Throw(); + + getValueFromIncompatibleItem = () => array1.Elements.GetName(2); + getValueFromIncompatibleItem.Should().Throw(); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayTests.cs new file mode 100644 index 00000000..fad00c82 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ArrayTests.cs @@ -0,0 +1,244 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class ArrayTests : ObjectModelTestsBase + { + //[Fact] + //public void BugWithNumbers() + //{ + // CreateDocument(nameof(BugWithNumbers)); + // var catalog = Document.Catalog; + + // var dict1 = new TestDict1(); + // Document.Internals.AddObject(dict1); + // catalog.Elements.Add("/TestDict1", dict1); + + // // + // { + // var item0 = catalog.Elements.GetRequiredValue("/TestDict1"); + // item0.GetType().Name.Should().Be(nameof(TestDict1)); + + // var item1 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.None, typeof(PdfDictionary)); + // item1.GetType().Name.Should().Be(nameof(TestDict1)); + + // var item2 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.None, typeof(PdfReference)); + // item2.GetType().Name.Should().Be(nameof(PdfReference)); + // ReferenceEquals(((PdfReference)item2).Value, item0).Should().BeTrue(); + // } + + //} + + [Fact] + public void Test_cloning() + { + } + + [Fact] + public void Test_basic_type_transformation() + { + // Indirect object. + + // Direct object. + } + + [Fact] + public void Test_index_adjustment_in_StructParent() + { + // Insert object. + + // Remove object. + } + + //[Fact] + //public void TestDictionaryApi() + //{ + // CreateDocument("InitialTest"); + // var dict = new PdfDictionary(); + // var dict1 = new TestDict1(); + // dict.CloneElementsOf(dict1); + // var c = dict.Elements.Count; + + // Document.Catalog.Elements.Add("/test", dict); + // Save(); + // Reload(); + // ReloadedDocument.Catalog.Elements.GetRequiredValue("/test").Count().Should().Be(1); + //} + + [Fact] + public void Reuse_of_array_object_correctly() + { + // === Test direct array reuse after remove === + { + var array = new TestArray1(); + array.IsIndirect.Should().BeFalse(); + array.ParentInfo.Should().BeNull(); + + var owningDict = new PdfArray() + { + Elements = { [0] = array } + }; + array.ParentInfo.Should().NotBeNull(); + array.ParentInfo!.OwningArray.Equals(owningDict).Should().BeTrue(); + // owning array not set + owningDict.Elements.Count.Should().Be(1); + + owningDict.Elements.RemoveAt(0); + array.ParentInfo.Should().BeNull(); + array.IsDead.Should().BeFalse(); + owningDict.Elements.Count.Should().Be(0); + + // array can be reused after removing. + owningDict.Elements[0] = array; + array.ParentInfo.Should().NotBeNull(); + array.ParentInfo!.OwningArray.Equals(owningDict).Should().BeTrue(); + owningDict.Elements.Count.Should().Be(1); + } + + // === Test direct dictionary reuse after remove === + { + var dict = new TestDict1(); + dict.IsIndirect.Should().BeFalse(); + dict.ParentInfo.Should().BeNull(); + + var owningArray = new PdfArray() + { + Elements = { [0] = dict } + }; + dict.ParentInfo.Should().NotBeNull(); + dict.ParentInfo!.OwningArray.Equals(owningArray).Should().BeTrue(); + owningArray.Elements.Count.Should().Be(1); + + owningArray.Elements.RemoveAt(0); + dict.ParentInfo.Should().BeNull(); + dict.IsDead.Should().BeFalse(); + owningArray.Elements.Count.Should().Be(0); + + // dict can be reused after removing. + owningArray.Elements[0] = dict; + dict.ParentInfo.Should().NotBeNull(); + dict.ParentInfo!.OwningArray.Equals(owningArray).Should().BeTrue(); + owningArray.Elements.Count.Should().Be(1); + } + + // === Test self replacement === + { + var dict = new TestDict1(); + var owningArray = new PdfArray + { + Elements = + { + [0] = dict, + // Replace by itself is senseless but must work. + [0] = dict + } + }; + dict.ParentInfo.Should().NotBeNull(); + dict.ParentInfo!.OwningArray.Equals(owningArray).Should().BeTrue(); + owningArray.Elements.Count.Should().Be(1); + } + } + + [Fact] + public void Illegal_reuse_of_direct_object() + { + // === TestArray1 must not be used twice in array === + { + var array = new TestArray1(); + array.IsIndirect.Should().BeFalse(); + var action = () => + { + var _ = new TestArray1 + { + Elements = + { + [0] = array, + [1] = array + } + }; + }; + action.Should().Throw(); + } + + // === TestDict1 must not be used twice in array === + { + var dict = new TestDict1(); + dict.IsIndirect.Should().BeFalse(); + var action = () => + { + var _ = new TestArray1 + { + + Elements = + { + [0] = dict, + [1] = dict + } + }; + }; + action.Should().Throw(); + } + + // === Test array must not be used twice === + { + var dict = new TestDict1(); + var action = () => + + { + var _1 = new TestArray1 + { + + Elements = + { + [0] = dict, + } + }; + var _2 = new TestArray1 + { + + Elements = + { + [0] = dict // Must throw because of reuse of direct container. + } + }; + }; + action.Should().Throw(); + } + } + + [Fact] + public void Illegal_direct_use_of_primitive_object() + { + // === Test primitive objects must not be direct === + { + // Primitive objects (non-containers) like PdfStringObject or + // PdfIntegerObject must not be used as direct objects. + var str = new PdfStringObject(); + str.IsIndirect.Should().BeFalse(); + var action = () => + { + var _ = new TestArray1 + { + Elements = + { + [0] = str // Use PdfString instead. + } + }; + }; + action.Should().Throw(); + } + } + + //[Fact] + //public void Test_ArrayElements_API() + //{ + //} + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/Dictionary620Tests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/Dictionary620Tests.cs new file mode 100644 index 00000000..71d19a15 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/Dictionary620Tests.cs @@ -0,0 +1,142 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; +using PdfSharp.Pdf; +using PdfSharp.Quality; +using PdfSharp.Pdf.Advanced; +using Xunit; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + /// + /// Ensure that 6.2 code compiles without any issues in 6.3 and later. + /// + [Collection("PDFsharp")] + public class Dictionary620Tests + { + [Fact] + public void Ensure_PdfDictionary_620_API() + { + // Ensure that all functions from 6.2 still compile. + + PdfDictionary dict = new PdfDictionary(); + _ = dict; + if (TrueOrFalse.False) + { + // Just compile, but do not execute. + + dict = new PdfDictionary((PdfDocument)null!); + dict = dict.Clone(); + PdfDictionary.DictionaryElements e = dict.Elements; +#pragma warning disable CS8619 // Nullability of reference types in value doesn’t match target type. + using IEnumerator> @enum = dict.GetEnumerator(); // 4STLA: 6.2.0: PdfItem?; 6.3.0: PdfItem +#pragma warning restore CS8619 + string str = dict.ToString(); +#pragma warning disable CS8600 // Converting null literal or possible null value to non-nullable type. + // BREAKING: Cannot set property Stream anymore. + PdfDictionary.PdfStream stream = dict.Stream; // 4STLA: 6.2.0: PdfStream; 6.3.0: PdfStream? +#pragma warning restore CS8600 // Converting null literal or possible null value to non-nullable type. + // ReSharper disable once UseCollectionExpression + PdfDictionary.PdfStream stream2 = dict.CreateStream(new byte[0]); + } + } + + [Fact] + public void Ensure_PdfDictionary_Elements_620_API() + { + // Ensure that all functions from 6.2 still compile. + + PdfDictionary dict = new PdfDictionary(); + PdfDictionary.DictionaryElements e = dict.Elements; + if (TrueOrFalse.False) + { + // Just compile, but do not execute. + + e = e.Clone(); + bool b = e.GetBoolean("0", false); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + b = e.GetBoolean("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 3 parameters + e.SetBoolean("0", false); + int i = e.GetInteger("0", false); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + i = e.GetInteger("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 3 parameters + uint ui = e.GetUnsignedInteger("0", false); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + ui = e.GetUnsignedInteger("0"); // ditto + e.SetInteger("0", 0); + double d = e.GetReal("0", false); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + d = e.GetReal("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 3 parameters + e.SetReal("0", 0d); + string str = e.GetString("0", false); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + str = e.GetString("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 3 parameters + b = e.TryGetString("0", out str!); + e.SetString("0", ""); + str = e.GetName("0"); + e.SetName("0", ""); + var rect = e.GetRectangle("0", false); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + rect = e.GetRectangle("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 3 parameters + e.SetRectangle("0", rect!); + XMatrix xm = e.GetMatrix("0", false); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + xm = e.GetMatrix("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 3 parameters + e.SetMatrix("0", xm); + // Breaking change + var dtm = e.GetDateTime("0", DateTime.Now); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + e.SetDateTime("0", dtm!.Value); + PdfItem? nitem = e.GetValue("0", VCF.None); // 4stla: 6.2.0: 2 parameters, 6.3.0: 3 parameters + nitem = e.GetValue("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 3 parameters + e.SetValue("0", nitem!); + PdfObject? nobj = e.GetObject("0"); + PdfDictionary? ndict = e.GetDictionary("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 2 parameters + PdfArray? narray = e.GetArray("0"); // 4stla: 6.2.0: 1 parameter, 6.3.0: 2 parameters + PdfReference? nref = e.GetReference("0"); + e.SetObject("0", nobj!); +#pragma warning disable CS0618 // Type or member is obsolete but was part of the PDFsharp 6.2 API + e.SetReference("0", nobj!); +#pragma warning restore CS0618 // Type or member is obsolete + e.SetReference("0", nref!); + + #region IDictionary Members + b = e.IsReadOnly; +#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. + using IEnumerator> @enum = e.GetEnumerator(); // Nullability changed. +#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. + nitem = e["0"]; + nitem = e[new PdfName()]; + b = e.Remove("0"); +#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + b = e.Remove(new KeyValuePair("0", nitem)); // Nullability. +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + b = e.ContainsKey("0"); +#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + b = e.Contains(new KeyValuePair("0", nitem)); // Nullability. +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + e.Clear(); + e.Add("0", nitem!); +#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + e.Add(new KeyValuePair("0", nitem)); // Nullability. +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + PdfName[] names = e.KeyNames; + ICollection keys = e.Keys; + b = e.TryGetValue("0", out nitem); +#pragma warning disable CS8619 // Nullability of reference types in value doesn't match target type. + ICollection coll = e.Values; +#pragma warning restore CS8619 // Nullability of reference types in value doesn't match target type. + b = e.IsFixedSize; + + #endregion + + #region ICollection Members + + b = e.IsSynchronized; + i = e.Count; +#pragma warning disable CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + // ReSharper disable once UseCollectionExpression + e.CopyTo(new KeyValuePair[0], i); // Nullability. +#pragma warning restore CS8620 // Argument cannot be used for parameter due to differences in the nullability of reference types. + object obj = e.SyncRoot; + + #endregion + + ArrayOrSingleItemHelper item = e.ArrayOrSingleItem; + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryElementsTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryElementsTests.cs new file mode 100644 index 00000000..02e78907 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryElementsTests.cs @@ -0,0 +1,986 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class DictionaryElementsTests : ObjectModelTestsBase + { + // Keep all tests in sync with ArrayElementsTests. + + [Fact] + public void Indexer_Tests() + { + // === Test indexer === + { + var dict = new TestDictionaryElements(); + + var array1 = dict.Elements["/TestArray1"]; + + var dict1 = dict.Elements["/TestDict1"]; + } + } + + [Fact] + public void GetValue_Tests() + { + // === GetValue with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = dict.Elements.GetValue("/TestArray1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = dict.Elements.GetValue("/TestDict1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = dict.Elements.GetValue("/TestArray1", VCF.NoTransform, typeof(PdfArray)); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = dict.Elements.GetValue("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetValue("/TestArray1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetValue("/TestDict1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetValue with primitives === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? item; + + // Get PDF integer. + item = dict.Elements.GetValue("/Integer"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(PdfInteger)); + } + + // === GetRequiredValue with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = dict.Elements.GetRequiredValue("/TestArray1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = dict.Elements.GetRequiredValue("/TestDict1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = dict.Elements.GetRequiredValue("/TestArray1", VCF.NoTransform, typeof(PdfArray)); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = dict.Elements.GetRequiredValue("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetRequiredValue("/TestArray1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetRequiredValue("/TestDict1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetRequiredValue with primitives === + // ... + + // === TryGetValue with containers === + { + var array = new TestDictionaryElements(); + // ReSharper disable once InlineOutVariableDeclaration + PdfItem? item; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + + // Get PDF array. + success = array.Elements.TryGetValue("/TestArray1", out item); + success.Should().BeTrue(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + success = array.Elements.TryGetValue("/TestDict1", out item); + success.Should().BeTrue(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + success = array.Elements.TryGetValue("/TestArray1", out item, typeof(PdfArray)); + success.Should().BeTrue(); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + success = array.Elements.TryGetValue("/TestDict1", out item, typeof(PdfDictionary)); + success.Should().BeTrue(); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + success = array.Elements.TryGetValue("/TestArray1", out item, typeof(PdfDictionary)); + success.Should().BeFalse(); + item.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + success = array.Elements.TryGetValue("/TestDict1", out item, typeof(PdfArray)); + success.Should().BeFalse(); + item.Should().BeNull(); + } + + // === GetValue with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = dict.Elements.GetValue("/TestArray1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = dict.Elements.GetValue("/TestDict1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = dict.Elements.GetValue("/TestArray1", VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = dict.Elements.GetValue("/TestDict1", VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetValue("/TestArray1", VCF.NoTransform); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetValue("/TestDict1", VCF.NoTransform); + action.Should().Throw(); + } + + // === GetRequiredValue with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = dict.Elements.GetRequiredValue("/TestArray1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = dict.Elements.GetRequiredValue("/TestDict1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = dict.Elements.GetRequiredValue("/TestArray1", VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = dict.Elements.GetRequiredValue("/TestDict1", VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetRequiredValue("/TestArray1", VCF.NoTransform); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetRequiredValue("/TestDict1", VCF.NoTransform); + action.Should().Throw(); + } + + // === TryGetValue with containers === + { + var dict = new TestDictionaryElements(); + //// ReSharper disable once InlineOutVariableDeclaration + //PdfItem item; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + + // Get PDF array. + success = dict.Elements.TryGetValue("/TestArray1", out var resultTestArray1); + success.Should().BeTrue(); + resultTestArray1!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + success = dict.Elements.TryGetValue("/TestDict1", out var resultTestDict1); + success.Should().BeTrue(); + resultTestDict1!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //success = dict.Elements.TryGetValue("/TestArray1",out resultTestArray1); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //success = dict.Elements.TryGetValue("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + success = dict.Elements.TryGetValue("/TestDict1", out resultTestArray1); + success.Should().BeFalse(); + resultTestArray1.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + success = dict.Elements.TryGetValue("/TestArray1", out resultTestDict1); + success.Should().BeFalse(); + resultTestDict1.Should().BeNull(); + } + } + + [Fact] + public void GetValue_Tests2() + { + + + // === Test array must not be used twice === + { + var array = new TestArray1(); + array.IsIndirect.Should().BeFalse(); + var action = () => + { + var _ = new TestDict1 + { + Elements = + { + ["Key1"] = array, + ["Key2"] = array + } + }; + }; + action.Should().Throw(); + } + + } + + [Fact] + public void GetObject_Tests() + { + // === GetObject with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfObject? result; + // ReSharper disable once JoinDeclarationAndInitializer + //Action action; + + // Get PDF array. + result = dict.Elements.GetObject("/TestArray1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = dict.Elements.GetObject("/TestDict1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = dict.Elements.GetObject("/TestArray1", VCF.NoTransform, typeof(PdfArray)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = dict.Elements.GetObject("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + result = dict.Elements.GetObject("/TestArray1", VCF.NoTransform, typeof(PdfDictionary)); + result.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + result = dict.Elements.GetObject("/TestDict1", VCF.NoTransform, typeof(PdfArray)); + result.Should().BeNull(); + } + + // === GetObject with primitives === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? result; + + // Get PDF integer. + result = dict.Elements.GetObject("/Integer"); + result.Should().BeNull(); + } + + // === GetRequiredObject with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = dict.Elements.GetRequiredObject("/TestArray1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = dict.Elements.GetRequiredObject("/TestDict1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = dict.Elements.GetRequiredObject("/TestArray1", VCF.NoTransform, typeof(PdfArray)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = dict.Elements.GetRequiredObject("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetRequiredObject("/TestArray1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetRequiredObject("/TestDict1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetRequiredValue with primitives === + // ... + + // === TryGetObjects with containers === + // Not exits. + + // === GetObject with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = dict.Elements.GetObject("/TestArray1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = dict.Elements.GetObject("/TestDict1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = dict.Elements.GetObject("/TestArray1", VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = dict.Elements.GetObject("/TestDict1", VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetObject("/TestArray1", VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetObject("/TestDict1", VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + } + + // === GetRequiredObject with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfItem? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = dict.Elements.GetRequiredObject("/TestArray1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = dict.Elements.GetRequiredObject("/TestDict1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = dict.Elements.GetRequiredObject("/TestArray1", VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = dict.Elements.GetRequiredObject("/TestDict1", VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetRequiredObject("/TestArray1", VCF.NoTransform); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetRequiredObject("/TestDict1", VCF.NoTransform); + action.Should().Throw(); + } + + // === TryGetValue with containers === + // Not exits. + } + + + [Fact] + public void GetArray_Tests() + { + // === GetArray with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = dict.Elements.GetArray("/TestArray1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = dict.Elements.GetArray("/TestDict1"); + result.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = dict.Elements.GetArray("/TestArray1", VCF.NoTransform, typeof(PdfArray)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + action = () => dict.Elements.GetArray("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + //item.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetArray("/TestArray1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetArray("/TestDict1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetArray with primitives === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray? result; + + // Get PDF integer. + result = dict.Elements.GetArray("/Integer"); + result.Should().BeNull(); + //item!.GetType().Should().BeNull(); + } + + // === GetRequiredArray with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + var result = dict.Elements.GetRequiredArray("/TestArray1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + action = () => dict.Elements.GetRequiredArray("/TestDict1"); + action.Should().Throw(); + //item.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = dict.Elements.GetRequiredArray("/TestArray1", VCF.NoTransform, typeof(PdfArray)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + action = () => dict.Elements.GetRequiredArray("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + //item.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetRequiredArray("/TestArray1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetRequiredArray("/TestDict1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetRequiredArray with primitives === + // ... + + // === TryGetArray with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once InlineOutVariableDeclaration + PdfArray? result; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + success = dict.Elements.TryGetArray("/TestArray1", out result); + success.Should().BeTrue(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + success = dict.Elements.TryGetArray("/TestDict1", out result); + success.Should().BeFalse(); + result.Should().BeNull(); + + // Get PDF array with existing type. + success = dict.Elements.TryGetArray("/TestArray1", out result, typeof(PdfArray)); + success.Should().BeTrue(); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + action = () => dict.Elements.TryGetArray("/TestDict1", out result, typeof(PdfDictionary)); + action.Should().Throw(); + //success.Should().BeFalse(); + //result.Should().BeNull(); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.TryGetArray("/TestArray1", out result, typeof(PdfDictionary)); + action.Should().Throw(); + //success.Should().BeFalse(); + //result.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + success = dict.Elements.TryGetArray("/TestDict1", out result, typeof(PdfArray)); + success.Should().BeFalse(); + result.Should().BeNull(); + } + + // === GetArray with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = dict.Elements.GetArray("/TestArray1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary. + //item = array.Elements.GetArray("/TestDict1"); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + result = dict.Elements.GetArray("/TestArray1", VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //item = array.Elements.GetArray("/TestDict1", VCF.NoTransform); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with unsuitable type. + //action = () => array.Elements.GetArray("/TestArray1", VCF.NoTransform); + ////item.Should().BeNull(); + //action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetArray("/TestDict1", VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + } + + // === GetRequiredArray with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfArray? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + item = dict.Elements.GetRequiredArray("/TestArray1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + //item = array.Elements.GetRequiredArray("/TestDict1"); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + item = dict.Elements.GetRequiredArray("/TestArray1", VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //item = array.Elements.GetRequiredArray("/TestDict1", VCF.NoTransform); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with unsuitable type. + //action = () => array.Elements.GetRequiredArray("/TestArray1", VCF.NoTransform); + //action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetRequiredArray("/TestDict1", VCF.NoTransform); + action.Should().Throw(); + } + + // === TryGetArray with containers === + { + var dict = new TestDictionaryElements(); + //// ReSharper disable once InlineOutVariableDeclaration + //PdfItem item; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + + // Get PDF array. + success = dict.Elements.TryGetArray("/TestArray1", out var resultTestArray1); + success.Should().BeTrue(); + resultTestArray1!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary. + //success = array.Elements.TryGetArray("/TestDict1", out var resultTestDict1); + //success.Should().BeTrue(); + //resultTestDict1!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //success = array.Elements.TryGetValue("/TestArray1", out resultTestArray1, typeof(PdfArray)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //success = array.Elements.TryGetValue("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with unsuitable type. + //success = array.Elements.TryGetArray("/TestArray1", out var resultTestDict1); + //success.Should().BeFalse(); + //resultTestDict1.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + success = dict.Elements.TryGetArray("/TestDict1", out resultTestArray1); + success.Should().BeFalse(); + resultTestArray1.Should().BeNull(); + } + } + + [Fact] + public void GetDictionary_Tests() + { + // === GetDictionary with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + result = dict.Elements.GetDictionary("/TestArray1"); + result.Should().BeNull(); + + // Get PDF dictionary. + result = dict.Elements.GetDictionary("/TestDict1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetDictionary("/TestArray1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = dict.Elements.GetDictionary("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetDictionary("/TestArray1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetDictionary("/TestDict1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetArray with primitives === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary? result; + + // Get PDF integer. + result = dict.Elements.GetDictionary("/Integer"); + result.Should().BeNull(); + //item!.GetType().Should().Be(typeof(PdfInteger)); + } + + // === GetRequiredDictionary with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + action = () => dict.Elements.GetRequiredDictionary("/TestArray1"); + action.Should().Throw(); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = dict.Elements.GetRequiredDictionary("/TestDict1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF dictionary with existing type. + action = () => dict.Elements.GetRequiredDictionary("/TestArray1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = dict.Elements.GetRequiredDictionary("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetRequiredDictionary("/TestArray1", VCF.NoTransform, typeof(PdfDictionary)); + action.Should().Throw(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.GetRequiredDictionary("/TestDict1", VCF.NoTransform, typeof(PdfArray)); + action.Should().Throw(); + } + + // === GetRequiredDictionary with primitives === + // ... + + // === TryGetDictionary with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once InlineOutVariableDeclaration + PdfDictionary? result; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + success = dict.Elements.TryGetDictionary("/TestArray1", out result); + success.Should().BeFalse(); + result.Should().BeNull(); + + // Get PDF dictionary. + success = dict.Elements.TryGetDictionary("/TestDict1", out result); + success.Should().BeTrue(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with existing type. + action = () => dict.Elements.TryGetDictionary("/TestArray1", out result, typeof(PdfArray)); + action.Should().Throw(); + //success.Should().BeFalse(); + //result.Should().BeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + success = dict.Elements.TryGetDictionary("/TestDict1", out result, typeof(PdfDictionary)); + action.Should().Throw(); + //success.Should().BeTrue(); + //result.Should().NotBeNull(); + //result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + success = dict.Elements.TryGetDictionary("/TestArray1", out result, typeof(PdfDictionary)); + success.Should().BeFalse(); + result.Should().BeNull(); + + // Get PDF dictionary with unsuitable type. + action = () => dict.Elements.TryGetDictionary("/TestDict1", out result, typeof(PdfArray)); + action.Should().Throw(); + //success.Should().BeFalse(); + //result.Should().BeNull(); + } + + // === GetDictionary with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary? item; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + //// Get PDF array. + //item = array.Elements.GetDictionary("/TestArray1"); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + item = dict.Elements.GetDictionary("/TestDict1"); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //item = array.Elements.GetDictionary("/TestArray1", VCF.NoTransform); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + item = dict.Elements.GetDictionary("/TestDict1", VCF.NoTransform); + item.Should().NotBeNull(); + item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetDictionary("/TestArray1", VCF.NoTransform); + //item.Should().BeNull(); + action.Should().Throw(); + + //// Get PDF dictionary with unsuitable type. + //action = () => array.Elements.GetDictionary("/TestDict1", VCF.NoTransform); + ////item.Should().BeNull(); + //action.Should().Throw(); + } + + // === GetRequiredDictionary with containers === + { + var dict = new TestDictionaryElements(); + // ReSharper disable once JoinDeclarationAndInitializer + PdfDictionary? result; + // ReSharper disable once JoinDeclarationAndInitializer + Action action; + + // Get PDF array. + //item = array.Elements.GetRequiredDictionary("/TestArray1"); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + result = dict.Elements.GetRequiredDictionary("/TestDict1"); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //item = array.Elements.GetRequiredDictionary("/TestArray1", VCF.NoTransform); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary with existing type. + result = dict.Elements.GetRequiredDictionary("/TestDict1", VCF.NoTransform); + result.Should().NotBeNull(); + result!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + action = () => dict.Elements.GetRequiredDictionary("/TestArray1", VCF.NoTransform); + action.Should().Throw(); + + //// Get PDF dictionary with unsuitable type. + //action = () => array.Elements.GetRequiredDictionary("/TestDict1", VCF.NoTransform); + //action.Should().Throw(); + } + + // === TryGetDictionary with containers === + { + var dict = new TestDictionaryElements(); + //// ReSharper disable once InlineOutVariableDeclaration + //PdfDictionary item; + // ReSharper disable once JoinDeclarationAndInitializer + bool success; + + //// Get PDF array. + //success = array.Elements.TryGetDictionary("/TestArray1", out var resultTestArray1); + //success.Should().BeTrue(); + //resultTestArray1!.GetType().Should().Be(typeof(TestArray1)); + + // Get PDF dictionary. + success = dict.Elements.TryGetDictionary("/TestDict1", out var resultTestDict1); + success.Should().BeTrue(); + resultTestDict1!.GetType().Should().Be(typeof(TestDict1)); + + //// Get PDF array with existing type. + //success = array.Elements.TryGetValue("/TestArray1", out resultTestArray1, typeof(PdfArray)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestArray1)); + + //// Get PDF dictionary with existing type. + //success = array.Elements.TryGetValue("/TestDict1", VCF.NoTransform, typeof(PdfDictionary)); + //item.Should().NotBeNull(); + //item!.GetType().Should().Be(typeof(TestDict1)); + + // Get PDF array with unsuitable type. + success = dict.Elements.TryGetDictionary("/TestArray1", out resultTestDict1); + success.Should().BeFalse(); + resultTestDict1.Should().BeNull(); + + //// Get PDF dictionary with unsuitable type. + //success = array.Elements.TryGetDictionary("/TestDict1", out var resultTestArray1); + //success.Should().BeFalse(); + //resultTestArray1.Should().BeNull(); + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryItemTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryItemTests.cs new file mode 100644 index 00000000..76d514ad --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryItemTests.cs @@ -0,0 +1,185 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Quality.Testing; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class DictionaryItemTests : ObjectModelTestsBase + { + [Fact] + public void DealingWithIndirectObjects() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryItemTests),nameof(DealingWithIndirectObjects)); + var catalog = rwh.Document.Catalog; + + var dict1 = new TestDict1(); + rwh.Document.Internals.AddObject(dict1); + catalog.Elements.Add("/TestDict1", dict1); + + // + { + var item0 = catalog.Elements.GetRequiredValue("/TestDict1"); + item0.GetType().Name.Should().Be(nameof(TestDict1)); + + var item1 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.None, typeof(PdfDictionary)); + item1.GetType().Name.Should().Be(nameof(TestDict1)); + + var item2 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.None, typeof(PdfReference)); + item2.GetType().Name.Should().Be(nameof(PdfReference)); + ReferenceEquals(((PdfReference)item2).Value, item0).Should().BeTrue(); + } + + // + { + var item10 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.None); + item10.GetType().Name.Should().Be(nameof(TestDict1)); + + var item11 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.None); + item11.GetType().Name.Should().Be(nameof(TestDict1)); + + var item12 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.None); + item12.GetType().Name.Should().Be(nameof(PdfReference)); + ReferenceEquals(((PdfReference)item12).Value, item10).Should().BeTrue(); + } + + // Do not create + { + catalog.Elements.Remove("/TestDict1"); + + var item0 = catalog.Elements.GetValue("/TestDict1"); + item0.Should().BeNull(); + + var item1 = catalog.Elements.GetValue("/TestDict1", VCF.None, typeof(PdfDictionary)); + item1.Should().BeNull(); + + var item2 = catalog.Elements.GetValue("/TestDict1", VCF.None, typeof(PdfReference)); + item2.Should().BeNull(); + } + + // Create + { + catalog.Elements.Remove("/TestDict1"); + + var item0 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.CreateIndirect, typeof(TestDict1)); + item0.GetType().Name.Should().Be(nameof(TestDict1)); + + catalog.Elements.Remove("/TestDict1"); + + var item1 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.CreateIndirect); + item1.GetType().Name.Should().Be(nameof(PdfDictionary)); + + var item2 = catalog.Elements.GetRequiredValue("/TestDict1", VCF.CreateIndirect); + item2.GetType().Name.Should().Be(nameof(TestDict1)); + item1.IsDead.Should().BeTrue(); + + catalog.Elements.Remove("/TestDict1"); + + var item3 = catalog.Elements.GetReference("/TestDict1"); + item3.Should().BeNull(); + + catalog.Elements.Remove("/TestDict1"); + + //var item4 = catalog.Elements.GetValue("/TestDict1", VCF.CreateIndirect, typeof(PdfReference)); + //item0.GetType().Name.Should().Be(nameof(TestDict1)); + + + //item3.GetType().Name.Should().Be(nameof(PdfReference)); + //item3.Value.GetType().Name.Should().Be(nameof(TestDict1)) + //ReferenceEquals(((PdfReference)item2).Value, item0).Should().BeTrue(); + } + } + + [Fact] + public void Test_PdfNull_and_PdfNullObject() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryItemTests), nameof(Test_PdfNull_and_PdfNullObject)); + var catalog = rwh.Document.Catalog; + + var dict1 = new TestDict1(); + rwh.Document.Internals.AddObject(dict1); + catalog.Elements.Add("/TestDict1", dict1); + + var nullObject = new PdfNullObject(rwh.Document); + + dict1.Elements.Add("/NullItem", PdfNull.Value); + dict1.Elements.Add("/NullObject", nullObject); + + var item1 = dict1.Elements.GetValue("/NullItem"); + var item2 = dict1.Elements.GetValue("/NullObject"); + + + rwh.Save(nameof(Test_PdfNull_and_PdfNullObject)); + var docReload = rwh.Reload(); + catalog = docReload.Catalog; + dict1 = catalog.Elements.GetRequiredDictionary("/TestDict1"); + } + + [Fact] + public void ToDo() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryItemTests), nameof(ToDo)); + var catalog = rwh.Document.Catalog; + + var dict1 = new ApiTestDict1(); + rwh.Document.Internals.AddObject(dict1); + catalog.Elements.Add("/TestDict1", dict1); + + dict1.Elements[ApiTestDict1.Keys.SomeBoolean] = new PdfBoolean(true); + dict1.Elements.GetBoolean(ApiTestDict1.Keys.SomeBoolean).Should().BeTrue(); + // TryGetBoolean + + var bool1 = dict1.Elements.GetValue(ApiTestDict1.Keys.SomeBoolean); + var bool2 = dict1.Elements.GetValue(ApiTestDict1.Keys.SomeBoolean); + + + dict1.Elements.Remove(ApiTestDict1.Keys.SomeBoolean); + dict1.Elements[ApiTestDict1.Keys.SomeBoolean].Should().BeNull(); + + bool1 = dict1.Elements.GetValue(ApiTestDict1.Keys.SomeBoolean); + bool1.Should().BeNull(); + + bool1 = dict1.Elements.GetValue(ApiTestDict1.Keys.SomeBoolean, VCF.Create); + bool1.Should().NotBeNull(); + + dict1.Elements.Remove(ApiTestDict1.Keys.SomeBoolean); + // TODO fail bool1 = dict1.Elements.GetValue(ApiTestDict1.Keys.SomeBoolean, VCF.CreateIndirect); + + + dict1.Elements[ApiTestDict1.Keys.SomeInteger] = new PdfInteger(7); + dict1.Elements.GetInteger(ApiTestDict1.Keys.SomeInteger).Should().Be(7); + + // ... + + // ----- GetArray ----- + + // ----- GetDictionary ----- + + var someDirectDict = dict1.Elements.GetDictionary(ApiTestDict1.Keys.SomeDirectDict)!; + someDirectDict.Should().BeNull(); + + someDirectDict = dict1.Elements.GetDictionary(ApiTestDict1.Keys.SomeDirectDict, VCF.Create)!; + someDirectDict.Should().NotBeNull(); + someDirectDict.IsIndirect.Should().BeFalse(); + + var someIndirectDirectDict = dict1.Elements.GetDictionary(ApiTestDict1.Keys.SomeIndirectDict)!; + someIndirectDirectDict.Should().BeNull(); + + someIndirectDirectDict = dict1.Elements.GetDictionary(ApiTestDict1.Keys.SomeIndirectDict, VCF.CreateIndirect)!; + someIndirectDirectDict.Should().NotBeNull(); + someIndirectDirectDict.IsIndirect.Should().BeTrue(); + + rwh.Save(nameof(ToDo)); + rwh.Reload(); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryPrimitivesTest.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryPrimitivesTest.cs new file mode 100644 index 00000000..ee9aaca1 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryPrimitivesTest.cs @@ -0,0 +1,512 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.PdfItemExtensions; +using PdfSharp.Quality.Testing; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class DictionaryPrimitivesTest : ObjectModelTestsBase + { + [Fact] + public void Primitives_GetBoolean_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_GetBoolean_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // Test cases that succeed for existing values. + + var b = dict1.Elements.GetBoolean("/bool"); + b.Should().BeTrue(); + + b = dict1.Elements.GetBoolean("/bool-obj"); + b.Should().BeTrue(); + + // Test cases that succeed for non-existing values. + + b = dict1.Elements.GetBoolean("/key-does-not-exist"); + b.Should().BeFalse(); + + // Test cases that throw. + + Action getValueFromIncompatibleItem = () => dict1.Elements.GetBoolean("/real"); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void Primitives_GetInteger_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_GetInteger_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // Test cases that succeed for existing values. + + var i = dict1.Elements.GetInteger("/int"); + i.Should().Be(42); + + i = dict1.Elements.GetInteger("/int-obj"); + i.Should().Be(42); + + // Test cases that succeed for non-existing values. + + i = dict1.Elements.GetInteger("/key-does-not-exist"); + i.Should().Be(0); + + // Test cases that throw. + + Action getIntegerFromString = () => i = dict1.Elements.GetInteger("/string"); + getIntegerFromString.Should().Throw(); + + Action getIntegerFromLong = () => i = dict1.Elements.GetInteger("/long-max"); + getIntegerFromLong.Should().Throw(); + + Action getIntegerFromNull = () => i = dict1.Elements.GetInteger("/null"); + getIntegerFromNull.Should().Throw(); + + Action getValueFromIncompatibleItem = () => i = dict1.Elements.GetInteger("/-.999"); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void Primitives_GetLongInteger_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_GetLongInteger_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // Test cases that succeed for existing values. + + var l = dict1.Elements.GetLongInteger("/int"); + l.Should().Be(42); + + l = dict1.Elements.GetLongInteger("/int-obj"); + l.Should().Be(42); + + l = dict1.Elements.GetLongInteger("/long-max"); + l.Should().Be(Int64.MaxValue); + + // Test cases that succeed for non-existing values. + + l = dict1.Elements.GetLongInteger("/key-does-not-exist"); + l.Should().Be(0); + + // Test cases that throw. + + Action getIntegerFromString = () => l = dict1.Elements.GetLongInteger("/string"); + getIntegerFromString.Should().Throw(); + + Action getIntegerFromNull = () => l = dict1.Elements.GetLongInteger("/null"); + getIntegerFromNull.Should().Throw(); + + Action getValueFromIncompatibleItem = () => l = dict1.Elements.GetLongInteger("/-.999"); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void Primitives_GetReal_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_GetReal_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // Test cases that succeed for existing values. + + var r = dict1.Elements.GetReal("/real"); + r.Should().Be(Math.PI); + + r = dict1.Elements.GetReal("/real-obj"); + r.Should().Be(Math.PI); + + // Test cases that succeed for non-existing values. + + r = dict1.Elements.GetReal("/key-does-not-exist"); + r.Should().Be(0d); + + // Test cases that throw. + + Action getValueFromIncompatibleItem = () => dict1.Elements.GetReal("/name"); + // TODO Should be IOE. + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void Primitives_GetDateTime_Tests() + { + var now = DateTimeOffset.Now; + var d1 = now.ToString(); + var d2 = now.ToString("zzz"); + + var xx = now.Offset; + + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_GetDateTime_Tests)); + + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // === Test cases that succeed for existing values === + { + var date = dict1.Elements.GetDateTime("/string-date1"); + date.Should().Be(new DateTimeOffset(1999, 12, 31, 23, 59, 59, TimeSpan.Zero)); + + date = dict1.Elements.GetDateTime("/string-date2"); + date.Should().Be(new DateTimeOffset(1999, 12, 31, 23, 59, 59, new TimeSpan(2, 0, 0))); + + date = dict1.Elements.GetDateTime("/string-date3"); + date.Should().Be(new DateTimeOffset(1999, 12, 31, 23, 59, 59, -new TimeSpan(3, 0, 0))); + + date = dict1.Elements.GetDateTime("/date1"); + date.Should().Be(new DateTimeOffset(1999, 12, 31, 23, 59, 59, TimeSpan.Zero)); + + date = dict1.Elements.GetDateTime("/date2"); + date.Should().Be(new DateTimeOffset(1999, 12, 31, 23, 59, 59, new TimeSpan(4, 0, 0))); + + date = dict1.Elements.GetDateTime("/date3"); + date.Should().Be(new DateTimeOffset(1999, 12, 31, 23, 59, 59, -new TimeSpan(3, 34, 0))); + } + + // === Test cases that succeed for non-existing values === + { + // Called with given defaultValue. + var date = dict1.Elements.GetDateTime("/key-does-not-exist", new DateTimeOffset()); + date.Should().Be(new DateTimeOffset()); + + // Called without specifying a defaultValue. + date = dict1.Elements.GetDateTime("/key-does-not-exist"); + date.Should().BeNull(); + } + + // === Test cases that throw === + { + // Get value from incompatible item. + + Action action = () => dict1.Elements.GetDateTime("/name"); + action.Should().Throw(); + + action = () => dict1.Elements.GetDateTime("/string"); + action.Should().Throw(); + + action = () => dict1.Elements.GetDateTime("/int"); + action.Should().Throw(); + + action = () => dict1.Elements.GetDateTime("/bool"); + action.Should().Throw(); + } + } + + [Fact] + public void Primitives_GetRectangle_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_GetRectangle_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // Test cases that succeed for existing values. + + var rect = dict1.Elements.GetRequiredRectangle("/rect"); + rect.X1.Should().Be(1); + rect.X2.Should().Be(4); + rect.Y1.Should().Be(2); + rect.Y2.Should().Be(6); + + rect = dict1.Elements.GetRequiredRectangle("/array"); + rect.X1.Should().Be(5); + rect.X2.Should().Be(7); + rect.Y1.Should().Be(6); + rect.Y2.Should().Be(8); + + + // Test cases that succeed for non-existing values. + + rect = dict1.Elements.GetRequiredRectangle("/key-does-not-exist", false, new()); + rect.X1.Should().Be(0); + rect.X2.Should().Be(0); + rect.Y1.Should().Be(0); + rect.Y2.Should().Be(0); + + + // Test cases that throw. + + Action getValueFromIncompatibleItem = () => dict1.Elements.GetRequiredRectangle("/name"); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void GetSetRectangle_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(GetSetRectangle_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + //PopulateDictionary(dict1); + + // Get direct rectangle + { + var rect = new PdfRectangle(1, 2, 3, 4); + + dict.Elements.Add("/rect1", rect); + var r1 = dict.Elements.GetRequiredRectangle("/rect1"); + ReferenceEquals(rect, r1).Should().BeFalse(); + var r2 = dict.Elements.GetArray("/rect1"); + r2.Should().NotBeNull(); + Debug.Assert(r2 != null); + r2!.Elements.Count.Should().Be(4); + } + + // Get indirect rectangle + { + var rect = new PdfArray(rwh.Document, new PdfReal(1), new PdfReal(2), new PdfReal(3), new PdfReal(4)); + var ind = rect.IsIndirect; + + rwh.Document.IrefTable.Add(rect); + + dict.Elements.Add("/rect2a", rect); + dict.Elements.Add("/rect2b", rect); + var r1 = dict.Elements.GetRequiredRectangle("/rect2a"); + //ReferenceEquals(rect, r1).Should().BeFalse(); + var r2 = dict.Elements.GetArray("/rect2b"); + r2.Should().NotBeNull(); + r2!.Elements.Count.Should().Be(4); + } + + // Create direct rectangle + { + var rect = new PdfRectangle(1, 2, 3, 4); + + var r1 = dict.Elements.GetRectangle("/rect-na", false, null); + r1.Should().BeNull(); + dict.Elements.GetValue("/rect-na").Should().BeNull(); + + var r2 = dict.Elements.GetRectangle("/rect-na", false, new(1, 2, 3, 4)); + r2.Should().NotBeNull(); + dict.Elements.GetValue("/rect-na").Should().BeNull(); + + //var r3 = dict.Elements.GetRectangle2("/rect-new1", true, null); + //dict.Elements.GetValue("/rect-new1").Should().NotBeNull(); + + var r4 = dict.Elements.GetRectangle("/rect-new1", true, new(1, 2, 3, 4)); + r4.Should().NotBeNull(); + dict.Elements.GetValue("/rect-new1").Should().NotBeNull(); + + + //dict.Elements.Add("/rect1", rect); + //var r1 = dict.Elements.GetRectangle("/rect1"); + //ReferenceEquals(rect, r1).Should().BeFalse(); + //var r2 = dict.Elements.GetArray("/rect1"); + //r2.Should().NotBeNull(); + //r2.Elements.Count.Should().Be(4); + } + + + // TODO Tests, where the type PdfRectangle comes from Key’s metadata + + //// Test cases that succeed for existing values. + + //var rect = dict1.Elements.GetRectangle("/rect"); + //rect.X1.Should().Be(1); + //rect.X2.Should().Be(4); + //rect.Y1.Should().Be(2); + //rect.Y2.Should().Be(6); + + //rect = dict1.Elements.GetRectangle("/array"); + //rect.X1.Should().Be(5); + //rect.X2.Should().Be(7); + //rect.Y1.Should().Be(6); + //rect.Y2.Should().Be(8); + + //// Test cases that succeed for non-existing values. + + //rect = dict1.Elements.GetRectangle("/key-does-not-exist"); + //rect.X1.Should().Be(0); + //rect.X2.Should().Be(0); + //rect.Y1.Should().Be(0); + //rect.Y2.Should().Be(0); + + //// Test cases that throw. + + //Action getValueFromIncompatibleItem = () => dict1.Elements.GetRectangle("/name"); + //getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void Primitives_GetString_GetName_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_GetString_GetName_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // Test cases that succeed for existing values. + + var s = dict1.Elements.GetString("/string"); + s.Should().Be("Hello"); + + s = dict1.Elements.GetString("/string-hex"); + s.Should().Be("HelloHex"); + var item1 = dict1.Elements.GetRequiredValue("/string-hex"); + item1.HexLiteral.Should().BeTrue(); + + var n = dict1.Elements.GetName("/name"); + n.Should().Be("/Mambo #5"); + + s = dict1.Elements.GetString("/string-obj"); + s.Should().Be("Hello"); + + s = dict1.Elements.GetString("/string-obj-hex"); + s.Should().Be("HelloHex"); + var item2 = dict1.Elements.GetRequiredValue("/string-obj-hex"); + item2.HexLiteral.Should().BeTrue(); + + n = dict1.Elements.GetName("/name-obj"); + n.Should().Be("/Mambo #5"); + + // Test cases that succeed for non-existing values. + + s = dict1.Elements.GetString("/key-does-not-exist"); + s.Should().Be(""); + + n = dict1.Elements.GetName("/key-does-not-exist"); + n.Should().Be("/"); + + // Test cases that throw. + + Action getValueFromIncompatibleItem = () => dict1.Elements.GetString("/int"); + getValueFromIncompatibleItem.Should().Throw(); + + getValueFromIncompatibleItem = () => dict1.Elements.GetName("/int"); + getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void Primitives_GetEnum_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_GetEnum_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // Test cases that succeed for existing values. + + // Enum from integer. + var pageLayout = dict1.Elements.GetEnum("/pagelayout-test"); + pageLayout.Should().Be(PdfPageLayout.TwoColumnRight); + + // Enum from name. + pageLayout = dict1.Elements.GetEnum("/pagelayout-test-string"); + pageLayout.Should().Be(PdfPageLayout.TwoColumnRight); + + // Enum from integer. + var pageMode = dict1.Elements.GetEnum("/pagemode-test"); + pageMode.Should().Be(PdfPageMode.FullScreen); + + // Enum from name. + pageMode = dict1.Elements.GetEnum("/pagemode-test-string"); + pageMode.Should().Be(PdfPageMode.FullScreen); + + pageMode = dict1.Elements.GetEnum("/real"); + pageMode.Should().Be(null); + + pageMode = dict1.Elements.GetEnum("/string-date1"); + pageMode.Should().Be(null); + + pageMode = dict1.Elements.GetEnum("/non-existent-item"); + pageMode.Should().Be(null); + + // Test cases that throw. + //Action getValueFromIncompatibleItem = () => dict1.Elements.GetEnum("/real"); + //getValueFromIncompatibleItem.Should().Throw(); + + //Action getValueFromIncompatibleItem = () => dict1.Elements.GetEnum("/string-date1"); + //getValueFromIncompatibleItem.Should().Throw(); + + //Action getValueFromIncompatibleItem = () => dict1.Elements.GetEnum("/non-existent-item"); + //getValueFromIncompatibleItem.Should().Throw(); + } + + [Fact] + public void Primitives_References_Tests() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryPrimitivesTest), nameof(Primitives_References_Tests)); + + var dict = new PdfDictionary(); + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(rwh.Document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + // Test cases that succeed for existing values. + + var l = dict1.Elements.GetLongInteger("/int-obj"); + l.Should().Be(42); + + var o = dict1.Elements["/int-obj"]!; + o!.AsReference().Should().NotBeNull(); + PdfReference.Dereference(ref o); + var i = (PdfIntegerObject)o; + i.Value.Should().Be(42); + + o = dict1.Elements.GetValue("/int-obj")!; + PdfReference.Dereference(ref o); + i = (PdfIntegerObject)o; + i.Value.Should().Be(42); + + // Test cases that throw. + //Action getValueFromReference = () => o = dict1.Elements.GetValue("/int-obj"); + //getValueFromReference.Should().Throw(); + + //// Test cases that succeed for non-existing values. + + //l = dict1.Elements.GetLongInteger("/key-does-not-exist"); + //l.Should().Be(0); + + //// Test cases that throw. + + //Action getIntegerFromString = () => l = dict1.Elements.GetLongInteger("/string"); + //getIntegerFromString.Should().Throw(); + + //Action getIntegerFromNull = () => l = dict1.Elements.GetLongInteger("/null"); + //getIntegerFromNull.Should().Throw(); + + //Action getValueFromIncompatibleItem = () => l = dict1.Elements.GetLongInteger("/-.999"); + //getValueFromIncompatibleItem.Should().Throw(); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryTests.cs new file mode 100644 index 00000000..5fff37f3 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/DictionaryTests.cs @@ -0,0 +1,294 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Quality.Testing; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class DictionaryTests : ObjectModelTestsBase + { + [Fact] + public void ToDo() + { + } + + [Fact] + public void Test_cloning() + { + } + + [Fact] + public void Test_basic_type_transformation() + { + // Indirect object. + + // Direct object. + } + + [Fact] + public void Dictionaries_with_streams_must_be_indirect() + { + } + + //[Fact] + //public void TestDictionaryApi() + //{ + // var rwh = new ReadWriteHelper(); + // rwh.CreateDocument(typeof(DictionaryTests), nameof(TestDictionaryApi)); + // var dict = new PdfDictionary(); + // var dict1 = new TestDict1(); + // dict.CloneElementsOf(dict1); + // var c = dict.Elements.Count; + + // rwh.Document.Catalog.Elements.Add("/test", dict); + // rwh.Save(); + // rwh.Reload(); + // rwh.ReloadedDocument.Catalog.Elements.GetRequiredValue("/test").Count().Should().Be(1); + //} + + [Fact] + public void Reuse_of_direct_object_correctly() + { + // === Test direct array reuse after remove === + { + var array = new TestArray1(); + array.IsIndirect.Should().BeFalse(); + array.ParentInfo.Should().BeNull(); + + var owningDict = new PdfDictionary() + { + Elements = { ["Key"] = array } + }; + array.ParentInfo.Should().NotBeNull(); + array.ParentInfo!.OwningDictionary.Equals(owningDict).Should().BeTrue(); + owningDict.Elements.Count.Should().Be(1); + + owningDict.Elements.Remove("Key"); + array.ParentInfo.Should().BeNull(); + array.IsDead.Should().BeFalse(); + owningDict.Elements.Count.Should().Be(0); + + // array can be reused after removing. + owningDict.Elements["Key"] = array; + array.ParentInfo.Should().NotBeNull(); + array.ParentInfo!.OwningDictionary.Equals(owningDict).Should().BeTrue(); + owningDict.Elements.Count.Should().Be(1); + } + + // === Test direct dictionary reuse after remove === + { + var dict = new TestDict1(); + dict.IsIndirect.Should().BeFalse(); + dict.ParentInfo.Should().BeNull(); + + var owningDict = new PdfDictionary() + { + Elements = { ["Key"] = dict } + }; + dict.ParentInfo.Should().NotBeNull(); + dict.ParentInfo!.OwningDictionary.Equals(owningDict).Should().BeTrue(); + owningDict.Elements.Count.Should().Be(1); + + owningDict.Elements.Remove("Key"); + dict.ParentInfo.Should().BeNull(); + dict.IsDead.Should().BeFalse(); + owningDict.Elements.Count.Should().Be(0); + + // dict can be reused after removing. + owningDict.Elements["Key"] = dict; + dict.ParentInfo.Should().NotBeNull(); + dict.ParentInfo!.OwningDictionary.Equals(owningDict).Should().BeTrue(); + owningDict.Elements.Count.Should().Be(1); + } + + // === Test self replacement === + { + var dict = new TestDict1(); + var owningDict = new PdfDictionary + { + Elements = + { + ["Key"] = dict, + // Replace by itself is senseless but must work. + ["Key"] = dict + } + }; + dict.ParentInfo.Should().NotBeNull(); + dict.ParentInfo!.OwningDictionary.Equals(owningDict).Should().BeTrue(); + owningDict.Elements.Count.Should().Be(1); + } + } + + [Fact] + public void Illegal_reuse_of_direct_object() + { + // === Test array must not be used twice === + { + var array = new TestArray1(); + array.IsIndirect.Should().BeFalse(); + var action = () => + { + var _ = new TestDict1 + { + Elements = + { + ["Key1"] = array, + ["Key2"] = array + } + }; + }; + action.Should().Throw(); + } + + // === Test dict must not be used twice === + { + var dict = new TestDict1(); + dict.IsIndirect.Should().BeFalse(); + var action = () => + { + var _ = new TestDict1 + { + + Elements = + { + ["Key1"] = dict, + ["Key2"] = dict + } + }; + }; + action.Should().Throw(); + } + + // === Test dict must not be used twice === + { + var dict = new TestDict1(); + + var action = () => + + { + var _1 = new TestDict1 + { + + Elements = + { + ["Key"] = dict, + } + }; + var _2 = new TestDict1 + { + + Elements = + { + ["Key"] = dict + } + }; + }; + action.Should().Throw(); + } + } + + [Fact] + public void Illegal_direct_use_of_primitive_object() + { + // === Test primitive objects must not be direct === + { + // Primitive objects (non-container) like PdfStringObject or + // PdfIntegerObject must not be used as direct objects. + var str = new PdfStringObject(); + str.IsIndirect.Should().BeFalse(); + var action = () => + { + var _ = new TestDict1 + { + Elements = + { + ["Key1"] = str // Use PdfString instead. + } + }; + }; + action.Should().Throw(); + } + } + + [Fact] + public void Test_multiple_transformation() + { + const string someKey = "/SomeKey"; + + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryTests), nameof(Test_multiple_transformation)); + var catalog = rwh.Document.Catalog; + + var dict1 = new PdfDictionary(); + rwh.Document.Internals.AddObject(dict1); + + catalog.Elements.Add(someKey, dict1); + + var value0 = catalog.Elements.GetRequiredValue(someKey); + value0.GetType().Name.Should().Be(nameof(PdfDictionary)); + ReferenceEquals(dict1, value0).Should().BeTrue(); + + var value1 = catalog.Elements.GetRequiredValue(someKey); + value1.GetType().Name.Should().Be(nameof(PdfDictionary)); + ReferenceEquals(value1, value0).Should().BeTrue(); + + var value2 = catalog.Elements.GetRequiredValue(someKey); + value2.GetType().Name.Should().Be(nameof(TestBaseDict)); + value1.IsDead.Should().BeTrue(); + + var value3 = catalog.Elements.GetRequiredValue(someKey); + value3.GetType().Name.Should().Be(nameof(TestDerivedDict)); + value2.IsDead.Should().BeTrue(); + + value2 = catalog.Elements.GetRequiredValue(someKey); + value2.GetType().Name.Should().Be(nameof(TestDerivedDict)); + } + + [Fact] + public void Test_DictionaryElements_API() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(DictionaryTests), nameof(Test_DictionaryElements_API)); + var catalog = rwh.Document.Catalog; + + var dict1 = new ApiTestDict1(); + rwh.Document.Internals.AddObject(dict1); + catalog.Elements.Add("/TestDict1", dict1); + + dict1.Elements[ApiTestDict1.Keys.SomeBoolean] = new PdfBoolean(true); + dict1.Elements.GetBoolean(ApiTestDict1.Keys.SomeBoolean).Should().BeTrue(); + // TryGetBoolean + + dict1.Elements[ApiTestDict1.Keys.SomeInteger] = new PdfInteger(7); + dict1.Elements.GetInteger(ApiTestDict1.Keys.SomeInteger).Should().Be(7); + + // ... + + // ----- GetArray ----- + + // ----- GetDictionary ----- + + var someDirectDict = dict1.Elements.GetDictionary(ApiTestDict1.Keys.SomeDirectDict)!; + someDirectDict.Should().BeNull(); + + someDirectDict = dict1.Elements.GetDictionary(ApiTestDict1.Keys.SomeDirectDict, VCF.Create)!; + someDirectDict.Should().NotBeNull(); + someDirectDict.IsIndirect.Should().BeFalse(); + + var someIndirectDirectDict = dict1.Elements.GetDictionary(ApiTestDict1.Keys.SomeIndirectDict)!; + someIndirectDirectDict.Should().BeNull(); + + someIndirectDirectDict = dict1.Elements.GetDictionary(ApiTestDict1.Keys.SomeIndirectDict, VCF.CreateIndirect)!; + someIndirectDirectDict.Should().NotBeNull(); + someIndirectDirectDict.IsIndirect.Should().BeTrue(); + + rwh.Save(nameof(Test_DictionaryElements_API)); + rwh.Reload(); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/HybridContainerTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/HybridContainerTests.cs new file mode 100644 index 00000000..87b3768f --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/HybridContainerTests.cs @@ -0,0 +1,148 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Diagnostics; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Forms; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Annotations; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class HybridContainerTests + { + const string TempRoot = "object-model/"; + + [Fact] + public void Test_the_concept() + { + int creationCounter = 0; + + var doc = new PdfDocument(); + doc.Pages.Add(); + + var catalog = doc.Catalog; + var testStuff = new PdfDictionary(doc, true); + catalog.Elements.Add("/TestStuff", testStuff); + + // Array of fields. + var fields = new PdfArray(doc, false); + fields.Elements.Add(new PdfInteger(++creationCounter)); + + // Array of widgets. + var widgets = new PdfArray(doc, false); + widgets.Elements.Add(new PdfInteger(++creationCounter)); + + // Create regular field. + var field = new SampleField(doc); + field.IsIndirect.Should().BeTrue(); + field.Elements.Add("/CreationCounter_3", new PdfInteger(++creationCounter)); + fields.Elements.Add(field); + field.Elements.Add("/Comment", new PdfString("This is a regular field.")); + + // Create regular widget. + var widget = new SampleWidget(doc); + widget.Elements.Add("/CreationCounter_4", new PdfInteger(++creationCounter)); + widget.IsIndirect.Should().BeTrue(); + widgets.Elements.Add(widget); + widget.Elements.Add("/Comment", new PdfString("This is a regular widget.")); + + testStuff.Elements.Add("/1_fields", fields); + testStuff.Elements.Add("/2_widgets", widgets); + testStuff.Elements.Add("/xxx", new PdfInteger(9048)); + testStuff.Elements.Add("/RegularField", field); + testStuff.Elements.Add("/RegularWidget", widget); + + // Create regular field also used as widget. + var hybridField = new SampleField(doc); + hybridField.Elements.Add("/CreationCounter_5", new PdfInteger(++creationCounter)); + hybridField.IsIndirect.Should().BeTrue(); + fields.Elements.Add(hybridField); + hybridField.Elements.Add("/Comment2", new PdfString("This is a regular field but also a widget.")); + + // Create widget from field. + var hybridWidget = SampleWidget.CreateFromField(hybridField); // <<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<<< + hybridWidget.Elements.Add("/CreationCounter_6", new PdfInteger(++creationCounter)); + widgets.Elements.Add(hybridWidget); + hybridWidget.Elements.Add("/Comment_2", new PdfString("This is added to the hybrid widget.")); + + testStuff.Elements.Add("/HybridField", hybridField); + testStuff.Elements.Add("/HybridWidget", hybridWidget); + + var dest = PdfFileUtility.GetTempPdfFullFileName(TempRoot + nameof(Test_the_concept)); + doc.Options.Layout = PdfWriterLayout.Verbose; + doc.Save(dest); + + var doc2 = PdfReader.Open(dest, PdfDocumentOpenMode.Modify); + + var dest2 = PdfFileUtility.GetTempPdfFullFileName(TempRoot + nameof(Test_the_concept) + "#2"); + var test = doc.Catalog.Elements.GetRequiredDictionary("/TestStuff"); + var field2 = test.Elements.GetRequiredDictionary("/HybridField"); + var widget2 = SampleWidget.CreateFromField(field2); + + // item1 and item2 should be the same object. + var item1 = field2.Elements.GetValue("/Iam-1"); + var item2 = widget2.Elements.GetValue("/Iam-1"); + item1.Should().BeEquivalentTo(item2); + ReferenceEquals(item1, item2).Should().BeTrue(); + + doc2.Options.Layout = PdfWriterLayout.Verbose; + doc2.Save(dest2); + } + } + + class SampleField : PdfFormField + { + public SampleField(PdfDocument doc) : base(doc) + { + Initialize(); + } + + void Initialize() + { + Elements.Add("/Iam-1", new PdfString(nameof(SampleField))); + } + + } + + class SampleWidget : PdfAnnotation + { + public SampleWidget(PdfDocument doc) : base(doc) + { + Initialize(); + } + + SampleWidget(SampleField field) + { + Debug.Assert(field.IsIndirect); + + var oldElementsFromBaseClasses = Elements; + this.Elements = null!; + this.Elements = field.Elements; + foreach (var item in oldElementsFromBaseClasses) + { + Elements.Add(item.Key, item.Value); + } + + // Create new reference. Same ObjectID, but different value. + var iref = new PdfReference(field, this); + Initialize(); + } + + void Initialize() + { + Elements.Add("/Iam-2", new PdfString(nameof(SampleWidget))); + } + + public static SampleWidget CreateFromField(SampleField field) + { + var widget = new SampleWidget(field); + return widget; + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/LayoutTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/LayoutTests.cs new file mode 100644 index 00000000..a1988f52 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/LayoutTests.cs @@ -0,0 +1,331 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality.Testing; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class LayoutTests : ObjectModelTestsBase + { + private readonly string _tempRoot = GetTempRoot(typeof(LayoutTests)); + + [Fact] + public void Test_Verbose_layout_arrays_in_arrays() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(LayoutTests), nameof(Test_Compact_layout_bug)); + + var catalog = rwh.Document.Catalog; + var dict = new PdfDictionary(rwh.Document, true); + catalog.Elements.Add("/TestStuff", dict); + + var array = new PdfArray(); + dict.Elements.Add("/DirectArray", array); + + array.Elements.Add(new PdfInteger(111)); + array.Elements.Add(new PdfArray(rwh.Document, new PdfInteger(7))); + array.Elements.Add(new PdfArray()); + array.Elements.Add(PdfNull.Value); + + rwh.Save(nameof(Test_Verbose_layout_arrays_in_arrays), PdfWriterLayout.Verbose); + rwh.Reload(); + rwh.Resave(PdfWriterLayout.Compact); + } + + [Fact] + public void Test_Compact_layout_bug() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(LayoutTests), nameof(Test_Compact_layout_bug)); + + var catalog = rwh.Document.Catalog; + var dict = new PdfDictionary(rwh.Document, true); + catalog.Elements.Add("/TestStuff", dict); + + var array = new PdfArray(); + dict.Elements.Add("/DirectArray", array); + + array.Elements.Add(new PdfInteger(33)); + array.Elements.Add(new PdfInteger(55)); + array.Elements.Add(new PdfInteger(-55)); + array.Elements.Add(new PdfInteger(-773)); + array.Elements.Add(new PdfReal(Math.PI)); + array.Elements.Add(new PdfReal(-Math.PI)); + + rwh.Save(nameof(Test_Compact_layout_bug), PdfWriterLayout.Compact); + rwh.Reload(); + rwh.Resave(PdfWriterLayout.Compact); + } + + [Fact] + public void Test_Compact_layout() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(LayoutTests), nameof(Test_Compact_layout)); + + CreateStuff(rwh.Document); + + rwh.Save(nameof(Test_Compact_layout), PdfWriterLayout.Compact); + rwh.Reload(); + rwh.Resave(PdfWriterLayout.Compact); + } + + [Fact] + public void Test_Standard_layout() + { + // TODO: US265 + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(LayoutTests), nameof(Test_Standard_layout)); + + CreateStuff(rwh.Document); + + rwh.Save(nameof(Test_Standard_layout), PdfWriterLayout.Standard); + rwh.Reload(); + rwh.Resave(PdfWriterLayout.Standard); + } + + public void Test_Indented_layout() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(LayoutTests), nameof(Test_Indented_layout)); + // TODO: US265 + } + + [Fact] + public void Test_Verbose_layout() + { + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(LayoutTests), nameof(Test_Verbose_layout)); + + CreateStuff(rwh.Document); + + rwh.Save(nameof(Test_Verbose_layout), PdfWriterLayout.Verbose); + rwh.Reload(); + rwh.Resave(PdfWriterLayout.Verbose); + } + + [Fact] + public void Test_empty_name() + { + const PdfWriterLayout layout = PdfWriterLayout.Compact; + + var rwh = new ReadWriteHelper(); + rwh.CreateDocument(typeof(LayoutTests), nameof(Test_Compact_layout)); + + var catalog = rwh.Document.Catalog; + var dict = new PdfDictionary(rwh.Document, true); + catalog.Elements.Add("/TestStuff", dict); + + var array1 = new PdfArray(); + array1.Elements.Add(new PdfName()); + array1.Elements.Add(new PdfInteger(123)); + array1.Elements.Add(new PdfName()); + array1.Elements.Add(new PdfInteger(123)); + array1.Elements.Add(new PdfName()); + array1.Elements.Add(new PdfName()); + array1.Elements.Add(new PdfName()); + + dict.Elements.Add("/DirectArray", array1); + + var dict1 = new PdfDictionary(); + dict1.Elements.Add("/1", new PdfName()); + dict1.Elements.Add("/2", new PdfInteger(123)); + dict1.Elements.Add("/3", new PdfName()); + dict1.Elements.Add("/4", new PdfInteger(123)); + dict1.Elements.Add("/5", new PdfName()); + dict1.Elements.Add("/6", new PdfName()); + + dict.Elements.Add("/DirectDictionary", dict1); + + var array2 = new PdfArray(); + array2.Elements.Add(new PdfNameObject(rwh.Document, "/")); + array2.Elements.Add(new PdfIntegerObject(rwh.Document, 123)); + array2.Elements.Add(new PdfNameObject(rwh.Document, "/")); + array2.Elements.Add(new PdfIntegerObject(rwh.Document, 123)); + array2.Elements.Add(new PdfNameObject(rwh.Document, "/")); + array2.Elements.Add(new PdfNameObject(rwh.Document, "/")); + array2.Elements.Add(new PdfNameObject(rwh.Document, "/")); + + dict.Elements.Add("/IndirectArray", array2); + + var dict2 = new PdfDictionary(); + dict2.Elements.Add("/1", new PdfNameObject(rwh.Document, "/")); + dict2.Elements.Add("/2", new PdfInteger(123)); + dict2.Elements.Add("/3", new PdfNameObject(rwh.Document, "/")); + dict2.Elements.Add("/4", new PdfInteger(123)); + dict2.Elements.Add("/5", new PdfNameObject(rwh.Document, "/")); + dict2.Elements.Add("/6", new PdfNameObject(rwh.Document, "/")); + + dict.Elements.Add("/DirectDictionary", dict2); + //var array2 = new PdfArray(Document, true); + //PopulateArray(array2); + //dict.Elements.Add("/IndirectArray", array2); + + //var dict1 = new PdfDictionary(); + //PopulateDictionary(dict1); + //dict.Elements.Add("/DirectDictionary", dict1); + + //var dict2 = new PdfDictionary(); + //PopulateDictionary(dict2); + + rwh.Save(nameof(Test_empty_name), layout); + rwh.Reload(); + rwh.Resave(layout); + } + + + void CreateStuff(PdfDocument document) + { + var catalog = document.Catalog; + var dict = new PdfDictionary(document, true); + catalog.Elements.Add("/TestStuff", dict); + + var array1 = new PdfArray(); + new ObjectModelTestHelper(document).PopulateArray(array1); + dict.Elements.Add("/DirectArray", array1); + + var array2 = new PdfArray(document, true); + new ObjectModelTestHelper(document).PopulateArray(array2); + dict.Elements.Add("/IndirectArray", array2); + + var dict1 = new PdfDictionary(); + new ObjectModelTestHelper(document).PopulateDictionary(dict1); + dict.Elements.Add("/DirectDictionary", dict1); + + var dict2 = new PdfDictionary(); + new ObjectModelTestHelper(document).PopulateDictionary(dict2); + dict.Elements.Add("/IndirectDictionary", dict2); + } + +#if true_ + void PopulateArray(PdfArray array) + { + // Primitives + array.Elements.Add(PdfNull.Value); + array.Elements.Add(new PdfBoolean(true)); + array.Elements.Add(new PdfInteger(42)); + array.Elements.Add(new PdfInteger(Int32.MinValue)); + array.Elements.Add(new PdfInteger(Int32.MaxValue)); + array.Elements.Add(new PdfLongInteger(Int64.MinValue)); + array.Elements.Add(new PdfLongInteger(Int64.MaxValue)); + array.Elements.Add(new PdfReal(Math.PI)); + array.Elements.Add(new PdfReal(Single.MinValue)); + array.Elements.Add(new PdfReal(Single.MaxValue)); + array.Elements.Add(new PdfString("Hello")); + array.Elements.Add(new PdfName("/Mambo #5")); + array.Elements.Add(new PdfDebugItem("-.99999")); + array.Elements.Add(new PdfName("/")); + + // Primitive objects + array.Elements.Add(new PdfNullObject(Document, true)); + array.Elements.Add(new PdfBooleanObject(Document, true)); + array.Elements.Add(new PdfIntegerObject(Document, 42)); + array.Elements.Add(new PdfIntegerObject(Document, Int32.MinValue)); + array.Elements.Add(new PdfIntegerObject(Document, Int32.MaxValue)); + array.Elements.Add(new PdfLongIntegerObject(Document, Int64.MinValue)); + array.Elements.Add(new PdfLongIntegerObject(Document, Int64.MaxValue)); + array.Elements.Add(new PdfRealObject(Document, Math.PI)); + array.Elements.Add(new PdfRealObject(Document, Single.MinValue)); + array.Elements.Add(new PdfRealObject(Document, Single.MaxValue)); + array.Elements.Add(new PdfStringObject(Document, "Hello")); + array.Elements.Add(new PdfNameObject(Document, "/Mambo #5")); + array.Elements.Add(new PdfDebugObject(Document, "-.99999")); + array.Elements.Add(new PdfNameObject(Document, "/")); + + // Direct and indirect array + array.Elements.Add(new TestArray1()); + array.Elements.Add(new TestArray1(Document, true)); + + // Direct and indirect dictionary + array.Elements.Add(new TestDict1()); + array.Elements.Add(new TestDict1(Document, true)); + + // Some direct array + array.Elements.Add(CreateSomeArray()); + + // Some direct dictionary + array.Elements.Add(CreateSomeDictionary()); + } + + void PopulateDictionary(PdfDictionary dict) + { + // Primitives + dict.Elements.Add("/null", PdfNull.Value); + dict.Elements.Add("/bool", new PdfBoolean(true)); + dict.Elements.Add("/int", new PdfInteger(42)); + dict.Elements.Add("/int-min", new PdfInteger(Int32.MinValue)); + dict.Elements.Add("/int-max", new PdfInteger(Int32.MaxValue)); + dict.Elements.Add("/long-min", new PdfLongInteger(Int64.MinValue)); + dict.Elements.Add("/long-max", new PdfLongInteger(Int64.MaxValue)); + dict.Elements.Add("/real", new PdfReal(Math.PI)); + dict.Elements.Add("/real-min", new PdfReal(Single.MinValue)); + dict.Elements.Add("/real-max", new PdfReal(Single.MaxValue)); + dict.Elements.Add("/string", new PdfString("Hello")); + dict.Elements.Add("/name", new PdfName("/Mambo #5")); + dict.Elements.Add("/-.999", new PdfDebugItem("-.99999")); + dict.Elements.Add("/", new PdfName("/")); + + + // Primitive objects + dict.Elements.Add("/null-obj", new PdfNullObject(Document, true)); + dict.Elements.Add("/bool-obj", new PdfBooleanObject(Document, true)); + dict.Elements.Add("/int-obj", new PdfIntegerObject(Document, 42)); + dict.Elements.Add("/int-min-obj", new PdfIntegerObject(Document, Int32.MinValue)); + dict.Elements.Add("/int-max-obj", new PdfIntegerObject(Document, Int32.MaxValue)); + dict.Elements.Add("/long-min-obj", new PdfLongIntegerObject(Document, Int64.MinValue)); + dict.Elements.Add("/long-max-obj", new PdfLongIntegerObject(Document, Int64.MaxValue)); + dict.Elements.Add("/real-obj", new PdfRealObject(Document, Math.PI)); + dict.Elements.Add("/real-min-obj", new PdfRealObject(Document, Single.MinValue)); + dict.Elements.Add("/real-max-obj", new PdfRealObject(Document, Single.MaxValue)); + dict.Elements.Add("/string-obj", new PdfStringObject(Document, "Hello")); + dict.Elements.Add("/name-obj", new PdfNameObject(Document, "/Mambo #5")); + dict.Elements.Add("/-.999", new PdfDebugObject(Document, "-.99999")); + dict.Elements.Add("/", new PdfNameObject("/")); + + // Direct and indirect array + dict.Elements.Add("/array-direct", new TestArray1()); + dict.Elements.Add("/array-indirect", new TestArray1(Document, true)); + + // Direct and indirect dictionary + dict.Elements.Add("/dict1-direct", new TestDict1()); + dict.Elements.Add("/dict1-indirect", new TestDict1(Document, true)); + + // Some direct array + dict.Elements.Add("/some-array", CreateSomeArray()); + + // Some direct dictionary + dict.Elements.Add("/some-dictionary", CreateSomeDictionary()); + } + + PdfArray CreateSomeArray() + { + var array = new PdfArray(); + array.Elements.Add(new PdfInteger(123)); + array.Elements.Add(PdfNull.Value); + array.Elements.Add(new PdfBoolean(true)); + array.Elements.Add(new PdfBoolean(false)); + array.Elements.Add(new PdfInteger(42)); + array.Elements.Add(new PdfLiteral("123456 0 R")); + + return array; + } + + PdfDictionary CreateSomeDictionary() + { + var dict = new PdfDictionary(); + dict.Elements.Add("/null", PdfNull.Value); + dict.Elements.Add("/true", new PdfBoolean(true)); + dict.Elements.Add("/false", new PdfBoolean(false)); + dict.Elements.Add("/42", new PdfInteger(42)); + dict.Elements.Add("/invalid-iref", new PdfLiteral("123456 0 R")); + + return dict; + } +#endif + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ObjectModelTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ObjectModelTests.cs new file mode 100644 index 00000000..97bd9e74 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/ObjectModelTests.cs @@ -0,0 +1,295 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.PdfItemExtensions; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; +using FluentAssertions; + +//using static PdfSharp.Diagnostics.DebugBreakHelper; +using PdfSharp.Pdf.Internal; + +#pragma warning disable CS8321 // Local function is declared but never used + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public partial class ObjectModelTests + { + [Fact] + public void TestTest() + { + var doc1 = CreateDocument(); + var catalog = doc1.Internals.Catalog; + + var dict3 = new TestDict3(doc1); + dict3.Comment = "Dict3"; + doc1.Internals.AddObject(dict3); + catalog.Elements.Add("/TestStuff3", dict3); + + // Save and reload. + var path = Save(doc1, nameof(TestTest) + "1"); + var doc2 = Load(path); + var cat2 = doc2.Internals.Catalog; + + var dict32Ref = (PdfReference?)cat2.Elements["/TestStuff3"]; + + //var abc = PdfObjectsHelper.TransformDictionary((PdfDictionary)(dict32Ref!.Value)); + + //var x = dict32Ref.Value.AsDictionary().Elements[TestDict3.Keys.TestDict1]; + //var xt = x.GetType(); + var y = dict32Ref!.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1); + var z = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1Ref); + var z2 = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1Ref); + + //var xxx = dict32.Elements.GetItemNew( + // TestDict3.Keys.TestDict1, ObjMagic.Default, typeof(TestDict1)); + + var path2 = Save(doc2, nameof(TestTest) + "2"); + } + + [Fact] + public void Null_Objects_Test() + { + var doc1 = CreateDocument(); + var cat1 = doc1.Internals.Catalog; + + cat1.Elements["/NullItem"] = PdfNull.Value; + + //var nullObject1 = new PdfNullObject(); + //cat1.Elements["/NullDirectObject"] = nullObject1; + + var nullObject2 = new PdfNullObject(); + doc1.Internals.AddObject(nullObject2); + cat1.Elements["/NullIndirectObject"] = nullObject2.RequiredReference; + + cat1.Elements["/undefRef"] = new PdfDebugItem(" 42000 0 R"); + + CheckParentInfoConsistency(cat1); + + // Save and reload. + var path = Save(doc1, nameof(Null_Objects_Test) + "1"); + //ShouldBreak2 = true; + var doc2 = Load(path); + //ShouldBreak2 = false; + var cat2 = doc2.Internals.Catalog; + + cat2.Elements["/NullItem"].Should().Be(PdfNull.Value); + + //// PDFsharp reads 'null' as PdfNull, not as PdfNullObject. + //cat2.Elements["/NullDirectObject"]!.GetType().Should().Be(typeof(PdfNull)); + + // PdfNullObject is only read when it is an indirect object. + cat2.Elements["/NullIndirectObject"]!.GetType().Should().Be(typeof(PdfReference)); + cat2.Elements["/NullIndirectObject"].AsReference().Value.GetType().Should().Be(typeof(PdfNullObject)); + + // An undefined reference is read as null object. + cat2.Elements["/undefRef"]!.Should().Be(PdfNull.Value); + + var pages1a = cat2.Elements["/Pages"].AsReference().Value.AsDictionary(); + //var pages1b = cat2.GetItem("/Pages").AsReference().Value.AsDictionary(); + //var pages1c = cat2.GetItem("/Pages").AsDictionary(); + //ReferenceEquals(pages1a, pages1b).Should().BeTrue(); + //ReferenceEquals(pages1a, pages1c).Should().BeTrue(); + var pages2 = cat2.Elements["/Pages"].AsReference().AsDictionary(); + var pages3 = cat2.Elements["/Pages"].AsDictionary(); + var k = pages3.Elements["/Kids"]; + var kids = pages3.Elements["/Kids"].AsArray(); + kids.IsIndirect.Should().BeFalse(); + kids.ParentInfo.Should().NotBeNull(); + kids.IsDead.Should().BeFalse(); + + //ShouldBreak1 = false; + + var path2 = Save(doc2, nameof(Null_Objects_Test) + "2"); + } + + [Fact] + public void Throw_on_used_object_Test() + { + var doc1 = CreateDocument(); + var cat1 = doc1.Internals.Catalog; + + var dict1 = new TestDict1(); + + cat1.Elements["/DirectObject"] = dict1; + + // Must not add direct object more than once. + var addTwice = () => cat1.Elements["/DirectObjectTwice"] = dict1; + addTwice.Should().Throw(); + + // Must not convert used direct object to indirect object. + var makeIndirect = () => doc1.Internals.AddObject(dict1); + makeIndirect.Should().Throw(); + + // Save and reload. + var path = Save(doc1, nameof(Throw_on_used_object_Test) + "1"); + var doc2 = Load(path); + var cat2 = doc2.Internals.Catalog; + + var dict21 = cat2.Elements["/DirectObject"]; + var dict22 = cat2.Elements.GetValue("/DirectObject"); + + + var path2 = Save(doc2, nameof(Throw_on_used_object_Test) + "2"); + + + + // Make used direct object indirect. + + // Set indirect object as direct one. + + // Ensure alive test + + // 2 reference to same object. + } + + [Fact] + public void Throw_on_dead_object_Test() + { + var doc1 = CreateDocument(); + var cat1 = doc1.Internals.Catalog; + + var dict3 = new TestDict3(doc1); + doc1.Internals.AddObject(dict3); + cat1.Elements["/IndirectObject3"] = dict3; + + // Save and reload. + var path = Save(doc1, nameof(Throw_on_dead_object_Test) + "1"); + var doc2 = Load(path); + var cat2 = doc2.Internals.Catalog; + + var dict32 = cat2.Elements["/IndirectObject3"]; + + var b1 = ((PdfReference)dict32!).AsDictionary(); + //var b2 = PdfObjectsHelper.TransformDictionary(b1); + //var abc = PdfObjectsHelper.TransformDictionary(((PdfReference)dict32!).AsDictionary()); + + //var a1 = abc.Elements[TestDict3.Keys.TestDict1Ref]; + //var a2 = a1.AsReference(); + //var a3 = a2.Value; + + //var dict1Base = abc.Elements[TestDict3.Keys.TestDict1Ref].AsReference().Value; + //dict1Base.IsDead.Should().BeFalse(); + //var dict1Der = abc.Elements.GetValue(TestDict3.Keys.TestDict1Ref); + //dict1Base.IsDead.Should().BeTrue(); + + //var d1 = dict1Base.IsDead; + ////var d2 = dict1Base.IsDead2; + ////var d3 = dict1Base.IsDead3; + //// var elements = dict1Base.As().Elements; + + //var test1 = () => _ = dict1Base.AsDictionary().Elements; + //test1.Should().Throw(); + + //var addDeadObject = () => cat2.Elements.Add("/TestDeadObject", dict1Base); + //addDeadObject.Should().Throw(); + + ////var reference = new PdfReference(cat2, PdfObjectID.Empty, 42); + + //var path2 = Save(doc2, nameof(Throw_on_used_object_Test) + "2"); + } + + [Fact] + public void Throw_on_direct_non_containers_Test() + { + var doc = CreateDocument(); + var cat = doc.Internals.Catalog; + + var addDirectObject = () => cat.Elements.Add("/test", new PdfIntegerObject(42)); + addDirectObject.Should().Throw(); + + var path2 = Save(doc, nameof(Throw_on_direct_non_containers_Test) + "2"); + } + + [Fact] + public void Basic_PdfDictionary_Test() + { + var doc = CreateDocument(); + var catalog = doc.Internals.Catalog; + + var dict1 = new TestDict1(); + catalog.Elements.Add("/TestStuff1", dict1); + //catalog.Elements.Add("/TestStuff1a", dict1); now fails + var addDirectObjectTwice = () => catalog.Elements.Add("/TestStuff1a", dict1); + addDirectObjectTwice.Should().Throw(); + + dict1.Elements.Add("/dummy", new PdfString("Hello")); + + var someInt = new PdfInteger(42); + catalog.Elements.Add("/SomeInt1", someInt); + catalog.Elements.Add("/SomeInt1a", someInt); + + + var dict2 = new TestDict2(); + doc.Internals.AddObject(dict2); + catalog.Elements.Add("/TestStuff2", dict2); + + var dict3 = new TestDict3(doc); + doc.Internals.AddObject(dict3); + catalog.Elements.Add("/TestStuff3", dict3); + + var path = Save(doc, nameof(Basic_PdfDictionary_Test)); + var doc2 = Load(path); + + + var cat2 = doc2.Internals.Catalog; + + var dict32Ref = (PdfReference?)cat2.Elements["/TestStuff3"]; + //var dict32 = (PdfDictionary?)cat2.Elements["/TestStuff3"]; + //var dict32 = PdfReference.Dereference(dict32Ref!); + var dict32 = dict32Ref.AsDictionary(); + + var dict1Dir = dict32.Elements[TestDict3.Keys.TestDict1]; + var dict1Ind = dict32.Elements[TestDict3.Keys.TestDict1Ref]; + //var dict1Ind2 = dict32.Elements[TestDict3.Keys.TestDict1Ref, ObjMagic.GetObject]; + } + + [Fact] + public void Basic_PdfArray_Test() + { + var doc = CreateDocument(); + var catalog = doc.Internals.Catalog; + + var dict1 = new TestDict1(); + var dict2 = new TestDict1(); + var array1 = new PdfArray(); + catalog.Elements.Add("/Array1", array1); + array1.Elements.Add(dict1); + array1.Elements.Add(dict2); + + dict1 = new TestDict1(); + dict2 = new TestDict1(); + var array2 = new PdfArray(); + doc.Internals.AddObject(array2); + catalog.Elements.Add("/Array2", array2); + array2.Elements.Add(dict1); + array2.Elements.Add(dict2); + + var path = Save(doc, nameof(Basic_PdfArray_Test)); + var doc2 = Load(path); + } + + //[Fact] moved + public void Reuse_of_direct_object_Test() + { + //var item = new PdfStringObject("abc", PdfStringEncoding.Unicode); + var item = new PdfDictionary(); + + var useDirectObjectTwice = () => + { + var dict = new TestDict1 + { + Elements = + { + ["Key1"] = item, + ["Key2"] = item + } + }; + }; + useDirectObjectTwice.Should().Throw(); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/RealNumberTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/RealNumberTests.cs new file mode 100644 index 00000000..01a2605c --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/RealNumberTests.cs @@ -0,0 +1,117 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Globalization; +using FluentAssertions; +using PdfSharp.Pdf; +using Xunit; + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + [Collection("PDFsharp")] + public class RealNumberTests : ObjectModelTestsBase + { + [Fact] + public void Leading_zeros() + { + // We had a bug in the format string of PageDestinations. + const string format = "#.#;-#.#;0"; + + var x1 = 0.ToString(format, CultureInfo.InvariantCulture); + x1.Should().Be("0"); + + var x2 = 0.2.ToString(format, CultureInfo.InvariantCulture); + x2.Should().Be(".2"); + + var x3 = 123.0.ToString(format, CultureInfo.InvariantCulture); + x3.Should().Be("123"); + + var x4 = (-.445).ToString(format, CultureInfo.InvariantCulture); + x4.Should().Be("-.4"); + } + + [Fact] + public void Test_Single_Write_and_Read() + { + /* + Setting a PdfReal to Single.MaxValue / Single.MinValue could not be parsed and a unit test failed. + Here is why. + + Single.MaxValue in IEEE 754 binary format 32 bit: + 0b_0_11111110_11111111111111111111111 (0x7F7FFFFF) + Sign (1 bit) : 0 -> positive + Exponent (8 bits) : 254 -> 2^127 (254 - 127 = 127) + Mantissa (23 bits) : (1.)11111111111111111111111 base 2, '1.' is implicitly defined. + + Formally this calculates to + 340282346638528859811704183484516925440 (39 decimal places) + ^- gibberish starts approximately here because + only the first ~7 decimal places are significant + + Now lets see what is this in Double. + Single.MaxValue in IEEE 754 binary format expanded to 64 bit: + 0b_0_10001111110_1111111111111111111111100000000000000000000000000000 + Sign (1 bit) : 0 -> positive + Exponent (11 bits) : 1150 -> 2^127 (1150 - 1023 = 127) + Mantissa (52 bits) : (1.)1111111111111111111111100000000000000000000000000000 base 2, '1.' is implicitly defined. + + This value formally also calculates to + 340282346638528859811704183484516925440 (39 decimal places) + ^- gibberish starts approximately here because + now the first ~15 decimal places are significant. + This additional 'virtual' precision comes from adding the 29 binary zeros to the mantissa. + It multiplies the Single value with 2^29 and makes the gibberish part of the value. + + This explains why Single.MaxValue is larger in Double when formatted as string: + + Single.MaxValue.ToString("#"); // is "340282300000000000000000000000000000000" + ((double)Single.MaxValue).ToString("#"); // is "340282346638529000000000000000000000000" + + I know the IEEE 754 number format since I programmed a Tektronix 4051 desktop computer + in the early 80s in 6800 assembler, but this result perplexed me. + + For nerds: Play around with IEEE 754 number: https://numeral-systems.com/ieee-754-converter/ + + Solution for PDFsharp + + PDFsharp stores floating point values as Double, but writes them to PDF formatted as Single. + */ +#if true_ + var s0 = PdfReal.ToPdfLiteral(Math.PI, 0, false); + var s8 = PdfReal.ToPdfLiteral(Math.PI, 8, false); + var s12 = PdfReal.ToPdfLiteral(Math.PI, 34, false); +#endif + + //long longMax = Int64.MaxValue; + //long longSingleMax = (unchecked((long)Single.MaxValue)); + + float fMax = Single.MaxValue; + string s1 = fMax.ToString("#"); + string s2 = ((double)fMax).ToString("#"); + string sx = ((double)fMax).ToString("#"); + string s3 = ((double)Single.MaxValue).ToString("#"); + //string s4 = ((decimal)fMax).ToString("#"); + + // From .NET source code. + // public const float MaxValue = 3.40282347E+38; // .NET Framework + // public const float MaxValue = (float)3.40282346638528859e+38; // .NET 6/8 + + // 340282346638528859811704183484516925440 + string max1 = Single.MaxValue.ToString("#"); // is "340282300000000000000000000000000000000" + string max2 = ((double)Single.MaxValue).ToString("#"); // is "340282346638529000000000000000000000000" + string max3 = ((float)(double)Single.MaxValue).ToString("#"); // is "340282346638529000000000000000000000000" + string max4 = ((float)Double.MaxValue).ToString("#"); // is "340282346638529000000000000000000000000" + + var parsed1 = Single.Parse(((double)Single.MaxValue).ToString("#")); + var parsed2 = Double.Parse(((double)Single.MaxValue).ToString("#")); + +#if NET8_0_OR_GREATER + // Throws in .NET Framework + var parsed3 = Single.Parse("3402823466385288598117041834845169259990"); +#endif + var parsed4 = Double.Parse(((double)Single.MaxValue).ToString("#")); + var xxxx = new PdfReal(Single.MaxValue); + var yyyy = new PdfReal(Single.MinValue); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModel.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModel.cs new file mode 100644 index 00000000..5053a8e9 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModel.cs @@ -0,0 +1,367 @@ +// DELETE (moved) +//// PDFsharp - A .NET library for processing PDF +//// See the LICENSE file in the solution root for more information. + +//using PdfSharp.Pdf; + +//namespace PdfSharp.Tests.Pdf.ObjectModel_ +//{ +// public class TestDict1 : PdfDictionary +// { +// public TestDict1() +// { +// Elements.Add(Keys.IAmA, new PdfName('/' + nameof(TestDict1))); +// } + +// public TestDict1(PdfDocument doc, bool createIndirect) : base(doc, createIndirect) +// { } + +// protected TestDict1(PdfDictionary dict) : base(dict) +// { } + +// public T? Foo(int x) => default(T); + +// public sealed class Keys : KeysBase +// { +// // ReSharper disable InconsistentNaming + +// [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict1))] +// public const string IAmA = "/I_am_a"; + +// /// +// /// Gets the KeysMeta for these keys. +// /// +// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); +// static DictionaryMeta? _meta; + +// // ReSharper restore InconsistentNaming +// } + +// /// +// /// Gets the KeysMeta of this dictionary type. +// /// +// internal override DictionaryMeta Meta => Keys.Meta; +// } + +// public class TestDict2 : PdfDictionary +// { +// public TestDict2() +// { +// Elements.Add("/I_am_a", new PdfName('/' + nameof(TestDict2))); +// } + +// /// +// /// Initializes a new instance of this class using the elements of the specified dictionary. +// /// After this type transformation the specified dictionary is dead and cannot be used anymore. +// /// +// internal TestDict2(PdfDictionary dict) : base(dict) +// { } + +// public sealed class Keys : KeysBase +// { +// // ReSharper disable InconsistentNaming + +// /// +// /// +// [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Outlines")] +// public const string Type = "/Type"; + +// /// +// /// Gets the KeysMeta for these keys. +// /// +// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); +// static DictionaryMeta? _meta; + +// // ReSharper restore InconsistentNaming +// } + +// /// +// /// Gets the KeysMeta of this dictionary type. +// /// +// internal override DictionaryMeta Meta => Keys.Meta; +// } + +// public class TestDict3 : PdfDictionary +// { +// public TestDict3(PdfDocument doc) : base(doc) +// { +// Elements.Add(Keys.IAmA, new PdfName('/' + nameof(TestDict3))); + +// var dict1 = new TestDict1(); +// Elements.Add(Keys.TestDict1, dict1); + +// var dict2 = new TestDict1(); +// doc.Internals.AddObject(dict2); +// Elements.Add(Keys.TestDict1Ref, dict2); +// } + +// TestDict3(PdfDictionary dict) : base(dict) +// { } + +// public sealed class Keys : KeysBase +// { +// // ReSharper disable InconsistentNaming + +// [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict3))] +// public const string IAmA = "/I_am_a"; + +// [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] +// public const string TestDict1 = "/TestDict1"; + +// [KeyInfo(KeyType.String | KeyType.Optional, typeof(TestDict1))] +// public const string TestDict1Ref = "/TestDict1Ref"; + +// /// +// /// Gets the KeysMeta for these keys. +// /// +// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); +// static DictionaryMeta? _meta; + +// // ReSharper restore InconsistentNaming +// } + +// /// +// /// Gets the KeysMeta of this dictionary type. +// /// +// internal override DictionaryMeta Meta => Keys.Meta; +// } + +// public class TestBaseDict : PdfDictionary +// { +// public TestBaseDict(PdfDocument doc) : base(doc) +// { +// Elements.Add(Keys.IAmA, new PdfName('/' + nameof(TestDict3))); + +// var dict1 = new TestDict1(); +// Elements.Add(Keys.TestDict1, dict1); + +// var dict2 = new TestDict1(); +// doc.Internals.AddObject(dict2); +// Elements.Add(Keys.TestDict1Ref, dict2); +// } + +// protected TestBaseDict(PdfDictionary dict) : base(dict) +// { } + +// public /*sealed*/ class Keys : KeysBase +// { +// // ReSharper disable InconsistentNaming + +// [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict3))] +// public const string IAmA = "/I_am_a"; + +// [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] +// public const string TestDict1 = "/TestDict1"; + +// [KeyInfo(KeyType.String | KeyType.Optional, typeof(TestDict1))] +// public const string TestDict1Ref = "/TestDict1Ref"; + +// /// +// /// Gets the KeysMeta for these keys. +// /// +// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); +// static DictionaryMeta? _meta; + +// // ReSharper restore InconsistentNaming +// } + +// /// +// /// Gets the KeysMeta of this dictionary type. +// /// +// internal override DictionaryMeta Meta => Keys.Meta; +// } + +// public class TestDerivedDict : TestBaseDict +// { +// public TestDerivedDict(PdfDocument doc) : base(doc) +// { +// Elements.Add(Keys.IAmA2, new PdfName('/' + nameof(TestDict3))); + +// var dict1 = new TestDict1(); +// Elements.Add(Keys.TestDict12, dict1); +// //Elements.Add(TestBaseDict.Keys.TestDict1, dict1); + +// var dict2 = new TestDict1(); +// doc.Internals.AddObject(dict2); +// Elements.Add(Keys.TestDict1Ref2, dict2); +// } + +// TestDerivedDict(PdfDictionary dict) : base(dict) +// { } + +// public new sealed class Keys : TestBaseDict.Keys +// { +// // ReSharper disable InconsistentNaming + +// [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict3))] +// public const string IAmA2 = "/I_am_a2"; + +// [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] +// public const string TestDict12 = "/TestDict12"; + +// [KeyInfo(KeyType.String | KeyType.Optional, typeof(TestDict1))] +// public const string TestDict1Ref2 = "/TestDict1Ref2"; + +// /// +// /// Gets the KeysMeta for these keys. +// /// +// internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); +// static DictionaryMeta? _meta; + +// // ReSharper restore InconsistentNaming +// } + +// /// +// /// Gets the KeysMeta of this dictionary type. +// /// +// internal override DictionaryMeta Meta => Keys.Meta; +// } + +// public class ApiTestDict1 : PdfDictionary +// { +// public ApiTestDict1() +// { +// Elements.Add(Keys.IAmA, new PdfName('/' + nameof(ApiTestDict1))); +// } + +// protected ApiTestDict1(PdfDocument doc, bool createIndirect) : base(doc, createIndirect) +// { } + +// protected ApiTestDict1(PdfDictionary dict) : base(dict) +// { } + +// public sealed class Keys : KeysBase +// { +// // ReSharper disable InconsistentNaming + +// [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict1))] +// public const string IAmA = "/I_am_a"; + +// [KeyInfo(KeyType.Boolean | KeyType.Optional)] +// public const string SomeBoolean = "/SomeBoolean"; + +// [KeyInfo(KeyType.Integer | KeyType.Optional)] +// public const string SomeInteger = "/SomeInteger"; + +// // TODO: double, long int, unsigned int?, + +// [KeyInfo(KeyType.String | KeyType.Optional)] +// public const string SomeString = "/SomeString"; + +// // TODO: XMatrix, etc. + +// [KeyInfo(KeyType.Name | KeyType.Optional)] +// public const string SomeName = "/SomeName"; + +// [KeyInfo(KeyType.Array | KeyType.Optional, typeof(TestArray1))] +// public const string SomeDirectArray = "/SomeDirectDict"; + +// [KeyInfo(KeyType.Array | KeyType.Optional, typeof(TestArray1))] +// public const string SomeIndirectArray = "/SomeIndirectDict "; + +// [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] +// public const string SomeDirectDict = "/SomeDirectDict"; + +// [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] +// public const string SomeIndirectDict = "/SomeIndirectDict"; + +// /// +// /// Gets the KeysMeta for these keys. +// /// +// internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); +// static DictionaryMeta? _meta; + +// // ReSharper restore InconsistentNaming +// } + +// /// +// /// Gets the KeysMeta of this dictionary type. +// /// +// internal override DictionaryMeta Meta => Keys.Meta; +// } + +// public class TestArray1 : PdfArray +// { +// public TestArray1() +// { } + +// public TestArray1(PdfDocument doc, bool createIndirect) : base(doc, createIndirect) +// { } + +// } + +// public class TestBaseArray : PdfArray +// { + +// } + +// public class TestDerivedArray : TestBaseArray +// { +// } + +// /// +// /// Test class for class ArrayElementsTests. +// /// +// public class TestArrayElements : PdfArray +// { +// public enum Index +// { +// TestArray1, +// TestDict1, +// Integer, +// IntegerObject +// } + +// public TestArrayElements() +// { +// Init(); +// } + +// public TestArrayElements(PdfDocument document, bool createIndirect = false) +// : base(document, createIndirect) +// { +// Init(); +// } + +// void Init() +// { +// Elements.Add(new TestArray1()); +// Elements.Add(new TestDict1()); +// Elements.Add(new PdfInteger(-42)); + +// if (IsIndirect) +// { + +// } +// } +// } + +// /// +// /// Test class for class DictionaryElementsTests. +// /// +// public class TestDictionaryElements : PdfDictionary +// { +// public TestDictionaryElements() +// { +// Init(); +// } + +// public TestDictionaryElements(PdfDocument document, bool createIndirect = false) +// : base(document, createIndirect) +// { +// Init(); +// } + +// void Init() +// { +// Elements.Add("/TestArray1", new TestArray1()); +// Elements.Add("/TestDict1", new TestDict1()); +// Elements.Add("/Integer", new PdfInteger(-42)); + +// if (IsIndirect) +// { + +// } +// } +// } +//} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelArrayTestsBase.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelArrayTestsBase.cs new file mode 100644 index 00000000..05b922b3 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelArrayTestsBase.cs @@ -0,0 +1,25 @@ +//// PDFsharp - A .NET library for processing PDF +//// See the LICENSE file in the solution root for more information. + +//using System.Diagnostics; +//using Xunit; +//using FluentAssertions; +//using PdfSharp.Pdf; +//using PdfSharp.Pdf.Advanced; +//using PdfSharp.Pdf.IO; +//using PdfSharp.Pdf.PdfItemExtensions; +//using PdfSharp.Pdf.PdfArrayExtensions; +//using PdfSharp.Pdf.PdfDictionaryExtensions; +//using PdfSharp.Tests.PdfObjectModel; +//using PdfSharp.Quality; +//using System.Linq; +//using PdfSharp.Pdf.Internal; + +//#pragma warning disable CS8321 // Local function is declared but never used + +//namespace PdfSharp.Tests.Pdf.ObjectModel +//{ +// public class ObjectModelArrayTestsBase : ObjectModelTestsBase +// { +// } +//} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelDictionaryTestsBase.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelDictionaryTestsBase.cs new file mode 100644 index 00000000..584583ff --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelDictionaryTestsBase.cs @@ -0,0 +1,23 @@ +//// PDFsharp - A .NET library for processing PDF +//// See the LICENSE file in the solution root for more information. + +//using System.Diagnostics; +//using Xunit; +//using FluentAssertions; +//using PdfSharp.Pdf; +//using PdfSharp.Pdf.Advanced; +//using PdfSharp.Pdf.Internal; +//using PdfSharp.Pdf.IO; +//using PdfSharp.Pdf.PdfItemExtensions; +//using PdfSharp.Pdf.PdfArrayExtensions; +//using PdfSharp.Pdf.PdfDictionaryExtensions; +//using PdfSharp.Tests.PdfObjectModel; +//using PdfSharp.Quality; + +//#pragma warning disable CS8321 // Local function is declared but never used + +//namespace PdfSharp.Tests.Pdf.ObjectModel +//{ +// public class ObjectModelDictionaryTestsBase : ObjectModelTestsBase +// { } +//} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelTests-Helper.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelTests-Helper.cs new file mode 100644 index 00000000..4360ed17 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelTests-Helper.cs @@ -0,0 +1,101 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Diagnostics; +using PdfSharp.Pdf; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using Xunit; +using FluentAssertions; + +#pragma warning disable CS8321 // Local function is declared but never used + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + public partial class ObjectModelTests + { + string Save(PdfDocument doc, string name) + { + string filename = PdfFileUtility.GetTempPdfFullFileName("Pdf-ObjectModel/" + name); + doc.Save(filename); + return filename; + } + + PdfDocument Load(string path) + { + var document = PdfReader.Open(path, PdfDocumentOpenMode.Modify); + return document; + } + + PdfDocument CreateDocument() + { + var doc = new PdfDocument(); + var page = new PdfPage(); + doc.AddPage(page); + return doc; + } + + // TODO + static void CheckParentInfoConsistency(PdfObject root) + { + Debug.Assert(root.IsIndirect); + var owner = root.Owner; + var closure = owner.IrefTable.TransitiveClosure(root); + + foreach (var reference in closure) + { + var obj = reference.Value; + } + return; + + // TODO + static void CheckArray(PdfArray array) + { + foreach (var item in array.Elements) + { + //var item = elements[name]; + if (item is PdfObject obj) + { + if (obj.Reference != null) + { + if (obj.ParentInfo != null) + throw new InvalidOperationException("ParentInfo must be null."); + } + else + { + if (obj.ParentInfo == null) + throw new InvalidOperationException("ParentInfo must not be null."); + + if (obj.ParentInfo.OwningArray != array) + throw new InvalidOperationException("ParentInfo must be owning array."); + } + } + } + } + + // TODO + static void CheckDictionary(PdfDictionary dict) + { + foreach (var item in dict.Elements.Values) + { + if (item is PdfObject obj) + { + if (obj.Reference != null) + { + if (obj.ParentInfo != null) + throw new InvalidOperationException("ParentInfo must be null."); + } + else + { + if (obj.ParentInfo == null) + throw new InvalidOperationException("ParentInfo must not be null."); + + if (obj.ParentInfo.OwningDictionary != dict) + throw new InvalidOperationException("ParentInfo must be owning dictionary."); + } + } + } + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelTestsBase.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelTestsBase.cs new file mode 100644 index 00000000..402aa6d8 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.ObjectModel/model/ObjectModelTestsBase.cs @@ -0,0 +1,346 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Internal; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using PdfSharp.Quality.Testing; +using PdfSharp.Quality.Testing.TestModel; +using Xunit; +using FluentAssertions; + +#pragma warning disable CS8321 // Local function is declared but never used + +namespace PdfSharp.Tests.Pdf.ObjectModel +{ + public class ObjectModelTestsBase : PdfSharpTestBase + { +#if true_ + protected ObjectModelTestsBase() + { + _document = null!; + _reloadedDocument = null!; + _testClass = null!; + _filename = null!; + _fullFilename = null!; + _fullFilename2 = null!; + _page = null!; + } + + protected PdfDocument CreateDocument(Type testClass, string name) + { + _testClass = testClass; + _document = PdfDocUtility.CreateNewPdfDocument(name); + _document.Info.Title = name; + // Add empty page to make it savable. + _page = _document.AddPage(); + + return _document; + } + + protected PdfDocument ImportDocument(string fileName, string passWord, PdfDocumentOpenMode mode = PdfDocumentOpenMode.Modify) + { + var name = Path.GetFileName(fileName); + _document = PdfReader.Open(fileName, passWord, mode); + + return _document; + } + + protected string Save(string? filename = null, PdfWriterLayout layout = PdfWriterLayout.Compact) + { + _filename = filename ?? Document.Info.Title; + //_fullFilename = PdfFileUtility.GetTempPdfFullFileName("Pdf-ObjectModel/" + filename); + _fullFilename = PdfFileUtility.GetTempPdfFullFileName(PdfFileUtility.GetUnitTestPath(_testClass) + _filename); + Document.Options.Layout = layout; + Document.Save(_fullFilename); + return _fullFilename; + } + + protected PdfDocument Reload() + { + _reloadedDocument = PdfReader.Open(_fullFilename, PdfDocumentOpenMode.Modify); + return _reloadedDocument; + } + + protected string Resave(PdfWriterLayout layout = PdfWriterLayout.Compact) + { + var filename = _filename + "_#2"; + //_fullFilename2 = PdfFileUtility.GetTempPdfFullFileName("Pdf-ObjectModel/" + filename); + _fullFilename2 = PdfFileUtility.GetTempPdfFullFileName(PdfFileUtility.GetUnitTestPath(_testClass) + _filename); + + _reloadedDocument.Options.Layout = layout; + _reloadedDocument.Save(_fullFilename2); + return _fullFilename2; + } + + PdfDocument Load(string path) + { + var document = PdfReader.Open(path, PdfDocumentOpenMode.Modify); + return document; + } + + PdfDocument CreateDocument() + { + var doc = new PdfDocument(); + var page = new PdfPage(); + doc.AddPage(page); + return doc; + } + + protected void SetDocument(PdfDocument doc) + { + _document = doc; + } + + protected void SetTestClass(Type testClass) + { + _testClass = testClass; + } + + protected PdfDocument Document => _document; + + protected PdfDocument ReloadedDocument => _reloadedDocument; + + protected PdfCatalog Catalog => _document.Internals.Catalog; + + protected PdfPage Page => _page; + + PdfDocument _document; + PdfDocument _reloadedDocument; + Type _testClass; + string _filename; + string _fullFilename; + string _fullFilename2; + PdfPage _page; + + // TODO + static void CheckParentInfoConsistency(PdfObject root) + { + Debug.Assert(root.IsIndirect); + var owner = root.Owner; + var closure = owner.IrefTable.TransitiveClosure(root); + + + foreach (var reference in closure) + { + var obj = reference.Value; + } + return; + + // TODO + static void CheckArray(PdfArray array) + { + foreach (var item in array.Elements) + { + //var item = elements[name]; + if (item is PdfObject obj) + { + if (obj.Reference != null) + { + if (obj.ParentInfo != null) + throw new InvalidOperationException("ParentInfo must be null."); + } + else + { + if (obj.ParentInfo == null) + throw new InvalidOperationException("ParentInfo must not be null."); + + if (obj.ParentInfo.OwningArray != array) + throw new InvalidOperationException("ParentInfo must be owning array."); + } + } + } + } + + // TODO + static void CheckDictionary(PdfDictionary dict) + { + foreach (var item in dict.Elements.Values) + { + if (item is PdfObject obj) + { + if (obj.Reference != null) + { + if (obj.ParentInfo != null) + throw new InvalidOperationException("ParentInfo must be null."); + } + else + { + if (obj.ParentInfo == null) + throw new InvalidOperationException("ParentInfo must not be null."); + + if (obj.ParentInfo.OwningDictionary != dict) + throw new InvalidOperationException("ParentInfo must be owning dictionary."); + } + } + } + } + } +#endif + +#if true_ + protected void PopulateDictionary(PdfDictionary dict) + { + // Primitives + dict.Elements.Add("/null", PdfNull.Value); + dict.Elements.Add("/array", new PdfArray(Document, new PdfReal(5), new PdfReal(6), new PdfReal(7), new PdfReal(8))); // For GetRectangle test. + dict.Elements.Add("/bool", new PdfBoolean(true)); + dict.Elements.Add("/int", new PdfInteger(42)); + dict.Elements.Add("/int-min", new PdfInteger(Int32.MinValue)); + dict.Elements.Add("/int-max", new PdfInteger(Int32.MaxValue)); + dict.Elements.Add("/long-min", new PdfLongInteger(Int64.MinValue)); + dict.Elements.Add("/long-max", new PdfLongInteger(Int64.MaxValue)); + dict.Elements.Add("/real", new PdfReal(Math.PI)); + dict.Elements.Add("/real-min", new PdfReal(Single.MinValue)); + dict.Elements.Add("/real-max", new PdfReal(Single.MaxValue)); + dict.Elements.Add("/string", new PdfString("Hello")); + dict.Elements.Add("/string-hex", new PdfString("HelloHex", true)); + dict.Elements.Add("/string-date1", new PdfString("D:19991231235959")); + dict.Elements.Add("/string-date2", new PdfString("D:19991231235959+02'00'")); + dict.Elements.Add("/string-date3", new PdfString("D:19991231235959-03'00'")); + dict.Elements.Add("/name", new PdfName("/Mambo #5")); + dict.Elements.Add("/rect", new PdfRectangle(new XRect(1, 2, 3, 4))); + dict.Elements.Add("/-.999", new PdfDebugItem("-.99999")); + dict.Elements.Add("/", new PdfNameObject(Document, "/")); + dict.Elements.Add("/int-default", new PdfInteger()); + dict.Elements.Add("/date1", new PdfDate(new DateTimeOffset(1999, 12, 31, 23, 59, 59, TimeSpan.Zero))); + dict.Elements.Add("/date2", new PdfDate(new DateTimeOffset(1999, 12, 31, 23, 59, 59, new TimeSpan(4, 0, 0)))); + dict.Elements.Add("/date3", new PdfDate(new DateTimeOffset(1999, 12, 31, 23, 59, 59, -new TimeSpan(3, 34, 0)))); + + // TODO PdfSignaturePlaceHolder + + // Primitive objects + dict.Elements.Add("/null-obj", new PdfNullObject(Document, true)); + dict.Elements.Add("/bool-obj", new PdfBooleanObject(Document, true)); + dict.Elements.Add("/int-obj", new PdfIntegerObject(Document, 42)); + dict.Elements.Add("/int-min-obj", new PdfIntegerObject(Document, Int32.MinValue)); + dict.Elements.Add("/int-max-obj", new PdfIntegerObject(Document, Int32.MaxValue)); + dict.Elements.Add("/long-min-obj", new PdfLongIntegerObject(Document, Int64.MinValue)); + dict.Elements.Add("/long-max-obj", new PdfLongIntegerObject(Document, Int64.MaxValue)); + dict.Elements.Add("/real-obj", new PdfRealObject(Document, Math.PI)); + dict.Elements.Add("/real-min-obj", new PdfRealObject(Document, Single.MinValue)); + dict.Elements.Add("/real-max-obj", new PdfRealObject(Document, Single.MaxValue)); + dict.Elements.Add("/string-obj", new PdfStringObject(Document, "Hello")); + dict.Elements.Add("/string-obj-hex", new PdfStringObject(Document, "HelloHex") { HexLiteral = true }); + dict.Elements.Add("/name-obj", new PdfNameObject(Document, "/Mambo #5")); + //dict.Elements.Add("/rect-obj", new PdfRectangleObject(new XRect(1, 2, 3, 4))); + dict.Elements.Add("/-.999", new PdfDebugObject(Document, "-.99999")); + dict.Elements.Add("/", new PdfNameObject(Document, "/")); + + // Direct and indirect array + dict.Elements.Add("/array-direct", new TestArray1()); + dict.Elements.Add("/array-indirect", new TestArray1(Document, true)); + + // Direct and indirect dictionary + dict.Elements.Add("/dict1-direct", new TestDict1()); + dict.Elements.Add("/dict1-indirect", new TestDict1(Document, true)); + + // Some direct array + dict.Elements.Add("/some-array", CreateSomeArray()); + + // Some direct dictionary + dict.Elements.Add("/some-dictionary", CreateSomeDictionary()); + + // Some enum test data + dict.Elements.Add("/pagelayout-test", new PdfInteger((int)PdfPageLayout.TwoColumnRight)); + dict.Elements.Add("/pagemode-test", new PdfInteger((int)PdfPageMode.FullScreen)); + dict.Elements.Add("/pagelayout-test-string", new PdfName("/" + PdfPageLayout.TwoColumnRight)); + dict.Elements.Add("/pagemode-test-string", new PdfName("/" + PdfPageMode.FullScreen)); + } + + protected void PopulateArray(PdfArray array) + { + // Primitives + array.Elements.Add(PdfNull.Value); + // 1 + array.Elements.Add(new PdfBoolean(true)); + array.Elements.Add(new PdfInteger(42)); + array.Elements.Add(new PdfInteger(Int32.MinValue)); + array.Elements.Add(new PdfInteger(Int32.MaxValue)); + array.Elements.Add(new PdfLongInteger(Int64.MinValue)); + // 6 + array.Elements.Add(new PdfLongInteger(Int64.MaxValue)); + array.Elements.Add(new PdfReal(Math.PI)); + array.Elements.Add(new PdfReal(Single.MinValue)); + array.Elements.Add(new PdfReal(Single.MaxValue)); + array.Elements.Add(new PdfString("Hello")); + // 11 + array.Elements.Add(new PdfName("/Mambo #5")); + array.Elements.Add(new PdfDebugItem("-.99999")); + array.Elements.Add(new PdfName("/")); + array.Elements.Add(new PdfInteger(-1)); + + // Primitive objects + // 15 + array.Elements.Add(new PdfNullObject(Document, true)); + array.Elements.Add(new PdfBooleanObject(Document, true)); + array.Elements.Add(new PdfIntegerObject(Document, 42)); + array.Elements.Add(new PdfIntegerObject(Document, Int32.MinValue)); + array.Elements.Add(new PdfIntegerObject(Document, Int32.MaxValue)); + // 20 + array.Elements.Add(new PdfLongIntegerObject(Document, Int64.MinValue)); + array.Elements.Add(new PdfLongIntegerObject(Document, Int64.MaxValue)); + array.Elements.Add(new PdfRealObject(Document, Math.PI)); + array.Elements.Add(new PdfRealObject(Document, Single.MinValue)); + array.Elements.Add(new PdfRealObject(Document, Single.MaxValue)); + // 25 + array.Elements.Add(new PdfStringObject(Document, "Hello")); + array.Elements.Add(new PdfNameObject(Document, "/Mambo #5")); + array.Elements.Add(new PdfDebugObject(Document, "-.99999")); + array.Elements.Add(new PdfNameObject(Document, "/")); + + // Direct and indirect array + // 29 + array.Elements.Add(new TestArray1()); + array.Elements.Add(new TestArray1(Document, true)); + + // Direct and indirect dictionary + // 31 + array.Elements.Add(new TestDict1()); + array.Elements.Add(new TestDict1(Document, true)); + + // Some direct array + array.Elements.Add(CreateSomeArray()); + + // Some direct dictionary + array.Elements.Add(CreateSomeDictionary()); + } + + protected PdfArray CreateSomeArray() + { + var array = new PdfArray(); + array.Elements.Add(new PdfInteger(123)); + array.Elements.Add(PdfNull.Value); + array.Elements.Add(new PdfBoolean(true)); + array.Elements.Add(new PdfBoolean(false)); + array.Elements.Add(new PdfInteger(42)); + array.Elements.Add(new PdfArray()); + array.Elements.Add(new PdfInteger(43)); + array.Elements.Add(new PdfArray(Document, new PdfDictionary())); + array.Elements.Add(new PdfInteger(44)); + array.Elements.Add(new PdfLiteral("123456 0 R")); + + return array; + } + + protected PdfDictionary CreateSomeDictionary() + { + var dict = new PdfDictionary(); + dict.Elements.Add("/null", PdfNull.Value); + dict.Elements.Add("/true", new PdfBoolean(true)); + dict.Elements.Add("/false", new PdfBoolean(false)); + dict.Elements.Add("/42", new PdfInteger(42)); + dict.Elements.Add("/arrEmpty", new PdfArray()); + dict.Elements.Add("/43", new PdfInteger(43)); + dict.Elements.Add("/arr2", new PdfArray(Document, new PdfDictionary())); + dict.Elements.Add("/44", new PdfInteger(44)); + dict.Elements.Add("/invalid-iref", new PdfLiteral("123456 0 R")); + + return dict; + } +#endif + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/DateTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/DateTests.cs new file mode 100644 index 00000000..af3e9690 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/DateTests.cs @@ -0,0 +1,53 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf.Metadata; +using Xunit; +using FluentAssertions; +using PdfSharp.Quality.Testing; + +namespace PdfSharp.Tests.Pdf.Objects +{ + [Collection("PDFsharp")] + public class DateTests : PdfSharpTestBase + { + readonly string _tempRoot = GetTempRoot(typeof(DateTests)); +#if true_ + [Fact] + public void Test_format_specifiers() + { + // How do I use the K and zzz format specifiers? + // See https://learn.microsoft.com/en-us/dotnet/api/system.datetimeoffset.tostring?view=net-8.0 + var now = DateTimeOffset.Now; + var d1 = now.ToString("zzz"); + var d2 = now.ToString("zz"); + var d3 = now.ToString("zzz"); + var d4 = now.ToString("ssK"); + var d5 = now.ToString("yyyy-MM-ddTHH:mm:ssK"); + + now = new DateTimeOffset(2025, 9, 22, 15, 16, 17, TimeSpan.Zero); + var d6 = now.ToString("yyyy-MM-ddTHH:mm:ssK"); + } +#endif + + [Fact] + public void Test_MetadataManager_ToXmpDateString() + { + DateTimeOffset? date1 = null; + var str1 = MetadataManager.ToXmpDateString(date1); + str1.Should().BeEmpty(); + + var date2 = new DateTimeOffset(2025, 9, 22, 15, 16, 17, TimeSpan.Zero); + var str2 = MetadataManager.ToXmpDateString(date2); + str2.Should().Be("2025-09-22T15:16:17+00:00"); + + var date3 = new DateTimeOffset(2025, 9, 22, 15, 16, 17, new TimeSpan(2, 30, 0)); + var str3 = MetadataManager.ToXmpDateString(date3); + str3.Should().Be("2025-09-22T15:16:17+02:30"); + + var date4 = new DateTimeOffset(2025, 9, 22, 15, 16, 17, -new TimeSpan(2, 45, 0)); + var str4 = MetadataManager.ToXmpDateString(date4); + str4.Should().Be("2025-09-22T15:16:17-02:45"); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfCatalogTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfCatalogTests.cs new file mode 100644 index 00000000..c67441de --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfCatalogTests.cs @@ -0,0 +1,39 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Quality.Testing; +using Xunit; + +namespace PdfSharp.Tests.Pdf.Objects +{ + [Collection("PDFsharp")] + public class PdfCatalogTests : PdfSharpTestBase + { + public PdfCatalogTests() + { } + + protected override void Dispose(bool _) + { } + + [Fact] + public void Test_Catalog_entries() + { + var doc = new PdfDocument(); + + var catalog = doc.Catalog; + catalog.HasNames.Should().BeFalse(); + + catalog.HasMetadata.Should().BeFalse(); + var metadata = catalog.GetOrCreateMetadata(); + metadata.Should().NotBeNull(); + metadata.IsIndirect.Should().BeTrue(); + + catalog.HasNames.Should().BeFalse(); + var names = catalog.Names; + names.Should().NotBeNull(); + names.IsIndirect.Should().BeTrue(); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfDateTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfDateTests.cs new file mode 100644 index 00000000..df43178f --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfDateTests.cs @@ -0,0 +1,52 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; +using Xunit; +using FluentAssertions; +using PdfSharp.Quality.Testing; + +namespace PdfSharp.Tests.Pdf.Objects +{ + [Collection("PDFsharp")] + public class PdfDateTests : PdfSharpTestBase + { + readonly string _tempRoot = GetTempRoot(typeof(DateTests)); + + [Fact] + public void Test_PdfDate() + { + // Standard test. + { + var now = new DateTimeOffset(2025, 10, 16, 15, 28, 7, new(2, 0, 0)); + var date = new PdfDate(now); + var result = date.ToString(); + result.Should().Be("D:20251016152807+02'00'"); + } + + // Some more tests. + { + var now = DateTimeOffset.Now; + var d1 = now.ToString(); + var d2 = now.ToString("zzz"); + + var date1 = new PdfDate(); + var str1 = date1.ToString(); + date1.Value.Should().BeNull(); + str1.Should().BeEmpty(); + + var date2 = new PdfDate(new DateTimeOffset(2025, 9, 22, 15, 16, 17, TimeSpan.Zero)); + var str2 = date2.ToString(); + date2.ToString().Should().Be("D:20250922151617Z"); + + var date3 = new PdfDate(new DateTimeOffset(2025, 9, 22, 15, 16, 17, new(2, 30, 0))); + var str3 = date3.ToString(); + str3.Should().Be("D:20250922151617+02'30'"); + + var date4 = new PdfDate(new DateTimeOffset(2025, 9, 22, 15, 16, 17, -new TimeSpan(2, 45, 0))); + var str4 = date4.ToString(); + str4.Should().Be("D:20250922151617-02'45'"); + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/BasicTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfPageTests.cs similarity index 76% rename from src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/BasicTests.cs rename to src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfPageTests.cs index e0b410af..3061a9ca 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/BasicTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Objects/PdfPageTests.cs @@ -1,24 +1,25 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using FluentAssertions; +using PdfSharp.Pdf; using PdfSharp.Diagnostics; using PdfSharp.Drawing; using PdfSharp.Fonts; -using PdfSharp.Pdf; using PdfSharp.Quality; -using PdfSharp.Snippets.Font; -using PdfSharp.TestHelper; -#if CORE -#endif using Xunit; +using FluentAssertions; +using PdfSharp.Quality.Testing; -namespace PdfSharp.Tests +namespace PdfSharp.Tests.Pdf.Objects { + // TODO: Replace obsolete properties with new properties. + [Collection("PDFsharp")] - public class BasicTests : IDisposable + public class PdfPageTests : PdfSharpTestBase { - public BasicTests() + readonly string _tempRoot = "unit-tests/" + typeof(PdfPageTests).Namespace + "/"; + + public PdfPageTests() { PdfSharpCore.ResetAll(); #if CORE @@ -26,68 +27,24 @@ public BasicTests() #endif } - public void Dispose() + protected override void Dispose(bool _) { PdfSharpCore.ResetAll(); } [Fact] - public void Create_Hello_World_BasicTests() - { - // Create a new PDF document. - var document = new PdfDocument(); - document.Info.Title = "Created with PDFsharp"; - - // Create an empty page in this document. - var page = document.AddPage(); - - // Get an XGraphics object for drawing on this page. - var gfx = XGraphics.FromPdfPage(page); - - // Draw two lines with a red default pen. - var width = page.Width.Point; - var height = page.Height.Point; - gfx.DrawLine(XPens.Red, 0, 0, width, height); - gfx.DrawLine(XPens.Red, width, 0, 0, height); - - // Draw a circle with a red pen which is 1.5 point thick. - var r = width / 5; - gfx.DrawEllipse(new XPen(XColors.Red, 1.5), XBrushes.White, new XRect(width / 2 - r, height / 2 - r, 2 * r, 2 * r)); - - // Create a font. - var font = new XFont("Times New Roman", 20, XFontStyleEx.BoldItalic); - - // Draw the text. -#if NET6_0_OR_GREATER - gfx.DrawString($"Hello, dotnet {Environment.Version.Major}.{Environment.Version.Minor}!", font, XBrushes.Black, - new XRect(0, 0, width, height), XStringFormats.Center); -#else - gfx.DrawString("Hello, World!", font, XBrushes.Black, - new XRect(0, 0, width, height), XStringFormats.Center); -#endif - - // Save the document... - string filename = PdfFileUtility.GetTempPdfFileName("HelloWorld"); - document.Save(filename); - // ...and start a viewer. - PdfFileUtility.ShowDocumentIfDebugging(filename); - } - - [Fact] - public void Create_CropBox_BasicTests() + public void Test_create_CropBox() { - // Create a new PDF document. var document = new PdfDocument(); - document.Info.Title = "Created with PDFsharp"; + document.Info.Title = nameof(Test_create_CropBox); - // Create an empty page in this document. var page = document.AddPage(); - var mediaBox = page.MediaBoxReadOnly; + var mediaBox = page.MediaBox; mediaBox.Should().NotBeNull(); mediaBox.Should().NotBe(XRect.Empty); mediaBox.IsZero.Should().BeFalse(); - +#if true_ // CropBox does not exist by default. var cropBox = page.CropBoxReadOnly; cropBox.IsZero.Should().BeTrue(); @@ -116,7 +73,7 @@ public void Create_CropBox_BasicTests() // For "new PdfRectangle()", IsZero is true. cropBox = page.CropBox; cropBox.Should().NotBeNull(); - cropBox.Should().NotBe(XRect.Empty); + cropBox.Should().NotBe(XRect.Empty); // Different type, so the two must be different. cropBox.IsZero.Should().BeTrue(); cropBox = page.EffectiveCropBoxReadOnly; @@ -141,7 +98,7 @@ public void Create_CropBox_BasicTests() cropBox.Should().NotBeNull(); cropBox.Should().NotBe(XRect.Empty); cropBox.IsZero.Should().BeFalse(); - +#endif // Get an XGraphics object for drawing on this page. var gfx = XGraphics.FromPdfPage(page); @@ -153,40 +110,36 @@ public void Create_CropBox_BasicTests() // Draw a circle with a red pen which is 1.5 point thick. var r = width / 6; - gfx.DrawEllipse(new XPen(XColors.Red, 1.5), XBrushes.White, new XRect(width / 2 - r, height / 2 - r, 2 * r, 2 * r)); + gfx.DrawEllipse(new XPen(XColors.Red, 1.5), XBrushes.White, + new XRect(width / 2 - r, height / 2 - r, 2 * r, 2 * r)); // Create a font. var font = new XFont("Times New Roman", 10, XFontStyleEx.BoldItalic); -#if NET6_0_OR_GREATER - gfx.DrawString($"Hello, dotnet {Environment.Version.Major}.{Environment.Version.Minor}!", font, XBrushes.Black, - new XRect(0, 0, width, height), XStringFormats.Center); -#else + // Draw the text. gfx.DrawString("Hello, World!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); -#endif - // Save the document... - string filename = PdfFileUtility.GetTempPdfFileName("BasicMediaBoxTest"); + // Save the document… + string filename = PdfFileUtility.GetTempPdfFullFileName(_tempRoot+nameof(Test_create_CropBox)); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } [Fact] - public void Create_all_boxes_BasicTests() + public void Test_create_all_boxes() { - // Create a new PDF document. var document = new PdfDocument(); - document.Info.Title = "Created with PDFsharp"; + document.Info.Title =nameof(Test_create_all_boxes); - // Create an empty page in this document. var page = document.AddPage(); var pageWidth = page.Width.Point; var pageHeight = page.Height.Point; var pageRectangle = new PdfRectangle(0, 0, pageWidth, pageHeight); var defaultRectangle = new PdfRectangle(); +#if false var mediaBox = page.MediaBoxReadOnly; mediaBox.Should().NotBeNull(); mediaBox.Should().Be(pageRectangle); @@ -354,7 +307,7 @@ public void Create_all_boxes_BasicTests() trimBox.Should().NotBeNull(); trimBox.Should().NotBe(XRect.Empty); trimBox.IsZero.Should().BeFalse(); - +#endif // Get an XGraphics object for drawing on this page. var gfx = XGraphics.FromPdfPage(page); @@ -366,23 +319,20 @@ public void Create_all_boxes_BasicTests() // Draw a circle with a red pen which is 1.5 point thick. var r = width / 6; - gfx.DrawEllipse(new XPen(XColors.Red, 1.5), XBrushes.White, new XRect(width / 2 - r, height / 2 - r, 2 * r, 2 * r)); + gfx.DrawEllipse(new XPen(XColors.Red, 1.5), XBrushes.White, + new XRect(width / 2 - r, height / 2 - r, 2 * r, 2 * r)); // Create a font. var font = new XFont("Times New Roman", 10, XFontStyleEx.BoldItalic); -#if NET6_0_OR_GREATER - gfx.DrawString($"Hello, dotnet {Environment.Version.Major}.{Environment.Version.Minor}!", font, XBrushes.Black, - new XRect(0, 0, width, height), XStringFormats.Center); -#else + // Draw the text. gfx.DrawString("Hello, World!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); -#endif - // Save the document... - string filename = PdfFileUtility.GetTempPdfFileName("BasicAllBoxesTest"); + // Save the document… + string filename = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(Test_create_all_boxes)); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/pdf-a/PdfATests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.PdfA/PdfATests.cs similarity index 96% rename from src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/pdf-a/PdfATests.cs rename to src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.PdfA/PdfATests.cs index 10415223..4f47e7a4 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/pdf-a/PdfATests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.PdfA/PdfATests.cs @@ -5,6 +5,7 @@ using PdfSharp.Drawing; using PdfSharp.Fonts; using PdfSharp.Pdf; +using PdfSharp.Pdf.PdfA; using PdfSharp.Quality; using PdfSharp.UniversalAccessibility; using Xunit; @@ -31,7 +32,7 @@ public void Dispose() public void Simple_PDF_A_document() { var document = new PdfDocument(); - document.SetPdfA(); + document.SetPdfA(PdfAFormats.PdfA_3b); // Get the manager for universal accessibility. var uaManager = UAManager.ForDocument(document); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Security/SecurityTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Security/SecurityTests.cs similarity index 92% rename from src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Security/SecurityTests.cs rename to src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Security/SecurityTests.cs index a784cf32..c495838c 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Security/SecurityTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Security/SecurityTests.cs @@ -11,11 +11,11 @@ namespace PdfSharp.Tests.Security [Collection("PDFsharp")] public class SecurityTests { - //[Fact] // The tests succeeds and is now skipped. + //[Fact] // The test succeeds and is now skipped. [Fact(Skip = "Test only once, because it is slow and does not depend on PDFsharp source code.")] public void MD5_creation() { - var rnd = new Random(DateTime.Now.Millisecond); + var rnd = new Random(DateTimeOffset.Now.Millisecond); for (int i = 0; i < 25_000; i++) { TestHashCreation(i); diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/BouncyCastleSignerTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Signatures/BouncyCastleSignerTests.cs similarity index 87% rename from src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/BouncyCastleSignerTests.cs rename to src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Signatures/BouncyCastleSignerTests.cs index 4865d9a1..25916930 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/BouncyCastleSignerTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Signatures/BouncyCastleSignerTests.cs @@ -2,6 +2,8 @@ // See the LICENSE file in the solution root for more information. using System.Globalization; +using System.Runtime.ConstrainedExecution; + #if WPF using System.IO; #endif @@ -59,7 +61,7 @@ public void Sign_new_file_Bouncy() layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144); var text = "Lorem ipsum..."; - textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), layoutRectangle, XStringFormats.TopLeft); + textFormatter.DrawString(text, font, XBrushes.Black, layoutRectangle, XStringFormats.TopLeft); var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216)); var options = new DigitalSignatureOptions @@ -71,12 +73,12 @@ public void Sign_new_file_Bouncy() AppearanceHandler = new SignAppearanceHandler() }; - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new BouncyCastleSigner(GetCertificate(), PdfMessageDigestType.SHA512), options); + var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new BouncyCastleSigner(GetCertificate(), PdfMessageDigestType.SHA512), options); - // Save the document... + // Save the document… string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/signatures/BouncySignerTest"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -98,12 +100,12 @@ public void Sign_existing_file_Bouncy() XUnit.FromCentimeter(9.9).Point, XUnit.FromCentimeter(1.3).Point), AppearanceHandler = new SignAppearanceHandler() }; - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new BouncyCastleSigner(GetCertificate(), PdfMessageDigestType.SHA512), options); + var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new BouncyCastleSigner(GetCertificate(), PdfMessageDigestType.SHA512), options); - // Save the document... + // Save the document… string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/signatures/BouncySignExistingPdfTest"); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -146,7 +148,7 @@ public void DrawAppearance(XGraphics gfx, XRect rect) var pngFile = Path.Combine(imageFolder ?? throw new InvalidOperationException("Call Download-Assets.ps1 before running the tests."), "JohnDoe.png"); var image = XImage.FromFile(pngFile); - string text = "John Doe\nSeattle, " + DateTime.Now.ToString(CultureInfo.GetCultureInfo("EN-US")); + string text = "John Doe\nSeattle, " + DateTimeOffset.Now.ToString(CultureInfo.GetCultureInfo("EN-US")); var font = new XFont("Verdana", 7.0, XFontStyleEx.Regular); var textFormatter = new XTextFormatter(gfx); double num = (double)image.PixelWidth / image.PixelHeight; @@ -156,7 +158,7 @@ public void DrawAppearance(XGraphics gfx, XRect rect) gfx.DrawImage(image, point.X, point.Y, signatureHeight * num, signatureHeight); // Adjust position for text. We draw it below image. point = new XPoint(point.X, rect.Height / 2d); - textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), new XRect(point.X, point.Y, rect.Width, rect.Height - point.Y), XStringFormats.TopLeft); + textFormatter.DrawString(text, font, XBrushes.Black, new XRect(point.X, point.Y, rect.Width, rect.Height - point.Y), XStringFormats.TopLeft); } } } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/DefaultSignerTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Signatures/DefaultSignerTests.cs similarity index 77% rename from src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/DefaultSignerTests.cs rename to src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Signatures/DefaultSignerTests.cs index 7142ad2d..adade4ca 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/signatures/DefaultSignerTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Signatures/DefaultSignerTests.cs @@ -15,12 +15,10 @@ using PdfSharp.Pdf.IO; using PdfSharp.Pdf.Signatures; using PdfSharp.Quality; -#if CORE -#endif using Xunit; using SecurityTestHelper = PdfSharp.TestHelper.SecurityTestHelper; -namespace PdfSharp.Tests.Pdf +namespace PdfSharp.Tests.Pdf.Signatures { [Collection("PDFsharp")] public class DefaultSignerTests : IDisposable @@ -63,7 +61,7 @@ public void Sign_new_file_with_DefaultAppearance(string certType, PdfMessageDige layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144); var text = "Lorem ipsum..."; - textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), layoutRectangle, XStringFormats.TopLeft); + textFormatter.DrawString(text, font, XBrushes.Black, layoutRectangle, XStringFormats.TopLeft); var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216)); var options = new DigitalSignatureOptions @@ -79,19 +77,19 @@ public void Sign_new_file_with_DefaultAppearance(string certType, PdfMessageDige Uri? timestampURI = String.IsNullOrEmpty(timestampURL) ? null : new Uri(timestampURL, UriKind.Absolute); - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options); + var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options); - // Save the document... + // Save the document… string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/signatures/DefaultAppearanceHandler-" + certType); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } [Theory] [InlineData("test-cert_rsa_1024", PdfMessageDigestType.SHA1, null)] [InlineData("test-cert_rsa_1024", PdfMessageDigestType.SHA256, null)] -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER // Time stamping not implemented for .NET Standard or .NET Framework. // Some arbitrarily selected timestamp servers. [InlineData("test-cert_rsa_1024", PdfMessageDigestType.SHA256, "http://zeitstempel.dfn.de")] @@ -134,7 +132,7 @@ public void Sign_new_file_Default(string certType, PdfMessageDigestType digestTy layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144); var text = "Lorem ipsum..."; - textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), layoutRectangle, XStringFormats.TopLeft); + textFormatter.DrawString(text, font, XBrushes.Black, layoutRectangle, XStringFormats.TopLeft); var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216)); var options = new DigitalSignatureOptions @@ -148,22 +146,22 @@ public void Sign_new_file_Default(string certType, PdfMessageDigestType digestTy Uri? timestampURI = String.IsNullOrEmpty(timestampURL) ? null : new Uri(timestampURL, UriKind.Absolute); - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options); + var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options); - // pdfSignatureHandler What to do with it? Set SHA-level? Set TimeStamp? + // digitalSignatureHandler What to do with it? Set SHA-level? Set TimeStamp? - // Save the document... + // Save the document… string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/signatures/DefaultSignerTest-" + certType); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER [SkippableTheory] //[InlineData("test-cert_rsa_1024", PdfMessageDigestType.SHA1, null)] //[InlineData("test-cert_rsa_1024", PdfMessageDigestType.SHA256, null)] -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER // Time stamping not implemented for .NET Standard or .NET Framework. // Some arbitrarily selected timestamp servers. [InlineData("test-cert_rsa_1024", PdfMessageDigestType.SHA256, "http://zeitstempel.dfn.de")] @@ -193,7 +191,7 @@ public void Sign_new_file_Default_in_a_loop(string certType, PdfMessageDigestTyp Skip.If(SkippableTests.SkipSlowTests()); int loops = 2; -#if DEBUG +#if DEBUG_ var version = PdfSharp.Internal.BuildInformation.BuildVersionNumber; loops = 100; // Reduce loops for slow timestamp servers. @@ -217,8 +215,7 @@ public void Sign_new_file_Default_in_a_loop(string certType, PdfMessageDigestTyp layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144); var text = "Lorem ipsum..."; - textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), - layoutRectangle, XStringFormats.TopLeft); + textFormatter.DrawString(text, font, XBrushes.Black, layoutRectangle, XStringFormats.TopLeft); var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216)); var options = new DigitalSignatureOptions @@ -232,16 +229,16 @@ public void Sign_new_file_Default_in_a_loop(string certType, PdfMessageDigestTyp Uri? timestampURI = String.IsNullOrEmpty(timestampURL) ? null : new Uri(timestampURL, UriKind.Absolute); - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, + var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate(certType), digestType, timestampURI), options); - // pdfSignatureHandler What to do with it? Set SHA-level? Set TimeStamp? + // digitalSignatureHandler What to do with it? Set SHA-level? Set TimeStamp? - // Save the document... + // Save the document… string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/signatures/DefaultSignerTest-" + certType); document.Save(filename); - // ...and start a viewer. + // … and start a viewer. // ReSharper disable once ConditionIsAlwaysTrueOrFalse if (loops <= 2) PdfFileUtility.ShowDocumentIfDebugging(filename); @@ -267,12 +264,10 @@ public void Sign_existing_file_Default() XUnit.FromCentimeter(9.9).Point, XUnit.FromCentimeter(1.3).Point), AppearanceHandler = new SignatureAppearanceHandler() }; - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate("test-cert_rsa_1024"), PdfMessageDigestType.SHA512), options); + var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate("test-cert_rsa_1024"), PdfMessageDigestType.SHA512), options); - // Save the document... string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/signatures/DefaultSignExistingPdfTest"); document.Save(filename); - // ...and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } @@ -313,7 +308,7 @@ public void Sign_with_Certificate_from_Store() layoutRectangle = new XRect(72, 144, pdfPage.Width.Point - 144, pdfPage.Height.Point - 144); var text = "Lorem ipsum..."; - textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), layoutRectangle, XStringFormats.TopLeft); + textFormatter.DrawString(text, font, XBrushes.Black, layoutRectangle, XStringFormats.TopLeft); var pdfPosition = xGraphics.Transformer.WorldToDefaultPage(new XPoint(144, 216)); var options = new DigitalSignatureOptions @@ -324,62 +319,11 @@ public void Sign_with_Certificate_from_Store() Rectangle = new XRect(pdfPosition.X, pdfPosition.Y, 200, 50), AppearanceHandler = new SignatureAppearanceHandler() }; - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(certificate, PdfMessageDigestType.SHA256), options); + var digitalSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(certificate, PdfMessageDigestType.SHA256), options); - // Save the document... string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/signatures/CertificateFromStore"); document.Save(filename); - // ...and start a viewer. - PdfFileUtility.ShowDocumentIfDebugging(filename); - } - - [Theory(Skip = "errors on writing and maybe reading encrypted files with signature")] - [ClassData(typeof(SecurityTestHelper.TestData.AllWriteVersions))] - [ClassData(typeof(SecurityTestHelper.TestData.AllWriteVersionsSkipped), Skip = SecurityTestHelper.SkippedTestOptionsMessage)] - public void Sign_and_encrypt_file_Default(SecurityTestHelper.TestOptionsEnum optionsEnum) - { - var testOptions = SecurityTestHelper.TestOptions.ByEnum(optionsEnum); - testOptions.SetDefaultPasswords(true, true); -#if DEBUG - var version = PdfSharp.Internal.BuildInformation.BuildVersionNumber; -#endif - IOUtility.EnsureAssetsVersion(RequiredAssets); - - var font = new XFont("Verdana", 10.0, XFontStyleEx.Regular); - using var document = new PdfDocument(); - var pdfPage = document.AddPage(); - var xGraphics = XGraphics.FromPdfPage(pdfPage); - var layoutRectangle = new XRect(0.0, 0.0, pdfPage.Width.Point, pdfPage.Height.Point); - xGraphics.DrawString("Signed encrypted sample document", font, XBrushes.Black, layoutRectangle, XStringFormats.TopCenter); - var options = new DigitalSignatureOptions - { - ContactInfo = "John Doe", - Location = "Seattle", - Reason = "License Agreement", - Rectangle = new XRect(36.0, 700.0, 400.0, 50.0), - AppearanceHandler = new SignatureAppearanceHandler() - }; - - // Set encryption parameters. - SecurityTestHelper.SecureDocument(document, testOptions); - - var pdfSignatureHandler = DigitalSignatureHandler.ForDocument(document, new PdfSharpDefaultSigner(GetCertificate("test-cert_rsa_1024"), PdfMessageDigestType.SHA512), options); - - // pdfSignatureHandler What to do with it? Set SHA-level? Set TimeStamp? - - // Save the document... - var filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/signatures/" + SecurityTestHelper.AddPrefixToFilename("DefaultSignAndEncryptPdfTest", testOptions)); - document.Save(filename); - // ...and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); - - // Read encrypted file and write it without encryption. - var pdfDocRead = PdfReader.Open(filename, SecurityTestHelper.PasswordOwnerDefault); - - var filenameRead = PdfFileUtility.GetTempPdfFullFileName(SecurityTestHelper.AddPrefixToFilename("read DefaultSignAndEncryptedPdfTest", testOptions)); - pdfDocRead.Save(filenameRead); - - PdfFileUtility.ShowDocumentIfDebugging(filenameRead); } static X509Certificate2 GetCertificate(string certName) @@ -413,7 +357,7 @@ public void DrawAppearance(XGraphics gfx, XRect rect) var pngFile = Path.Combine(imageFolder ?? throw new InvalidOperationException("Call Download-Assets.ps1 before running the tests."), "JohnDoe.png"); var image = XImage.FromFile(pngFile); - string text = "John Doe\nSeattle, " + DateTime.Now.ToString(CultureInfo.GetCultureInfo("EN-US")); + string text = "John Doe\nSeattle, " + DateTimeOffset.Now.ToString(CultureInfo.GetCultureInfo("EN-US")); var font = new XFont("Verdana", 7.0, XFontStyleEx.Regular); var textFormatter = new XTextFormatter(gfx); double num = (double)image.PixelWidth / image.PixelHeight; @@ -423,7 +367,7 @@ public void DrawAppearance(XGraphics gfx, XRect rect) gfx.DrawImage(image, point.X, point.Y, signatureHeight * num, signatureHeight); // Adjust position for text. We draw it below image. point = new XPoint(point.X, rect.Height / 2d); - textFormatter.DrawString(text, font, new XSolidBrush(XColor.FromKnownColor(XKnownColor.Black)), new XRect(point.X, point.Y, rect.Width, rect.Height - point.Y), XStringFormats.TopLeft); + textFormatter.DrawString(text, font, XBrushes.Black, new XRect(point.X, point.Y, rect.Width, rect.Height - point.Y), XStringFormats.TopLeft); } } } diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Structure/.gitkeep b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf.Structure/.gitkeep new file mode 100644 index 00000000..e69de29b diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/CreationTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/CreationTests.cs index f8377677..e99e4921 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/CreationTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/creation/CreationTests.cs @@ -1,11 +1,10 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.IO; -using FluentAssertions; using PdfSharp.Pdf; using PdfSharp.Quality; using Xunit; +using FluentAssertions; namespace PdfSharp.Tests { @@ -15,7 +14,7 @@ public class CreationTests [Fact] public void Create_for_Stream() { - var filename = PdfFileUtility.GetTempPdfFileName("CreationTest"); + var filename = PdfFileUtility.GetTempPdfFullFileName("unittests/pdfsharp/PDF/creation/CreationTest"); using (var outputStream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite)) { diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/DictionaryTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/DictionaryTests.cs deleted file mode 100644 index 88c80639..00000000 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/objectmodel/DictionaryTests.cs +++ /dev/null @@ -1,23 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using PdfSharp.Diagnostics; -using PdfSharp.Drawing; -using PdfSharp.Fonts; -using PdfSharp.Pdf; -using PdfSharp.Quality; -using PdfSharp.Snippets.Font; -using PdfSharp.TestHelper; -using Xunit; - -namespace PdfSharp.Tests.Pdf -{ - [Collection("PDFsharp")] - public class DictionaryTests - { - [Fact] - public void ToDo() - { - } - } -} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/streams/StreamTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/streams/StreamTests.cs new file mode 100644 index 00000000..60e4875a --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Pdf/streams/StreamTests.cs @@ -0,0 +1,224 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Filters; +using PdfSharp.Pdf.Internal; +using PdfSharp.Pdf.IO; +using PdfSharp.Quality; +using Xunit; +using static PdfSharp.Pdf.PdfDictionary; + +namespace PdfSharp.Tests.Pdf +{ + [Collection("PDFsharp")] + public class StreamTests + { + [Fact] + public void Create_a_raw_stream() + { + var document = new PdfDocument(); + var page1 = document.AddPage(); + + var dict = new PdfDebugDictionary(document) + { + StreamLength = -42 // Illegal value. + }; + dict.Stream = new PdfDictionary.PdfStream("Hello, World!"u8.ToArray(), dict); + + document.Internals.AddObject(dict); + document.Catalog.Elements.Add("/SomeRawDict", dict.RequiredReference); + + string filename = PdfFileUtility.GetTempPdfFullFileName("PDFsharp/UnitTest/pdf/BasicStreamTest"); + document.Save(filename); + //PdfFileUtility.ShowDocumentIfDebugging(filename); + } + + /// + /// Creates test files with a manipulated, unfiltered stream. + /// + /// Size of the raw stream. + /// Length given in PDF file. + /// Content of the stream. + /// True if created files throws an exception on PdfReader.Open. + [Theory] + [InlineData(20, 10, Chars.LF, false)] + [InlineData(20, 10, 'x', false)] + [InlineData(6000, 10, Chars.LF, true)] // Should work, but does not yet work with 6.2.0 Preview 2. + [InlineData(6000, 10, 'X', true)] // Should work, but does not yet work with 6.2.0 Preview 2. + [InlineData(0, 5555, Chars.NUL, false)] + [InlineData(5555, 0, Chars.SP, true)] // Should work, but does not yet work with 6.2.0 Preview 2. + public void Create_and_read_a_raw_stream(int size, int reportedLength, char content, bool shouldFail) + { +#pragma warning disable CS0162 // Unreachable code detected + // Set SaveToFile to true if you want to inspect the created files. + // Otherwise, set SaveToFile to false for better performance. +#if DEBUG + const bool saveToFile = true; + //const bool saveToFile = false; +#else + const bool saveToFile = false; +#endif + + var document = new PdfDocument(); + var page1 = document.AddPage(); + + var dict = new PdfDebugDictionary(document) + { + StreamLength = reportedLength + }; + var streamData = new byte[size]; + for (int x = 0; x < size; ++x) + streamData[x] = (byte)content; + + dict.Stream = new PdfDictionary.PdfStream(streamData, dict); + + document.Internals.AddObject(dict); + document.Catalog.Elements.Add("/SomeRawDict", dict.RequiredReference); + + string filename = PdfFileUtility.GetTempPdfFullFileName($"PDFsharp/UnitTest/pdf/StreamTests--StreamSize_{size}-Length_{reportedLength}-Contents_{(byte)content}-ShouldFail{shouldFail}"); + // ReSharper disable once RedundantAssignment + var memoryStream = saveToFile ? null : new MemoryStream(); + + if (saveToFile) + { + document.Save(filename); + //PdfFileUtility.ShowDocumentIfDebugging(filename); + } + else + { + document.Save(memoryStream); + } + + if (shouldFail) + { + // The created file is corrupted and cannot be opened by PDFsharp. + if (saveToFile) + { + var openDocument = () => PdfReader.Open(filename); + openDocument.Should().Throw(); + } + else + { + memoryStream.Position = 0; + var openDocument = () => PdfReader.Open(memoryStream); + openDocument.Should().Throw(); + } + + } + else + { + // The created file is OK or slightly corrupted and can be opened by PDFsharp. + PdfDocument openedDocument; + if (saveToFile) + { + openedDocument = PdfReader.Open(filename); + } + else + { + memoryStream.Position = 0; + openedDocument = PdfReader.Open(memoryStream); + } + openedDocument.PageCount.Should().Be(1); + } +#pragma warning restore CS0162 // Unreachable code detected + } + + /// + /// Creates test files with a manipulated, flate-encoded stream. + /// + /// Size of the raw stream. + /// Length given in PDF file. Set -1 to get length of filtered stream. + /// Content of the stream. Use Chars.NumberSign to get random data. + /// True if created files throws an exception on PdfReader.Open. + [Theory] + [InlineData(100, -1, 'x', false)] // -1 means use correct length. + [InlineData(100, 1, 'x', false)] + [InlineData(100, 512, 'x', false)] + [InlineData(100, -1, Chars.NumberSign, false)] // -1 means use correct length. + [InlineData(100, 1, Chars.NumberSign, true)] // Should work, but does not yet work with 6.2.0 Preview 2. + [InlineData(100, 512, Chars.NumberSign, false)] + public void Create_and_read_a_flate_decoded_raw_stream(int size, int reportedLength, char content, bool shouldFail) + { +#pragma warning disable CS0162 // Unreachable code detected + // Set SaveToFile to true if you want to inspect the created files. + // Otherwise, set SaveToFile to false for better performance. +#if DEBUG + const bool saveToFile = true; + //const bool saveToFile = false; +#else + const bool saveToFile = false; +#endif + + var rnd = new Random(17); + var document = new PdfDocument(); + var page1 = document.AddPage(); + + var dict = new PdfDebugDictionary(document) + { + StreamLength = reportedLength + }; + var streamData = new byte[size]; + for (int x = 0; x < size; ++x) + streamData[x] = content == Chars.NumberSign ? (byte)rnd.Next(255) : (byte)content; + + FlateDecode fd = new FlateDecode(); + byte[] compressed = fd.Encode(streamData, PdfFlateEncodeMode.BestCompression); + dict.Stream = new PdfDictionary.PdfStream(compressed, dict); + dict.Elements.SetName(PdfStream.Keys.Filter, "/FlateDecode"); + if (reportedLength == -1) + dict.StreamLength = compressed.Length; + + document.Internals.AddObject(dict); + document.Catalog.Elements.Add("/SomeRawDict", dict.RequiredReference); + + string filename = PdfFileUtility.GetTempPdfFullFileName($"PDFsharp/UnitTest/pdf/FilteredStreamTests-StreamSize_{size}-Length_{reportedLength}-Contents_{(content == Chars.NumberSign ? "RND" : (byte)content)}-ShouldFail{shouldFail}"); + // ReSharper disable once RedundantAssignment + var memoryStream = saveToFile ? null : new MemoryStream(); + + if (saveToFile) + { + document.Save(filename); + //PdfFileUtility.ShowDocumentIfDebugging(filename); + } + else + { + document.Save(memoryStream); + } + + if (shouldFail) + { + // The created file is corrupted and cannot be opened by PDFsharp. + if (saveToFile) + { + var openDocument = () => PdfReader.Open(filename); + openDocument.Should().Throw(); + } + else + { + memoryStream.Position = 0; + var openDocument = () => PdfReader.Open(memoryStream); + openDocument.Should().Throw(); + } + + } + else + { + // The created file is OK or slightly corrupted and can be opened by PDFsharp. + PdfDocument openedDocument; + if (saveToFile) + { + openedDocument = PdfReader.Open(filename); + } + else + { + memoryStream.Position = 0; + openedDocument = PdfReader.Open(memoryStream); + } + openedDocument.PageCount.Should().Be(1); + } +#pragma warning restore CS0162 // Unreachable code detected + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModel.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModel.cs new file mode 100644 index 00000000..22c58508 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModel.cs @@ -0,0 +1,140 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using Xunit; +using FluentAssertions; +using System.Security.Cryptography; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.Security; +using PdfSharp.TestHelper; +using PdfSharp.Quality; +using System.Reflection; + +// TODO: DELETE + +namespace PdfSharp.Tests.PdfObjectModel +{ + public class TestDict1 : PdfDictionary + { + public TestDict1() + { + Elements.Add("/I_am_a", new PdfName('/' + nameof(TestDict1))); + } + + protected TestDict1(PdfDictionary dict) : base(dict) + { } + + public T? Foo(int x) => default(T); + + public sealed class Keys : KeysBase + { + // ReSharper disable InconsistentNaming + + [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict1))] + public const string IAmA = "/I_am_a"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class TestDict2 : PdfDictionary + { + public TestDict2() + { + Elements.Add("/I_am_a", new PdfName('/' + nameof(TestDict2))); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal TestDict2(PdfDictionary dict) : base(dict) + { } + + public sealed class Keys : KeysBase + { + // ReSharper disable InconsistentNaming + + /// + /// + [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Outlines")] + public const string Type = "/Type"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class TestDict3 : PdfDictionary + { + public TestDict3(PdfDocument doc) : base(doc) + { + Elements.Add(Keys.IAmA, new PdfName('/' + nameof(TestDict3))); + + var dict1 = new TestDict1(); + Elements.Add(Keys.TestDict1, dict1); + + var dict2 = new TestDict1(); + doc.Internals.AddObject(dict2); + Elements.Add(Keys.TestDict1Ref, dict2); + } + + TestDict3(PdfDictionary dict) : base(dict) + { } + + public sealed class Keys : KeysBase + { + // ReSharper disable InconsistentNaming + + [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict3))] + public const string IAmA = "/I_am_a"; + + [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] + public const string TestDict1 = "/TestDict1"; + + [KeyInfo(KeyType.String | KeyType.Optional, typeof(TestDict1))] + public const string TestDict1Ref = "/TestDict1Ref"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class TestArray1 : PdfArray + { + + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelMetaTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelMetaTests.cs new file mode 100644 index 00000000..6bf643a4 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelMetaTests.cs @@ -0,0 +1,84 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using Xunit; +using FluentAssertions; +using System.Security.Cryptography; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.Security; +using PdfSharp.TestHelper; +using PdfSharp.Quality; +using System.Reflection; + +// TODO: DELETE + +namespace PdfSharp.Tests.PdfObjectModel +{ + [Collection("PDFsharp")] + public class ObjectModelMetaTests + { + [Fact] + public void Object_transformation_constructor_Test() + { + // Not yet a real test. + + // TODO: Check ctor of all classes derived from PdfDictionary + // to have the right constructors. + var assemblies = + AppDomain.CurrentDomain.GetAssemblies() + .Where(a => a.FullName?.StartsWith("PdfSharp", StringComparison.OrdinalIgnoreCase) ?? false); + int fromPdfDocumentCounter = 0; + int fromPdfDictionaryCounter = 0; + foreach (Assembly assembly in assemblies) + { + if (assembly.FullName?.StartsWith("PdfSharp") ?? false) + { + var types = assembly.GetTypes(); + foreach (Type type in assembly.GetTypes()) + { + ConstructorInfo? ctorInfo; + if (type.IsSubclassOf(typeof(PdfDictionary))) + { + if (type.IsAbstract) + continue; + + ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [typeof(PdfDocument)], null); + if (ctorInfo == null) + _ = typeof(int); + //ctorInfo.Should().NotBeNull(); + + if (ctorInfo != null) + fromPdfDocumentCounter++; + + ctorInfo = type.GetConstructor( + BindingFlags.Instance | BindingFlags.Public | BindingFlags.NonPublic, + null, [typeof(PdfDictionary)], null); + if (ctorInfo == null) + _ = typeof(int); + //ctorInfo.Should().NotBeNull(); + + if (ctorInfo != null) + fromPdfDictionaryCounter++; + else + { + var name = type.FullName; + _ = typeof(int); + + } + } + else if (type.IsSubclassOf(typeof(PdfArray))) + { + // TODO + _ = typeof(int); + + } + } + } + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Arrays.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Arrays.cs new file mode 100644 index 00000000..7c98c74e --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Arrays.cs @@ -0,0 +1,78 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Diagnostics; +using Xunit; +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.PdfItemExtensions; +using PdfSharp.Pdf.PdfArrayExtensions; +using PdfSharp.Pdf.PdfDictionaryExtensions; +using PdfSharp.Tests.PdfObjectModel; +using PdfSharp.Quality; + +//using static PdfSharp.Diagnostics.DebugBreakHelper; + +#pragma warning disable CS8321 // Local function is declared but never used + +// TODO: DELETE + +namespace PdfSharp.Tests.PdfObjectModel +{ + //[Collection("PDFsharp")] + public partial class ObjectModelTests + { + // Tests for PdfArray. + + [Fact] + public void Test_Catalog_AF() + { + var doc1 = CreateDocument(); + var catalog = doc1.Internals.Catalog; + + var af0 = catalog.Elements[PdfCatalog.Keys.AF]; + + var af2 = catalog.Elements.GetArray(PdfCatalog.Keys.AF); + //var af3 = catalog.Elements.GetArray("/AF", VCF.Create); + + //ShouldBreak5 = true; + + var af4 = catalog.Elements.GetRequiredArray(PdfCatalog.Keys.AF, VCF.Create); + + var dict1 = new TestDict1(); + doc1.Internals.AddObject(dict1); + af4.AddDictionary(dict1); + var count = af4.Elements.Count; + + //af4.RemoveDictionary(dict1); + + //var dict3 = new TestDict3(doc1); + //dict3.Comment = "Dict3"; + //doc1.Internals.AddObject(dict3); + //catalog.Elements.Add("/TestStuff3", dict3); + + // Save and reload. + var path = Save(doc1, nameof(Test_Catalog_AF) + "1"); + var doc2 = Load(path); + var cat2 = doc2.Internals.Catalog; + + //var dict32Ref = (PdfReference?)cat2.Elements["/TestStuff3"]; + + //var abc = PdfObjectsHelper.TransformDictionary((PdfDictionary)(dict32Ref!.Value)); + + ////var x = dict32Ref.Value.AsDictionary().Elements[TestDict3.Keys.TestDict1]; + ////var xt = x.GetType(); + //var y = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1); + //var z = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1Ref); + //var z2 = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1Ref); + + ////var xxx = dict32.Elements.GetItemNew( + //// TestDict3.Keys.TestDict1, ObjMagic.Default, typeof(TestDict1)); + + var path2 = Save(doc2, nameof(Test_Catalog_AF) + "2"); + } + + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Dictionaries.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Dictionaries.cs new file mode 100644 index 00000000..eac2e575 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Dictionaries.cs @@ -0,0 +1,62 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Diagnostics; +using Xunit; +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.PdfItemExtensions; +using PdfSharp.Pdf.PdfArrayExtensions; +using PdfSharp.Pdf.PdfDictionaryExtensions; +using PdfSharp.Tests.PdfObjectModel; +using PdfSharp.Quality; + +//using static PdfSharp.Diagnostics.DebugBreakHelper; +using System.Linq; + +#pragma warning disable CS8321 // Local function is declared but never used + +// TODO: DELETE + +namespace PdfSharp.Tests.PdfObjectModel +{ + //[Collection("PDFsharp")] + public partial class ObjectModelTests + { + // Tests for PdfDictionary. + + //[Fact] + public void ToDoTest() + { + var doc1 = CreateDocument(); + var catalog = doc1.Internals.Catalog; + + var dict3 = new TestDict3(doc1); + dict3.Comment = "Dict3"; + doc1.Internals.AddObject(dict3); + catalog.Elements.Add("/TestStuff3", dict3); + + // Save and reload. + var path = Save(doc1, nameof(TestTest) + "1"); + var doc2 = Load(path); + var cat2 = doc2.Internals.Catalog; + + var dict32Ref = cat2.Elements.GetRequiredReference("/TestStuff3"); + + //var abc = PdfObjectsHelper.TransformDictionary((PdfDictionary)(dict32Ref!.Value)); + + //var x = dict32Ref.Value.AsDictionary().Elements[TestDict3.Keys.TestDict1]; + //var xt = x.GetType(); + var y = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1); + var z = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1Ref); + var z2 = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1Ref); + + //var xxx = dict32.Elements.GetItemNew( + // TestDict3.Keys.TestDict1, ObjMagic.Default, typeof(TestDict1)); + + var path2 = Save(doc2, nameof(TestTest) + "2"); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Helper.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Helper.cs new file mode 100644 index 00000000..8282b0c5 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests-Helper.cs @@ -0,0 +1,110 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Diagnostics; +using Xunit; +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; +using PdfSharp.Pdf.PdfItemExtensions; +using PdfSharp.Pdf.PdfArrayExtensions; +using PdfSharp.Pdf.PdfDictionaryExtensions; +using PdfSharp.Tests.PdfObjectModel; +using PdfSharp.Quality; + +//using static PdfSharp.Diagnostics.DebugBreakHelper; +using System.Linq; + +// TODO: DELETE + +#pragma warning disable CS8321 // Local function is declared but never used + +namespace PdfSharp.Tests.PdfObjectModel +{ + public partial class ObjectModelTests + { + string Save(PdfDocument doc, string name) + { + string filename = PdfFileUtility.GetTempPdfFullFileName("_newStuff/" + name); + doc.Save(filename); + return filename; + } + + PdfDocument Load(string path) + { + var document = PdfReader.Open(path, PdfDocumentOpenMode.Modify); + return document; + } + + PdfDocument CreateDocument() + { + var doc = new PdfDocument(); + var page = new PdfPage(); + doc.AddPage(page); + return doc; + } + + // TODO + static void CheckParentInfoConsistency(PdfObject root) + { + Debug.Assert(root.IsIndirect); + var owner = root.Owner; + var closure = owner.IrefTable.TransitiveClosure(root); + + + foreach (var reference in closure) + { + var obj = reference.Value; + } + return; + + static void CheckArray(PdfArray array) + { + foreach (var item in array.Elements) + { + //var item = elements[name]; + if (item is PdfObject obj) + { + if (obj.Reference != null) + { + if (obj.ParentInfo != null) + throw new InvalidOperationException("ParentInfo must be null."); + } + else + { + if (obj.ParentInfo == null) + throw new InvalidOperationException("ParentInfo must not be null."); + + if (obj.ParentInfo.OwningArray != array) + throw new InvalidOperationException("ParentInfo must be owning array."); + } + } + } + } + + static void CheckDictionary(PdfDictionary dict) + { + foreach (var item in dict.Elements.Values) + { + if (item is PdfObject obj) + { + if (obj.Reference != null) + { + if (obj.ParentInfo != null) + throw new InvalidOperationException("ParentInfo must be null."); + } + else + { + if (obj.ParentInfo == null) + throw new InvalidOperationException("ParentInfo must not be null."); + + if (obj.ParentInfo.OwningDictionary != dict) + throw new InvalidOperationException("ParentInfo must be owning dictionary."); + } + } + } + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests.cs new file mode 100644 index 00000000..45ad9dae --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfObjectModel/ObjectModelTests.cs @@ -0,0 +1,298 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using Xunit; +using FluentAssertions; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.Internal; +using PdfSharp.Pdf.PdfItemExtensions; +using PdfSharp.Pdf.PdfDictionaryExtensions; + +//using static PdfSharp.Diagnostics.DebugBreakHelper; + +#pragma warning disable CS8321 // Local function is declared but never used + +// TODO: DELETE + +namespace PdfSharp.Tests.PdfObjectModel +{ + [Collection("PDFsharp")] + public partial class ObjectModelTests + { + [Fact] + public void TestTest() + { + var doc1 = CreateDocument(); + var catalog = doc1.Internals.Catalog; + + var dict3 = new TestDict3(doc1); + dict3.Comment = "Dict3"; + doc1.Internals.AddObject(dict3); + catalog.Elements.Add("/TestStuff3", dict3); + + // Save and reload. + var path = Save(doc1, nameof(TestTest) + "1"); + var doc2 = Load(path); + var cat2 = doc2.Internals.Catalog; + + var dict32Ref = (PdfReference?)cat2.Elements["/TestStuff3"]; + + //var abc = PdfObjectsHelper.TransformDictionary((PdfDictionary)(dict32Ref!.Value)); + + //var x = dict32Ref.Value.AsDictionary().Elements[TestDict3.Keys.TestDict1]; + //var xt = x.GetType(); + var y = dict32Ref!.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1); + var z = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1Ref); + var z2 = dict32Ref.Value.AsDictionary().Elements.GetValue(TestDict3.Keys.TestDict1Ref); + + //var xxx = dict32.Elements.GetItemNew( + // TestDict3.Keys.TestDict1, ObjMagic.Default, typeof(TestDict1)); + + var path2 = Save(doc2, nameof(TestTest) + "2"); + } + + [Fact] + public void Null_Objects_Test() + { + var doc1 = CreateDocument(); + var cat1 = doc1.Internals.Catalog; + + cat1.Elements["/NullItem"] = PdfNull.Value; + + //var nullObject1 = new PdfNullObject(); + //cat1.Elements["/NullDirectObject"] = nullObject1; + + var nullObject2 = new PdfNullObject(); + doc1.Internals.AddObject(nullObject2); + cat1.Elements["/NullIndirectObject"] = nullObject2.RequiredReference; + + cat1.Elements["/undefRef"] = new PdfDebugItem(" 42000 0 R"); + + CheckParentInfoConsistency(cat1); + + // Save and reload. + var path = Save(doc1, nameof(Null_Objects_Test) + "1"); + //ShouldBreak2 = true; + var doc2 = Load(path); + //ShouldBreak2 = false; + var cat2 = doc2.Internals.Catalog; + + cat2.Elements["/NullItem"].Should().Be(PdfNull.Value); + + // PDFsharp reads 'null' as PdfNull, not as PdfNullObject. + //cat2.Elements["/NullDirectObject"]!.GetType().Should().Be(typeof(PdfNull)); + + // PdfNullObject is only read when it is an indirect object. + cat2.Elements["/NullIndirectObject"]!.GetType().Should().Be(typeof(PdfReference)); + cat2.Elements["/NullIndirectObject"].AsReference().Value.GetType().Should().Be(typeof(PdfNullObject)); + + // An undefined reference is read as null object. + cat2.Elements["/undefRef"]!.Should().Be(PdfNull.Value); + + var pages1a = cat2.Elements["/Pages"].AsReference().Value.AsDictionary(); + //var pages1b = cat2.GetItem("/Pages").AsReference().Value.AsDictionary(); + //var pages1c = cat2.GetItem("/Pages").AsDictionary(); + //ReferenceEquals(pages1a, pages1b).Should().BeTrue(); + //ReferenceEquals(pages1a, pages1c).Should().BeTrue(); + var pages2 = cat2.Elements["/Pages"].AsReference().AsDictionary(); + var pages3 = cat2.Elements["/Pages"].AsDictionary(); + var k = pages3.Elements["/Kids"]; + var kids = pages3.Elements["/Kids"].AsArray(); + kids.IsIndirect.Should().BeFalse(); + kids.ParentInfo.Should().NotBeNull(); + kids.IsDead.Should().BeFalse(); + + //ShouldBreak1 = false; + + var path2 = Save(doc2, nameof(Null_Objects_Test) + "2"); + } + + [Fact] + public void Throw_on_used_object_Test() + { + var doc1 = CreateDocument(); + var cat1 = doc1.Internals.Catalog; + + var dict1 = new TestDict1(); + + cat1.Elements["/DirectObject"] = dict1; + + // Must not add direct object more than once. + var addTwice = () => cat1.Elements["/DirectObjectTwice"] = dict1; + addTwice.Should().Throw(); + + // Must not convert used direct object to indirect object. + var makeIndirect = () => doc1.Internals.AddObject(dict1); + makeIndirect.Should().Throw(); + + // Save and reload. + var path = Save(doc1, nameof(Throw_on_used_object_Test) + "1"); + var doc2 = Load(path); + var cat2 = doc2.Internals.Catalog; + + var dict21 = cat2.Elements["/DirectObject"]; + var dict22 = cat2.Elements.GetValue("/DirectObject"); + + + var path2 = Save(doc2, nameof(Throw_on_used_object_Test) + "2"); + + + + // Make used direct object indirect. + + // Set indirect object as direct one. + + // Ensure alive test + + // 2 reference to same object. + } + + [Fact] + public void Throw_on_dead_object_Test() + { + var doc1 = CreateDocument(); + var cat1 = doc1.Internals.Catalog; + + var dict3 = new TestDict3(doc1); + doc1.Internals.AddObject(dict3); + cat1.Elements["/IndirectObject3"] = dict3; + + // Save and reload. + var path = Save(doc1, nameof(Throw_on_dead_object_Test) + "1"); + var doc2 = Load(path); + var cat2 = doc2.Internals.Catalog; + + var dict32 = cat2.Elements["/IndirectObject3"]; + + var b1 = ((PdfReference)dict32!).AsDictionary(); + ////var b2 = PdfObjectsHelper.TransformDictionary(b1); + ////var abc = PdfObjectsHelper.TransformDictionary(((PdfReference)dict32!).AsDictionary()); + + //var a1 = abc.Elements[TestDict3.Keys.TestDict1Ref]; + //var a2 = a1.AsReference(); + //var a3 = a2.Value; + + //var dict1Base = abc.Elements[TestDict3.Keys.TestDict1Ref].AsReference().Value; + //dict1Base.IsDead.Should().BeFalse(); + //var dict1Der = abc.Elements.GetValue(TestDict3.Keys.TestDict1Ref); + //dict1Base.IsDead.Should().BeTrue(); + + //var d1 = dict1Base.IsDead; + ////var d2 = dict1Base.IsDead2; + ////var d3 = dict1Base.IsDead3; + //// var elements = dict1Base.As().Elements; + + //var test1 = () => _ = dict1Base.AsDictionary().Elements; + //test1.Should().Throw(); + + //var addDeadObject = () => cat2.Elements.Add("/TestDeadObject", dict1Base); + //addDeadObject.Should().Throw(); + + ////var reference = new PdfReference(cat2, PdfObjectID.Empty, 42); + + //var path2 = Save(doc2, nameof(Throw_on_used_object_Test) + "2"); + } + + [Fact] + public void Throw_on_direct_non_containers_Test() + { + var doc = CreateDocument(); + var cat = doc.Internals.Catalog; + + var addDirectObject = () => cat.Elements.Add("/test", new PdfIntegerObject(42)); + addDirectObject.Should().Throw(); + + var path2 = Save(doc, nameof(Throw_on_direct_non_containers_Test) + "2"); + } + + [Fact] + public void Basic_PdfDictionary_Test() + { + var doc = CreateDocument(); + var catalog = doc.Internals.Catalog; + + var dict1 = new TestDict1(); + catalog.Elements.Add("/TestStuff1", dict1); + //catalog.Elements.Add("/TestStuff1a", dict1); now fails + var addDirectObjectTwice = () => catalog.Elements.Add("/TestStuff1a", dict1); + addDirectObjectTwice.Should().Throw(); + + dict1.Elements.Add("/dummy", new PdfString("Hello")); + + var someInt = new PdfInteger(42); + catalog.Elements.Add("/SomeInt1", someInt); + catalog.Elements.Add("/SomeInt1a", someInt); + + + var dict2 = new TestDict2(); + doc.Internals.AddObject(dict2); + catalog.Elements.Add("/TestStuff2", dict2); + + var dict3 = new TestDict3(doc); + doc.Internals.AddObject(dict3); + catalog.Elements.Add("/TestStuff3", dict3); + + + + var path = Save(doc, nameof(Basic_PdfDictionary_Test)); + var doc2 = Load(path); + + + var cat2 = doc2.Internals.Catalog; + + var dict32Ref = (PdfReference?)cat2.Elements["/TestStuff3"]; + //var dict32 = (PdfDictionary?)cat2.Elements["/TestStuff3"]; + //var dict32 = PdfReference.Dereference(dict32Ref!); + var dict32 = cat2.Elements["/TestStuff3"].AsDictionary(); + + var dict1Dir = dict32.Elements[TestDict3.Keys.TestDict1]; + var dict1Ind = dict32.Elements[TestDict3.Keys.TestDict1Ref]; + } + + [Fact] + public void Basic_PdfArray_Test() + { + var doc = CreateDocument(); + var catalog = doc.Internals.Catalog; + + var dict1 = new TestDict1(); + var dict2 = new TestDict1(); + var array1 = new PdfArray(); + catalog.Elements.Add("/Array1", array1); + array1.Elements.Add(dict1); + array1.Elements.Add(dict2); + + dict1 = new TestDict1(); + dict2 = new TestDict1(); + var array2 = new PdfArray(); + doc.Internals.AddObject(array2); + catalog.Elements.Add("/Array2", array2); + array2.Elements.Add(dict1); + array2.Elements.Add(dict2); + + var path = Save(doc, nameof(Basic_PdfArray_Test)); + var doc2 = Load(path); + } + + [Fact] + public void Reuse_of_direct_object_Test() + { + //var item = new PdfStringObject("abc", PdfStringEncoding.Unicode); + var item = new PdfDictionary(); + + var useDirectObjectTwice = () => + { + var dict = new TestDict1 + { + Elements = + { + ["Key1"] = item, + ["Key2"] = item + } + }; + }; + useDirectObjectTwice.Should().Throw(); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj index 3fd160cc..4286a509 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj @@ -1,45 +1,45 @@  - net8.0;net9.0;net10.0;net462 - CORE + $(PDFsharpTargetFrameworks_Tests_Exe) + $(DefineConstants);CORE - CS1685,CS0436 + $(NoWarn);1685;0436 True ..\..\..\..\..\StrongnameKey.snk + $(DefineConstants);TRACE;CORE + False $(DefineConstants);TRACE;CORE - - False - - - - False - - + + + + - + + + @@ -54,7 +54,8 @@ - + + diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj.DotSettings b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj.DotSettings index cf35687a..3653b90a 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj.DotSettings +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/PdfSharp.Tests.csproj.DotSettings @@ -7,7 +7,13 @@ True True True + False + True True True + True True + True + True + True True \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Properties/GlobalDeclarations.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Properties/GlobalDeclarations.cs index e4265a5c..3fbded23 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Properties/GlobalDeclarations.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Properties/GlobalDeclarations.cs @@ -1,7 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +global using System.Diagnostics; global using System.IO; +global using PdfSharp.DotNetFrameworkExtensions; global using PdfSharp.Internal; diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Structs/NameTests.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Structs/NameTests.cs new file mode 100644 index 00000000..b468ce28 --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/Structs/NameTests.cs @@ -0,0 +1,218 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; +using PdfSharp.Pdf.Forms; +using PdfSharp.Snippets.Fonts.Text; +using Xunit; +using FluentAssertions; + +namespace PdfSharp.Tests.Structs +{ + [Collection("PDFsharp")] + public class NameTests + { + [Fact] + public void Test_Name_ASCII_escaping() + { + const string ascii = "/!\"#$%&'()*+,_./:;<=>?[\\]^_`{|}~"; + + // Name + { + var name = Name.FromCanonicalName(ascii); + var literal = name.LiteralValue; + Name.FromLiteralName(literal).Value.Should().Be(ascii); + } + + // PdfName + { + new PdfName().Should().Be(PdfName.Empty); + + var name = new PdfName(ascii); + var literal = name.Name.LiteralValue; + new PdfName(Name.FromLiteralName(literal)).Value.Should().Be(ascii); + } + } + + [Fact] + public void Test_Name_constructor() + { + // Default constructor. + { + var name = new Name(); + name.LiteralValue.Should().Be("/"); + name.Value.Should().Be("/"); + } + + // Canonical name constructor. + { + var name = new Name("/Name"); + name.LiteralValue.Should().Be("/Name"); + name.Value.Should().Be("/Name"); + + name = new Name("/@Name_123"); + name.LiteralValue.Should().Be("/@Name_123"); + name.Value.Should().Be("/@Name_123"); + + name = new Name("/Ä"); + } + } + + [Fact] + public void Test_Name_creation() + { + // FromLiteralName + { + var name = Name.FromLiteralName("/@Simple_Name"); + name.LiteralValue.Should().Be("/@Simple_Name"); + name.Value.Should().Be("/@Simple_Name"); + + name = Name.FromLiteralName("/Lime#20Green"); + name.LiteralValue.Should().Be("/Lime#20Green"); + name.Value.Should().Be("/Lime Green"); + + name = Name.FromLiteralName("/#41"); + name.LiteralValue.Should().Be("/#41"); + name.Value.Should().Be("/A"); + } + + // FromCanonicalName + { + var name = Name.FromCanonicalName("/@Simple_Name"); + name.LiteralValue.Should().Be("/@Simple_Name"); + name.Value.Should().Be("/@Simple_Name"); + + name = Name.FromCanonicalName("/Lime#20Green"); + name.LiteralValue.Should().Be("/Lime#2320Green"); + name.Value.Should().Be("/Lime#20Green"); + + name = Name.FromCanonicalName("////"); + name.LiteralValue.Should().Be("/#2F#2F#2F"); + name.Value.Should().Be("////"); + + name = Name.FromCanonicalName("/#41"); + name.LiteralValue.Should().Be("/#2341"); + name.Value.Should().Be("/#41"); + } + } + + [Fact] + public void Test_Name_UTF8() + { + // FromLiteralName + { + // An invalid literal name is reevaluated + var name = Name.FromLiteralName("/Umlauts-#41#C3#96#C3#9C#C3#A4#C3#B6#C3#BC#C3#9F-AÖÜäöüß"); + name.LiteralValue.Should().Be("/Umlauts-A#C3#96#C3#9C#C3#A4#C3#B6#C3#BC#C3#9F-A#C3#96#C3#9C#C3#A4#C3#B6#C3#BC#C3#9F"); + name.Value.Should().Be("/Umlauts-AÖÜäöüß-AÖÜäöüß"); + } + + // FromCanonicalName + { + var name = Name.FromCanonicalName("/UmlautsÄÖÜäöüß"); + name.LiteralValue.Should().Be("/Umlauts#C3#84#C3#96#C3#9C#C3#A4#C3#B6#C3#BC#C3#9F"); + name.Value.Should().Be("/UmlautsÄÖÜäöüß"); + + name = Name.FromCanonicalName(Name.MakeName(ShortTestTexts.GoodMorning_Korean)); + var l = name.LiteralValue; + var c = name.Value; + c.Should().Be(Name.FromLiteralName(l).Value); + } + } + + [Fact] + public void Test_Name_Enums() + { + var name = Name.FromEnum(PdfFormFieldFlags.DoNotSpellCheckChoiceField); + name.Value.Should().Be("/DoNotSpellCheckChoiceField"); + } + + [Fact] + public void Test_ill_formatted_Names() + { + // Ill formatted but legal. + { + // Invalid hex values. + var name = Name.FromLiteralName("/Name#4"); + name.LiteralValue.Should().Be("/Name#40"); + name.Value.Should().Be("/Name@"); + } + + // Ill formatted and illegal. + { + var name = Name.FromLiteralName("/Name#0"); + name.LiteralValue.Should().Be("/Name"); + name.Value.Should().Be("/Name"); + + name = Name.FromLiteralName("/Name#"); + name.LiteralValue.Should().Be("/Name"); + name.Value.Should().Be("/Name"); + + name = Name.FromLiteralName("/Name#XY"); + name.LiteralValue.Should().Be("/Name"); + name.Value.Should().Be("/Name"); + + name = Name.FromLiteralName("/Name#0X"); + name.LiteralValue.Should().Be("/Name"); + name.Value.Should().Be("/Name"); + + name = Name.FromLiteralName("/Name#00XY"); + name.LiteralValue.Should().Be("/NameXY"); + name.Value.Should().Be("/NameXY"); + + name = Name.FromLiteralName("/Name#XYAB"); + name.LiteralValue.Should().Be("/NameAB"); + name.Value.Should().Be("/NameAB"); + + name = Name.FromLiteralName("/Name#0XYAB"); + name.LiteralValue.Should().Be("/NameYAB"); + name.Value.Should().Be("/NameYAB"); + } + } + + [Fact] + public void Test_Name_comparison() + { + var ar = String.Compare("A", "A", StringComparison.Ordinal); + var br = String.Compare("A", "B", StringComparison.Ordinal); + var cr = String.Compare("B", "A", StringComparison.Ordinal); + var dr = String.Compare("A", null, StringComparison.Ordinal); + var er = String.Compare(null, "B", StringComparison.Ordinal); + var fr = String.Compare(null, null, StringComparison.Ordinal); + + // Name + { + var comp = Name.Comparer; + var an = comp.Compare(new("/A"), new("/A")); + an.Should().Be(ar); + var bn = comp.Compare(new("/A"), new("/B")); + bn.Should().Be(br); + var cn = comp.Compare(new("/B"), new("/A")); + cn.Should().Be(cr); + var dn = comp.Compare(new("/A"), null); + dn.Should().Be(dr); + var en = comp.Compare(null, new("/B")); + en.Should().Be(er); + var fn = comp.Compare(null, null); + fn.Should().Be(fr); + } + + // PdfName + { + var comp = Name.Comparer; + var an = comp.Compare(new("/A"), new("/A")); + an.Should().Be(ar); + var bn = comp.Compare(new("/A"), new("/B")); + bn.Should().Be(br); + var cn = comp.Compare(new("/B"), new("/A")); + cn.Should().Be(cr); + var dn = comp.Compare(new("/A"), null); + dn.Should().Be(dr); + var en = comp.Compare(null, new("/B")); + en.Should().Be(er); + var fn = comp.Compare(null, null); + fn.Should().Be(fr); + } + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/TestConfig.cs b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/TestConfig.cs new file mode 100644 index 00000000..a7a2ddcc --- /dev/null +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/TestConfig.cs @@ -0,0 +1,14 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Tests +{ + public static class TestConfig + { + public static void CheckManually(DateTime date) + { + if (date < new DateTime(2025, 11, 9)) + throw new InvalidOperationException("Check this test manually and update the date."); + } + } +} diff --git a/src/foundation/src/PDFsharp/tests/PdfSharp.tests-wpf/PdfSharp.tests-wpf.csproj b/src/foundation/src/PDFsharp/tests/PdfSharp.tests-wpf/PdfSharp.tests-wpf.csproj index 8649ba7a..a25d7e7f 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.tests-wpf/PdfSharp.tests-wpf.csproj +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.tests-wpf/PdfSharp.tests-wpf.csproj @@ -1,14 +1,14 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Tests_Windows) true true - WPF + $(DefineConstants);WPF - CS1685,CS0436 + $(NoWarn);1685;0436 True ..\..\..\..\..\StrongnameKey.snk @@ -28,8 +28,12 @@ - + + + + + @@ -49,14 +53,7 @@ - - - - - - - @@ -76,8 +73,5 @@ - - - diff --git a/src/foundation/src/extensions/src/PdfSharp.Extensions/PdfSharp.Extensions.csproj b/src/foundation/src/extensions/src/PdfSharp.Extensions/PdfSharp.Extensions.csproj new file mode 100644 index 00000000..bc4d6bed --- /dev/null +++ b/src/foundation/src/extensions/src/PdfSharp.Extensions/PdfSharp.Extensions.csproj @@ -0,0 +1,21 @@ + + + + $(PDFsharpTargetFrameworks_Library) + PdfSharp + True + ..\..\..\..\..\StrongnameKey.snk + + + + true + + + + + + + + + + diff --git a/src/foundation/src/extensions/src/PdfSharp.Extensions/README.md b/src/foundation/src/extensions/src/PdfSharp.Extensions/README.md new file mode 100644 index 00000000..dfa12ccb --- /dev/null +++ b/src/foundation/src/extensions/src/PdfSharp.Extensions/README.md @@ -0,0 +1,5 @@ +# PDFsharp.Fonts + +This is the PDFsharp.Extensions build project. +It is not yet in use and currently a placeholder. +PDFsharp does not depend on this project. diff --git a/src/foundation/src/shared/src/PdfSharp.BuildConfig/BuildConfig.json b/src/foundation/src/shared/src/PdfSharp.BuildConfig/BuildConfig.json new file mode 100644 index 00000000..f96651ff --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.BuildConfig/BuildConfig.json @@ -0,0 +1,4 @@ +{ // PDFsharp build configuration + "PDFsharp_PackageName": "PDFsharp", + "PDFsharpMigraDoc_PackageName": "PDFsharp-MigraDoc" +} \ No newline at end of file diff --git a/src/foundation/src/shared/src/PdfSharp.BuildConfig/CreateBuildConfiguration.ps1 b/src/foundation/src/shared/src/PdfSharp.BuildConfig/CreateBuildConfiguration.ps1 new file mode 100644 index 00000000..fb54ac04 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.BuildConfig/CreateBuildConfiguration.ps1 @@ -0,0 +1,456 @@ +# The PdfSharp.BuildConfig project allows us to eliminate the GitVersion build task from our projects. +# CreateBuildConfiguration.ps1 is the central workhorse of this project. +# This script collects data from git to emulate the behavior of the GitVersion build task. +# Additionally, it supports two files that can be used to overwrite the values determined from git. + +# File SemVersion.json: +# This file must be in the same folder as this PowerShell script file. +# It can be used to overwrite settings when creating DLLs or NuGet packages. + +# File PdfSharpBuildConfig.json: +# This file must be located in a folder above the PDFsharp repository. +# When PDFsharp is used as a submodule, this file allows overwriting settings from outside, +# allowing the project that uses PDFsharp as a submodule to create DLLs or NuGet packages +# with individual version settings. + +# PDFSHARP_BUILD_VERSION are the days since 2005-01-01. #CHECK_BEFORE_RELEASE +# Command Window: ? (DateTime.Now - new DateTime(2005, 1, 1)).Days; +# C# Interactive: Console.WriteLine((DateTime.Now - new DateTime(2005, 1, 1)).Days); +# assembly-file-versioning-format: '{Major}.{Minor}.{Patch}.{env:PDFSHARP_BUILD_VERSION ?? 7342}' + +# Revision: 26-03-20 StLa Works now without .git folder. +# Revision: 25-10-09 ThHo Review + +# FilterBranchName: Replace characters that are not allowed in SemVer names. +function FilterBranchName([string]$name) +{ + return $name.Replace('/', '-').Replace('_', '-') +} + +#CHECK_BEFORE_RELEASE +# Predefined values if we have no .git at all or git but no tag. +$NoVersionTag = "v7.0.0-preview-1" # Default value if no git tag can be found. +$Product = "PDFsharp 7.0-Preview-1" # Default product name. +$branchName = "release/7.0.0-preview-1" # Default branch name if not compiled from a git repo. +$commitDate = "2026-03-23" # Default commit date if not compiled from a git repo. + +Push-Location $PSScriptRoot + +# The first directory above the PDFsharp repository. +$buildConfigFolder = "..\..\..\..\..\..\..\" + +if (Test-Path -Path "..\..\..\..\..\..\.git") { + # Get all required information directly from git. + $branchName = git branch --show-current # Get name of current branch (or more general: git rev-parse --abbrev-ref HEAD). + $commitDate = git log -1 --format="%cd" --date=format:'%Y-%m-%d' # Get commit date in format 'YYYY-MM-DD'. + $gitTag = git describe --abbrev=0 --tags # Get the last tag (required form: 'v6.2.0[-pre-release-label]'). + if ($gitTag.Length -eq 0) { + $gitTag = $NoVersionTag + } + $dashPos = $gitTag.IndexOf('-') # Check if pre-release label is part of the git tag. + $gitVersionTag = $dashPos -gt 0 ? $($gitTag.Substring(0, $dashPos)) : $gitTag + $gitLabelTag = $dashPos -gt 0 ? $gitTag.Substring($dashPos + 1) : "" + $buildVersion = ($(Get-Date) - $([datetime]"2005-01-01")).Days # Days since January 1st 2005. + $preReleaseNumber = git rev-list --count HEAD ^$gitTag # Get number of commits since last tag. + $majorMinorPatch = $gitVersionTag.Substring(1) # Git tags must start with a 'v'. + $sha = git rev-parse HEAD # Get full commit SHA. + $shortSHA = git rev-parse --short HEAD # Get short commit SHA. +} +else { + # Maybe PDFsharp is from a zip file. We must fake it. + # $branchName is taken from above. + # $commitDate is taken from above. + $gitTag = $NoVersionTag + $dashPos = $gitTag.IndexOf('-') + $gitVersionTag = $dashPos -gt 0 ? $($gitTag.Substring(0, $dashPos)) : $gitTag + $gitLabelTag = $dashPos -gt 0 ? $gitTag.Substring($dashPos + 1) : "" + $buildVersion = ($(Get-Date) - $([datetime]"2005-01-01")).Days + $preReleaseNumber = 0 + $majorMinorPatch = $gitVersionTag.Substring(1) + $sha = "0000000000000000000000000000000000000000" + $shortSHA = "000000000" +} + +# Read SemVersion.json. +# This file is read from the script directory. +$predefinedSemVersion = Get-Content -Path "SemVersion.json" -Raw | ConvertFrom-Json -AsHashtable +# Sample contents: +# "UseThisInformation": 1, +# "MajorMinorPatch": "6.9.100", +# "SemVer": "6.9.100", +# "InformationalVersion": "6.3.0-dev-factur-x0171", +# "BranchName": "empira/v6.9.100", +# "PreReleaseTag": "", +# "PreReleaseLabel": "", +# "PreReleaseNumber": 0, +if ($predefinedSemVersion.UseThisInformation -eq 1) +{ + $majorMinorPatch = $predefinedSemVersion.MajorMinorPatch.Length -gt 0 ? $predefinedSemVersion.MajorMinorPatch : $majorMinorPatch + $branchName = $predefinedSemVersion.BranchName.Length -gt 0 ? $predefinedSemVersion.BranchName : $branchName + $preReleaseLabel = $predefinedSemVersion.PreReleaseLabel.Length -gt 0 ? $predefinedSemVersion.PreReleaseLabel : $preReleaseLabel + $preReleaseLabelOverride = $preReleaseLabel # A given tag must not be overwritten by a calculated tag. + $preReleaseNumber = $predefinedSemVersion.PreReleaseNumber.Length -gt 0 ? $predefinedSemVersion.PreReleaseNumber : $preReleaseNumber +} +else { + $preReleaseLabel = $gitLabelTag +} + +# Read PdfSharpBuildConfig.json. +# This file is read from the first directory above the PDFsharp repository. +# Values used: +# "SemVer": Provides "MajorMinorPatch" and "PreReleaseTag" if present. +# "MajorMinorPatch" +# "PreReleaseTag" +# "BranchName" +$buildConfigPath = "$($buildConfigFolder)PdfSharpBuildConfig.json" +if (Test-Path $buildConfigPath) { + $buildConfig = Get-Content -Path $buildConfigPath -Raw | ConvertFrom-Json -AsHashtable +} +else { + $buildConfig = @{ } +} + +# Examine SemVer, if it was specified. Split it to get MajorMinorPatch and PreReleaseTag. +if ($buildConfig.SemVer.Length -gt 0) { + $semVersion = $buildConfig.SemVer + $hyphenPos = $semVersion.IndexOf('-') + # Override BranchName if SemVer is given. + if ($hyphenPos -gt 0) { + $majorMinorPatch = $semVersion.Substring(0, $hyphenPos) + $preReleaseLabelOverride = $preReleaseTag = $semVersion.Substring($hyphenPos + 1) # A given tag must not be overwritten by a calculated tag. + $branchName = "master/$($semVersion)" + } else { + $majorMinorPatch = $semVersion + $preReleaseLabelOverride = $preReleaseTag = "" + $branchName = "master/$($semVersion)" + } +} +$majorMinorPatch = $buildConfig.MajorMinorPatch.Length -gt 0 ? $buildConfig.MajorMinorPatch : $majorMinorPatch +$Product = $buildConfig.Product.Length -gt 0 ? $buildConfig.Product : $Product +if ($buildConfig.PreReleaseTag.Length -gt 0) { + $preReleaseLabelOverride = $preReleaseTag = $buildConfig.PreReleaseTag # A given tag must not be overwritten by a calculated tag. +} +$branchName = $buildConfig.BranchName.Length -gt 0 ? $buildConfig.BranchName : $branchName + +# If branch name could not be determined, use "develop" as branch name. +if ($null -eq $branchName) { + $branchName = "develop" +} + +$slashPos = $branchName.LastIndexOf('/') +$shortBranchName = $slashPos -gt 0 ? $branchName.Substring($slashPos + 1) : $branchName + +# Create an empty dictionary. +$semVersion = @{ } + +# Get branch specific information. +$semVersion['BranchName'] = $branchName + +# We allow only ASCII characters, digits, and dashes ('-') in branch names. We support accidentally added underscores. +$semVersion['EscapedBranchName'] = FilterBranchName($branchName) # BranchName suitable for Semver. +$semVersion['ShortBranchName'] = $shortBranchName +$semVersion['CommitDate'] = $commitDate + +$semVersion['BuildVersion'] = $buildVersion +$semVersion['PreReleaseNumber'] = $preReleaseNumber # Does not depend on branch name. +$semVersion['MajorMinorPatch'] = $majorMinorPatch +# PDFsharp build number. +$semVersion['AssemblySemFileVer'] = "$majorMinorPatch.$buildVersion" # Does not depend on branch name. +$tagSplit = [Array]$majorMinorPatch.Split(".") +$semVersion['Major'] = $tagSplit[0] +$semVersion['Minor'] = $tagSplit[1] +$semVersion['Patch'] = $tagSplit[2] + +# SHA +$semVersion["SHA"] = $SHA +$semVersion["ShortSHA"] = $shortSHA +$semVersion['GitTag'] = $gitTag + +# Analyze branch name to determine SemVer. +# We have/need: +# * develop +# * feature, bug, ... +# * release or master +# * anything else (user/stla/myfeatures/...) + +# Cases depend on branch name. +$branch = $branchName.ToLower() + +# Branch names that are to be treated like "feature". +if ($branch.StartsWith("customer/")) { + $branch = $branch.Replace("customer/", "feature/") +} + +switch -Regex ($branch) { + '^develop$' { + # Treat as prerelease. + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "develop" + $preReleaseNumber + $preReleaseLabel = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "develop" + } + '^feature/(.*)$' { + # Branches that begin with "feature/" will always be treated as prereleases unless the final folder begins with a version number. + while($true) { + $name = $($Matches[1]) + $idxSlash = $name.LastIndexOf('/') + if ($idxSlash -ge 0) { + $lastElement = $name.Substring($idxSlash + 1) + # SemVer: "^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9A-Za-z-][0-9A-Za-z-]*)(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$" + $pattern = '^(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)\.(0|[1-9][0-9]*)(-(0|[1-9A-Za-z-][0-9A-Za-z-]*)(\.[0-9A-Za-z-]+)*)?(\+[0-9A-Za-z-]+(\.[0-9A-Za-z-]+)*)?$' + if ($lastElement -match $pattern) { + # Last element seems to be a valid Semver, so we extract version information from it. + $idxHyphen = $lastElement.IndexOf('-') + if ($idxHyphen -ge 0) { + # It is a prerelease. + $majorMinorPatch = $lastElement.Substring(0, $idxHyphen) + $preReleaseTag = $lastElement.Substring($idxHyphen + 1) + $preReleaseLabel = $preReleaseTag + } else { + # It is a final release. + $majorMinorPatch = $lastElement + $preReleaseTag = '' + $preReleaseLabel = '' + } + $semVersion['MajorMinorPatch'] = $majorMinorPatch + # PDFsharp build number. + $semVersion['AssemblySemFileVer'] = "$majorMinorPatch.$buildVersion" # Does not depend on branch name. + $tagSplit = [Array]$majorMinorPatch.Split(".") + $semVersion['Major'] = $tagSplit[0] + $semVersion['Minor'] = $tagSplit[1] + $semVersion['Patch'] = $tagSplit[2] + break + } + } + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "dev-$(FilterBranchName($Matches[1]))-" + $preReleaseNumber + $preReleaseLabel = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "dev-$(FilterBranchName($Matches[1]))" + break + } + } + '^user/(.*)$' { + # Treat basically like "feature" for now, but do not read version from last folder. + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "dev-$(FilterBranchName($Matches[1]))-" + $preReleaseNumber + $preReleaseLabel = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "dev-$(FilterBranchName($Matches[1]))" + } + # Release: "release/6.2.0-preview-3" => "preview-3"; "release/6.2.0" => "" + '^release/(.*)$' { + $idx = $Matches[1].IndexOf("-") + if ($idx -lt 0) { + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "" + } else { + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : (FilterBranchName($Matches[1].Substring($idx + 1))) + } + $preReleaseLabel = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : $preReleaseTag + } + # Treat empira like Release for now. + '^empira/(.*)$' { + $idx = $Matches[1].IndexOf("-") + if ($idx -lt 0) { + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "" + } else { + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : (FilterBranchName($Matches[1].Substring($idx + 1))) + } + $preReleaseLabel = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : $preReleaseTag + } + # Treat master like Release for now. + '^master/(.*)$' { + $idx = $Matches[1].IndexOf("-") + if ($idx -lt 0) { + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "" + } else { + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : (FilterBranchName($Matches[1].Substring($idx + 1))) + } + $preReleaseLabel = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : $preReleaseTag + } + default { + $preReleaseTag = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : ("dev-" + (FilterBranchName($branch))) + $preReleaseLabel = -not $null -eq $preReleaseLabelOverride ? $preReleaseLabelOverride : "dev-" + (FilterBranchName($branch)) + } +} + +if ($preReleaseTag -eq "") { # Case: final release. + $semVersion['PreReleaseTag'] = "" + $semVersion['PreReleaseLabel'] = "" + $semVersion['SemVer'] = "$majorMinorPatch" + $semVersion['AssemblySemFileVer'] = "$majorMinorPatch.$buildVersion" + $semVersion['InformationalVersion'] = "$majorMinorPatch" +} else { # Case: pre-release. + $semVersion['PreReleaseTag'] = $preReleaseTag + $semVersion['PreReleaseLabel'] = $preReleaseLabel + $semVersion['SemVer'] = "$majorMinorPatch-$preReleaseTag" + $semVersion['AssemblySemFileVer'] = "$majorMinorPatch.$buildVersion" + $semVersion['InformationalVersion'] = "$majorMinorPatch-$preReleaseLabel-$preReleaseNumber" +} + +# Define template for SemVersion.props. +$templateSemVerProps = @" + + + + + {Version} + {FileVersion} + {InformationalVersion} + {Major} + {Minor} + {Patch} + {PreReleaseLabel} + {SemVer} + {BranchName} + {CommitDate} + {Sha} + {ShortSha} + + + +"@ + +$outputSemVerProps = $templateSemVerProps ` + -replace "{Version}", $semVersion['MajorMinorPatch'] ` + -replace "{FileVersion}", $semVersion['AssemblySemFileVer'] ` + -replace "{InformationalVersion}", $semVersion['InformationalVersion'] ` + -replace "{Major}", $semVersion['Major'] ` + -replace "{Minor}", $semVersion['Minor'] ` + -replace "{Patch}", $semVersion['Patch'] ` + -replace "{PreReleaseLabel}", $semVersion['PreReleaseLabel'] ` + -replace "{SemVer}", $semVersion['SemVer'] ` + -replace "{BranchName}", $semVersion['BranchName'] ` + -replace "{CommitDate}", $semVersion['CommitDate'] ` + -replace "{Sha}", $semVersion['Sha'] ` + -replace "{ShortSha}", $semVersion['ShortSha'] + +# Read old file. +$oldOutputSemVerProps = "" +try { + $oldOutputSemVerProps = Get-Content -Path "../../../../../SemVersion.props" -Raw -ErrorAction Stop +} +catch { + $oldOutputSemVerProps = "" +} + +# Write SemVersion.props in {solution-root}/src if contents have changed. +if ($oldOutputSemVerProps.Length -eq 0 -or -not $oldOutputSemVerProps.Contains($outputSemVerProps)) { + $outputSemVerProps | Out-File -FilePath "../../../../../SemVersion.props" +} +else { + # Write-Output "Skipped writing SemVersion.props because contents did not change." +} + +$templateSemVerInfo = @" +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +//------------------------------------------------------------------------------ +// +// This code was generated by a build step of project PdfSharp.BuildConfig. +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace PdfSharp.Internal +{ + public static partial class SemVersionInformation + { + const string SemMajor = "{Major}"; + const string SemMinor = "{Minor}"; + const string SemPatch = "{Patch}"; + const string SemVersion = "{SemVersion}"; + const string SemFileVersion = "{FileVersion}"; + const string SemPreReleaseLabel = "{PreReleaseLabel}"; + const string SemInformationalVersion = "{InformationalVersion}"; + const string SemBranchName = "{BranchName}"; + const string SemCommitDate = "{CommitDate}"; + const string SemSha = "{Sha}"; + const string SemShortSha = "{ShortSha}"; + } +} +"@ + +# Replace placeholders. +$outputSemVerInfo = $templateSemVerInfo ` + -replace "{Major}", $semVersion['Major'] ` + -replace "{Minor}", $semVersion['Minor'] ` + -replace "{Patch}", $semVersion['Patch'] ` + -replace "{SemVersion}", $semVersion['SemVer'] ` + -replace "{FileVersion}", $semVersion['AssemblySemFileVer'] ` + -replace "{PreReleaseLabel}", $semVersion['PreReleaseLabel'] ` + -replace "{InformationalVersion}", $semVersion['InformationalVersion'] ` + -replace "{BranchName}", $semVersion['BranchName'] ` + -replace "{CommitDate}", $semVersion['CommitDate'] ` + -replace "{Sha}", $semVersion['Sha'] ` + -replace "{ShortSha}", $semVersion['ShortSha'] + +# Read old file. +$oldOutputSemVerInfo = "" +try { + $oldOutputSemVerInfo = Get-Content -Path "../PdfSharp.Shared/internal/SemVersionInformation-generated.cs" -Raw -ErrorAction Stop +} +catch { + $oldOutputSemVerInfo = "" +} +# Write SemVersionInformation-generated.cs if contents have changed. +if ($oldOutputSemVerInfo -eq 0 -or -not $oldOutputSemVerInfo.Contains($outputSemVerInfo)) { + $outputSemVerInfo | Out-File -FilePath "../PdfSharp.Shared/internal/SemVersionInformation-generated.cs" +} +else { + # Write-Output "Skipped writing SemVersionInformation-generated.cs because contents did not change." +} + +################### + +# Define template for BuildConfig.props. +$templateBuildConfigProps = @" + + + + {PDFsharp_PackageName} + {PDFsharpMigraDoc_PackageName} + {Product} + + +"@ + +if ($buildConfig.Length -ne 0) { + $outputBuildConfigProps = $templateBuildConfigProps ` + -replace "{PDFsharp_PackageName}", $buildConfig['PDFsharp_PackageName'] ` + -replace "{PDFsharpMigraDoc_PackageName}", $buildConfig['PDFsharpMigraDoc_PackageName'] ` + -replace "{Product}", $buildConfig['Product'] ` + + # Read old file. + $oldOutputBuildConfigProps = "" + try { + $oldOutputBuildConfigProps = Get-Content -Path "../../../../../BuildConfig.props" -Raw -ErrorAction Stop + } + catch { + $oldOutputBuildConfigProps = "" + } + + # Write PdfSharpBuildConfig.props if contents have changed. + if ($oldOutputBuildConfigProps.Length -eq 0 -or -not $oldOutputBuildConfigProps.Contains($outputBuildConfigProps)) { + $outputBuildConfigProps | Out-File -FilePath "../../../../../PdfSharpBuildConfig.props" + } + else { + # Write-Output "Skipped writing PdfSharpBuildConfig.props because contents did not change." + } +} + +Pop-Location + +# Contents of SemVersion.props file generated by this script. +# BuildConfig: Can be set in PdfSharpBuildConfig.json located outside the repository so the project using PDFsharp as a submodule can control the names of the NuGet packages. +# +# # Build Comments +# { # Config +# "Version": "6.3.0", # ✔ "MajorMinorPatch" in BuildConfig. +# "FileVersion": "6.3.0.7443", # The 4th number is essential for installer software. It counts the days since January 1st 2005. +# "InformationalVersion": "6.3.0-develop0001", # +# "Major": 6, # +# "Minor": 3, # +# "Patch": 0, # +# "PreReleaseLabel": "develop", # ✔ Set from "PreReleaseTag" in BuildConfig. +# "SemVer": "6.3.0-develop.1", # ✔ Also used for NuGet. +# "BranchName": "develop", # ✔ +# "CommitDate": "2025-06-07", # +# "Sha": "a8e9a5dd283153ef472b755ee392651d8c6e9c99", # TODO: Write in PDF file. +# "ShortSha": "a8e9a5d" # TODO: Write in PDF file. +# } diff --git a/src/foundation/src/shared/src/PdfSharp.BuildConfig/PdfSharp.BuildConfig.csproj b/src/foundation/src/shared/src/PdfSharp.BuildConfig/PdfSharp.BuildConfig.csproj new file mode 100644 index 00000000..ae1834c2 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.BuildConfig/PdfSharp.BuildConfig.csproj @@ -0,0 +1,29 @@ + + + + net10.0 + enable + enable + PdfSharp.Internal + + + + + + + + + + + + + + + diff --git a/src/foundation/src/shared/src/PdfSharp.BuildConfig/Program.cs b/src/foundation/src/shared/src/PdfSharp.BuildConfig/Program.cs new file mode 100644 index 00000000..05266edb --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.BuildConfig/Program.cs @@ -0,0 +1,90 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using Console = System.Console; + +namespace PdfSharp.Internal +{ + internal class Program + { + // We completely eliminate GitVersion + /// + /// Why so cumbersome? + /// We want to create the GitVersion information during a pre-build step only once, + /// not more than 100 times for every single project and every target framework. + /// Executing + /// dotnet gitversion > GitVersion.json + /// in PowerShell would work fine, but requires GitVersion.Tool to be installed + /// as a global dotnet tool. We don’t want to force every user to install this + /// just to compile PDFsharp. So we create the GitVersion.json with this dummy + /// project and execute it during the pre-build step. + /// In this project GitVersion comes with a NuGet package. + /// + static void Main(string[] args) + { +#if true + Console.WriteLine("This program does nothing anymore."); + return; +#else + // 1st approach: + // Compile and execute this project in a pre-build step of PdfSharp.Shared. + // Comes to an endless recursion in Visual Studio build system and consumes + // all processor power, all memory and freezes Windows. + // Does not obviously not word. + var gitVersionJson = $$""" + { + "Major": "{{GitVersionInformation.Major}}", + "Minor": "{{GitVersionInformation.Minor}}", + "Patch": "{{GitVersionInformation.Patch}}", + "PreReleaseTag": "{{GitVersionInformation.PreReleaseTag}}", + "PreReleaseTagWithDash": "{{GitVersionInformation.PreReleaseTagWithDash}}", + "PreReleaseLabel": "{{GitVersionInformation.PreReleaseLabel}}", + "PreReleaseLabelWithDash": "{{GitVersionInformation.PreReleaseLabelWithDash}}", + "PreReleaseNumber":"{{GitVersionInformation.PreReleaseNumber}}", + "WeightedPreReleaseNumber": "{{GitVersionInformation.WeightedPreReleaseNumber}}", + "BuildMetaData":"{{GitVersionInformation.BuildMetaData}}", + "BuildMetaDataPadded":"{{GitVersionInformation.BuildMetaDataPadded}}", + "FullBuildMetaData": "{{GitVersionInformation.FullBuildMetaData}}", + "MajorMinorPatch": "{{GitVersionInformation.MajorMinorPatch}}", + "SemVer": "{{GitVersionInformation.SemVer}}", + "LegacySemVer": "{{GitVersionInformation.LegacySemVer}}", + "LegacySemVerPadded": "{{GitVersionInformation.LegacySemVerPadded}}", + "AssemblySemVer": "{{GitVersionInformation.AssemblySemVer}}", + "AssemblySemFileVer":"{{GitVersionInformation.AssemblySemFileVer}}", + "FullSemVer": "{{GitVersionInformation.FullSemVer}}", + "InformationalVersion":"{{GitVersionInformation.InformationalVersion}}", + "BranchName": "{{GitVersionInformation.BranchName}}", + "EscapedBranchName":"{{GitVersionInformation.EscapedBranchName}}", + "Sha": "{{GitVersionInformation.Sha}}", + "ShortSha": "{{GitVersionInformation.ShortSha}}", + "NuGetVersionV2": "{{GitVersionInformation.NuGetVersionV2}}", + "NuGetVersion": "{{GitVersionInformation.NuGetVersion}}", + "NuGetPreReleaseTagV2": "{{GitVersionInformation.NuGetPreReleaseTagV2}}", + "NuGetPreReleaseTag": "{{GitVersionInformation.NuGetPreReleaseTag}}", + "VersionSourceSha": "{{GitVersionInformation.VersionSourceSha}}", + "CommitsSinceVersionSource": "{{GitVersionInformation.CommitsSinceVersionSource}}", + "CommitsSinceVersionSourcePadded": "{{GitVersionInformation.CommitsSinceVersionSourcePadded}}", + "UncommittedChanges": "{{GitVersionInformation.UncommittedChanges}}", + "CommitDate": "{{GitVersionInformation.CommitDate}}" + } + """; + + var path = Directory.GetCurrentDirectory(); + if (Path.GetExtension(path) == ".GitVersion") + { + var file = Path.Combine(path, "../PdfSharp.Shared/GitVersion.json"); + //Console.WriteLine(file); + // We are executed from during a pre-build step. + File.WriteAllText(file, gitVersionJson); + } + else + { + Console.WriteLine("file"); + + // We are executed from a developer in Visual Studio. + File.WriteAllText("../../../../PdfSharp.Shared/GitVersion.json", gitVersionJson); + } +#endif + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.BuildConfig/README.md b/src/foundation/src/shared/src/PdfSharp.BuildConfig/README.md new file mode 100644 index 00000000..62e0f56d --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.BuildConfig/README.md @@ -0,0 +1,56 @@ +# PDFsharp.BuildConfig + +TODO: not yet completed + +This is the PDFsharp.BuildConfig project. +It is a dummy project only used to create GitVersion stuff with the `CreateBuildConfiguration.ps1` script. + +The purpose of this project is to create version information directly from git. +Starting with PDFsharp 6.0 we used the tool GitVersion to create the information. +After version 6.2.x we will not use GitVersion anymore. + +GitVersion used information from the current branch and the last tag to calculate all values for [semantic versioning](https://semver.org/). +These information is used in the build process to add version information to all assemblies and NuGet packages. +This is great for automatic build processes. +Unfortunately, retrieving the same information for each project in the solution and each framework of every project more than doubles the build time. +Therefore, we decided to get information only once directly with git commands and store the result in two files. +A props file for MS Build and a C# file to make the information available at runtime. + +This sounds easy but there are several unexpected complication we had to solve. +The generated props file must not be controlled by git because otherwise we had changes in the repository after each commit. +We use a counter in the prerelease tag of the semver version. +After each commit the number is incremented and the generated file changes. + +## More details + +What this project does is to create two files when it is compiled: `SemVersion.props` and `SemVersionInformation-generated.cs`. + +`SemVersion.props` is located in the `./src directory` and contains the following MS Build variables + +* Version +* FileVersion +* InformationalVersion +* Major +* Minor +* Patch +* PreReleaseLabel +* SemVer +* BranchName +* CommitDate + +`SemVersionInformation-generated.cs` is located in the `internals` folder of the `PdfSharp.Shared` project. +It contains constants used in the practical class **SemVersionInformation**. + +Because the files are not in the repository they do not exist after a git clone, but are required to build the solution. +To resolve this egg and chicken problem, `SemVersion.props` is only included from `Directory.Build.props` if it exists. +The version information is therefore only available after the second build. + +`SemVersionInformation-generated.cs` is more complicated. +`PdfSharp.Shared` cannot compile without this file. +Even if `PdfSharp.BuildConfig` is complied before `PdfSharp.Shared` it does not work. +It seems that MS Build or the C# compiler takes a snapshot of all file of the solution when parallel compilation starts. +Our solution is to include the dummy C# file `SemVersionInformation-substitute.cs` in the `PdfSharp.Shared` project if `SemVersionInformation-generated.cs` does not exist. + +## Override GitVersion values + +In `SemVersion.json` you can set `UseThisInformation` to 1 to override the Gitversion information with own values. See `CreateBuildConfiguration.ps1` for more information. \ No newline at end of file diff --git a/src/foundation/src/shared/src/PdfSharp.BuildConfig/SemVersion.json b/src/foundation/src/shared/src/PdfSharp.BuildConfig/SemVersion.json new file mode 100644 index 00000000..629d9b80 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.BuildConfig/SemVersion.json @@ -0,0 +1,37 @@ +{ // Hard coded version information + "UseThisInformation": 0, + "MajorMinorPatch": "6.4.0", + "BranchName": "release/6.4.0" + // "SemVer": "6.9.100", + // "InformationalVersion": "6.3.0-dev-factur-x0171", + + //"PreReleaseLabel": "preview-2", + //"PreReleaseNumber": 3456 + + //"Major": 6, + //"Minor": 9, + //"Patch": 100, + //"PreReleaseTagWithDash": "", + //"PreReleaseLabelWithDash": "", + //"WeightedPreReleaseNumber": 0, + //"BuildMetaData": null, + //"BuildMetaDataPadded": "", + //"FullBuildMetaData": "", + //"LegacySemVer": "6.3.0-dev-factur-x171", + //"LegacySemVerPadded": "6.3.0-dev-factur-x0171", + //"AssemblySemVer": "6.3.0.0", + //"AssemblySemFileVer": "6.3.0.7443", + //"FullSemVer": "6.3.0-dev-factur-x.171", + //"EscapedBranchName": "feature-factur-x", + //"Sha": "a8e9a5dd283153ef472b755ee392651d8c6e9c99", + //"ShortSha": "a8e9a5d", + //"NuGetVersionV2": "6.3.0-dev-factur-x0171", + //"NuGetVersion": "6.3.0-dev-factur-x0171", + //"NuGetPreReleaseTagV2": "dev-factur-x0171", + //"NuGetPreReleaseTag": "dev-factur-x0171", + //"VersionSourceSha": "6b44fd61bb92647043c2f851bc44be1092480c0d", + // "CommitsSinceVersionSource": 0 + //"CommitsSinceVersionSourcePadded": "0171", + //"UncommittedChanges": 2 + //"CommitDate": "2025-06-07" +} \ No newline at end of file diff --git a/src/foundation/src/shared/src/PdfSharp.BuildConfig/SemVersionInformation-substitute.cs b/src/foundation/src/shared/src/PdfSharp.BuildConfig/SemVersionInformation-substitute.cs new file mode 100644 index 00000000..dfbebc1d --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.BuildConfig/SemVersionInformation-substitute.cs @@ -0,0 +1,37 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Internal +{ + /// + /// This is a substitute included by the PdfSharp.Shared project in case the + /// SemVersionInformation-generated.cs file was not yet generated when + /// the parallel build process starts. + /// + // ReSharper disable once PartialTypeWithSinglePart because this file is linked into another project. + public static partial class SemVersionInformation + { + const string SemMajor = "0"; + const string SemMinor = "0"; + const string SemPatch = "0"; + const string SemVersion = "0.0.1"; + const string SemFileVersion = "0.0.1.0"; + const string SemPreReleaseLabel = "null"; + const string SemInformationalVersion = "0.0.0-null-0"; + const string SemBranchName = "branch/null"; + const string SemCommitDate = "0000-00-00"; + const string SemSha = "0000000000000000000000000000000000000000"; + const string SemShortSha = "00000000"; + } + + // Note + // Maybe we should generate SemVersionInformation-generated.cs in the debug/release folder. + // When it is generated in the regular source folder the file is not used even if it + // exists before the project is compiled. I assume regular files are collected in a snapshot + // taken when the parallel build process starts. + // But we will not try this, because SemVersion.props nevertheless does not exist when + // build begins. Therefore, we must compile PdfSharp.Gitversion.proj at least once before + // we build the solution to get correct version information. + // But because of conditions in the build process the build will not fail even after a + // git clone command after that the two generated files do not yet exist. +} diff --git a/src/foundation/src/shared/src/PdfSharp.BuildConfig/WaitForDownloadAssets.ps1 b/src/foundation/src/shared/src/PdfSharp.BuildConfig/WaitForDownloadAssets.ps1 new file mode 100644 index 00000000..be5f9dc9 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.BuildConfig/WaitForDownloadAssets.ps1 @@ -0,0 +1,146 @@ +<# +.SYNOPSIS + This script waits for the download of assets to complete. + +.DESCRIPTION + This script waits for the download of assets to complete if called without the switch -InvokeDownload. + If called with the switch -InvokeDownload, it will invoke the download script and wait for its termination. + +.PARAMETER InvokeDownload + Specifies to invoke the download script. +#> + +#Requires -Version 7 +#Requires -PSEdition Core + + +param ( + [Parameter(Mandatory = $false)] [switch]$InvokeDownload, + [Parameter(Mandatory = $false)] [string]$Target, + [Parameter(Mandatory = $false)] [string]$RequiredAssetsVersion +) + +# Get-Location|Write-Host +# Write-Host "*$InvokeDownload*" +# Write-Host "*$Target*" +# Write-Host "*$RequiredAssetsVersion*" + +$assetsFolder = "..\..\..\..\..\..\assets" +$devFolder = "..\..\..\..\..\..\dev" +$flagFile = "$download.flg" + +function GetAssetsVersion() { + try { + # Write-Host "Entering GetAssetsVersion." + $version = Get-Content -Path "$assetsFolder/.assets-version" -Raw -ErrorAction Stop + $versionTrimmed = $version.Trim() + # Write-Host "Version: *$version*" + # Write-Host "Version: *$versionTrimmed*" + # Write-Host "Leaving GetAssetsVersion successfully." + return $versionTrimmed + } + catch { + # Write-Host "Exception in GetAssetsVersion." + } + # Write-Host "Leaving GetAssetsVersion unsuccessfully." + return "0000" +} + +function CheckFlagFile([bool]$dummy) { + $result = Test-Path $flagFile -PathType Leaf + # Write-Host "CheckFlagFile: $result" + return $result +} + +function CreateFlagFile() { + New-Item $flagFile -type file | Out-Null +} + +function RemoveFlagFile() { + if (Test-Path $flagFile -PathType Leaf) { + Remove-Item $flagFile -verbose + } +} + +function UpdateRequired() { + $current = GetAssetsVersion + return ($RequiredAssetsVersion -gt $current) +} + +function DownloadAssets() { + # 1. Create flag file. + # 2. Check if assets require update. + # 3. Download assets if necessary. + # 4. Delete flag file. + + # Write-Host "Entering DownloadAssets." + CreateFlagFile + if (UpdateRequired) { + # Write-Host "Starting download-assets.ps1." + $updateScript = ".\download-assets.ps1" + # & "$updateScript" + # Invoke-Expression -Command "$updateScript" + Push-Location $devFolder + # Get-Location|Write-Host + & $updateScript + Pop-Location + # Write-Host "download-assets.ps1 has finished." + } + RemoveFlagFile + # Write-Host "Leaving DownloadAssets." +} + +function WaitForDownload() { + # 1. If flag file does not exist, check if assets require update. + # 2. If update necessary, wait a while for creation of flag file. Exit if update no longer necessary. + # 3. If flag file exists, wait for removal of flag file. + + # Write-Output "WaitForDownload does nothing now." + return + + # Write-Output "Entering WaitForDownload." + $haveFlagFile = CheckFlagFile($false) + if ($false -eq $haveFlagFile) { + $update = UpdateRequired + if ($false -eq $update) { + Write-Output "No update required." + return + } + # Write-Output "Update required." + $wait = 100 + # Write-Output "Start waiting for flag file." + $check = CheckFlagFile($false) + while (($wait -gt 0) -and ($check -eq $false)) { + $wait-- + # Write-Output "Waiting for flag file." + Start-Sleep .25 + $check = CheckFlagFile($false) + } + # Write-Output "Finished waiting for flag file." + } + $check = CheckFlagFile($false) + while ($check) { + # Write-Output "Waiting for deletion of flag file." + Start-Sleep .25 + $check = CheckFlagFile($false) + } + # Write-Output "Leaving WaitForDownload." +} + +Push-Location $PSScriptRoot + +if ($InvokeDownload -eq $true) { + # Old: This should be called from the PdfSharp.GitVersion project only. + # Old: The calling project must have just one target framework. + # New: This should be called from the PdfSharp.BuildConfig project only. + # New: The calling project must have just one target framework. + + # Write-Output "Master" + DownloadAssets +} +else { + # Write-Output "Slave" + WaitForDownload +} + +Pop-Location diff --git a/src/foundation/src/shared/src/PdfSharp.Fonts/PdfSharp.Fonts.csproj b/src/foundation/src/shared/src/PdfSharp.Fonts/PdfSharp.Fonts.csproj index d9ce4ad0..fdb8d23a 100644 --- a/src/foundation/src/shared/src/PdfSharp.Fonts/PdfSharp.Fonts.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Fonts/PdfSharp.Fonts.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp True ..\..\..\..\..\StrongnameKey.snk diff --git a/src/foundation/src/shared/src/PdfSharp.Imaging/BmpImageImporter.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/BmpImageImporter.cs new file mode 100644 index 00000000..993ef842 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/BmpImageImporter.cs @@ -0,0 +1,620 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Internal.Imaging +{ + sealed class BmpImageImporter : BitmapImageImporter + { + public override bool TryImport(byte[] bitmap, + [MaybeNullWhen(false)] out ImportedImage importedImage) + { + importedImage = null; + + try + { + Reset(); + + var dataParser = new DataParser(bitmap); + dataParser.CurrentOffset = 0; + + if (TestBitmapFileHeader(dataParser, out var offsetImageData)) + { + var result = new ImportedBmpImage(); + if (TestBitmapInfoHeader(dataParser, result, offsetImageData, out var colorPaletteOffset)) + { + result.ImageData = bitmap; + + // Determine width and height. + UpdateWidthAndHeight(result); + + importedImage = result; + + var importer = new BmpImporter(bitmap, result, offsetImageData, colorPaletteOffset, HaveRgbMask); + importer.CopyBitmap(); + + return true; + } + } + } + catch (Exception ex) + { + // Eat exceptions to have this image importer skipped. + // We try to find an image importer that can handle the image. + LogTryOpenFailure(nameof(BmpImageImporter), ex); + } + + return false; + } + + void Reset() + { + HaveRgbMask = false; + } + + bool TestBitmapFileHeader(DataParser dataParser, out int offset) + { + offset = 0; + // File must start with "BM". + if (dataParser.GetWord(0, true) == 0x424d) + { + int fileSize = (int)dataParser.GetDWord(2, false); + // Integrity check: fileSize set in BM header should match size of the stream. + // We test "<" instead of "!=" to allow extra bytes at the end of the stream. + if (fileSize < dataParser.Length) + return false; + + offset = (int)dataParser.GetDWord(10, false); + dataParser.CurrentOffset += 14; + return true; + } + return false; + } + + bool TestBitmapInfoHeader(DataParser dataParser, ImportedBmpImage ii, int offset, out int colorPaletteOffset) + { + colorPaletteOffset = 0; + + int size = (int)dataParser.GetDWord(0, false); + if (size is 40 or 108 or 124) // sizeof BITMAPINFOHEADER == 40, sizeof BITMAPV4HEADER == 108, sizeof BITMAPV5HEADER == 124 + { + int width = (int)dataParser.GetDWord(4, false); + int height = (int)dataParser.GetDWord(8, false); + int planes = dataParser.GetWord(12, false); + int bitCount = dataParser.GetWord(14, false); + int compression = (int)dataParser.GetDWord(16, false); + int sizeImage = (int)dataParser.GetDWord(20, false); + int xPelsPerMeter = (int)dataParser.GetDWord(24, false); + int yPelsPerMeter = (int)dataParser.GetDWord(28, false); + int colorsUsed = (int)dataParser.GetDWord(32, false); + int colorsImportant = (int)dataParser.GetDWord(36, false); + // TODO_OLD Integrity and plausibility checks. + if (sizeImage != 0 && sizeImage + offset > dataParser.Length) + return false; + + if (xPelsPerMeter > 0 && yPelsPerMeter > 0) + { + // Calculate from dots per meter. + ii.DpiX = xPelsPerMeter / ImportedImage.InchPerMeter; + ii.DpiY = yPelsPerMeter / ImportedImage.InchPerMeter; + RoundDpiAfterConversionFromMetricValue(ii); + } + else + { + // Use default values. + ii.DpiX = 96; + ii.DpiY = 96; + } + + // Return true only for supported formats. + if (compression is 0 or 3) // BI_RGB == 0, BI_BITFIELDS == 3 + { + //data.Offset = offset; + colorPaletteOffset = dataParser.CurrentOffset + size; + ii.BitCount = bitCount; + ii.PixelWidth = width; + ii.PixelHeight = Math.Abs(height); + // Implement flipped images when we encounter files that require this. + var flippedImage = height < 0; + if (planes == 1 && bitCount == 24) + { + // RGB24 + ii.ImageFormat = ImageFormats.Bgr32; + + // Do we have to verify Mask if size >= 108 && compression == 3 also for bitCount == 24? + return true; + } + if (planes == 1 && bitCount == 32) + { + // ARGB32 + ii.ImageFormat = ImageFormats.Bgra32; + + // Mask for bytes if size >= 108 && compression == 3. + HaveRgbMask = size >= 108 && compression == 3; + + // Verify Mask if size >= 108 && compression == 3. + if (HaveRgbMask) + { + uint maskRed = dataParser.GetDWord(40, false); + uint maskGreen = dataParser.GetDWord(44, false); + uint maskBlue = dataParser.GetDWord(48, false); + uint maskAlpha = dataParser.GetDWord(52, false); + // Reject images that do not have the expected byte order. + if (maskRed != 0xff000000 || + maskGreen != 0x00ff0000 || + maskBlue != 0x0000ff00 || + maskAlpha != 0x000000ff) + { + // Not yet supported. + return false; + } + } + return true; + } + if (planes == 1 && bitCount == 8) + { + // Palette8 + ii.ImageFormat = ImageFormats.Indexed8; + ii.ColorsUsed = colorsUsed; + + return true; + } + if (planes == 1 && bitCount == 4) + { + // Palette4 + ii.ImageFormat = ImageFormats.Indexed4; + ii.ColorsUsed = colorsUsed; + + return true; + } + if (planes == 1 && bitCount == 1) + { + // Palette1 + ii.ImageFormat = ImageFormats.Indexed1; + ii.ColorsUsed = colorsUsed; + + return true; + } + } + } + return false; + } + + bool HaveRgbMask { get; set; } = false; + } + + /// + /// Imports data from a BMP "file" into an ImportedBmpImage. + /// + class BmpImporter + { + /// + /// Initializes a new instance of the class. + /// + public BmpImporter(byte[] data, ImportedBmpImage image, + int offset, int colorPaletteOffset, bool haveRgbMask) + { + Data = data; + Image = image; + Offset = offset; + ColorPaletteOffset = colorPaletteOffset; + HaveRgbMask = haveRgbMask; + } + + public void CopyBitmap() + { + switch (Image.ImageFormat) + { + case ImageFormats.Bgra32: + CopyTrueColorMemoryBitmap(4, 8, true); + break; + + case ImageFormats.Bgr32: + if (Image.BitCount == 32) + CopyTrueColorMemoryBitmap(4, 8, false); + else + CopyTrueColorMemoryBitmap(3, 8, false); + break; + + case ImageFormats.Indexed8: + CopyIndexedMemoryBitmap(8, true/*, options*/); + break; + + case ImageFormats.Indexed4: + CopyIndexedMemoryBitmap(4, true/*, options*/); + break; + + case ImageFormats.Indexed1: + CopyIndexedMemoryBitmap(1, false/*, options*/); + break; + + default: + throw new NotSupportedException($"Image format {Image.ImageFormat} is not implemented here."); + } + } + + /// + /// Copies images without color palette. + /// + /// 4 (32bpp RGB), 3 (24bpp RGB, 32bpp ARGB) + /// 8 + /// true (ARGB), false (RGB) + void CopyTrueColorMemoryBitmap(int components, int bits, bool hasAlpha) + { + int width = Image.PixelWidth; + int height = Image.PixelHeight; + + int logicalComponents = components; + if (components == 4) + logicalComponents = 3; + + byte[] imageData = new byte[logicalComponents * width * height]; + + bool hasMask = false; + bool hasAlphaMask = false; + byte[]? alphaMask = hasAlpha ? new byte[width * height] : null; + MonochromeMask? mask = hasAlpha ? + new MonochromeMask(width, height) : null; + + int nFileOffset = Offset; + int nOffsetRead = 0; + bool maskAllTransparent = true; + if (logicalComponents == 3) + { + if (hasAlpha && HaveRgbMask) + { + // Improvement: Get byte order for V5 bitmaps from bitmap header. + // https://docs.microsoft.com/en-us/windows/win32/api/wingdi/ns-wingdi-bitmapv5header + // "If the bV5Compression member of the BITMAPV5HEADER is BI_BITFIELDS, the bmiColors member contains three DWORD color masks that specify the red, green, and blue components of each pixel. Each DWORD in the bitmap array represents a single pixel." + + // Experimental: Expected BGRA, but found ARGB. + ++nFileOffset; + } + + for (int y = 0; y < height; ++y) + { + // Handle flipped images if we find files that require it. + int nOffsetWrite = 3 * (height - 1 - y) * width; + int nOffsetWriteAlpha = 0; + if (hasAlpha) + { + mask!.StartLine(y); // NRT + nOffsetWriteAlpha = (height - 1 - y) * width; + } + + for (int x = 0; x < width; ++x) + { + imageData[nOffsetWrite] = Data[nFileOffset + nOffsetRead + 2]; + imageData[nOffsetWrite + 1] = Data[nFileOffset + nOffsetRead + 1]; + imageData[nOffsetWrite + 2] = Data[nFileOffset + nOffsetRead]; + if (hasAlpha) + { + //byte maskValue = Data[nFileOffset + nOffsetRead + 3]; // Expected, but does not always work. + byte maskValue = HaveRgbMask ? + Data[nFileOffset + nOffsetRead - 1] : // Experimental. See above. + Data[nFileOffset + nOffsetRead + 3]; + mask!.AddPel(maskValue); + alphaMask![nOffsetWriteAlpha] = maskValue; + if (!hasMask || !hasAlphaMask || maskAllTransparent) + { + if (maskValue != 255) + { + hasMask = true; // Found a value different from 100 %. + if (maskValue != 0) + hasAlphaMask = true; // Found a value different from 0 % and 100 %. + } + else + { + maskAllTransparent = false; // Found a value different from 0 %. + } + } + ++nOffsetWriteAlpha; + } + nOffsetRead += hasAlpha ? 4 : components; + nOffsetWrite += 3; + } + nOffsetRead = 4 * ((nOffsetRead + 3) / 4); // Align to 32 bit boundary + } + } + else if (components == 1) + { + // Grayscale + throw new NotSupportedException("Image format not supported (grayscales)."); + } + + Image.ExtractedImageData = imageData; + + if (alphaMask != null && hasAlphaMask) + { + Image.AlphaMaskData = alphaMask; + } + + if (mask != null && hasMask && !maskAllTransparent) + { + Image.BitmapMaskData = mask.MaskData; + Image.BitmapMask = mask; + } + } + + void CopyIndexedMemoryBitmap(int bits, bool checkTransparency) + { + int firstMaskColor = -1, lastMaskColor = -1; + bool segmentedColorMask = false; + + int bytesColorPaletteOffset = ColorPaletteOffset; // GDI+ always returns Windows bitmaps: sizeof BITMAPFILEHEADER + sizeof BITMAPINFOHEADER + + int bytesFileOffset = Offset; + int paletteColors = Image.ColorsUsed; + int width = Image.PixelWidth; + int height = Image.PixelHeight; + + MonochromeMask? mask = checkTransparency ? new MonochromeMask(width, height) : null; + + bool isGray = bits == 8 && (paletteColors == 256 || paletteColors == 0); + int isBitonal = 0; // 0: false; >0: true; <0: true (inverted) + byte[] paletteData = new byte[3 * paletteColors]; + for (int color = 0; color < paletteColors; ++color) + { + paletteData[3 * color] = Data[bytesColorPaletteOffset + 4 * color + 2]; + paletteData[3 * color + 1] = Data[bytesColorPaletteOffset + 4 * color + 1]; + paletteData[3 * color + 2] = Data[bytesColorPaletteOffset + 4 * color + 0]; + if (isGray) + isGray = paletteData[3 * color] == paletteData[3 * color + 1] && + paletteData[3 * color] == paletteData[3 * color + 2]; + + if (checkTransparency && Data[bytesColorPaletteOffset + 4 * color + 3] < 128) + { + // We treat this as transparency: + if (firstMaskColor == -1) + firstMaskColor = color; + if (lastMaskColor == -1 || lastMaskColor == color - 1) + lastMaskColor = color; + if (lastMaskColor != color) + segmentedColorMask = true; + } + //else + //{ + // // We treat this as opacity: + //} + } + + if (firstMaskColor == 0 && lastMaskColor == paletteColors - 1) + { + // If all colors are masked out, ignore mask colors. + firstMaskColor = -1; + lastMaskColor = -1; + } + + if (bits == 1) + { + if (paletteColors == 0) + isBitonal = 1; + if (paletteColors == 2) + { + if (paletteData[0] == 0 && + paletteData[1] == 0 && + paletteData[2] == 0 && + paletteData[3] == 255 && + paletteData[4] == 255 && + paletteData[5] == 255) + isBitonal = 1; // Black on white + if (paletteData[5] == 0 && + paletteData[4] == 0 && + paletteData[3] == 0 && + paletteData[2] == 255 && + paletteData[1] == 255 && + paletteData[0] == 255) + isBitonal = -1; // White on black + } + } + + // NYI: (no sample found where this was required) + // if (segmentedColorMask = true) + // { ... } + + byte[] imageData = new byte[((width * bits + 7) / 8) * height]; + //bool isFGaxEncoded = false; + //byte[]? imageDataFax = null; + //int k = 0; + + // No fax encoding here +#if false + if (bits == 1 && options.EnableCcittCompressionForBilevelImages) + { + // We try Group 4 (2D) encoding here and keep the smaller byte array. + byte[] tempG4 = new byte[imageData.Length]; + // Size will be 0 if fax encoding increases the size. + int ccittSizeG4 = PdfImage.DoFaxEncodingGroup4(ref tempG4, Data, (uint)bytesFileOffset, (uint)width, (uint)height); + + isFGaxEncoded = ccittSizeG4 > 0; + if (isFGaxEncoded) + { + Array.Resize(ref tempG4, ccittSizeG4); + imageDataFax = tempG4; + k = -1; + } + } +#endif + + { + int bytesOffsetRead = 0; + if (bits == 8 || bits == 4 || bits == 1) + { + int bytesPerLine = (width * bits + 7) / 8; + for (int y = 0; y < height; ++y) + { + mask?.StartLine(y); + int bytesOffsetWrite = (height - 1 - y) * ((width * bits + 7) / 8); + for (int x = 0; x < bytesPerLine; ++x) + { + if (isGray) + { + // Lookup the gray value from the palette: + imageData[bytesOffsetWrite] = paletteData[3 * Data[bytesFileOffset + bytesOffsetRead]]; + } + else + { + // Store the palette index. + imageData[bytesOffsetWrite] = Data[bytesFileOffset + bytesOffsetRead]; + } + if (firstMaskColor != -1) + { + int n = Data[bytesFileOffset + bytesOffsetRead]; + if (bits == 8) + { + // Support segmentedColorMask when we encounter files that require it. + mask?.AddPel((n >= firstMaskColor) && (n <= lastMaskColor)); + } + else if (bits == 4) + { + // Support segmentedColorMask when we encounter files that require it. + int n1 = (n & 0xf0) / 16; + int n2 = (n & 0x0f); + mask?.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor)); + mask?.AddPel((n2 >= firstMaskColor) && (n2 <= lastMaskColor)); + } + else // (bits == 1) + { + // Support segmentedColorMask when we encounter files that require it. + for (int bit = 1; bit <= 8; ++bit) + { + int n1 = (n & 0x80) / 128; + mask?.AddPel((n1 >= firstMaskColor) && (n1 <= lastMaskColor)); + n *= 2; + } + } + } + bytesOffsetRead += 1; + bytesOffsetWrite += 1; + } + bytesOffsetRead = 4 * ((bytesOffsetRead + 3) / 4); // Align to 32 bit boundary + } + } + else + { + throw new NotSupportedException("ReadIndexedMemoryBitmap: unsupported format #3"); + } + } + + Image.ExtractedImageData = imageData; + + // CCITT FAX encoding not yet supported, but has no advantages over FlateDecode. +#if false + if (imageDataFax != null) + { + Image.CcittFaxData = imageDataFax; + } + Image.CcittFaxK = k; +#endif + + Image.IsGray = isGray; + Image.Bitonal = isBitonal; + Image.BytesFileOffset = bytesFileOffset; + + Image.PaletteData = paletteData; + Image.PaletteColors = paletteColors; + Image.SegmentedColorMask = segmentedColorMask; + Image.FirstMaskColor = firstMaskColor; + Image.LastMaskColor = lastMaskColor; + + if (mask != null && mask.MaskUsed && firstMaskColor != -1) + { + Image.BitmapMaskData = mask.MaskData; + Image.BitmapMask = mask; + } + } + + public byte[] Data { get; } + public ImportedBmpImage Image { get; } + public int Offset { get; } + public int ColorPaletteOffset { get; } + public bool HaveRgbMask { get; } + } + + /// + /// Helper class for creating bitmap masks (8 pels per byte). + /// + public class MonochromeMask + { + /// + /// Returns the bitmap mask that will be written to PDF. + /// + public byte[] MaskData => _maskData; + + readonly byte[] _maskData; + + /// + /// Indicates whether the mask has transparent pels. + /// + public bool MaskUsed => _used; + + /// + /// Creates a bitmap mask. + /// + public MonochromeMask(int sizeX, int sizeY) + { + _sizeX = sizeX; + _sizeY = sizeY; + int byteSize = ((sizeX + 7) / 8) * sizeY; + _maskData = new byte[byteSize]; + StartLine(0); + } + + /// + /// Starts a new line. + /// + public void StartLine(int newCurrentLine) + { + _bitsWritten = 0; + _byteBuffer = 0; + _writeOffset = ((_sizeX + 7) / 8) * (_sizeY - 1 - newCurrentLine); + } + + /// + /// Adds a pel to the current line. + /// + /// + public void AddPel(bool isTransparent) + { + if (_bitsWritten < _sizeX) + { + // Mask: 0: opaque, 1: transparent (default mapping) + if (isTransparent) + { + _byteBuffer = (_byteBuffer << 1) + 1; + _used = true; + } + else + _byteBuffer = _byteBuffer << 1; + ++_bitsWritten; + if ((_bitsWritten & 7) == 0) + { + _maskData[_writeOffset] = (byte)_byteBuffer; + ++_writeOffset; + _byteBuffer = 0; + } + else if (_bitsWritten == _sizeX) + { + int n = 8 - (_bitsWritten & 7); + _byteBuffer = _byteBuffer << n; + _maskData[_writeOffset] = (byte)_byteBuffer; + } + } + } + + /// + /// Adds a pel from an alpha mask value. + /// + public void AddPel(int shade) + { + // NYI: dithering. + AddPel(shade < 128); + } + + readonly int _sizeX; + readonly int _sizeY; + int _writeOffset; + int _byteBuffer; + int _bitsWritten; + bool _used; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Imaging/Imaging.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Imaging.cs new file mode 100644 index 00000000..d5c1acdb --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Imaging.cs @@ -0,0 +1,812 @@ +// 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 System.IO; + +namespace PdfSharp.Internal.Imaging +{ + abstract class BitmapImageImporter + { + public abstract bool TryImport(byte[] bitmap, + [MaybeNullWhen(false)] out ImportedImage importedImage); + + protected static void LogTryOpenFailure(string classname, Exception ex) + { + LogHost.Logger.LogWarning( + $"Unhandled exception in {classname}, maybe caused by an unsupported image format. {ex.ToString()}"); + } + + /// + /// Updates the height and width based on PixelHeight, PixelWidth, and the DPI settings. + /// + internal void UpdateWidthAndHeight(ImportedImage importedImage) + { + importedImage.Width = importedImage.PixelWidth / importedImage.DpiX * 72; + importedImage.Height = importedImage.PixelHeight / importedImage.DpiY * 72; + } + + /// + /// When DPI values are calculated from metric values, try to round a integer when appropriate. + /// + internal void RoundDpiAfterConversionFromMetricValue(ImportedImage importedImage) + { + importedImage.DpiX = ProcessDpi(importedImage.DpiX); + importedImage.DpiY = ProcessDpi(importedImage.DpiY); + return; + + float_ ProcessDpi(float_ dpi) + { + int i = (int)(dpi + .5); + double diff = Math.Abs(dpi - (double)i); + // If DPI value is less than 0.1 % of the nearest integer, return the integer. +#if true + // Rounding only for 72 and 96 DPI. + if ((i == 72 || i == 96) && diff * 1000 < i) + return i; +#else + // Rounding for all values. + if (diff * 1000 < i) + return i; +#endif + return dpi; + } + } + } + + /// + /// Image importer class. + /// + public static class ToBeNamed // Also rename file. @@@ + { + /// + /// Tries to import an image. Returns true on success. + /// + /// The bitmap image stream. + /// The imported image. + public static bool TryImportImage(Stream bitmapImageStream, + [MaybeNullWhen(false)] out ImportedImage importedImage) + { + try + { + byte[] bitmapBytes; + using (var worker = new StreamReaderWorker(bitmapImageStream)) + { + bitmapBytes = worker.Data; + Debug.Assert(bitmapBytes.Length == worker.Length); + } + return TryImportImage(bitmapBytes, out importedImage); + } + catch (Exception ex) + { + LogHost.Logger.LogWarning( + $"Unhandled exception in TryImportImage, maybe caused by an unsupported image format. {ex.ToString()}"); + } + + importedImage = null!; + return false; + } + + /// + /// Tries to import an image. Returns true on success. + /// + /// The bitmap bytes. + /// The imported image. + public static bool TryImportImage(byte[] bitmapBytes, + [MaybeNullWhen(false)] out ImportedImage importedImage) + { +#if true + foreach (var importer in Importers) + { + if (importer.TryImport(bitmapBytes, out importedImage)) + return true; + } +#else + if (PngImporter.TryImport(bitmapBytes, out importedImage)) + return true; + + if (JpegImporter.TryImport(bitmapBytes, out importedImage)) + return true; + + if (BmpImporter.TryImport(bitmapBytes, out importedImage)) + return true; +#endif + importedImage = null!; + return false; + } + + /// + /// Tries to import a BMP image. Returns true on success. + /// + /// The bitmap bytes. + /// The imported image. + public static bool TryImportImageBmp(byte[] bitmapBytes, + [MaybeNullWhen(false)] out ImportedImage importedImage) + { + if (BmpImporter.TryImport(bitmapBytes, out importedImage)) + return true; + importedImage = null!; + return false; + } + + static readonly BmpImageImporter BmpImporter = new(); + static readonly JpegImageImporter JpegImporter = new(); + static readonly PngImageImporter PngImporter = new(); + static readonly BitmapImageImporter[] Importers = [JpegImporter, PngImporter, BmpImporter]; + } +} + +namespace PdfSharp.Internal.Imaging // #FILE +{ + /// + /// Represents an imported image. + /// + public class ImportedImage + { + /// + /// The number of inches per meter + /// + public const float_ InchPerMeter = 100 / 2.54f; // 39.37007874015748031496062992126 + + /// + /// Gets the image format. + /// + public ImageFormats ImageFormat { get; internal set; } = ImageFormats.Unknown; + + /// + /// Gets the width of the image in Point. + /// + public float_ Width { get; internal set; } + + /// + /// Gets the height of the image in Point. + /// + public float_ Height { get; internal set; } + + /// + /// Gets the horizontal DPI value. + /// + public float_ DpiX { get; internal set; } = 96; // Never 0 + + /// + /// Gets the vertical DPI value. + /// + public float_ DpiY { get; internal set; } = 96; + + /// + /// Gets the width of the image in pixel. + /// + public int PixelWidth { get; internal set; } + + /// + /// Gets the height of the image in pixel. + /// + public int PixelHeight { get; internal set; } + + /// + /// Gets the raw image data. + /// + public byte[] ImageData { get; internal set; } = null!; + + /// + /// Gets the image data needed for PDF. + /// + public virtual byte[] GetPdfImageData( /* some options if we have them*/) + { + // With JPEG, we embed the unaltered JPEG file. No conversion required. Override for images that require conversion. + return ImageData; + } + } +} + +namespace PdfSharp.Internal.Imaging +{ + /// + /// Represents an imported raster image. + /// + /// + public class ImportedRasterImage : ImportedImage + { + /// + /// Gets the alpha mask data. + /// + public byte[]? AlphaMaskData { get; internal set; } = null; + + /// + /// Gets the bitmap mask data. + /// + public byte[]? BitmapMaskData { get; internal set; } = null; + + /// + /// Gets the bitmap mask. + /// + public MonochromeMask? BitmapMask { get; internal set; } = null; + + /// + /// Gets the extracted image data. + /// + public byte[]? ExtractedImageData { get; internal set; } = null; + + /// + /// Gets the color palette data. + /// + public byte[]? PaletteData { get; internal set; } = null; + + /// + /// Gets the number of palette colors. + /// + public int PaletteColors { get; internal set; } = 0; + + /// + /// Gets the image data needed for PDF. + /// + /// + public new byte[] GetPdfImageData( /* some options if we have them*/) + { + return ExtractedImageData!; + } + + /// + /// Gets the alpha mask data needed for PDF. + /// + /// + public virtual byte[]? GetPdfAlphaMaskData( /* some options if we have them*/) + { + return AlphaMaskData; + } + + /// + /// Gets the bitmap mask data needed for PDF. + /// + public virtual byte[]? GetPdfBitmapMaskData( /* some options if we have them*/) + { + if (BitmapMaskData is null && AlphaMaskData is not null) + { + // Can be generated from AlphaMask. + throw new NotImplementedException("GetPdfBitmapMaskData not yet implemented for this image file."); + } + + return BitmapMaskData; + } + + /// + /// Gets the PDF bitmap mask. + /// + /// + public virtual MonochromeMask? GetPdfBitmapMask( /* some options if we have them*/) + { + return BitmapMask; + } + + /// + /// Gets the palette data needed for PDF. + /// + /// + public virtual byte[]? GetPdfPaletteData( /* some options if we have them*/) + { + return PaletteData; + } + + /// + /// The bit count per pixel. + /// + public int BitCount { get; internal set; } + + /// + /// The colors used. Only valid for images with palettes, will be 0 otherwise. + /// + public int ColorsUsed { get; internal set; } + } +} + +namespace PdfSharp.Internal.Imaging +{ + /// + /// Represents an imported BMP image. + /// + /// + public class ImportedBmpImage : ImportedRasterImage + { + /// + /// Gets a value indicating if the image is bitonal. + /// + public int Bitonal { get; internal set; } + + /// + /// Gets a value indicating whether this image is grayscale. + /// + public bool IsGray { get; internal set; } + + /// + /// Gets a value indicating whether the image has a segmented color mask. + /// + public bool SegmentedColorMask { get; internal set; } + + /// + /// Gets the first color of the mask. + /// + public int FirstMaskColor { get; internal set; } = -1; + + /// + /// Gets the last color of the mask. + /// + public int LastMaskColor { get; internal set; } = -1; + + /// + /// Gets the file offset of the data bytes. + /// + public int BytesFileOffset { get; internal set; } + } +} + +namespace PdfSharp.Internal.Imaging +{ + /// + /// Represents an imported PNG image. + /// + /// + public class ImportedPngImage : ImportedRasterImage + { + // Additional Png specific stuff. + } +} + +namespace PdfSharp.Internal.Imaging +{ + /// + /// Represents an imported JPEG image. + /// + /// + public class ImportedJpegImage : ImportedImage + { + // Additional Jpeg specific stuff. + + /// + /// The horizontal DPM (dots per meter). Can be 0 if not supported by the image format. + /// + public float_ DpmX { get; internal set; } + + /// + /// The vertical DPM (dots per meter). Can be 0 if not supported by the image format. + /// + public float_ DpmY { get; internal set; } + + /// + /// The horizontal component of the aspect ratio. Can be 0 if not supported by the image format. + /// Note: Aspect ratio will be set if either DPI or DPM was set but may also be available in the absence of both DPI and DPM. + /// + public float_ HorizontalAspectRatio { get; internal set; } + + /// + /// The vertical component of the aspect ratio. Can be 0 if not supported by the image format. + /// + public float_ VerticalAspectRatio { get; internal set; } + } +} + +namespace PdfSharp.Internal.Imaging +{ + /// + /// The image formats. + /// + public enum ImageFormats + { + /// + /// The unknown format. + /// + Unknown, + + // WPF names for raster images. + + // Not yet used: BlackWhite, + /// + /// Color palette with 1 bit per pixel. + /// + Indexed1, + /// + /// Color palette with 4 bits per pixel. + /// + Indexed4, + /// + /// Color palette with 8 bits per pixel. + /// + Indexed8, + /// + /// Grayscale image. + /// + Gray8, + + /// + /// RGB image with 24 bits per pixel. + /// + Bgr32, + /// + /// RGB image with 24 bits per pixel and alpha channel. + /// + Bgra32, + // Not yet used: Pbgra32 + + // Own names for JPEG formats that have no individual names in WPF. + + /// + /// Standard JPEG format (RGB). + /// + JPEG, + + /// + /// Gray-scale JPEG format. + /// + JPEGGRAY, + + /// + /// JPEG file with inverted CMYK, thus RGBW. + /// + JPEGRGBW, + + /// + /// JPEG file with CMYK. + /// + JPEGCMYK, + } +} + +namespace PdfSharp.Internal.Imaging +{ + /// + /// Helper for dealing with byte[] data. + /// + class DataParser + { + /// + /// Initializes a new instance of the class. + /// + /// The raw data. + public DataParser(byte[] data) + { + Data = data; + Length = data.Length; + } + + internal byte GetByte(int offset) + { + if (CurrentOffset + offset >= Length) + throw new InvalidOperationException("Index out of range."); + + return Data[CurrentOffset + offset]; + } + + internal ushort GetWord(int offset, bool bigEndian) + { + if (CurrentOffset + offset + 1 >= Length) + throw new InvalidOperationException("Index out of range."); + + return (ushort)(bigEndian + ? (Data[CurrentOffset + offset++] << 8) + Data[CurrentOffset + offset] + : Data[CurrentOffset + offset++] + (Data[CurrentOffset + offset] << 8)); + } + + internal uint GetDWord(int offset, bool bigEndian) + { + if (CurrentOffset + offset + 3 >= Length) + throw new InvalidOperationException("Index out of range."); + + return (uint)(bigEndian + ? (Data[CurrentOffset + offset++] << 24) + + (Data[CurrentOffset + offset++] << 16) + + (Data[CurrentOffset + offset++] << 8) + + Data[CurrentOffset + offset] + : Data[CurrentOffset + offset++] + + (Data[CurrentOffset + offset++] << 8) + + (Data[CurrentOffset + offset++] << 16) + + (Data[CurrentOffset + offset] << 24)); + } + + /// + /// Resets this instance. + /// + public void Reset() => CurrentOffset = 0; + + internal int CurrentOffset { get; set; } + + /// + /// Gets the data as byte[]. + /// + public byte[] Data { get; } + + /// + /// Gets the length of Data. + /// + public int Length { get; } + } +} + +namespace PdfSharp.Internal.Imaging +{ + /// + /// Helper for dealing with Streams and files. + /// + public class ReaderHelper + { + /// + /// Reads from a file. + /// + /// Image files larger than 2 GiB are not supported. + public static byte[] ReadFromFile(string filename) + { + using (var stream = new FileStream(filename, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + if (stream.Length > Int32.MaxValue) + throw new InvalidOperationException("Image files larger than 2 GiB are not supported."); + return ReadFromStream(stream); + } + } + + /// + /// Reads from a stream. + /// + /// stream - Stream has no content byte array. + public static byte[] ReadFromStream(Stream stream) + { + int streamLength = (int)stream.Length; + byte[] data = null!; + int length = 0; + + if (stream is MemoryStream ms) + { + // If the given stream is a MemoryStream, work with it. + if (ms.TryGetBuffer(out var buffer)) + { + // Buffer is accessible - use it. + data = buffer.Array ?? + throw new ArgumentNullException(nameof(stream), "Stream has no content byte array."); + length = (int)ms.Length; + // If buffer is larger than needed, create a new buffer with required size. + if (data.Length > length) + { + var tmp = new Byte[length]; + Buffer.BlockCopy(data, 0, tmp, 0, length); + data = tmp; + } + } + else + { + // Buffer of given stream is not accessible, so read stream into new buffer. + var memoryStream = new MemoryStream(streamLength); + stream.CopyTo(memoryStream); + data = memoryStream.GetBuffer(); + length = (int)memoryStream.Length; + LogHost.Logger.LogWarning( + "LoadImage: MemoryStream with buffer that is not publicly visible was used. " + + "For better performance, set 'publiclyVisible' to true when creating the MemoryStream."); + } + } + else + { + // If the given stream is not a MemoryStream, copy the stream to a new MemoryStream. + if (streamLength > -1) + { + // Simple case: length of stream is known, create a MemoryStream with correct buffer size. + var memoryStream = new MemoryStream(streamLength); + stream.CopyTo(memoryStream); + data = memoryStream.GetBuffer(); + length = (int)memoryStream.Length; + } + else + { + // Complex case: length of stream is not known. + // This only occurs with streams that do not support the Length property. + var memoryStream = new MemoryStream(streamLength); + stream.CopyTo(memoryStream); + data = memoryStream.GetBuffer(); + length = (int)memoryStream.Length; + // If buffer is larger than needed, create a new buffer with required size. Should never happen. + if (data.Length > length) + { + var tmp = new Byte[length]; + Buffer.BlockCopy(data, 0, tmp, 0, length); + data = tmp; + } + } + } + + Debug.Assert(data.Length == length); + return data; + } + } +} + +namespace PdfSharp.Internal.Imaging +{ + /// + /// Helper for dealing with Stream data, including streams that do not implement the Length property. + /// + internal class StreamReaderWorker : IDisposable + { + public StreamReaderWorker(string path) + { + OriginalStream = null!; + using (var stream = new FileStream(path, FileMode.Open, FileAccess.Read, FileShare.Read)) + { + var worker = new StreamReaderWorker(stream); + Data = worker.Data; + Length = worker.Length; + } + } + + public StreamReaderWorker(byte[] data) + { + OriginalStream = null!; + Data = data; + Length = data.Length; + } + + /// + /// Gets the length of the stream. Return -1 if length cannot be determined. + /// + static int StreamLengthFromStream(Stream stream) + { + long length = -1; + try + { + length = stream.Length; + } + catch (NotSupportedException) + { + // We eat this exception. + // We can handle streams that do not return their length. + } + catch (Exception ex) + { + // Unexpected exception. + throw new InvalidOperationException("Cannot determine the length of the stream. Use a stream that supports the Length property. Consider copying the image to a MemoryStream.", ex); + } + + if (length is < -1 or > Int32.MaxValue) + throw new InvalidOperationException($"Image files with a size of {length} bytes are not supported. Use image files smaller than 2 GiB."); + + return (int)length; + } + + public StreamReaderWorker(Stream stream) + { + OriginalStream = stream; + + int streamLength = StreamLengthFromStream(stream); + + if (stream is MemoryStream ms && streamLength > -1) + { + // If the given stream is a MemoryStream, work with it. + if (ms.TryGetBuffer(out var buffer)) + { + // Buffer is accessible - use it. + Data = buffer.Array ?? throw new ArgumentNullException(nameof(stream), "Stream has no content byte array."); + Length = (int)ms.Length; + // If buffer is larger than needed, create a new buffer with required size. + if (Data.Length > Length) + { + var tmp = new Byte[Length]; + Buffer.BlockCopy(Data, 0, tmp, 0, Length); + Data = tmp; + } + } + else + { + // Buffer of given stream is not accessible, so read stream into new buffer. + OwnedMemoryStream = new(streamLength); + stream.CopyTo(OwnedMemoryStream); + Data = OwnedMemoryStream.GetBuffer(); + Length = (int)OwnedMemoryStream.Length; + LogHost.Logger.LogWarning("LoadImage: MemoryStream with buffer that is not publicly visible was used. " + + "For better performance, set 'publiclyVisible' to true when creating the MemoryStream."); + } + } + else + { + // If the given stream is not a MemoryStream, copy the stream to a new MemoryStream. + if (streamLength > -1) + { + // Simple case: length of stream is known, create a MemoryStream with correct buffer size. + OwnedMemoryStream = new(streamLength); + stream.CopyTo(OwnedMemoryStream); + Data = OwnedMemoryStream.GetBuffer(); + Length = (int)OwnedMemoryStream.Length; + } + else + { + // Complex case: length of stream is not known. + // This only occurs with streams that do not support the Length property. + OwnedMemoryStream = new(); // Length is not yet known. + stream.CopyTo(OwnedMemoryStream); + Data = OwnedMemoryStream.GetBuffer(); + Length = (int)OwnedMemoryStream.Length; + // If buffer is larger than needed, create a new buffer with required size. + if (Data.Length > Length) + { + var tmp = new Byte[Length]; + Buffer.BlockCopy(Data, 0, tmp, 0, Length); + Data = tmp; + } + } + } + } + + public byte GetByte(int offset) + { + if (CurrentOffset + offset >= Length) + throw new InvalidOperationException("Index out of range."); + + return Data[CurrentOffset + offset]; + } + + public ushort GetWord(int offset, bool bigEndian) + { + if (CurrentOffset + offset + 1 >= Length) + throw new InvalidOperationException("Index out of range."); + + return (ushort)(bigEndian + ? (Data[CurrentOffset + offset++] << 8) + Data[CurrentOffset + offset] + : Data[CurrentOffset + offset++] + (Data[CurrentOffset + offset] << 8)); + } + + public uint GetDWord(int offset, bool bigEndian) + { + if (CurrentOffset + offset + 3 >= Length) + throw new InvalidOperationException("Index out of range."); + + // Are you a good developer? + // What’s wrong with this code? + //return (bigEndian + // ? ((uint)Data[CurrentOffset + offset++] << 24) + ((uint)Data[CurrentOffset + offset++] << 16) + // + ((uint)Data[CurrentOffset + offset++] << 8) + Data[CurrentOffset + offset] + // : Data[CurrentOffset + offset++] + ((uint)Data[CurrentOffset + offset++] << 8)) + // + ((uint)Data[CurrentOffset + offset++] << 16) + ((uint)Data[CurrentOffset + offset] << 24); + return (uint)(bigEndian + ? (Data[CurrentOffset + offset++] << 24) + + (Data[CurrentOffset + offset++] << 16) + + (Data[CurrentOffset + offset++] << 8) + + Data[CurrentOffset + offset] + : Data[CurrentOffset + offset++] + + (Data[CurrentOffset + offset++] << 8) + + (Data[CurrentOffset + offset++] << 16) + + (Data[CurrentOffset + offset] << 24)); + } + + /// + /// Resets this instance. + /// + public void Reset() => CurrentOffset = 0; + + /// + /// Gets the original stream. + /// + public Stream OriginalStream { get; } + + public int CurrentOffset { get; set; } + + /// + /// Gets the data as byte[]. + /// + public byte[] Data { get; } + + /// + /// Gets the length of Data. + /// + public int Length { get; } + +#if CORE || GDI || WPF + /// + /// Gets the owned memory stream. Can be null if no MemoryStream was created. + /// + public MemoryStream? OwnedMemoryStream { get; private set; } +#endif + + public void Dispose() + { +#if CORE || GDI || WPF + OwnedMemoryStream?.Dispose(); + OwnedMemoryStream = null; +#endif + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Imaging/JpegImageImporter.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/JpegImageImporter.cs new file mode 100644 index 00000000..e650e2c1 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/JpegImageImporter.cs @@ -0,0 +1,352 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Text; + +namespace PdfSharp.Internal.Imaging +{ + sealed class JpegImageImporter : BitmapImageImporter + { + public override bool TryImport(byte[] bitmap, + [MaybeNullWhen(false)] out ImportedImage importedImage) + { + importedImage = null; + try + { + var dataParser = new DataParser(bitmap); + dataParser.CurrentOffset = 0; + // Test 2 magic bytes. + if (TestFileHeader(dataParser)) + { + var result = new ImportedJpegImage(); + importedImage = result; + + // Skip over 2 magic bytes. + dataParser.CurrentOffset += 2; + + result.DpiX = 0; + result.DpiY = 0; + + // Check for known headers here, but treat header as optional. + var header = TestJfifHeader(dataParser, result); + + bool colorHeader = false, infoHeader = false; + + while (MoveToNextHeader(dataParser)) + { + if (TestColorFormatHeader(dataParser, result)) + { + colorHeader = true; + } + else if (TestInfoHeader(dataParser, result)) + { + infoHeader = true; + } + } + + if (colorHeader && infoHeader) + { + importedImage.ImageData = bitmap; + if (result.DpiX == 0 || result.DpiY == 0) + { + result.DpiX = 72; // Assume 72 DPI if information not provided in the file. + if (result.HorizontalAspectRatio != 0 && result.VerticalAspectRatio != 0) + { + // Calculate DpiY to reflect the aspect ratio of the image. + result.DpiY = 72 * result.VerticalAspectRatio / result.HorizontalAspectRatio; + } + else + { + result.DpiY = 72; // Assume 72 DPI if information not provided in the file. + } + } + + UpdateWidthAndHeight(result); + + + return true; + } + } + } + catch (Exception ex) + { + // Eat exceptions to have this image importer skipped. + // We try to find an image importer that can handle the image. + LogTryOpenFailure(nameof(JpegImageImporter), ex); + } + return false; + } + + bool TestFileHeader(DataParser dataParser) + { + // File must start with 0xffd8. + return dataParser.GetWord(0, true) == 0xffd8; + } + + bool TestJfifHeader(DataParser dataParser, ImportedJpegImage ii) + { + // Just in case the header is not the first block in the file, search through all blocks now. + //var currentOffset = dataParser.CurrentOffset; + + bool header = TestJfifHeaderWorker(dataParser, ii) || + TestExifHeaderWorker(dataParser/*, ii*/) || + TestApp2HeaderWorker(dataParser) || + TestApp13HeaderWorker(dataParser); + + return header; + } + + bool TestJfifHeaderWorker(DataParser dataParser, ImportedJpegImage ii) + { + // The App0 header should be the first header in every JFIF file. + if (dataParser.GetWord(0, true) == 0xffe0) + { + // TODO_OLD Skip EXIF header if it precedes the JFIF header. + + // Now check for text "JFIF". + if (dataParser.GetDWord(4, true) == 0x4a464946) + { + int blockLength = dataParser.GetWord(2, true); + if (blockLength >= 16) + { + // ReSharper disable once UnusedVariable + int version = dataParser.GetWord(9, true); + int units = dataParser.GetByte(11); + int densityX = dataParser.GetWord(12, true); + int densityY = dataParser.GetWord(14, true); + + switch (units) + { + case 0: // Aspect ratio only. Leave DpiX at 72 and adjust DpiY. + ii.HorizontalAspectRatio = densityX; + ii.VerticalAspectRatio = densityY; + if (densityX > 0 && densityY > 0) + { + ii.DpiX = 72; + ii.DpiY = (int)(72d * densityX / densityY); + } + break; + case 1: // DPI. + ii.DpiX = densityX; + ii.DpiY = densityY; + break; + case 2: // DPCM: Calculate DotsPerMeter and DotsPerInch from DotsPerCentimeter. + ii.DpmX = densityX * 100; + ii.DpmY = densityY * 100; + ii.DpiX = ii.DpmX / ImportedImage.InchPerMeter; + ii.DpiY = ii.DpmY / ImportedImage.InchPerMeter; + RoundDpiAfterConversionFromMetricValue(ii); + break; + } + + // More information here? More tests? + return true; + } + } + } + return false; + } + + bool TestExifHeaderWorker(DataParser dataParser) + { + // The App0 header should be the first header in every JFIF file. + if (dataParser.GetWord(0, true) == 0xffe1) + { + // Now check for text "Exif". + if (dataParser.GetDWord(4, true) == 0x45786966) + { + // We expect 00 00 after Exif. + int eos = dataParser.GetWord(8, true); + // We expect MM or II. + int ft = dataParser.GetWord(10, true); + if (eos == 0 && + (ft == 0x4d4d || ft == 0x4949)) + { + // More information here? More tests? + // TODO_OLD Find JFIF header in the EXIF header. + // EXIF headers are similar to TIFF. + return true; + } + } + } + return false; + } + + bool TestApp2HeaderWorker(DataParser dataParser) + { + // Check for APP2 header. + if (dataParser.GetWord(0, true) == 0xffe2) + { + int length = dataParser.GetWord(2, true); + + StringBuilder identifier = new(); + int idx = 4; + do + { + byte c = dataParser.GetByte(idx); + if (c == 0x00) + { + break; + } + identifier.Append((char)c); + idx++; + } while (idx < length); + + var id = identifier.ToString(); + if (!id.Equals("ICC_PROFILE")) + { + return false; + } + + return true; + } + return false; + } + + bool TestApp13HeaderWorker(DataParser dataParser) + { + // Check for APP13 header. + if (dataParser.GetWord(0, true) == 0xffed) + { + int length = dataParser.GetWord(2, true); + + StringBuilder identifier = new(); + int idx = 4; + do + { + byte c = dataParser.GetByte(idx); + if (c == 0x00) + { + break; + } + identifier.Append((char)c); + idx++; + } while (idx < length); + + var id = identifier.ToString(); + if (!id.StartsWith("Photoshop ") && !id.StartsWith("Adobe_Photoshop")) // "Photoshop 3.0", "Adobe_Photoshop2.5:", etc. + { + return false; + } + + idx++; + if (idx + 3 < length && dataParser.GetDWord(idx, true) == 0x3842494d) // 8BIM + { + return true; + } + } + return false; + } + + bool TestColorFormatHeader(DataParser dataParser, ImportedJpegImage ii) + { + // The SOS header (start of scan). + if (dataParser.GetWord(0, true) == 0xffda) + { + int components = dataParser.GetByte(4); + if (components < 1 || components > 4 || components == 2) + return false; + // 1 for grayscale, 3 for RGB, 4 for CMYK. + + int blockLength = dataParser.GetWord(2, true); + // Integrity check: correct size? + if (blockLength != 6 + 2 * components) + return false; + + // Eventually do more tests here. + // Info: we assume that all JPEG files with 4 components are RGBW (inverted CMYK) and not CMYK. + // We add a test to tell CMYK from RGBW when we encounter a test file in CMYK format. + var format = components == 3 + ? ImageFormats.JPEG + : (components == 1 + ? ImageFormats.JPEGGRAY + : ImageFormats.JPEGRGBW); + if (ii.ImageFormat == ImageFormats.Unknown) + ii.ImageFormat = format; +#if DEBUG + else + { + Debug.Assert(format == ii.ImageFormat); + } +#endif + + return true; + } + return false; + } + + bool TestInfoHeader(DataParser dataParser, ImportedJpegImage ii) + { + // The SOF header (start of frame). + int header = dataParser.GetWord(0, true); + if (header >= 0xffc0 && header <= 0xffc3 || + header >= 0xffc9 && header <= 0xffcb) + { + // Lines in image. + int sizeY = dataParser.GetWord(5, true); + // Samples per line. + int sizeX = dataParser.GetWord(7, true); + + int components = dataParser.GetByte(9); + if (components is 1 or 3 or 4) + { + // Info: we assume that all JPEG files with 4 components are RGBW (inverted CMYK) and not CMYK. + // We add a test to tell CMYK from RGBW when we encounter a test file in CMYK format. + var format = components == 3 + ? ImageFormats.JPEG + : (components == 1 + ? ImageFormats.JPEGGRAY + : ImageFormats.JPEGRGBW); + + // We have more faith in the information from the SOF header than the SOS header. +#if DEBUG + // Assert information in Debug mode. + if (ii.ImageFormat != ImageFormats.Unknown) + { + Debug.Assert(format == ii.ImageFormat); + } +#endif + // Always use information from SOF header. Just overwrite previously set format, maybe from SOS header. + ii.ImageFormat = format; + } + + ii.PixelWidth = sizeX; + ii.PixelHeight = sizeY; + + return true; + } + return false; + } + + bool MoveToNextHeader(DataParser dataParser) + { + int blockLength = dataParser.GetWord(2, true); + + int headerMagic = dataParser.GetByte(0); + int headerType = dataParser.GetByte(1); + + if (headerMagic == 0xff) + { + // EOI: last header. + if (headerType == 0xd9) + return false; + + // 0xff followed by 0x00 is not a valid JPEG tag. Skip this entry. + // If the value 0xFF is ever needed in a JPEG file, it must be escaped by immediately + // following it with 0x00. This is called "byte stuffing". + // Source: https://www.ccoderun.ca/programming/2017-01-31_jpeg/ + // Check for standalone markers 0x01 and 0xd0 through 0xd7. + if (headerType is 0x00 or 0x01 or >= 0xd0 and <= 0xd7) + { + dataParser.CurrentOffset += 2; + return true; + } + + // Now assume header with block size. + dataParser.CurrentOffset += 2 + blockLength; + return true; + } + return false; + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Imaging/PdfSharp.Imaging.csproj b/src/foundation/src/shared/src/PdfSharp.Imaging/PdfSharp.Imaging.csproj new file mode 100644 index 00000000..db735f38 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/PdfSharp.Imaging.csproj @@ -0,0 +1,33 @@ + + + + $(PDFsharpTargetFrameworks_Library) + PdfSharp.Internal.Imaging + CORE + True + ..\..\..\..\..\StrongnameKey.snk + + + + true + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Adam7.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Adam7.cs similarity index 97% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Adam7.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Adam7.cs index 48af2e6f..fba19a04 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Adam7.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Adam7.cs @@ -4,10 +4,8 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { - using System.Collections.Generic; - internal static class Adam7 { /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Adler32Checksum.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Adler32Checksum.cs similarity index 96% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Adler32Checksum.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Adler32Checksum.cs index f137043e..97191b1f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Adler32Checksum.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Adler32Checksum.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System.Collections.Generic; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ChunkHeader.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ChunkHeader.cs similarity index 97% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ChunkHeader.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ChunkHeader.cs index 8e1ef7f0..3079accf 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ChunkHeader.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ChunkHeader.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ColorType.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ColorType.cs similarity index 94% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ColorType.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ColorType.cs index f127ab93..172de440 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ColorType.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ColorType.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/CompressionMethod.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/CompressionMethod.cs similarity index 91% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/CompressionMethod.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/CompressionMethod.cs index fe0701a5..216d1392 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/CompressionMethod.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/CompressionMethod.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { /// /// The method used to compress the image data. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Crc32.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Crc32.cs similarity index 98% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Crc32.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Crc32.cs index fc69b5e8..402d4ca7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Crc32.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Crc32.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { /// /// 32-bit Cyclic Redundancy Code used by the PNG for checking the data is intact. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Decoder.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Decoder.cs similarity index 98% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Decoder.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Decoder.cs index cf8cf98d..4638ad67 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Decoder.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Decoder.cs @@ -4,7 +4,9 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +using PdfSharp.Internal.Imaging.Png.BigGustave; + +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/FilterMethod.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/FilterMethod.cs similarity index 91% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/FilterMethod.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/FilterMethod.cs index 1a6a25f7..2cb95466 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/FilterMethod.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/FilterMethod.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { /// /// Indicates the pre-processing method applied to the image data before compression. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/FilterType.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/FilterType.cs similarity index 94% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/FilterType.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/FilterType.cs index 608dce8b..51ad0998 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/FilterType.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/FilterType.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { internal enum FilterType { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/HeaderValidationResult.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/HeaderValidationResult.cs similarity index 97% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/HeaderValidationResult.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/HeaderValidationResult.cs index fb8915b7..052e6bd8 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/HeaderValidationResult.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/HeaderValidationResult.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { internal readonly struct HeaderValidationResult { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/IChunkVisitor.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/IChunkVisitor.cs similarity index 92% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/IChunkVisitor.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/IChunkVisitor.cs index 7c2cd606..ab27550d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/IChunkVisitor.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/IChunkVisitor.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System.IO; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ImageHeader.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ImageHeader.cs similarity index 98% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ImageHeader.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ImageHeader.cs index 67e7c281..f417e140 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/ImageHeader.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/ImageHeader.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System; using System.Collections.Generic; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/InterlaceMethod.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/InterlaceMethod.cs similarity index 92% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/InterlaceMethod.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/InterlaceMethod.cs index ab9dfc91..1fba6f75 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/InterlaceMethod.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/InterlaceMethod.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { /// /// Indicates the transmission order of the image data. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/LICENSE b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/LICENSE similarity index 100% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/LICENSE rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/LICENSE diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Palette.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Palette.cs similarity index 97% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Palette.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Palette.cs index 359c144e..4a669af2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Palette.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Palette.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { /// /// The Palette class. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Pixel.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Pixel.cs similarity index 98% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Pixel.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Pixel.cs index a8e1b6af..9bfca955 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Pixel.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Pixel.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { /// /// A pixel in a image. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Png.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Png.cs similarity index 97% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Png.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Png.cs index 90db658e..1df172f6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/Png.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/Png.cs @@ -4,15 +4,15 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { /// /// A PNG image. Call to open from file or bytes. /// public class Png { - private readonly RawPngData data; - private readonly bool hasTransparencyChunk; + readonly RawPngData data; + readonly bool hasTransparencyChunk; /// /// The header data from the PNG image. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngBuilder.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngBuilder.cs similarity index 96% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngBuilder.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngBuilder.cs index 1d7f34cd..fec8a753 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngBuilder.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngBuilder.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System; using System.Collections.Generic; @@ -18,21 +18,21 @@ namespace PdfSharp.Internal.Png.BigGustave /// public class PngBuilder { - private const byte Deflate32KbWindow = 120; - private const byte ChecksumBits = 1; + const byte Deflate32KbWindow = 120; + const byte ChecksumBits = 1; - private readonly byte[] rawData; - private readonly bool hasAlphaChannel; - private readonly int width; - private readonly int height; - private readonly int bytesPerPixel; + readonly byte[] rawData; + readonly bool hasAlphaChannel; + readonly int width; + readonly int height; + readonly int bytesPerPixel; - private bool hasTooManyColorsForPalette; + bool hasTooManyColorsForPalette; - private readonly int backgroundColorInt; - private readonly Dictionary colorCounts; + readonly int backgroundColorInt; + readonly Dictionary colorCounts; - private readonly List<(string keyword, byte[] data)> storedStrings = new List<(string keyword, byte[] data)>(); + readonly List<(string keyword, byte[] data)> storedStrings = new List<(string keyword, byte[] data)>(); /// /// Create a builder for a PNG with the given width and size. @@ -207,7 +207,7 @@ public PngBuilder SetPixel(Pixel pixel, int x, int y) /// /// /// A keyword identifying the text data between 1-79 characters in length. - /// Must not start with, end with, or contain consecutive whitespace characters. + /// Must not start with, end with, or contain consecutive white-space characters. /// Only characters in the range 32 - 126 and 161 - 255 are permitted. /// /// @@ -243,10 +243,10 @@ public PngBuilder StoreText(string keyword, string text) if (!isValid) { throw new ArgumentException("The keyword can only contain printable Latin 1 characters and spaces in the ranges 32 - 126 or 161 -255. " + - $"The provided keyword '{keyword}' contained an invalid character ({c}) at index {i}.", nameof(keyword)); + $"The provided keyword '{keyword}' contained an invalid character ({c}) at index '{i}'.", nameof(keyword)); } - // pngTODO: trailing, leading and consecutive whitespaces are also prohibited. + // pngTODO: trailing, leading and consecutive white-space are also prohibited. } var bytes = Encoding.UTF8.GetBytes(text); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngOpener.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngOpener.cs similarity index 99% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngOpener.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngOpener.cs index 6e0673b2..dd60c65a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngOpener.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngOpener.cs @@ -7,7 +7,7 @@ using System.IO.Compression; using System.Text; -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { internal static class PngOpener { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngOpenerSettings.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngOpenerSettings.cs similarity index 94% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngOpenerSettings.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngOpenerSettings.cs index c6d0187a..6aca4fe4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngOpenerSettings.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngOpenerSettings.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { /// /// Settings to use when opening a PNG using diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngStreamWriteHelper.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngStreamWriteHelper.cs similarity index 92% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngStreamWriteHelper.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngStreamWriteHelper.cs index 419dcb6e..37925fcd 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/PngStreamWriteHelper.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/PngStreamWriteHelper.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System; using System.Collections.Generic; @@ -13,8 +13,8 @@ namespace PdfSharp.Internal.Png.BigGustave internal class PngStreamWriteHelper : Stream { - private readonly Stream inner; - private readonly List written = new List(); + readonly Stream inner; + readonly List written = new List(); public override bool CanRead => inner.CanRead; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/RawPngData.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/RawPngData.cs similarity index 94% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/RawPngData.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/RawPngData.cs index 1f0940ad..94e8041f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/RawPngData.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/RawPngData.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System; @@ -13,13 +13,13 @@ namespace PdfSharp.Internal.Png.BigGustave /// internal class RawPngData { - private readonly byte[] data; - private readonly int bytesPerPixel; - private readonly int width; - private readonly Palette? palette; - private readonly ColorType colorType; - private readonly int rowOffset; - private readonly int bitDepth; + readonly byte[] data; + readonly int bytesPerPixel; + readonly int width; + readonly Palette? palette; + readonly ColorType colorType; + readonly int rowOffset; + readonly int bitDepth; /// /// Create a new . diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/StreamHelper.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/StreamHelper.cs similarity index 96% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/StreamHelper.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/StreamHelper.cs index d8a7f0e6..796e9e21 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Png/BigGustave/StreamHelper.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/Png/BigGustave/StreamHelper.cs @@ -4,7 +4,7 @@ // BigGustave is distributed with PDFsharp, but was published under a different license. // See file LICENSE in the folder containing this file. -namespace PdfSharp.Internal.Png.BigGustave +namespace PdfSharp.Internal.Imaging.Png.BigGustave { using System; using System.IO; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterPng.cs b/src/foundation/src/shared/src/PdfSharp.Imaging/PngImageImporter.cs similarity index 63% rename from src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterPng.cs rename to src/foundation/src/shared/src/PdfSharp.Imaging/PngImageImporter.cs index d63d4f0a..f10d91a2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Internal/ImageImporterPng.cs +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/PngImageImporter.cs @@ -1,52 +1,51 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -#if CORE -using PdfSharp.Internal.Png.BigGustave; -#endif -using PdfSharp.Pdf; +using PdfSharp.Internal.Imaging.Png.BigGustave; -namespace PdfSharp.Drawing.Internal +namespace PdfSharp.Internal.Imaging { - class ImageImporterPng : ImageImporterRoot, IImageImporter + sealed class PngImageImporter : BitmapImageImporter { - public ImportedImage? ImportImage(StreamReaderHelper stream) + public override bool TryImport(byte[] bitmap, + [MaybeNullWhen(false)] out ImportedImage importedImage) { - // Only used for Core build. - // TODO_OLD Enable for GDI and WPF for testing? -#if WPF || GDI - // We don’t handle any files for WPF or GDI+ build. - return null; -#endif - -#if CORE + importedImage = null!; + try { - stream.CurrentOffset = 0; - if (TestPngFileHeader(stream)) + var dataParser = new DataParser(bitmap); + dataParser.CurrentOffset = 0; + + if (TestPngFileHeader(dataParser)) { - ImportedImage ii = new ImportedImagePng(); - if (TestPngInfoHeader(stream, ii)) + var result = new ImportedPngImage(); + if (TestPngInfoHeader(dataParser, result)) { - return ii; + result.ImageData = bitmap; + + // Determine width and height. + UpdateWidthAndHeight(result); + + importedImage = result; + return true; } } } - // ReSharper disable once EmptyGeneralCatchClause - catch (Exception) + catch (Exception ex) { // Eat exceptions to have this image importer skipped. // We try to find an image importer that can handle the image. + LogTryOpenFailure(nameof(PngImageImporter), ex); } - return null; -#endif + + return false; } -#if CORE /// /// A quick check for PNG files, checking the first 16 bytes. /// - bool TestPngFileHeader(StreamReaderHelper stream) + bool TestPngFileHeader(DataParser dataParser) { var header = new Byte[] { @@ -61,7 +60,7 @@ bool TestPngFileHeader(StreamReaderHelper stream) // File must start with specific header bytes. foreach (var headerByte in header) { - if (stream.GetByte(offset) != headerByte) + if (dataParser.GetByte(offset) != headerByte) return false; ++offset; } @@ -72,7 +71,7 @@ bool TestPngFileHeader(StreamReaderHelper stream) /// /// Read information from PNG image header. /// - private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) + private bool TestPngInfoHeader(DataParser dataParser, ImportedPngImage ii) { // Width: 4 bytes // Height: 4 bytes @@ -82,26 +81,26 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) // Filter method: 1 byte // Interlace method: 1 byte - stream.CurrentOffset = 16; - var width = stream.GetDWord(0, true); - var height = stream.GetDWord(4, true); - var bitDepth = stream.GetByte(8); - var colorType = stream.GetByte(9); - var compressionMethod = stream.GetByte(10); - var filterMethod = stream.GetByte(11); - var interlaceMethod = stream.GetByte(12); - - ii.Information.Width = width; - ii.Information.Height = height; - ii.Information.HorizontalDPI = 0; - ii.Information.VerticalDPI = 0; - ii.Information.HorizontalDPM = 0; - ii.Information.VerticalDPM = 0; - ii.Information.HorizontalAspectRatio = 0; - ii.Information.VerticalAspectRatio = 0; - ii.Information.DefaultDPI = 96; // Assume 96 DPI if information not provided in the file. - - ii.Information.ColorsUsed = 0; + dataParser.CurrentOffset = 16; + var width = dataParser.GetDWord(0, true); + var height = dataParser.GetDWord(4, true); + var bitDepth = dataParser.GetByte(8); + var colorType = dataParser.GetByte(9); + var compressionMethod = dataParser.GetByte(10); + var filterMethod = dataParser.GetByte(11); + var interlaceMethod = dataParser.GetByte(12); + + ii.PixelWidth = (int)width; + ii.PixelHeight = (int)height; + ii.DpiX = 96; + ii.DpiY = 96; + //ii.Information.HorizontalDPM = 0; + //ii.Information.VerticalDPM = 0; + //ii.Information.HorizontalAspectRatio = 0; + //ii.Information.VerticalAspectRatio = 0; + //ii.Information.DefaultDPI = 96; // Assume 96 DPI if information not provided in the file. + + ii.ColorsUsed = 0; // colorType can be 0, 2, 3, 4, or 6. switch (colorType) { @@ -110,7 +109,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) switch (bitDepth) { case 8: - ii.Information.ImageFormat = ImageInformation.ImageFormats.Grayscale8; + ii.ImageFormat = ImageFormats.Gray8; break; default: throw new InvalidOperationException($"Unsupported bit depth {bitDepth} for PNG color type {colorType}."); @@ -125,7 +124,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) switch (bitDepth) { case 8: - ii.Information.ImageFormat = ImageInformation.ImageFormats.RGB24; + ii.ImageFormat = ImageFormats.Bgr32; break; default: throw new InvalidOperationException($"Unsupported bit depth {bitDepth} for PNG color type {colorType}."); @@ -137,13 +136,13 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) switch (bitDepth) { case 1: - ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette1; + ii.ImageFormat = ImageFormats.Indexed1; break; case 4: - ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette4; + ii.ImageFormat = ImageFormats.Indexed4; break; case 8: - ii.Information.ImageFormat = ImageInformation.ImageFormats.Palette8; + ii.ImageFormat = ImageFormats.Indexed8; break; default: throw new InvalidOperationException($"Unsupported bit depth {bitDepth} for PNG color type {colorType}."); @@ -155,20 +154,19 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) switch (bitDepth) { case 8: - ii.Information.ImageFormat = ImageInformation.ImageFormats.Grayscale8; + ii.ImageFormat = ImageFormats.Gray8; break; default: throw new InvalidOperationException($"Unsupported bit depth {bitDepth} for PNG color type {colorType}."); } break; - // TODO_OLD case 4: case 6: // Each pixel is an R,G,B triple, followed by an alpha sample. 8, 16. switch (bitDepth) { case 8: - ii.Information.ImageFormat = ImageInformation.ImageFormats.ARGB32; + ii.ImageFormat = ImageFormats.Bgra32; break; default: throw new InvalidOperationException($"Unsupported bit depth {bitDepth} for PNG color type {colorType}."); @@ -182,56 +180,69 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) // Now access the PNG pixels. // Png does not implement IDisposable. { - // Do not use OriginalStream if we have Data. - if (stream.Data == null! && stream.OriginalStream != null!) - stream.OriginalStream.Position = 0; var myVisitor = new MyVisitor(); - var png = stream.Data == null! && stream.OriginalStream != null ? - Png.Open(stream.OriginalStream, myVisitor) : - Png.Open(stream.Data!, myVisitor); + var png = PdfSharp.Internal.Imaging.Png.BigGustave.Png.Open(dataParser.Data!, myVisitor); - if (png.Width != ii.Information.Width || - png.Height != ii.Information.Height) + if (png.Width != ii.PixelWidth || + png.Height != ii.PixelHeight) { throw new InvalidOperationException($"Unsupported PNG image - internal error."); } if (myVisitor.IsValid) { + var x = myVisitor.Horizontal; + var y = myVisitor.Vertical; if (myVisitor.IsMeter) { - ii.Information.HorizontalDPM = myVisitor.Horizontal; - ii.Information.VerticalDPM = myVisitor.Vertical; + // Unit is meter. + ii.DpiX = x / ImportedImage.InchPerMeter; + ii.DpiY = y / ImportedImage.InchPerMeter; + if (ii.DpiX == 0 || ii.DpiY == 0) + { + ii.DpiX = 96; + ii.DpiY = 96; + } + else + { + RoundDpiAfterConversionFromMetricValue(ii); + } + } + else + { + // Unit is unknown. + if (x > 0 && y > 0) + { + ii.DpiX = 96; + ii.DpiY = (float)(96d * x / y); + } } - ii.Information.HorizontalAspectRatio = myVisitor.Horizontal; - ii.Information.VerticalAspectRatio = myVisitor.Vertical; } - switch (ii.Information.ImageFormat) + switch (ii.ImageFormat) { // Copy image data later, only when it is needed? Or do it here because the stream is already open? - case ImageInformation.ImageFormats.RGB24: - case ImageInformation.ImageFormats.ARGB32: + case ImageFormats.Bgr32: + case ImageFormats.Bgra32: { if (png.HasAlphaChannel != true && - ii.Information.ImageFormat == ImageInformation.ImageFormats.ARGB32) + ii.ImageFormat == ImageFormats.Bgra32) { throw new InvalidOperationException($"Unsupported PNG ARGB32 image - internal error."); } if (png.HasAlphaChannel != false && - ii.Information.ImageFormat == ImageInformation.ImageFormats.RGB24) + ii.ImageFormat == ImageFormats.Bgr32) { throw new InvalidOperationException($"Unsupported PNG RGB24 image - internal error."); } - bool hasMask = ii.Information.ImageFormat == ImageInformation.ImageFormats.ARGB32; + bool hasMask = ii.ImageFormat == ImageFormats.Bgra32; var length = png.Width * 3 * png.Height; var lengthMask = png.Width * png.Height; var data = new Byte[length]; var mask = hasMask ? new Byte[lengthMask] : null; - ImagePrivateDataPng pngData; - ii.Data = pngData = new ImagePrivateDataPng(data, mask); - ii.Data.Image = ii; + ii.ExtractedImageData = data; + ii.AlphaMaskData = mask; int offset = 0; int maskOffset = 0; bool maskUsed = false; @@ -239,7 +250,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) { for (int x = 0; x < png.Width; ++x) { - // TODO_OLD Add GetRow to PNG library? + // Performance idea: Add GetRow to PNG library? var pel = png.GetPixel(x, y); data[offset] = pel.R; data[offset + 1] = pel.G; @@ -256,13 +267,13 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) if (!maskUsed) { // No pixels with transparency found, delete the mask. - pngData.AlphaMask = null; + ii.AlphaMaskData = null; } } break; // Image with palette and 1 BPP. - case ImageInformation.ImageFormats.Palette1: + case ImageFormats.Indexed1: { var hasAlpha = png.HasAlphaChannel; var palette = png.GetPalette(); @@ -273,21 +284,20 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) var length = bytesPerLine * png.Height; var data = new Byte[length]; var alphaMask = hasAlpha ? new Byte[png.Width * png.Height] : null; - ImagePrivateDataPng pngData; - ii.Data = pngData = new ImagePrivateDataPng(data, alphaMask); - ii.Data.Image = ii; + ii.ExtractedImageData = data; + ii.AlphaMaskData = alphaMask; - uint colors = (uint)palette.Data.Length / 4; - ii.Information.ColorsUsed = colors; - pngData.PaletteData = new Byte[colors * 3]; + int colors = palette.Data.Length / 4; + ii.ColorsUsed = colors; + ii.PaletteData = new Byte[colors * 3]; var alpha = hasAlpha ? new Byte[colors] : null; int offset = 0; for (int c = 0; c < colors; ++c) { var pel = palette.GetPixel(c); - pngData.PaletteData[offset++] = pel.R; - pngData.PaletteData[offset++] = pel.G; - pngData.PaletteData[offset++] = pel.B; + ii.PaletteData[offset++] = pel.R; + ii.PaletteData[offset++] = pel.G; + ii.PaletteData[offset++] = pel.B; //if (hasAlpha) if (alpha != null) alpha[c] = pel.A; @@ -300,7 +310,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) { for (int x = 0; x < bytesPerLine; ++x) { - // TODO_OLD Add GetRow to PNG library? Performance optimization. + // Performance idea: Add GetRow to PNG library? int pels = 0; for (var index = 0; index < 8; index++) { @@ -312,7 +322,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) Debug.Assert(alphaMask != null, nameof(alphaMask) + " != null"); Debug.Assert(alpha != null, nameof(alpha) + " != null"); - alphaMask[offsetAlpha] = alpha[pel]; + alphaMask![offsetAlpha] = alpha![pel]; alphaUsed |= alphaMask[offsetAlpha] != 255; ++offsetAlpha; } @@ -323,12 +333,12 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) } if (!alphaUsed) - pngData.AlphaMask = null; + ii.AlphaMaskData = null; } break; // Image with palette and 4 BPP. - case ImageInformation.ImageFormats.Palette4: + case ImageFormats.Indexed4: { var hasAlpha = png.HasAlphaChannel; var palette = png.GetPalette(); @@ -339,24 +349,23 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) var length = lineBytes * png.Height; var data = new Byte[length]; var alphaMask = hasAlpha ? new Byte[png.Width * png.Height] : null; - ImagePrivateDataPng pngData; - ii.Data = pngData = new ImagePrivateDataPng(data, alphaMask); - ii.Data.Image = ii; + ii.ExtractedImageData = data; + ii.AlphaMaskData = alphaMask; - uint colors = (uint)palette.Data.Length / 4; - ii.Information.ColorsUsed = colors; - pngData.PaletteData = new Byte[colors * 3]; + int colors = palette.Data.Length / 4; + ii.ColorsUsed = colors; + var paletteData = new Byte[colors * 3]; + ii.PaletteData = paletteData; var alpha = hasAlpha ? new Byte[colors] : null; int offset = 0; for (int c = 0; c < colors; ++c) { var pel = palette.GetPixel(c); - pngData.PaletteData[offset++] = pel.R; - pngData.PaletteData[offset++] = pel.G; - pngData.PaletteData[offset++] = pel.B; + paletteData[offset++] = pel.R; + paletteData[offset++] = pel.G; + paletteData[offset++] = pel.B; //if (hasAlpha) - if (alpha != null) - alpha[c] = pel.A; + alpha?[c] = pel.A; } var alphaUsed = false; @@ -366,17 +375,14 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) { for (int x = 0; x < png.Width; x += 2) { - // TODO_OLD Add GetRow to PNG library? Performance optimization. + // Performance idea: Add GetRow to PNG library? var pel = png.GetPixelIndex(x, y); var pel2 = x + 1 < png.Width ? png.GetPixelIndex(x + 1, y) : 0; data[offset] = (byte)(pel2 + (pel << 4)); if (hasAlpha) { // alphaMask and alpha cannot be null here if hasAlpha is true. Suppress warnings in editor. - Debug.Assert(alphaMask != null, nameof(alphaMask) + " != null"); - Debug.Assert(alpha != null, nameof(alpha) + " != null"); - - alphaMask[offsetAlpha] = alpha[pel]; + alphaMask![offsetAlpha] = alpha![pel]; alphaUsed |= alphaMask[offsetAlpha] != 255; if (x + 1 < png.Width) { @@ -390,12 +396,12 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) } if (!alphaUsed) - pngData.AlphaMask = null; + ii.AlphaMaskData = null; } break; // Image with palette and 8 BPP. - case ImageInformation.ImageFormats.Palette8: + case ImageFormats.Indexed8: { var hasAlpha = png.HasAlphaChannel; var palette = png.GetPalette(); @@ -405,24 +411,22 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) var length = png.Width * png.Height; var data = new Byte[length]; var alphaMask = hasAlpha ? new Byte[length] : null; - ImagePrivateDataPng pngData; - ii.Data = pngData = new ImagePrivateDataPng(data, alphaMask); - ii.Data.Image = ii; + ii.ExtractedImageData = data; + ii.AlphaMaskData = alphaMask; - uint colors = (uint)palette.Data.Length / 4; - ii.Information.ColorsUsed = colors; - pngData.PaletteData = new Byte[colors * 3]; + int colors = palette.Data.Length / 4; + ii.ColorsUsed = colors; + var paletteData = new Byte[colors * 3]; + ii.PaletteData = paletteData; var alpha = hasAlpha ? new Byte[colors] : null; int offset = 0; for (int c = 0; c < colors; ++c) { var pel = palette.GetPixel(c); - pngData.PaletteData[offset++] = pel.R; - pngData.PaletteData[offset++] = pel.G; - pngData.PaletteData[offset++] = pel.B; - //if (hasAlpha) - if (alpha != null) - alpha[c] = pel.A; + paletteData[offset++] = pel.R; + paletteData[offset++] = pel.G; + paletteData[offset++] = pel.B; + alpha?[c] = pel.A; } var alphaUsed = false; @@ -431,7 +435,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) { for (int x = 0; x < png.Width; ++x) { - // TODO_OLD Add GetRow to PNG library? Performance optimization. + // Performance idea: Add GetRow to PNG library? var pel = png.GetPixelIndex(x, y); data[offset] = (byte)pel; if (hasAlpha) @@ -440,7 +444,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) Debug.Assert(alphaMask != null, nameof(alphaMask) + " != null"); Debug.Assert(alpha != null, nameof(alpha) + " != null"); - alphaMask[offset] = alpha[pel]; + alphaMask![offset] = alpha![pel]; alphaUsed |= alphaMask[offset] != 255; } ++offset; @@ -448,21 +452,20 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) } if (!alphaUsed) - pngData.AlphaMask = null; + ii.AlphaMaskData = null; } break; // Image with grayscale and 8 BPP. - case ImageInformation.ImageFormats.Grayscale8: + case ImageFormats.Gray8: { var hasAlpha = png.HasAlphaChannel; var length = png.Width * png.Height; var data = new Byte[length]; var alphaMask = hasAlpha ? new Byte[length] : null; - ImagePrivateDataPng pngData; - ii.Data = pngData = new ImagePrivateDataPng(data, alphaMask); - ii.Data.Image = ii; + ii.ExtractedImageData = data; + ii.AlphaMaskData = alphaMask; var alphaUsed = false; var offset = 0; @@ -470,7 +473,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) { for (int x = 0; x < png.Width; ++x) { - // TODO_OLD Add GetRow to PNG library? Performance optimization. + // Performance idea: Add GetRow to PNG library? var pel = png.GetPixel(x, y); data[offset] = pel.R; if (hasAlpha) @@ -478,7 +481,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) // alphaMask and alpha cannot be null here if hasAlpha is true. Suppress warnings in editor. Debug.Assert(alphaMask != null, nameof(alphaMask) + " != null"); - alphaMask[offset] = pel.A; + alphaMask![offset] = pel.A; alphaUsed |= alphaMask[offset] != 255; } @@ -487,12 +490,12 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) } if (!alphaUsed) - pngData.AlphaMask = null; + ii.AlphaMaskData = null; } break; default: - throw new InvalidOperationException($"Unsupported PNG format {ii.Information.ImageFormat}."); + throw new InvalidOperationException($"Unsupported PNG format {ii.ImageFormat}."); } } @@ -502,6 +505,7 @@ private Boolean TestPngInfoHeader(StreamReaderHelper stream, ImportedImage ii) class MyVisitor : IChunkVisitor { internal bool IsValid; + // Unit is Meter if true, otherwise unknown. internal bool IsMeter; internal int Vertical; internal int Horizontal; @@ -527,63 +531,5 @@ public void Visit(Stream stream, ImageHeader header, ChunkHeader chunkHeader, by } } } - - public ImageData PrepareImage(ImagePrivateData data) - { - throw new NotImplementedException(); - } -#endif -#if GDI || WPF - // Not used, not implemented. - public ImageData PrepareImage(ImagePrivateData data) - { - throw new NotImplementedException(); - } -#endif - } - -#if CORE - /// - /// Data imported from PNG files. Used to prepare the data needed for PDF. - /// - class ImportedImagePng : ImportedImage - { - /// - /// Initializes a new instance of the class. - /// - public ImportedImagePng() - : base() - { } - - internal override ImageData PrepareImageData(PdfDocumentOptions options) - { - var data = (ImagePrivateDataPng?)Data ?? NRT.ThrowOnNull(); - ImageDataBitmap imageData = new ImageDataBitmap(data.Bitmap, data.AlphaMask!); // NRT Check in constructor. - - if (data.PaletteData != null) - { - imageData.PaletteData = data.PaletteData; - imageData.PaletteDataLength = data.PaletteData.Length; - } - return imageData; - } - } - - /// - /// Image data needed for PDF bitmap images. - /// - class ImagePrivateDataPng : ImagePrivateData - { - public ImagePrivateDataPng(byte[] bitmap, byte[]? alphaMask) - { - Bitmap = bitmap; - AlphaMask = alphaMask; - } - - internal readonly byte[] Bitmap; - internal byte[]? AlphaMask; - - internal byte[]? PaletteData { get; set; } } -#endif } diff --git a/src/foundation/src/shared/src/PdfSharp.Imaging/README.md b/src/foundation/src/shared/src/PdfSharp.Imaging/README.md new file mode 100644 index 00000000..f0cc11b4 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Imaging/README.md @@ -0,0 +1,4 @@ +# PDFsharp.Imaging + +This is the PDFsharp assembly that handles importing and exporting bitmap images. +It it used by both PDFsharp XGraphics and the new PDFsharp Graphics. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/CharacterMap.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/CharacterMap.cs similarity index 82% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/CharacterMap.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/CharacterMap.cs index 21402531..b75d162d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/CharacterMap.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/CharacterMap.cs @@ -3,12 +3,12 @@ using System.Collections; -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { /// /// Filled by cmap type 4 and 12. /// - internal sealed class CharacterMap : IDictionary // Called IntMap in WPF. + internal sealed class CharacterMap : IDictionary // RENAME CodePointMap { /// /// Glyph count is used for validating cmap contents. @@ -22,7 +22,7 @@ internal void SetGlyphCount(ushort glyphCount) internal void SetCharacterEntry(int i, ushort value) { - // Some fonts have cmap entries that point to glyphs outside of the font. + // Some fonts have cmap entries that point to glyphs outside the font. // Just skip such entries. if (value >= _glyphCount) return; @@ -42,11 +42,11 @@ internal void SetCharacterEntry(int i, ushort value) public void Clear() => throw new NotSupportedException(); - public Boolean Contains(KeyValuePair item) => throw new NotSupportedException(); + public bool Contains(KeyValuePair item) => throw new NotSupportedException(); public void CopyTo(KeyValuePair[] array, int arrayIndex) => throw new NotImplementedException(); - public Boolean Remove(KeyValuePair item) => throw new NotSupportedException(); + public bool Remove(KeyValuePair item) => throw new NotSupportedException(); public int Count => _map.Count; @@ -54,9 +54,9 @@ internal void SetCharacterEntry(int i, ushort value) public void Add(int key, ushort value) => throw new NotSupportedException(); - public Boolean ContainsKey(int key) => _map.ContainsKey(key); + public bool ContainsKey(int key) => _map.ContainsKey(key); - public Boolean Remove(int key) => throw new NotSupportedException(); + public bool Remove(int key) => throw new NotSupportedException(); public bool TryGetValue(int key, out ushort value) => _map.TryGetValue(key, out value); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CodePointGlyphIndexPair.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/CodePointGlyphIndexPair.cs similarity index 85% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CodePointGlyphIndexPair.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/CodePointGlyphIndexPair.cs index 8455cbf4..c727eb07 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts/CodePointGlyphIndexPair.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/CodePointGlyphIndexPair.cs @@ -1,9 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using PdfSharp.Fonts.OpenType; - -namespace PdfSharp.Fonts +namespace PdfSharp.Internal.OpenType { /// /// The combination of a Unicode code point and the glyph index of this code point in a particular font face. @@ -27,7 +25,7 @@ public struct CodePointGlyphIndexPair(int codePoint, ushort glyphIndex) /// /// The combination of a glyph index and its glyph record in the color table, if it exists. /// - struct GlyphIndexGlyphColorRecordPair(ushort glyphIndex, ColorTable.GlyphRecord? colorRecord) + public struct GlyphIndexGlyphColorRecordPair(ushort glyphIndex, ColorTable.GlyphRecord? colorRecord) { /// /// The glyph index. @@ -37,6 +35,6 @@ struct GlyphIndexGlyphColorRecordPair(ushort glyphIndex, ColorTable.GlyphRecord? /// /// The color-record of the glyph if provided by the font. /// - internal ColorTable.GlyphRecord? ColorRecord = colorRecord; + public ColorTable.GlyphRecord? ColorRecord = colorRecord; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GenericFontTable.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/GenericFontTable.cs similarity index 94% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GenericFontTable.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/GenericFontTable.cs index 5fc7c440..70521798 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/GenericFontTable.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/GenericFontTable.cs @@ -5,7 +5,7 @@ //using FWord = System.Int16; //using UFWord = System.UInt16; -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { #if true_ /// @@ -28,7 +28,7 @@ public GenericFontTable(OpenTypeFontTable fontTable) public GenericFontTable(OpenTypeFontFace fontData, string tag) : base(fontData, tag) { - _fontData = fontData; + FontData = fontData; } protected override OpenTypeFontTable DeepCopy() diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/GlyphDataTable.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/GlyphDataTable.cs new file mode 100644 index 00000000..62f901d8 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/GlyphDataTable.cs @@ -0,0 +1,370 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +#define VERBOSE_ + +using PdfSharp.Internal.Threading; + +//using PdfSharp.Internal; + +//using Fixed = System.Int32; +//using FWord = System.Int16; +//using UFWord = System.UInt16; + +// ReSharper disable UnusedMember.Global +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +namespace PdfSharp.Internal.OpenType +{ + [Flags] + enum SimpleGlyphFlags : byte + { + /// + /// Bit 0: If set, the point is on the curve; otherwise, it is off the curve. + /// + ON_CURVE_POINT = 0x01, + + /// + /// Bit 1: If set, the corresponding x-coordinate is 1 byte long, and the sign is determined + /// by the X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR flag. If not set, its interpretation depends + /// on the X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR flag: If that other flag is set, the x-coordinate + /// is the same as the previous x-coordinate, and no element is added to the xCoordinates + /// array. If both flags are not set, the corresponding element in the xCoordinates array is + /// two bytes and interpreted as a signed integer. See the description of the + /// X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR flag for additional information. + /// + X_SHORT_VECTOR = 0x02, + + /// + /// Bit 2: If set, the corresponding y-coordinate is 1 byte long, and the sign is determined + /// by the Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR flag. If not set, its interpretation depends + /// on the Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR flag: If that other flag is set, the y-coordinate + /// is the same as the previous y-coordinate, and no element is added to the yCoordinates + /// array. If both flags are not set, the corresponding element in the yCoordinates array is + /// two bytes and interpreted as a signed integer. See the description of the + /// Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR flag for additional information. + /// + Y_SHORT_VECTOR = 0x04, + + /// + /// Bit 3: If set, the next byte (read as unsigned) specifies the number of additional times + /// this flag byte is to be repeated in the logical flags array — that is, the number of + /// additional logical flag entries inserted after this entry. (In the expanded logical + /// array, this bit is ignored.) In this way, the number of flags listed can be smaller + /// than the number of points in the glyph description. + /// + REPEAT_FLAG = 0x08, + + /// + /// Bit 4: This flag has two meanings, depending on how the X_SHORT_VECTOR flag is set. + /// If X_SHORT_VECTOR is set, this bit describes the sign of the value, with 1 equaling + /// positive and 0 negative. If X_SHORT_VECTOR is not set and this bit is set, then the + /// current x-coordinate is the same as the previous x-coordinate. If X_SHORT_VECTOR is + /// not set and this bit is also not set, the current x-coordinate is a signed 16-bit + /// delta vector. + /// + X_IS_SAME_OR_POSITIVE_X_SHORT_VECTOR = 0x10, + + /// + /// Bit 5: This flag has two meanings, depending on how the Y_SHORT_VECTOR flag is set. + /// If Y_SHORT_VECTOR is set, this bit describes the sign of the value, with 1 equaling + /// positive and 0 negative. If Y_SHORT_VECTOR is not set and this bit is set, then the + /// current y-coordinate is the same as the previous y-coordinate. If Y_SHORT_VECTOR is + /// not set and this bit is also not set, the current y-coordinate is a signed 16-bit + /// delta vector. + /// + Y_IS_SAME_OR_POSITIVE_Y_SHORT_VECTOR = 0x20, + + /// + /// Bit 6: If set, contours in the glyph description could overlap. Use of this flag is + /// not required — that is, contours may overlap without having this flag set. When used, + /// it must be set on the first flag byte for the glyph. See additional details below. + /// + OVERLAP_SIMPLE = 0x40, + + /// + /// Bit 7 is reserved: set to zero. + /// + Reserved = 0x80 + } + + [Flags] + enum ComponentGlyphFlags : ushort + { + /// + /// Bit 0: If this is set, the arguments are 16-bit (uint16 or int16); + /// otherwise, they are bytes (uint8 or int8). + /// + ARG_1_AND_2_ARE_WORDS = 0x0001, + + /// + /// Bit 1: If this is set, the arguments are signed xy values; + /// otherwise, they are unsigned point numbers. + /// + ARGS_ARE_XY_VALUES = 0x0002, + + /// + /// Bit 2: If set and ARGS_ARE_XY_VALUES is also set, the xy values are rounded + /// to the nearest grid line. Ignored if ARGS_ARE_XY_VALUES is not set. + /// + ROUND_XY_TO_GRID = 0x0004, + + /// + /// Bit 3: This indicates that there is a simple scale for the component. + /// Otherwise, scale = 1.0. + /// + WE_HAVE_A_SCALE = 0x0008, + + /// + /// Bit 5: Indicates at least one more glyph after this one. + /// + MORE_COMPONENTS = 0x0020, + + /// + /// Bit 6: The x direction will use a different scale from the y direction. + /// + WE_HAVE_AN_X_AND_Y_SCALE = 0x0040, + + /// + /// Bit 7: There is a 2 by 2 transformation that will be used to scale the component. + /// + WE_HAVE_A_TWO_BY_TWO = 0x0080, + + /// + /// Bit 8: Following the last component are instructions for the composite glyph. + /// + WE_HAVE_INSTRUCTIONS = 0x0100, + + /// + /// Bit 9: If set, this forces the aw and lsb (and rsb) for the composite to be equal + /// to those from this component glyph. This works for hinted and unhinted glyphs. + /// + USE_MY_METRICS = 0x0200, + + /// + /// Bit 10: If set, the components of the compound glyph overlap. Use of this flag is not + /// required — that is, component glyphs may overlap without having this flag set. When used, + /// it must be set on the flag word for the first component. Some rasterizer implementations + /// may require fonts to use this flag to obtain correct behavior — see additional remarks, + /// above, for the similar OVERLAP_SIMPLE flag used in simple-glyph descriptions. + /// + OVERLAP_COMPOUND = 0x0400, + + /// + /// Bit 11: The composite is designed to have the component offset scaled. + /// Ignored if ARGS_ARE_XY_VALUES is not set. + /// + SCALED_COMPONENT_OFFSET = 0x0800, + + /// + /// Bit 12: The composite is designed not to have the component offset scaled. + /// Ignored if ARGS_ARE_XY_VALUES is not set. + /// + UNSCALED_COMPONENT_OFFSET = 0x1000, + + /// + /// Bits 4, 13, 14 and 15 are reserved: set to 0. + /// + Reserved = 0xE010 + } + + ////// Re/Sharper restore InconsistentNaming + ////// Re/Sharper restore UnusedMember.Global + + /// + /// This table contains information that describes the glyphs in the font in the TrueType outline format. + /// Information regarding the rasterizer (scaler) refers to the TrueType rasterizer. + /// http://www.microsoft.com/typography/otspec/glyf.htm + /// + public class GlyphDataTable : OpenTypeFontTable + { + public const string Tag = TableTagNames.Glyf; + + internal byte[] GlyphTable = null!; + + + public GlyphDataTable() + : base(null, Tag) + { + DirectoryEntry.Tag = TableTagNames.Glyf; + } + + public GlyphDataTable(OpenTypeFontFace fontData) + : base(fontData, Tag) + { + DirectoryEntry.Tag = TableTagNames.Glyf; + Read(); + } + + /// + /// Converts the bytes in a handy representation. + /// + public void Read() + { + try + { + // Not yet needed... + } + // ReSharper disable once RedundantCatchClause + catch (Exception) + { + throw; + } + } + + /// + /// Gets the data of the specified glyph. + /// + public byte[] GetGlyphData(int glyph) + { + //var loca = FontData!.loca; + int start = GetOffset(glyph); + int next = GetOffset(glyph + 1); + int count = next - start; + byte[] bytes = new byte[count]; + Buffer.BlockCopy(FontData!.OTFontSource!.Bytes, start, bytes, 0, count); + return bytes; + } + + /// + /// Gets the size of the byte array that defines the glyph. + /// + public int GetGlyphSize(int glyph) + { + //var loca = FontData.loca; + return GetOffset(glyph + 1) - GetOffset(glyph); + } + + /// + /// Gets the offset of the specified glyph relative to the first byte of the font image. + /// + public int GetOffset(int glyph) + { + return DirectoryEntry.Offset + FontData!.loca.LocaTable[glyph]; + } + + /// + /// Adds for all composite glyphs, the glyphs the composite one is made of. + /// + public void CompleteGlyphClosure(Dictionary glyphs) + { + int count = glyphs.Count; + ushort[] glyphArray = new ushort[glyphs.Count]; + glyphs.Keys.CopyTo(glyphArray, 0); + // ReSharper disable once CanSimplifyDictionaryLookupWithTryAdd because of .NET Framework + if (!glyphs.ContainsKey(0)) + glyphs.Add(0, null); + // #NFM + // Ensure no other threads can alter the Position property of this OpenTypeFontFace instance, + // see https://forum.pdfsharp.net/viewtopic.php?f=2&t=2248#p10378 + try + { + Locks.EnterFontManagement(); + for (int idx = 0; idx < count; idx++) + AddCompositeGlyphs(glyphs, glyphArray[idx]); + } + finally { Locks.ExitFontManagement(); } + } + + /// + /// If the specified glyph is a composite glyph add the glyphs it is made of to the glyph table. + /// + void AddCompositeGlyphs(Dictionary glyphs, int glyph) + { + //int start = fontData.loca.GetOffset(glyph); + int start = GetOffset(glyph); + // Has no contour? + if (start == GetOffset(glyph + 1)) + return; + FontData!.Position = start; + int numContours = FontData.ReadShort(); + // Isn’t a composite glyph? + if (numContours >= 0) + return; + FontData.SeekOffset(8); + for (; ; ) + { + ComponentGlyphFlags flags = (ComponentGlyphFlags)FontData.ReadUFWord(); + ushort cGlyph = FontData.ReadUFWord(); + ////if (!glyphs.ContainsKey(cGlyph)) + //// glyphs.Add(cGlyph, null); + glyphs.TryAdd(cGlyph, null); + if ((flags & ComponentGlyphFlags.MORE_COMPONENTS) == 0) + return; + int offset = (flags & ComponentGlyphFlags.ARG_1_AND_2_ARE_WORDS) == 0 ? 2 : 4; + if ((flags & ComponentGlyphFlags.WE_HAVE_A_SCALE) != 0) + offset += 2; + else if ((flags & ComponentGlyphFlags.WE_HAVE_AN_X_AND_Y_SCALE) != 0) + offset += 4; + if ((flags & ComponentGlyphFlags.WE_HAVE_A_TWO_BY_TWO) != 0) + offset += 8; + FontData.SeekOffset(offset); + } + } + + /// + /// Gets the glyph header for the specified glyph. + /// Used in PDFsharp Graphics to calculate right and bottom side bearings. + /// + public GlyphHeader GetGlyphHeader(ushort glyphIndex) + { + FontData!.Position = GetOffset(glyphIndex); + var glyphHeader = new GlyphHeader + { + numberOfContours = FontData.ReadShort(), + xMin = FontData.ReadShort(), + yMin = FontData.ReadShort(), + xMax = FontData.ReadShort(), + yMax = FontData.ReadShort() + }; + return glyphHeader; + } + + /// + /// Prepares the font table to be compiled into its binary representation. + /// + public override void PrepareForCompilation() + { + base.PrepareForCompilation(); + + if (DirectoryEntry.Length == 0) + DirectoryEntry.Length = GlyphTable.Length; + DirectoryEntry.CheckSum = CalcChecksum(GlyphTable); + } + + /// + /// Converts the font into its binary representation. + /// + public override void Write(OpenTypeFontWriter writer) + { + writer.Write(GlyphTable, 0, DirectoryEntry.PaddedLength); + } + + //// Constants from OpenType spec. + //const int ARG_1_AND_2_ARE_WORDS = 1; + //const int WE_HAVE_A_SCALE = 8; + //const int MORE_COMPONENTS = 32; + //const int WE_HAVE_AN_X_AND_Y_SCALE = 64; + //const int WE_HAVE_A_TWO_BY_TWO = 128; + } + + public struct GlyphHeader + { + /// + /// If the number of contours is greater than or equal to zero, this is a simple glyph. + /// If negative, this is a composite glyph — the value -1 should be used for composite glyphs. + /// + public short numberOfContours; + + public short xMin; // Minimum x for coordinate data. + + public short yMin; // Minimum y for coordinate data. + + public short xMax; // Maximum x for coordinate data. + + public short yMax; // Maximum y for coordinate data. + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/IRefFontTable.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/IRefFontTable.cs similarity index 83% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/IRefFontTable.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/IRefFontTable.cs index 27336a8c..e2cc347a 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/IRefFontTable.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/IRefFontTable.cs @@ -5,7 +5,7 @@ //using FWord = System.Int16; //using UFWord = System.UInt16; -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { /// /// Represents an indirect reference to an existing font table in a font image. @@ -17,7 +17,7 @@ sealed class IRefFontTable : OpenTypeFontTable public IRefFontTable(OpenTypeFontFace fontData, OpenTypeFontTable fontTable) : base(null, fontTable.DirectoryEntry.Tag) { - _fontData = fontData; + FontData = fontData; _irefDirectoryEntry = fontTable.DirectoryEntry; } @@ -36,7 +36,7 @@ public override void PrepareForCompilation() if (DirectoryEntry.Tag != TableTagNames.Head) { byte[] bytes = new byte[DirectoryEntry.PaddedLength]; - Buffer.BlockCopy(_irefDirectoryEntry.FontTable._fontData!.FontSource.Bytes, _irefDirectoryEntry.Offset, bytes, 0, DirectoryEntry.PaddedLength); + Buffer.BlockCopy(_irefDirectoryEntry.FontTable.FontData!.OTFontSource.Bytes, _irefDirectoryEntry.Offset, bytes, 0, DirectoryEntry.PaddedLength); uint checkSum1 = DirectoryEntry.CheckSum; uint checkSum2 = CalcChecksum(bytes); // TODO_OLD: Sometimes this Assert fails, @@ -50,7 +50,7 @@ public override void PrepareForCompilation() /// public override void Write(OpenTypeFontWriter writer) { - writer.Write(_irefDirectoryEntry.FontTable._fontData!.FontSource.Bytes, _irefDirectoryEntry.Offset, _irefDirectoryEntry.PaddedLength); + writer.Write(_irefDirectoryEntry.FontTable.FontData!.OTFontSource.Bytes, _irefDirectoryEntry.Offset, _irefDirectoryEntry.PaddedLength); } } } diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/KeyHelper.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/KeyHelper.cs new file mode 100644 index 00000000..76f03acb --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/KeyHelper.cs @@ -0,0 +1,51 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Internal.OpenType +{ + public static class KeyHelper + { + public static string CalcFontFaceKey(string familyName, string faceName, + OpenTypeFontStyle style, OpenTypeFontWeight weight, OpenTypeFontStretch stretch) + { + var key = Invariant($"{familyName}({faceName})|{(int)style}|{(int)weight}|{(int)stretch}"); + return key; + } + public static string CalcGlyphTypefaceKey(string familyName, string faceName, + OpenTypeFontStyle style, OpenTypeFontWeight weight, OpenTypeFontStretch stretch, + bool isBoldSimulated, bool isObliqueSimulated) + { + var key = Invariant($"{familyName}({faceName})|{(int)style}|{(int)weight}|{(int)stretch}|{ + (isBoldSimulated ? "B" : "")}{(isObliqueSimulated ? "O" : "")}"); + return key; + } + + public static string CalcTypefaceKey(OpenTypeFontStyle style, OpenTypeFontWeight weight, OpenTypeFontStretch stretch) + { + var key = Invariant($"{(int)style}|{(int)weight}|{(int)stretch}"); + return key; + } + + // Replace this code. + public static string ComputeFdKey(string name, OTFontStyleHack style) + => ComputeFdKey(name, + (style & OTFontStyleHack.Bold) != 0, + (style & OTFontStyleHack.Italic) != 0); + + // Replace this code. + public static string ComputeFdKey(string name, bool isBold, bool isItalic) + { + name = name.ToLowerInvariant(); + var key = isBold switch + { + false when !isItalic => name + '/', + true when !isItalic => name + "/b", + false when isItalic => name + "/i", + _ => name + "/bi" + }; + return key; + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/MakeItCompile.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/MakeItCompile.cs new file mode 100644 index 00000000..f764b0f0 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/MakeItCompile.cs @@ -0,0 +1,102 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Internal.OpenType +{ + static class OtMsgs + { + public static string ErrorReadingFontData + => "Error while parsing an OpenType font."; + } + + + public struct OTColor + { + public OTColor(uint argb) + { + A = (byte)((argb >> 24) & 0xff); + R = (byte)((argb >> 16) & 0xff); + G = (byte)((argb >> 8) & 0xff); + B = (byte)(argb & 0xff); + } + + public static OTColor FromArgb(byte alpha, byte red, byte green, byte blue) + { + return new((uint)(alpha << 24) | (uint)(red << 16) | (uint)(green << 8) | blue); + } + + public byte A; + public byte R; + public byte G; + public byte B; + + public uint Argb => (uint)((A << 24) | (R << 16) | (G << 8) | B); + } + + + /// + /// Specifies style information applied to text. + /// Note that this enum was named XFontStyle in PDFsharp versions prior to 6.0. + /// + [Flags] + public enum OTFontStyleHack // Same values as System.Drawing.FontStyle. + { + // Will be renamed to XGdiFontStyle or XWinFontStyle. + + /// + /// Bold text. + /// + Bold = 1, + + /// + /// Italic text. + /// + Italic = 2, + + /// + /// Bold and italic text. + /// + BoldItalic = 3, + } + + static class NRT + { + /// + /// Throws an InvalidOperationException because an expression which must not be null is null. + /// + /// The message. + /// + [DoesNotReturn] + public static void ThrowOnNull(string? message = null) + => throw new InvalidOperationException(message ?? "Expression must not be null here."); + + /// + /// Throws InvalidOperationException. Use this function during the transition from older C# code + /// to new code that uses nullable reference types. + /// + /// The type this function must return to be compiled correctly. + /// An optional message used for the exception. + /// Nothing, always throws. + /// + [DoesNotReturn] + public static TResult ThrowOnNull(string? message = null) + => throw new InvalidOperationException(message ?? $"'{typeof(TResult).Name}' must not be null here."); + } + + public enum OpenTypeFontEmbedding // Keep in sync with PdfFontEmbedding. + { + /// + /// OpenType font files with TrueType outline are embedded as a font subset. + /// OpenType font files with PostScript outline are embedded as they are, + /// because PDFsharp cannot compute subsets from this type of font files. + /// + TryComputeSubset = 0, + + /// + /// The font file is completely embedded. No subset is computed. + /// + EmbedCompleteFontFile = 1, + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeDescriptor.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontDescriptor.cs similarity index 60% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeDescriptor.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontDescriptor.cs index b852c8ba..eaf1e591 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeDescriptor.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontDescriptor.cs @@ -1,111 +1,110 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + using System.Text; using Microsoft.Extensions.Logging; -#if GDI -using System.Drawing; -using System.Drawing.Drawing2D; -#endif -#if WPF -using System.Windows; -//using System.Windows.Media; -#endif -using PdfSharp.Pdf.Internal; -using PdfSharp.Drawing; -using PdfSharp.Fonts.Internal; using PdfSharp.Logging; -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { + // ReSharper disable once InconsistentNaming + public enum OpenTypeFontStyle + { + // Values must be in sync with WPF FontStyle and PDFsharp Graphics FontStyle. + Normal = 0, + Oblique = 1, + Italic = 2 + } + + // ReSharper disable once InconsistentNaming + public enum OpenTypeFontWeight + { + Thin = 100, + UltraLight = 150, // StL: added 24-03-04 + ExtraLight = 200, + Light = 300, + SemiLight = 350, // StL: added 24-03-04 + Normal = 400, + Medium = 500, + SemiBold = 600, + Bold = 700, + ExtraBold = 800, + Black = 900, + ExtraBlack = 950, + } + + // ReSharper disable once InconsistentNaming + public enum OpenTypeFontStretch + { + UltraCondensed = 1, + ExtraCondensed = 2, + Condensed = 3, + SemiCondensed = 4, + Normal = 5, + SemiExpanded = 6, + Expanded = 7, + ExtraExpanded = 8, + UltraExpanded = 9 + } + /// /// The OpenType font descriptor. - /// Currently, the only font type PDFsharp supports. + /// This class consolidates all relevant properties of a font face needed for glyph typeface, typeface, + /// and font family. + /// The calculation of some properties is identical to the calculation of these values in WPF. /// - sealed class OpenTypeDescriptor : FontDescriptor + public sealed class OpenTypeFontDescriptor { - ///// - ///// New... - ///// - //public OpenTypeDescriptor(string fontDescriptorKey, string name, XFontStyleEx style, OpenTypeFontFace fontFace, XPdfFontOptions options) - // : base(fontDescriptorKey) - //{ - // FontFace = fontFace; - // FontName = name; - // Initialize(); - //} - - public OpenTypeDescriptor(string fontDescriptorKey, XFont font) - : base(fontDescriptorKey) + // Invoked from cache. + public OpenTypeFontDescriptor(string fontDescriptorKey, OpenTypeFontFace fontFace, + string familyName, string faceName) { - try - { - FontFace = font.GlyphTypeface.FontFace; - FontName3 = font.Name2; - Initialize(); - } - catch - { - _ = typeof(int); - throw; - } + Key = fontDescriptorKey; + OTFamilyName = FamilyName = familyName; + OTSubfamilyName = FaceName = faceName; + FontFace = fontFace; + + Initialize(); } - public OpenTypeDescriptor(string fontDescriptorKey, XGlyphTypeface glyphTypeface) - : base(fontDescriptorKey) + // Invoked from cache. + public OpenTypeFontDescriptor(string fontDescriptorKey, OpenTypeFontFace fontFace, + string familyName, string faceName, int dummy) { - try - { - FontFace = glyphTypeface.FontFace; - FontName3 = glyphTypeface.FaceName; - Initialize(); - } - catch - { - _ = typeof(int); - throw; - } + Key = fontDescriptorKey; + OTFamilyName = FamilyName = familyName; + OTSubfamilyName = FaceName = faceName; + FontFace = fontFace; + Initialize(); } - internal OpenTypeDescriptor(string fontDescriptorKey, string idName, byte[] fontData) - : base(fontDescriptorKey) + // Invoked from OpenTypeFontFace. + public OpenTypeFontDescriptor(OpenTypeFontFace fontFace) { - try - { - FontFace = new OpenTypeFontFace(fontData, idName); - // Try to get real name from name table - if (idName.Contains("XPS-Font-") && FontFace.name != null! && FontFace.name.Name.Length != 0) - { - string tag = ""; - if (idName.IndexOf("+", StringComparison.Ordinal) == 6) - tag = idName.Substring(0, 6); - idName = tag + "+" + FontFace.name.Name; - if (FontFace.name.Style.Length != 0) - idName += "," + FontFace.name.Style; - //idName = idName.Replace(" ", ""); - } - FontName3 = idName; - Initialize(); - } - catch (Exception) - { -#if DEBUG - _ = typeof(int); -#endif - throw; - } + Key = "(yet undefined)"; + OTFamilyName = FamilyName = fontFace.name.OTFamilyName; + OTSubfamilyName = FaceName = fontFace.name.OTSubfamilyName; + FontFace = fontFace; + Initialize(); + Key = KeyHelper.CalcFontFaceKey(FamilyName, FaceName, OTFontStyle, OTFontWeight, OTFontStretch); + } - internal OpenTypeFontFace FontFace; + public OpenTypeFontDescriptor Clone() + { + var clone = (OpenTypeFontDescriptor)MemberwiseClone(); + return clone; + } void Initialize() { - // TODO_OLD: Respect embedding restrictions. - //bool embeddingRestricted = fontData.os2.fsType == 0x0002; - - //fontName = image.n + GlyphCount = FontFace.maxp.numGlyphs; + IsSymbolFont = FontFace.cmap.symbol; ItalicAngle = FontFace.post.italicAngle; + Debug.Assert(FontFace.head != null); XMin = FontFace.head!.xMin; YMin = FontFace.head.yMin; XMax = FontFace.head.xMax; @@ -114,18 +113,11 @@ void Initialize() UnderlinePosition = FontFace.post.underlinePosition; UnderlineThickness = FontFace.post.underlineThickness; - // PDFlib states that some Apple fonts miss the OS/2 table. - Debug.Assert(FontFace.os2 != null, "TrueType font has no OS/2 table."); - StrikeoutPosition = FontFace.os2.yStrikeoutPosition; StrikeoutSize = FontFace.os2.yStrikeoutSize; - // No documentation found how to get the set vertical stems width from the - // TrueType tables. - // The following formula comes from PDFlib Lite source code. Acrobat 5.0 sets - // /StemV to 0 always. I think the value doesn’t matter. - //float weight = (float)(image.os2.usWeightClass / 65.0f); - //stemV = (int)(50 + weight * weight); // MAGIC + // No documentation found how to get the set vertical stems width from the TrueType tables. + // Acrobat sets StemV to 0. StemV = 0; UnitsPerEm = FontFace.head.unitsPerEm; @@ -136,6 +128,7 @@ void Initialize() bool os2SeemsToBeEmpty = FontFace.os2 is { sTypoAscender: 0, sTypoDescender: 0, sTypoLineGap: 0 }; //Debug.Assert(!os2SeemsToBeEmpty); // Are there fonts without OS/2 table? + // Check bit 8 of fsSelection (WWS). bool dontUseWinLineMetrics = (FontFace.os2.fsSelection & 128) != 0; if (!os2SeemsToBeEmpty && dontUseWinLineMetrics) { @@ -145,13 +138,14 @@ void Initialize() int typoDescender = FontFace.os2.sTypoDescender; int typoLineGap = FontFace.os2.sTypoLineGap; - // Comment from WPF: We include the line gap in the ascent so that white space is distributed above the line. (Note that + // Comment from WPF: We include the line gap in the ascent so that white-space is distributed above the line. (Note that // the typo line gap is a different concept than "external leading".) Ascender = typoAscender + typoLineGap; // Comment from WPF: Typo descent is a signed value where the positive direction is up. It is therefore typically negative. // A signed typo descent would be quite unusual as it would indicate the descender was above the baseline Descender = -typoDescender; LineSpacing = typoAscender + typoLineGap - typoDescender; + LineGap = typoLineGap; } else { @@ -172,7 +166,8 @@ void Initialize() // platform-specific Windows values. We take the absolute value of the win32descent in case some // fonts get the sign wrong. int winAscent = FontFace.os2.usWinAscent; - int winDescent = Math.Abs(FontFace.os2.usWinDescent); + //int winDescent = Math.Abs(FontFace.os2.usWinDescent); + int winDescent = FontFace.os2.usWinDescent; // usWinDescent is already unsigned. Ascender = winAscent; Descender = winDescent; @@ -185,12 +180,14 @@ void Initialize() // height. However, Windows has never allowed this for TrueType fonts, and fonts built for Windows // sometimes rely on this behavior and get the hha values wrong or set them all to zero. LineSpacing = Math.Max(lineGap + ascender + descender, winAscent + winDescent); + LineGap = lineGap; } else { Ascender = ascender; Descender = descender; LineSpacing = ascender + descender + lineGap; + LineGap = lineGap; } } @@ -200,8 +197,13 @@ void Initialize() int internalLeading = cellHeight - UnitsPerEm; // Not used, only for debugging. int externalLeading = LineSpacing - cellHeight; Leading = externalLeading; +#if DEBUG + CellHeight = cellHeight; + InternalLeading = internalLeading; + ExternalLeading = externalLeading; +#endif - // sCapHeight and sxHeight are only valid if Version >= 2 + // sCapHeight and sxHeight are only valid if Version >= 2. if (FontFace.os2.version >= 2 && FontFace.os2.sCapHeight != 0) CapHeight = FontFace.os2.sCapHeight; else @@ -212,9 +214,36 @@ void Initialize() else XHeight = (int)(0.66 * Ascender); - //flags = image. - - Encoding ansi = PdfEncoders.WinAnsiEncoding; + // Get values used for FontWeight, FontStretch, and FontStyle. + OTFontWeight = (OpenTypeFontWeight)FontFace.os2.usWeightClass; + OTFontStretch = (OpenTypeFontStretch)FontFace.os2.usWidthClass; + var fs = FontFace.os2.fsSelection; + OTFontStyle = (fs & OS2Table.FontSelectionFlags.Italic) != 0 ? OpenTypeFontStyle.Italic : + (fs & OS2Table.FontSelectionFlags.Oblique) != 0 ? OpenTypeFontStyle.Oblique : + OpenTypeFontStyle.Normal; + + IsBoldFace = FontFace.os2.IsBold; + IsItalicFace = FontFace.os2.IsItalic; + + Height = Ascender + Descender; + + // WPF documentation of BaseLine: + // The distance between the baseline and the character cell top. + // + // From the WPF 3.0 source code: + // The ascent is from the top of the cell. Per [....], we want baseline to be relative to the + // top of a logical line (represented by lineSpacing) in which the cell is vertically centered. + // Thus, we want half the external leading to be above the cell. The external leading is equal + // to (lineSpacing - (ascent + descent)), giving us the following formula: + // + // baseline = ascent + (lineSpacing - (ascent + descent)) * 0.5 + // = ascent + lineSpacing * 0.5 - ascent * 0.5 - descent * 0.5 + // = ascent * 0.5 + lineSpacing * 0.5 - descent * 0.5 + // = (ascent + lineSpacing - descent) * 0.5 + // + Baseline = ((Ascender + LineSpacing - Descender) * .5f); + + Encoding ansi = AnsiEncoding.Encoder; Encoding unicode = Encoding.Unicode; byte[] bytes = new byte[256]; @@ -223,11 +252,8 @@ void Initialize() for (int idx = 0; idx < 256; idx++) { bytes[idx] = (byte)idx; - // PDFlib handles some font flaws here... - // We wait for bug reports. char ch = (char)idx; -#if true if (isSymbolFont) { ch = RemapSymbolChar(ch); @@ -238,36 +264,180 @@ void Initialize() if (s.Length != 0) ch = s[0]; } -#else - string s = ansi.GetString(bytes, idx, 1); - if (s.Length != 0) - { - //if (s[0] != ch) - ch = s[0]; - } - - // Remap ch for symbol fonts. - if (isSymbolFont) - ch = RemapSymbolChar(ch); -#endif var glyphIndex = BmpCodepointToGlyphIndex(ch); Widths[idx] = GlyphIndexToPdfWidth(glyphIndex); } } - public int[] Widths = default!; + + // ========== Properties ========== + + public string Key { get; private set; } + + /// + /// Name ID 1 (Font Family name) from the names table in en-US. + /// + public string OTFamilyName { get; private set; } + + /// + /// Name ID 2 (Font Subfamily name) from the names table in en-US. + /// + public string OTSubfamilyName { get; private set; } + + /// + /// Initially same as OTFamilyName. + /// The value can be overridden during font registration. + /// + public string FamilyName { get; set; } + + /// + /// Initially same as OTSubfamilyName. + /// The value can be overridden during font registration. + /// + public string FaceName { get; set; } + + /// + /// The number of glyphs in the font. + /// + public int GlyphCount { get; private set; } + + /// + /// Value depending on bit 0, 6, and 9 of fsSelection (font selection flag) from OS/2 table. + /// Value is 0, 1, or 2: Normal, Oblique, or Italic. + /// The value can be overridden during font registration. + /// + // ReSharper disable once InconsistentNaming + public OpenTypeFontStyle OTFontStyle { get; set; } + + /// + /// Value of usWeightClass from OS/2 table. + /// The value can be overridden during font registration. + /// + // ReSharper disable once InconsistentNaming + public OpenTypeFontWeight OTFontWeight { get; set; } + + /// + /// Value of usWidthClass from OS/2 table. + /// The value can be overridden during font registration. + /// + // ReSharper disable once InconsistentNaming + public OpenTypeFontStretch OTFontStretch { get; set; } /// /// Gets a value indicating whether this instance belongs to a bold font. /// - public override bool IsBoldFace => FontFace.os2.IsBold; + public bool IsBoldFace { get; private set; } /// /// Gets a value indicating whether this instance belongs to an italic font. /// - public override bool IsItalicFace => FontFace.os2.IsItalic; + public bool IsItalicFace { get; private set; } + + /// + /// head unitsPerEm. + /// + public int UnitsPerEm { get; private set; } + + /// + /// Same as Ascender + Descender. + /// + public int Height { get; private set; } + + /// + /// (Ascender + LineSpacing - Descender) * .5 + /// + public float Baseline { get; private set; } + + /// + /// OS/2 sCapHeight. + /// + public int CapHeight { get; private set; } + + /// + /// OS/2 sxHeight. + /// + public int XHeight { get; private set; } + + /// + /// post underlinePosition. + /// + public int UnderlinePosition { get; private set; } + + /// + /// post underlineThickness. + /// + public int UnderlineThickness { get; private set; } + + /// + /// OS/2 yStrikeoutPosition. + /// + public int StrikeoutPosition { get; private set; } + + /// + /// OS/2 yStrikeoutSize. + /// + public int StrikeoutSize { get; private set; } + + /// + /// OS/2 usWinDescent. + /// + public int Ascender { get; private set; } + + /// + /// OS/2 usWinDescent. + /// + public int Descender { get; private set; } - internal int DesignUnitsToPdf(double value) - => (int)Math.Round(value * 1000.0 / FontFace.head!.unitsPerEm); + /// + /// LineSpacing - (Ascender + Descender);. + /// + public int Leading { get; private set; } + + /// + /// hhea lineGap. + /// + public int LineGap { get; private set; } + + /// + /// max(lineGap + ascender + descender, winAscent + winDescent). + /// + public int LineSpacing { get; private set; } + + /// + /// (Not used). + /// + public float ItalicAngle { get; private set; } + + /// + /// + /// + public int XMin { get; private set; } + + /// + /// + /// + public int YMin { get; private set; } + + /// + /// + /// + public int XMax { get; private set; } + + /// + /// + /// + public int YMax { get; private set; } + + /// + /// + /// + public int StemV { get; private set; } + + internal readonly int GlobalVersion = OtGlobals.Global.Version; + + // ========== Utilities ========== + + public int DesignUnitsToPdf(double value) + => (int)Math.Round(value * 1000.0 / UnitsPerEm); /// /// Maps a Unicode code point from the BMP to the index of the corresponding glyph. @@ -312,7 +482,7 @@ public ushort BmpCodepointToGlyphIndex(char ch) return 0; glyphIndex = (ushort)((glyphIndex + (uint)cmap.idDelta[seg]) & 0xFFFF); - Debug.Assert((glyphIndex & 0xFFFF0000) == 0, "Glyph index larger than 65535."); + // Cannot happen anymore with glyphIndex as of type ushort: Debug.Assert((glyphIndex & 0xFFFF0000) == 0, "Glyph index larger than 65535."); return glyphIndex; } @@ -348,7 +518,7 @@ public ushort CodepointToGlyphIndex(int codePoint) { if (codePoint <= 0xFFFF) { - PdfSharpLogHost.FontManagementLogger.LogWarning("For code points from the BMP call BmpCharacterToGlyphID directly."); + LogHost.Logger./*FontManagementLogger.*/LogWarning("For code points from the BMP call BmpCharacterToGlyphID directly."); return BmpCodepointToGlyphIndex((char)codePoint); } @@ -373,7 +543,6 @@ public ushort CodepointToGlyphIndex(int codePoint) return 0; var glyphIndex = (ushort)(cmap.groups[seg].startGlyphIndex + codePoint - cmap.groups[seg].startCharCode); - // Debug.Assert((glyphIndex & 0xFFFF0000) == 0, "Glyph index larger than 65535."); return glyphIndex; } @@ -386,23 +555,22 @@ public int GlyphIndexToPdfWidth(ushort glyphIndex) try { var numberOfHMetrics = FontFace.hhea.numberOfHMetrics; - var unitsPerEm = FontFace.head!.unitsPerEm; // glyphIndex >= numberOfHMetrics means the font is monospaced and all glyphs have the same width. if (glyphIndex >= numberOfHMetrics) glyphIndex = (ushort)(numberOfHMetrics - 1); - int width = FontFace.hmtx.Metrics[glyphIndex].advanceWidth; + int width = FontFace.hmtx.HorzMetrics[glyphIndex].advanceWidth; // Sometimes the unitsPerEm is 1000, sometimes a power of 2. - if (unitsPerEm == 1000) + if (UnitsPerEm == 1000) return width; - return width * 1000 / unitsPerEm; // normalize + return width * 1000 / UnitsPerEm; // normalize } // ReSharper disable once RedundantCatchClause catch (Exception) { - PdfSharpLogHost.FontManagementLogger.LogError("Invalid glyph index hmtx: 0x{Glyph:X4}", glyphIndex); + LogHost.Logger./*FontManagementLogger.*/LogError("Invalid glyph index hmtx: 0x{Glyph:X4}", glyphIndex); throw; } } @@ -410,23 +578,22 @@ public int GlyphIndexToPdfWidth(ushort glyphIndex) /// /// Converts the width of a glyph identified by its index to PDF design units. /// - public double GlyphIndexToEmWidth(uint glyphIndex, double emSize) + public double GlyphIndexToEmWidth(ushort glyphIndex, double emSize) { try { uint numberOfHMetrics = FontFace.hhea.numberOfHMetrics; - int unitsPerEm = FontFace.head!.unitsPerEm; // glyphIndex >= numberOfHMetrics means the font is monospaced and all glyphs have the same width. if (glyphIndex >= numberOfHMetrics) - glyphIndex = numberOfHMetrics - 1; + glyphIndex = (ushort)(numberOfHMetrics - 1); - int width = FontFace.hmtx.Metrics[glyphIndex].advanceWidth; - return width * emSize / unitsPerEm; + int width = FontFace.hmtx.HorzMetrics[glyphIndex].advanceWidth; + return width * emSize / UnitsPerEm; } catch (Exception ex) { - PdfSharpLogHost.Logger.LogError(ex, "Error calculating em-size for glyph 0x{Glyph:X4}.", glyphIndex); + LogHost.Logger.LogError(ex, "Error calculating em-size for glyph 0x{Glyph:X4}.", glyphIndex); throw; } } @@ -436,8 +603,6 @@ public double GlyphIndexToEmWidth(uint glyphIndex, double emSize) /// public int GlyphIndexToWidth(int glyphIndex) { - //if (glyphIndex == 0) - // return 0; try { int numberOfHMetrics = FontFace.hhea.numberOfHMetrics; @@ -446,12 +611,12 @@ public int GlyphIndexToWidth(int glyphIndex) if (glyphIndex >= numberOfHMetrics) glyphIndex = numberOfHMetrics - 1; - int width = FontFace.hmtx.Metrics[glyphIndex].advanceWidth; + int width = FontFace.hmtx.HorzMetrics[glyphIndex].advanceWidth; return width; } catch (Exception ex) { - PdfSharpLogHost.Logger.LogError(ex, "Error find advance width for glyph 0x{Glyph:X4}.", glyphIndex); + LogHost.Logger.LogError(ex, "Error find advance width for glyph 0x{Glyph:X4}.", glyphIndex); throw; } } @@ -459,9 +624,9 @@ public int GlyphIndexToWidth(int glyphIndex) /// /// Converts the code units of a UTF-16 string into the glyph identifier of this font. /// If useAnsiCharactersOnly is true, only valid ANSI code units a taken into account. - /// All non-ANSI characters are skipped and not part of the result + /// All non-ANSI characters are skipped and not part of the result. /// - public bool IsSymbolFont => FontFace.cmap.symbol; + public bool IsSymbolFont { get; private set; } public CodePointGlyphIndexPair[] GlyphIndicesFromString(string s, bool useAnsiCharactersOnly = false) { @@ -497,7 +662,7 @@ public CodePointGlyphIndexPair[] GlyphIndicesFromCodePoints(int[] codePoints, bo if ((ch & 0xFF00) != 0) { // Just log a hint but do not skip the character. - PdfSharpLogHost.FontManagementLogger.LogDebug("Unexpected character found for symbol font: 0x{Char:X2}", ch); + LogHost.Logger./*FontManagementLogger.*/LogDebug("Unexpected character found for symbol font: 0x{Char:X2}", ch); } // Remap ch for symbol fonts. @@ -548,14 +713,14 @@ public char RemapSymbolChar(char ch) { Debug.Assert(IsSymbolFont); - // Check if ch is either a byte or in range [0xf000..0xf0ff], wich is both valid for a + // Check if ch is either a byte or in range [0xf000..0xf0ff], which is both valid for a // code unit of a symbol font. // Second expression is clever code from WPF source meaning: // 'ch >= 0xf000 && ch <= 0xf0ff' done with one test and branch. if (ch > 255 && !((uint)(ch - 0xf000) <= 0xff)) { var value = ((int)ch).ToString("x4"); - PdfSharpLogHost.FontManagementLogger.LogError("Character 0u{char} of a symbol font is not in valid range.", value); + LogHost.Logger./*FontManagementLogger.*/LogError("Character 0u{char} of a symbol font is not in valid range.", value); } // Used | instead of + because of: http://pdfsharp.codeplex.com/workitem/15954 @@ -577,5 +742,15 @@ public char RemapSymbolChar(char ch) } return null; } + + public readonly OpenTypeFontFace FontFace; + + public int[] Widths = null!; + +#if DEBUG + public int CellHeight; + public int InternalLeading; + public int ExternalLeading; +#endif } } diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFace-Helper.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFace-Helper.cs new file mode 100644 index 00000000..5521eb40 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFace-Helper.cs @@ -0,0 +1,72 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable 0649 +// ReSharper disable InconsistentNaming +// ReSharper disable IdentifierTypo + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Internal.OpenType +{ + sealed partial class OpenTypeFontFace + { + public OpenTypeGlyphMetrics CreateGlyphMetrics(ushort glyphIndex) + { + var otgm = new OpenTypeGlyphMetrics(); + + var (lsb, advw) = hmtx.GetLsbAndAdvanceWidth(glyphIndex); + var (tsb, advh) = (0, 0); + if (vmtx != null!) + (tsb, advh) = vmtx.GetTsbAndAdvanceHeight(glyphIndex); + + var header = glyf.GetGlyphHeader(glyphIndex); + + int leftSideBearing = lsb; + int advanceWidth = advw; + // It seems WPF takes lsb for tsb if no vmtx table exists. + int topSideBearing = lsb; + int advanceHeight = advh; + + // Calculate rsb like this: + // rsb = aw - (lsb + xMax - xMin) + // see https://learn.microsoft.com/de-de/typography/opentype/spec/hmtx + int rightSideBearing = (advw - (lsb + header.xMax - header.xMin)); + int bottomSideBearing = (advh - (tsb + header.yMax - header.yMin)); + + otgm.LeftSideBearing = leftSideBearing; + otgm.AdvanceWidth = advanceWidth; + otgm.RightSideBearing = rightSideBearing; + otgm.TopSideBearing = topSideBearing; + otgm.AdvanceHeight = advanceWidth; + otgm.BottomSideBearing = bottomSideBearing = 0; //os2.sTypoDescender; + otgm.DistancesFromHorizontalBaselineToBlackBoxBottom = 0; //os2.sTypoDescender; + + // TODO NYI + otgm.VerticalOrigin = 0; + + otgm.DrawBounds = new(header.xMin, header.yMin, + header.xMax - header.xMin, header.yMax - header.yMin); + return otgm; + } + + public RenderingGlyphMetrics CreateGlyphMetrics(OpenTypeGlyphMetrics metrics) + { + var unitsPerEm = (float)head!.unitsPerEm; + + var rgm = new RenderingGlyphMetrics() + { + LeftSideBearing = metrics.LeftSideBearing / unitsPerEm, + AdvanceWidth = metrics.AdvanceWidth / unitsPerEm, + RightSideBearing = metrics.RightSideBearing / unitsPerEm, + TopSideBearing = metrics.TopSideBearing / unitsPerEm, + AdvanceHeight = metrics.AdvanceHeight / unitsPerEm, + BottomSideBearing = metrics.BottomSideBearing / unitsPerEm, + DrawBounds = new( + metrics.DrawBounds.X / unitsPerEm, metrics.DrawBounds.Y / unitsPerEm, + metrics.DrawBounds.Width / unitsPerEm, metrics.DrawBounds.Height / unitsPerEm) + }; + return rgm; + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontface.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFace.cs similarity index 74% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontface.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFace.cs index 4643237d..df6c7aa9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontface.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFace.cs @@ -1,52 +1,25 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -//#define VERBOSE - -//using System.Runtime.InteropServices; -#if GDI -using System.Drawing; -using System.Drawing.Drawing2D; -using GdiFontFamily = System.Drawing.FontFamily; -using GdiFont = System.Drawing.Font; -using GdiFontStyle = System.Drawing.FontStyle; -#endif -#if WPF -using WpfFontFamily = System.Windows.Media.FontFamily; -using WpfTypeface = System.Windows.Media.Typeface; -using WpfGlyphTypeface = System.Windows.Media.GlyphTypeface; -#endif +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Globalization; using Microsoft.Extensions.Logging; -using PdfSharp.Drawing; -//using PdfSharp.Internal; -//using PdfSharp.Fonts; -using PdfSharp.Fonts.Internal; using PdfSharp.Logging; -using PdfSharp.Pdf; using Fixed = System.Int32; -using FWord = System.Int16; -using UFWord = System.UInt16; + +// v7.0 TODO review #pragma warning disable 0649 -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { /// /// Represents an OpenType font face in memory. /// [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] - sealed class OpenTypeFontFace // Note: In English, it’s spelled 'typeface', but 'font face'. + public sealed partial class OpenTypeFontFace // In English, it’s spelled 'typeface', but 'font face'. { - // Implementation Notes - // OpenTypeFontFace represents a 'decompiled' font file in memory. - // - // * An OpenTypeFontFace can belong to more than one - // XGlyphTypeface because of StyleSimulations. - // - // * Currently there is a one-to-one relationship to XFontSource. - // - // * Consider OpenTypeFontFace as a decompiled XFontSource. - // // http://www.microsoft.com/typography/otspec/ /// @@ -56,40 +29,46 @@ sealed class OpenTypeFontFace // Note: In English, it’s spelled 'typeface', b { _offsetTable = fontFace._offsetTable; _fullFaceName = fontFace._fullFaceName; + OTDescriptor = fontFace.OTDescriptor; } - /// - /// Initializes a new instance of the class. - /// - public OpenTypeFontFace(byte[] data, string faceName) + OpenTypeFontFace(OpenTypeFontSource fontSource) { - _fullFaceName = faceName; - // Always save a copy of the font bytes. - int length = data.Length; - //FontSource = new XFontSource(faceName, new byte[length]); - Array.Copy(data, FontSource.Bytes, length); - Read(); + Debug.Assert(fontSource.OTFontFace == null); + OTFontSource = fontSource; + Read(); // This unimpressive line reads all the font tables we need. + OTDescriptor = new OpenTypeFontDescriptor(this); + _fullFaceName = name.OTFullFontName; + Debug.Assert(fontSource.FontFaceKey == null); + fontSource.OTFontFace = this; + var key = OTDescriptor.Key; } - public OpenTypeFontFace(XFontSource fontSource) - { - FontSource = fontSource; - Read(); - _fullFaceName = name.FullFontName; - } + public readonly OpenTypeFontDescriptor OTDescriptor; - public static OpenTypeFontFace CetOrCreateFrom(XFontSource fontSource) + public static OpenTypeFontFace GetOrCreateFrom(OpenTypeFontSource otFontSource) { - if (OpenTypeFontFaceCache.TryGetFontFace(fontSource.Key, out var fontFace)) - return fontFace; - - // Each font source already contains its OpenTypeFontFace. - Debug.Assert(fontSource.FontFace != null); - fontFace = OpenTypeFontFaceCache.AddFontFace(fontSource.FontFace); - Debug.Assert(ReferenceEquals(fontSource.FontFace, fontFace)); - return fontFace; + Debug.Assert(otFontSource.OTFontFace == null); + + var openTypeFontFaceCache = OtGlobals.Global.OTFonts.OpenTypeFontFaceCache; + if (openTypeFontFaceCache.TryGetFontFace(otFontSource.ChecksumKey, out var otFontFace)) + { + // We should not come here if the font source is in the cache but has no font face. + Debug.Assert(otFontSource.OTFontFace != null, "Ooops, something went wrong."); + return otFontFace; + } + + Debug.Assert(otFontSource.FontFaceKey == null); + otFontFace = new(otFontSource); + Debug.Assert(otFontSource.FontFaceKey != null); + otFontSource.OTFontFace = otFontFace; + otFontFace = openTypeFontFaceCache.AddFontFace(otFontFace); + Debug.Assert(ReferenceEquals(otFontSource.OTFontFace, otFontFace)); + return otFontFace; } + public int GlyphCount => OTDescriptor.GlyphCount; + /// /// Gets the full face name from the name table. /// Name is also used as the key. @@ -98,22 +77,25 @@ public static OpenTypeFontFace CetOrCreateFrom(XFontSource fontSource) readonly string _fullFaceName; + /// + /// Gets the 64-bit check sum to identify the font face by its font source. + /// public ulong CheckSum { get { if (_checkSum == 0) - _checkSum = FontHelper.CalcChecksum(FontSource.Bytes); + _checkSum = ChecksumHelper.CalcChecksum(OTFontSource.Bytes); return _checkSum; } } ulong _checkSum; - public void SetFontEmbedding(PdfFontEmbedding fontEmbedding) + public void SetFontEmbedding(OpenTypeFontEmbedding fontEmbedding) { - Debug.Assert(fontEmbedding is PdfFontEmbedding.TryComputeSubset or PdfFontEmbedding.EmbedCompleteFontFile); + Debug.Assert(fontEmbedding is OpenTypeFontEmbedding.TryComputeSubset or OpenTypeFontEmbedding.EmbedCompleteFontFile); - if (_fontEmbedding == (PdfFontEmbedding)(-1)) + if (_fontEmbedding == (OpenTypeFontEmbedding)(-1)) { _fontEmbedding = fontEmbedding; return; @@ -122,31 +104,53 @@ public void SetFontEmbedding(PdfFontEmbedding fontEmbedding) if (fontEmbedding == _fontEmbedding) return; - if (fontEmbedding == PdfFontEmbedding.TryComputeSubset) + if (fontEmbedding == OpenTypeFontEmbedding.TryComputeSubset) { // Case: _fontEmbedding is already set to EmbedCompleteFontFile. - PdfSharpLogHost.Logger.LogError("Font embedding option was already set to EmbedCompleteFontFile. Setting to TryComputeSubset is ignored."); + LogHost.Logger.LogError("Font embedding option was already set to EmbedCompleteFontFile. Setting to TryComputeSubset is ignored."); } else { // Case: _fontEmbedding is already set to TryComputeSubset. - PdfSharpLogHost.Logger.LogError("Font embedding option was already set to TryComputeSubset. Now it is reset to EmbedCompleteFontFile."); + LogHost.Logger.LogError("Font embedding option was already set to TryComputeSubset. Now it is reset to EmbedCompleteFontFile."); _fontEmbedding = fontEmbedding; } } - PdfFontEmbedding _fontEmbedding = (PdfFontEmbedding)(-1); + OpenTypeFontEmbedding _fontEmbedding = (OpenTypeFontEmbedding)(-1); + + public OpenTypeGlyphMetrics GetOpenTypeGlyphMetrics(ushort glyphIndex) + { + // Create glyph metrics only on demand for the glyphs needed. + + _otMetrics ??= new OpenTypeGlyphMetrics?[GlyphCount]; + var otMetric = _otMetrics[glyphIndex] ??= CreateGlyphMetrics(glyphIndex); + + return otMetric; + } + OpenTypeGlyphMetrics?[]? _otMetrics; // _metricses??? + + public RenderingGlyphMetrics GetRenderingGlyphMetrics(ushort glyphIndex) + { + // Create glyph metrics only on demand for the glyphs needed. + + _rMetrics ??= new RenderingGlyphMetrics?[GlyphCount]; + var rMetrics = _rMetrics[glyphIndex] ??= CreateGlyphMetrics(GetOpenTypeGlyphMetrics(glyphIndex)); + + return rMetrics; + } + RenderingGlyphMetrics?[]? _rMetrics; /// /// Gets the bytes that represents the font data. /// - public XFontSource FontSource + public OpenTypeFontSource OTFontSource { - get => _fontSource ?? NRT.ThrowOnNull(); + get => _otFontSource ?? NRT.ThrowOnNull(); private set => // Stop working if font was not found. - _fontSource = value ?? throw new InvalidOperationException("Font cannot be resolved."); + _otFontSource = value ?? throw new InvalidOperationException("Font cannot be resolved."); } - XFontSource? _fontSource; + OpenTypeFontSource? _otFontSource; #pragma warning disable CS0414 // Field is assigned but its value is never used /*internal*/ @@ -163,30 +167,30 @@ public XFontSource FontSource // Keep names identical to OpenType spec. // ReSharper disable InconsistentNaming // ReSharper disable IdentifierTypo - internal CMapTable cmap = default!; // NRT TODO_OLD Change programming model so that it fits NRTs. - internal ColorTable? colr; - internal ColorPalletTable? cpal; - internal ControlValueTable cvt = default!; // NRT - internal FontProgram fpgm = default!; // NRT - internal MaximumProfileTable maxp = default!; // NRT - internal NameTable name = default!; // NRT - internal ControlValueProgram? prep; - internal FontHeaderTable? head; - internal HorizontalHeaderTable hhea = default!; // NRT - internal HorizontalMetricsTable hmtx = default!; // NRT - internal OS2Table os2 = default!; // NRT - internal PostScriptTable post = default!; // NRT - internal GlyphDataTable glyf = default!; // NRT - internal IndexToLocationTable loca = default!; // NRT - internal GlyphSubstitutionTable gsub = default!; // NRT - internal VerticalHeaderTable? vhea; // TODO_OLD - internal VerticalMetricsTable? vmtx; // TODO_OLD + public CMapTable cmap = null!; // NRT TODO_OLD Change programming model so that it fits NRTs. + public ColorTable? colr; + public ColorPalletTable? cpal; + public ControlValueTable cvt = null!; // NRT + public FontProgram fpgm = null!; // NRT + public MaximumProfileTable maxp = null!; // NRT + public NameTable name = null!; // NRT + public ControlValueProgram? prep; + public FontHeaderTable? head; + public HorizontalHeaderTable hhea = null!; // NRT + public HorizontalMetricsTable hmtx = null!; // NRT + public OS2Table os2 = null!; // NRT + public PostScriptTable post = null!; // NRT + public GlyphDataTable glyf = null!; // NRT + public IndexToLocationTable loca = null!; // NRT + public GlyphSubstitutionTable gsub = null!; // NRT + public VerticalHeaderTable? vhea; + public VerticalMetricsTable? vmtx; // ReSharper restore IdentifierTypo // ReSharper restore InconsistentNaming - public bool CanRead => _fontSource != null; + public bool CanRead => _otFontSource != null; - public bool CanWrite => _fontSource == null; + public bool CanWrite => _otFontSource == null; /// /// Adds the specified table to this font image. @@ -199,13 +203,13 @@ public void AddTable(OpenTypeFontTable fontTable) if (fontTable == null) throw new ArgumentNullException(nameof(fontTable)); - if (fontTable._fontData == null) + if (fontTable.FontData == null) { - fontTable._fontData = this; + fontTable.FontData = this; } else { - Debug.Assert(fontTable._fontData.CanRead); + Debug.Assert(fontTable.FontData.CanRead); // Create a reference to this font table. fontTable = new IRefFontTable(this, fontTable); } @@ -279,6 +283,10 @@ public void AddTable(OpenTypeFontTable fontTable) case TableTagNames.Prep: prep = (fontTable as ControlValueProgram); // ?? NRT.ThrowOnNull(); break; + + default: + _ = typeof(int); + break; } } @@ -383,10 +391,16 @@ internal void Read() if (Seek(ControlValueProgram.Tag) != -1) prep = new ControlValueProgram(this); + + if (Seek(VerticalHeaderTable.Tag) != -1) + vhea = new VerticalHeaderTable(this); + + if (Seek(VerticalMetricsTable.Tag) != -1) + vmtx = new VerticalMetricsTable(this); } catch (Exception ex) { - PdfSharpLogHost.FontManagementLogger.LogCritical($"Error while reading OpenType font face: {ex.Message}"); + LogHost.Logger./*FontManagementLogger.*/LogCritical($"Error while reading OpenType font face: {ex.Message}"); throw; } } @@ -398,7 +412,7 @@ public OpenTypeFontFace CreateFontSubset(Dictionary glyphs, boo { // Do not create a subset? // No loca table means font has postscript outline. - if (_fontEmbedding == PdfFontEmbedding.EmbedCompleteFontFile || loca == null!) + if (_fontEmbedding == OpenTypeFontEmbedding.EmbedCompleteFontFile || loca == null!) return this; // Create new font image. @@ -429,7 +443,7 @@ public OpenTypeFontFace CreateFontSubset(Dictionary glyphs, boo fontData.AddTable(prep); // Get closure of used glyphs. - Debug.Assert(glyphs != null); + //Debug.Assert(glyphs != null); glyf.CompleteGlyphClosure(glyphs); // Create a sorted array of all used glyphs. @@ -529,7 +543,7 @@ void Compile() #endif writer.Stream.Flush(); int l = (int)writer.Stream.Length; - FontSource = XFontSource.CreateCompiledFont(stream.ToArray()); + OTFontSource = OpenTypeFontSource.CreateCompiledFont(stream.ToArray()); } // 2^entrySelector[n] <= n static readonly int[] _entrySelectors = [0, 0, 1, 1, 2, 2, 2, 2, 3, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4, 4]; @@ -551,14 +565,14 @@ public int Seek(string tag) /// /// Reads a System.Byte. /// - public byte ReadByte() => FontSource.Bytes[Position++]; + public byte ReadByte() => OTFontSource.Bytes[Position++]; /// /// Reads a System.Int16. /// public short ReadShort() { - return (short)((FontSource.Bytes[Position++] << 8) | (FontSource.Bytes[Position++])); + return (short)((OTFontSource.Bytes[Position++] << 8) | (OTFontSource.Bytes[Position++])); } /// @@ -566,7 +580,7 @@ public short ReadShort() /// public ushort ReadUShort() { - return (ushort)((FontSource.Bytes[Position++] << 8) | (FontSource.Bytes[Position++])); + return (ushort)((OTFontSource.Bytes[Position++] << 8) | (OTFontSource.Bytes[Position++])); } /// @@ -574,7 +588,7 @@ public ushort ReadUShort() /// public int ReadLong() { - return (FontSource.Bytes[Position++] << 24) | (FontSource.Bytes[Position++] << 16) | (FontSource.Bytes[Position++] << 8) | FontSource.Bytes[Position++]; + return (OTFontSource.Bytes[Position++] << 24) | (OTFontSource.Bytes[Position++] << 16) | (OTFontSource.Bytes[Position++] << 8) | OTFontSource.Bytes[Position++]; } /// @@ -582,7 +596,7 @@ public int ReadLong() /// public uint ReadULong() { - return (uint)((FontSource.Bytes[Position++] << 24) | (FontSource.Bytes[Position++] << 16) | (FontSource.Bytes[Position++] << 8) | FontSource.Bytes[Position++]); + return (uint)((OTFontSource.Bytes[Position++] << 24) | (OTFontSource.Bytes[Position++] << 16) | (OTFontSource.Bytes[Position++] << 8) | OTFontSource.Bytes[Position++]); } /// @@ -590,7 +604,7 @@ public uint ReadULong() /// public Fixed ReadFixed() { - return (FontSource.Bytes[Position++] << 24) | (FontSource.Bytes[Position++] << 16) | (FontSource.Bytes[Position++] << 8) | FontSource.Bytes[Position++]; + return (OTFontSource.Bytes[Position++] << 24) | (OTFontSource.Bytes[Position++] << 16) | (OTFontSource.Bytes[Position++] << 8) | OTFontSource.Bytes[Position++]; } /// @@ -598,7 +612,7 @@ public Fixed ReadFixed() /// public short ReadFWord() { - return (short)((FontSource.Bytes[Position++] << 8) | FontSource.Bytes[Position++]); + return (short)((OTFontSource.Bytes[Position++] << 8) | OTFontSource.Bytes[Position++]); } /// @@ -607,7 +621,7 @@ public short ReadFWord() // ReSharper disable once InconsistentNaming public ushort ReadUFWord() { - return (ushort)((FontSource.Bytes[Position++] << 8) | FontSource.Bytes[Position++]); + return (ushort)((OTFontSource.Bytes[Position++] << 8) | OTFontSource.Bytes[Position++]); } /// @@ -617,7 +631,7 @@ public long ReadLongDate() { int pos = Position; Position += 8; - byte[] bytes = FontSource.Bytes; + byte[] bytes = OTFontSource.Bytes; return (((long)bytes[pos]) << 56) | (((long)bytes[pos + 1]) << 48) | (((long)bytes[pos + 2]) << 40) | (((long)bytes[pos + 3]) << 32) | (((long)bytes[pos + 4]) << 24) | (((long)bytes[pos + 5]) << 16) | (((long)bytes[pos + 6]) << 8) | bytes[pos + 7]; } @@ -629,7 +643,7 @@ public string ReadString(int size) { char[] chars = new char[size]; for (int idx = 0; idx < size; idx++) - chars[idx] = (char)FontSource.Bytes[Position++]; + chars[idx] = (char)OTFontSource.Bytes[Position++]; return new string(chars); } @@ -640,7 +654,7 @@ public byte[] ReadBytes(int size) { byte[] bytes = new byte[size]; for (int idx = 0; idx < size; idx++) - bytes[idx] = FontSource.Bytes[Position++]; + bytes[idx] = OTFontSource.Bytes[Position++]; return bytes; } @@ -654,7 +668,7 @@ public byte[] ReadBytes(int size) /// public void Read(byte[] buffer, int offset, int length) { - Buffer.BlockCopy(FontSource.Bytes, Position, buffer, offset, length); + Buffer.BlockCopy(OTFontSource.Bytes, Position, buffer, offset, length); Position += length; } diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFaceCache.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFaceCache.cs new file mode 100644 index 00000000..7aec0ac2 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFaceCache.cs @@ -0,0 +1,138 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Collections.Concurrent; +using System.Globalization; +using System.Text; +using PdfSharp.Internal.Threading; + +namespace PdfSharp.Internal.OpenType +{ + /// + /// Global table of all OpenType font faces cached by their face name and check sum. + /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + public class OpenTypeFontFaceCache + { + internal OpenTypeFontFaceCache(OtGlobals.OtFontStorage storage) + { + _storage = storage; + } + + /// + /// Tries to get font face by its key. + /// + public bool TryGetFontFace(string key, + [MaybeNullWhen(false)] + out OpenTypeFontFace fontFace) + { + try + { + Locks.EnterFontManagement(); + var result = _storage.FontFaceCache.TryGetValue(key, out fontFace); + return result; + } + finally { Locks.ExitFontManagement(); } + } + + /// + /// Tries to get font face by its font source check sum. + /// + public bool TryGetFontFace(ulong checkSum, + [MaybeNullWhen(false)] + out OpenTypeFontFace fontFace) + { + try + { + Locks.EnterFontManagement(); + var result = _storage.FontFacesByCheckSum.TryGetValue(checkSum, out fontFace); + return result; + } + finally { Locks.ExitFontManagement(); } + } + + public OpenTypeFontFace AddFontFace(OpenTypeFontFace fontFace) + { + try + { + Locks.EnterFontManagement(); + if (TryGetFontFace(fontFace.FullFaceName, out var fontFaceCheck)) + { + if (fontFaceCheck.CheckSum != fontFace.CheckSum) + throw new InvalidOperationException("OpenTypeFontFace with same signature but different bytes."); + return fontFaceCheck; + } + _storage.FontFaceCache.TryAdd(fontFace.FullFaceName, fontFace); + _storage.FontFacesByCheckSum.TryAdd(fontFace.CheckSum, fontFace); + return fontFace; + } + finally { Locks.ExitFontManagement(); } + } + + //public static void Reset() + //{ + // try + // { + // var fontStorage = OtGlobals.Global.OTFonts; + + // Locks.EnterFontManagement(); + // fontStorage.FontFaceCache.Clear(); + // fontStorage.FontFacesByCheckSum.Clear(); + // } + // finally { Locks.ExitFontManagement(); } + //} + + readonly OtGlobals.OtFontStorage _storage; + + public string GetCacheState() + { + StringBuilder state = new StringBuilder(); + state.Append("====================\n"); + state.Append("OpenType font faces by name\n"); + var familyKeys = _storage.FontFaceCache.Keys; + int count = familyKeys.Count; + string[] keys = new string[count]; + familyKeys.CopyTo(keys, 0); + Array.Sort(keys, StringComparer.OrdinalIgnoreCase); + foreach (string key in keys) + state.AppendFormat(" {0}: {1}\n", key, _storage.FontFaceCache[key].DebuggerDisplay); + state.Append("\n"); + return state.ToString(); + } + + /// + /// Gets the DebuggerDisplayAttribute text. + /// + // ReSharper disable UnusedMember.Local + static string DebuggerDisplay + // ReSharper restore UnusedMember.Local + => String.Format(CultureInfo.InvariantCulture, "Font faces: {0}", OtGlobals.Global.OTFonts.FontFaceCache.Count); + } +} + +namespace PdfSharp.Internal.OpenType +{ + partial class OtGlobals + { + partial class OtFontStorage + { + /// + /// Maps face name to OpenType font face. + /// + public readonly ConcurrentDictionary FontFaceCache = []; + + /// + /// Maps font source key to OpenType font face. + /// + public readonly ConcurrentDictionary FontFacesByCheckSum = []; + + /// + /// Maps font descriptor key to OpenTypeFontDescriptor. + /// TODO: Replace by FontFaceCache. + /// + public readonly ConcurrentDictionary FontDescriptorCache = []; + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFactory-DELETE.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFactory-DELETE.cs new file mode 100644 index 00000000..4980febc --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFactory-DELETE.cs @@ -0,0 +1,17 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Internal.Threading; + +// Re-Sharper disable RedundantNameQualifier + +namespace PdfSharp.Internal.OpenType +{ + ///// + ///// Provides functionality to map a font face request to a physical font. + ///// + //static class OpenTypeFontFactory + //{ + + //} +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFamily.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFamily.cs new file mode 100644 index 00000000..2bab8af1 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFamily.cs @@ -0,0 +1,103 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Collections.Concurrent; +using System.Collections.ObjectModel; + +// v7.0 TODO review + +namespace PdfSharp.Internal.OpenType +{ + /// + /// Represents a family of related OpenType fonts. + /// + public class OpenTypeFontFamily + { + /// + /// Initializes a new instance of the class. + /// + public OpenTypeFontFamily(string name) + { + FamilyName = name; + } + + public void AddFontFace(OpenTypeGlyphTypeface otGlyphTypefaces) + { + // Note that the descriptor may differ from otGlyphTypefaces.OTFontFace.OTDescriptor. + var descr = otGlyphTypefaces.OTDescriptor; + var key = KeyHelper.CalcTypefaceKey(descr.OTFontStyle, descr.OTFontWeight, descr.OTFontStretch); + _glyphTypefaces.TryAdd(key, otGlyphTypefaces); + _referenceTypeFace = null; // Reevaluate ReferenceFontFace. + } + + //public void AddFontFace(OpenTypeGlyphTypeface glyphTypefaces, OpenTypeFontStyle overrideStyle, OpenTypeFontWeight overrideWeight, OpenTypeFontStretch overrideStretch) + //{ + // var key = KeyHelper.CalcTypefaceKey(overrideStyle, overrideWeight, overrideStretch); + // _glyphTypefaces.TryAdd(key, glyphTypefaces); + //} + + /// + /// Gets a collection of the OpenType glyph typefaces of this family + /// + public IReadOnlyCollection GetGlyphTypefaces() + { + return new ReadOnlyCollection(_glyphTypefaces.Values.ToArray()); + } + + public OpenTypeGlyphTypeface? ResolveTypeface(OpenTypeFontStyle style, OpenTypeFontWeight weight, OpenTypeFontStretch stretch) + { + var key = KeyHelper.CalcTypefaceKey(style, weight, stretch); + var fontFace = _glyphTypefaces.GetValueOrDefault(key); + return fontFace; + } + + public OpenTypeFontFace ReferenceFontFace + { + get + { + const string normalKey = "0|400|5"; + const string boldKey = "0|700|5"; + const string italicKey = "2|400|5"; + + if (_referenceTypeFace == null) + { + if (_glyphTypefaces.Count == 0) + throw new InvalidOperationException("An empty font family has no ReferenceFontFace."); + + var glyphTypeface = _glyphTypefaces.GetValueOrDefault(normalKey) + ?? _glyphTypefaces.GetValueOrDefault(boldKey) + ?? _glyphTypefaces.GetValueOrDefault(italicKey); + _referenceTypeFace = glyphTypeface != null + ? glyphTypeface.OTFontFace + // HACK: Just return first font. + : _glyphTypefaces.Values.ToArray()[0].OTFontFace; + } + return _referenceTypeFace; + } + } + + /// + /// Gets the name of the font family. + /// + public readonly string FamilyName; + + public static OpenTypeFontFamily GetOrCreateFrom(string familyName) + { + var openTypeFontFamilyCache = OtGlobals.Global.OTFonts.OpenTypeFontFamilyCache; + if (openTypeFontFamilyCache.TryGetFontFamily(familyName, out var otFontFamily)) + return otFontFamily; + otFontFamily = new(familyName); + openTypeFontFamilyCache.AddFontFamily(otFontFamily); + return otFontFamily; + } + + OpenTypeFontFace? _referenceTypeFace; + + // Maps typefaceKey to glyph typefaces. + // An OpenTypeFontFamily is composed of OpenTypeGlyphTypeface objects (not of OpenTypeTypeface objects) + // because of style simulation. + readonly ConcurrentDictionary _glyphTypefaces = new(StringComparer.OrdinalIgnoreCase); + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFamilyCache.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFamilyCache.cs new file mode 100644 index 00000000..5743cfed --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontFamilyCache.cs @@ -0,0 +1,92 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Collections.Concurrent; +using PdfSharp.Internal.Threading; + +namespace PdfSharp.Internal.OpenType +{ + /// + /// Global table of all OpenType font faces cached by their face name and check sum. + /// + //[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + public class OpenTypeFontFamilyCache + { + internal OpenTypeFontFamilyCache(OtGlobals.OtFontStorage storage) + { + _storage = storage; + } + + /// + /// Tries to get font face by its family name. + /// + public bool TryGetFontFamily(string familyName, [MaybeNullWhen(false)] out OpenTypeFontFamily fontFamily) + { + try + { + familyName = familyName.ToLowerInvariant(); + Locks.EnterFontManagement(); + var result = _storage.FontFamilyByName.TryGetValue(familyName, out fontFamily); + return result; + } + finally { Locks.ExitFontManagement(); } + } + + public OpenTypeFontFamily AddFontFamily(OpenTypeFontFamily otFontFamily) + { + try + { + Locks.EnterFontManagement(); + if (_storage.FontFamilyByName.TryGetValue(otFontFamily.FamilyName, out var otFontFamilyCheck)) + { + return otFontFamilyCheck; + } + _storage.FontFamilyByName.TryAdd(otFontFamily.FamilyName, otFontFamily); + return otFontFamily; + } + finally { Locks.ExitFontManagement(); } + } + + readonly OtGlobals.OtFontStorage _storage; + + //public static string GetCacheState() + //{ + // StringBuilder state = new StringBuilder(); + // state.Append("====================\n"); + // state.Append("OpenType font faces by name\n"); + // var familyKeys = OpenTypeGlobals.Global.OTFonts.FontFaceCache.Keys; + // int count = familyKeys.Count; + // string[] keys = new string[count]; + // familyKeys.CopyTo(keys, 0); + // Array.Sort(keys, StringComparer.OrdinalIgnoreCase); + // foreach (string key in keys) + // state.AppendFormat(" {0}: {1}\n", key, OpenTypeGlobals.Global.OTFonts.FontFaceCache[key].DebuggerDisplay); + // state.Append("\n"); + // return state.ToString(); + //} + + ///// + ///// Gets the DebuggerDisplayAttribute text. + ///// + //// ReSharper disable UnusedMember.Local + //static string DebuggerDisplay + //// ReSharper restore UnusedMember.Local + // => String.Format(CultureInfo.InvariantCulture, "Font faces: {0}", OpenTypeGlobals.Global.OTFonts.FontFaceCache.Count); + } +} + +namespace PdfSharp.Internal.OpenType +{ + partial class OtGlobals + { + partial class OtFontStorage + { + /// + /// Maps family name to OpenType font family. + /// + public readonly ConcurrentDictionary FontFamilyByName = new(StringComparer.OrdinalIgnoreCase); + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontRegistry.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontRegistry.cs new file mode 100644 index 00000000..070cdd29 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontRegistry.cs @@ -0,0 +1,163 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Collections.Concurrent; + +namespace PdfSharp.Internal.OpenType +{ + /// + /// A hack to bootstrap the interaction of FontFamily, Typeface, and FontFace. + /// + public class OpenTypeFontRegistry + { + // Example + // Windows comes with 10 Arial font files. 4 times Arial (regular, bold, italic, and bold italic), + // 4 times Arial Narrow (also 4 faces), Arial Black, and Arial Rounded. Windows groups them into two + // families. The first family contains the first 9 faces and the second family only Arial Rounded. WPF + // does it // the same way. A look at the source code of WPF 3.0 reveals that this is done with a lot + // of specialized knowledge about fonts. E.g. WPF knows the meaning of 'narrow' in Arial Narrow and + // removed it from the family name. In Arial Narrow the stretch entry in the OS/2 table is correct. + // But in Arial Black the weight value in the OS/2 table is 400 as it is for a regular with. WPF again + // 'knows' the meaning of Black, removes it from the family name and corrects the wrong weight value + // to 900. + // For details see https://assets.pdfsharp.net/docs/WPF%20Font%20Selection%20Model.pdf + // PDFsharp does not use these heuristics. It creates 4 families: Arial (4 faces), Arial Narrow (4 faces), + // Arial Black (1 face), and Arial Rounded (1 face). + enum State // not yet used + { + Created, + Initializing, + Ready + } + + public OpenTypeFontRegistry() + { } + + public OpenTypeGlyphTypeface RegisterFont(OpenTypeFontSource fontSource) + { + var fontFace = fontSource.OTFontFace; + return RegisterFont(fontFace); + } + + public OpenTypeGlyphTypeface RegisterFont(OpenTypeFontFace fontFace, string? overrideFamilyName = null, string? overrideFaceName = null, + OpenTypeFontStyle? overrideStyle = null, OpenTypeFontWeight? overrideWeight = null, OpenTypeFontStretch? overrideStretch = null) + { + if (_state == State.Created) + _state = State.Initializing; + if (_state == State.Ready) + throw new InvalidOperationException("Cannot add fonts after registry is initialized."); + + var fontSource = fontFace.OTFontSource; + Debug.Assert(ReferenceEquals(fontSource.OTFontFace, fontFace)); + + // We clone the descriptor because some values may be overridden in a GlyphTypeface. + var descr = fontFace.OTDescriptor.Clone(); + //var key = fontSource.FontFaceKey; + //var fontFace = OpenTypeGlobals.Global.OTFonts.FontFaceCache[key]; + + descr.FamilyName = overrideFamilyName ?? fontFace.OTDescriptor.FamilyName; + descr.FaceName = overrideFaceName ?? fontFace.OTDescriptor.FaceName; + descr.OTFontStyle = overrideStyle ?? fontFace.OTDescriptor.OTFontStyle; + descr.OTFontWeight = overrideWeight ?? fontFace.OTDescriptor.OTFontWeight; + descr.OTFontStretch = overrideStretch ?? fontFace.OTDescriptor.OTFontStretch; + + //if (!String.IsNullOrEmpty(overrideFamilyName)) + //{ + // familyName = overrideFamilyName; + // fontFace.OTDescriptor.FamilyName = familyName; + //} + + //if (!String.IsNullOrEmpty(overrideFaceName)) + //{ + // faceName = overrideFaceName; + // fontFace.OTDescriptor.FaceName = faceName; + //} + + // Get or create appropriate font family. + if (!_fontFamilies.TryGetValue(descr.FamilyName, out var otFontFamily)) + { + otFontFamily = new(descr.FamilyName); + _fontFamilies.TryAdd(otFontFamily.FamilyName, otFontFamily); + } + + var otGlyphTypeface = new OpenTypeGlyphTypeface(fontFace, descr); + _glyphTypefaces.TryAdd(otGlyphTypeface.Key, otGlyphTypeface); + otFontFamily.AddFontFace(otGlyphTypeface/*, style, weight, stretch*/); + + return otGlyphTypeface; + //// Add font to family with alternate family name. + //if (altFamilyName != null && String.Compare(altFamilyName, familyName, StringComparison.OrdinalIgnoreCase) != 0) + //{ + // if (_fontFamilies.TryGetValue(altFamilyName, out family)) + // { + // family.AddFontFace(fontFace, style, weight, stretch); + // } + // else + // { + // family = new(altFamilyName); + // family.AddFontFace(fontFace, style, weight, stretch); + // _fontFamilies.TryAdd(altFamilyName, family); + // } + //} + } + + // TODO + public OpenTypeGlyphTypeface RegisterGlyphTypeface(OpenTypeGlyphTypeface otGlyphTypeface, + bool isBoldSimulated, bool isItalicSimulated) + { + var variant = otGlyphTypeface.CreateVariant(isBoldSimulated, isItalicSimulated); + + // Get or create appropriate font family. + if (!_fontFamilies.TryGetValue(variant.OTDescriptor.FamilyName, out var otFontFamily)) + { + otFontFamily = new(variant.OTDescriptor.FamilyName); + _fontFamilies.TryAdd(otFontFamily.FamilyName, otFontFamily); + } + + // var otGlyphTypeface = new OpenTypeGlyphTypeface(fontFace, descr); + _glyphTypefaces.TryAdd(otGlyphTypeface.Key, otGlyphTypeface); + otFontFamily.AddFontFace(otGlyphTypeface/*, style, weight, stretch*/); + + return variant; + } + + public ICollection GetFamilies() + { + var families = _fontFamilies.Values; + return families; + } + + public OpenTypeFontFamily? GetFamily(string familyName) + { + _fontFamilies.TryGetValue(familyName, out var fontFamily); + return fontFamily; + } + + public OpenTypeGlyphTypeface? ResolveTypeface(string familyName, OpenTypeFontStyle style, OpenTypeFontWeight weight, OpenTypeFontStretch stretch) + { + //OpenTypeFontFace? fontFace; + if (_fontFamilies.TryGetValue(familyName, out var family)) + { + var glyphTypeface = family.ResolveTypeface(style, weight, stretch); + return glyphTypeface; + } + + //if (_defaultFamily != null) + //{ + // var glyphTypeface = _defaultFamily.ResolveTypeface(style, weight, stretch); + // return glyphTypeface; + //} + return null; + } + + //OpenTypeFontFamily? _defaultFamily; + + State _state; + + readonly ConcurrentDictionary _glyphTypefaces = new(StringComparer.OrdinalIgnoreCase); + + readonly ConcurrentDictionary _fontFamilies = new(StringComparer.OrdinalIgnoreCase); + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontSource.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontSource.cs new file mode 100644 index 00000000..95786e33 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontSource.cs @@ -0,0 +1,161 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Globalization; + +// v7.0 TODO review + +namespace PdfSharp.Internal.OpenType +{ + /// + /// The raw bytes of a font file in memory. + /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + public class OpenTypeFontSource + { + // Signature of a true type collection font. + const uint ttcf = 0x66637474; + + OpenTypeFontSource(byte[] bytes, ulong checksumKey) + { + Bytes = bytes; + ChecksumKey = checksumKey; // Is calculated later if 0. + } + + /// + /// Gets an existing font source or creates a new one. + /// A new font source is cached in OpenTypeFontSourceCache. + /// + public static OpenTypeFontSource GetOrCreateFrom(byte[] bytes, ulong checksumKey = 0) + { + checksumKey = checksumKey == 0 ? ChecksumHelper.CalcChecksum(bytes) : checksumKey; + Debug.Assert(checksumKey == ChecksumHelper.CalcChecksum(bytes)); + + var openTypeFontSourceCache = OtGlobals.Global.OTFonts.OpenTypeFontSourceCache; + if (openTypeFontSourceCache.TryGetFontSourceByKey(checksumKey, out var fontSource)) + return fontSource; + + fontSource = new(bytes, checksumKey); + fontSource = openTypeFontSourceCache.CacheFontSource(fontSource); + return fontSource; + } + + /// + /// Creates an OpenTypeFontSource from a URI. + /// Returns null if the URI does not exist or is not a file URI. + /// + public static OpenTypeFontSource? GetOrCreateFrom(Uri fontUri) + { + if (fontUri.IsFile) + { + var path = fontUri.LocalPath; + if (String.IsNullOrEmpty(path)) + throw new ArgumentNullException(nameof(path), "URI does not contain a local path."); + + var bytes = File.ReadAllBytes(path); + return GetOrCreateFrom(bytes); + } + return null; + } + +#if not_used // DELETE + static OpenTypeFontSource GetOrCreateFromBytes(string typefaceKey, byte[] fontBytes) + { + ulong checksumKey = ChecksumHelper.CalcChecksum(fontBytes); + if (OpenTypeFontFactory.TryGetFontSourceByKey(checksumKey, out var fontSource)) + { + // The font source already exists, but is not yet cached under the specified typeface key. + OpenTypeFontFactory.CacheExistingFontSourceWithNewTypefaceKey(typefaceKey, fontSource); + } + else + { + // No font source exists. Create new one and cache it. + fontSource = new(fontBytes, checksumKey); + OpenTypeFontFactory.CacheNewFontSource(typefaceKey, fontSource); + } + return fontSource; + } +#endif + + /// + /// Creates a font source from a byte array. + /// The result is not cached. + /// + public static OpenTypeFontSource CreateCompiledFont(byte[] bytes) + { + var fontSource = new OpenTypeFontSource(bytes, 0); + return fontSource; + } + + /// + /// Gets or sets the (first) font face. + /// + public OpenTypeFontFace OTFontFace { get; set; } = null!; + + public bool IsTrueTypeCollection => NumberOfFontFaces > 1; + + public int NumberOfFontFaces => 1; // OpenType collections is not yet implemented + + public OpenTypeFontFace GetOTFontFace(int index) + { +#if NET8_0_OR_GREATER // TODO EXTENSIONS + ArgumentOutOfRangeException.ThrowIfGreaterThanOrEqual(index, NumberOfFontFaces, nameof(index)); +#endif + + if (index == 1) + return OTFontFace; + + throw new NotSupportedException("TODO other font faces"); + } + + /// + /// Gets the key that uniquely identifies this font source. + /// + public ulong ChecksumKey + { + get + { + if (field == 0) + field = ChecksumHelper.CalcChecksum(Bytes); + return field; + } + } + + /// + /// //Gets the name of the font’s name table. + /// + public string FontFaceKey => OTFontFace?.OTDescriptor.Key ?? null!; //_fontFaceKey; + + //string _fontFaceKey = null!; + + /// + /// Gets the bytes of the font. + /// + public byte[] Bytes { get; } + + /// + /// Returns a hash code for this instance. + /// + public override int GetHashCode() + => (int)((ChecksumKey >> 32) ^ ChecksumKey); + + /// + /// Determines whether the specified object is equal to the current object. + /// + public override bool Equals(object? obj) + { + if (obj is not OpenTypeFontSource fontSource) + return false; + return ChecksumKey == fontSource.ChecksumKey; + } + + /// + /// Gets the DebuggerDisplayAttribute text. + /// + // ReSharper disable UnusedMember.Local + internal string DebuggerDisplay => + String.Format(CultureInfo.InvariantCulture, "OTFontSource: '{0}', keyhash={1}", FontFaceKey, ChecksumKey % 99991 /* largest prime number less than 100000 */); + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontSourceCache.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontSourceCache.cs new file mode 100644 index 00000000..ce76a223 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontSourceCache.cs @@ -0,0 +1,244 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using PdfSharp.Internal.Threading; +using System.Collections.Concurrent; + +// Re-Sharper disable RedundantNameQualifier + +// v7.0 TODO review + +namespace PdfSharp.Internal.OpenType +{ + /// + /// Provides functionality to map a font face request to a physical font. + /// + public class OpenTypeFontSourceCache + { + internal OpenTypeFontSourceCache(OtGlobals.OtFontStorage storage) + { + _storage = storage; + } + + ///// + ///// Gets the bytes of a physical font with specified face name. + ///// + //public static OpenTypeFontSource GetFontSourceByFontName(string fontName) + //{ + // if (OpenTypeGlobals.Global.OTFonts.FontSourcesByName.TryGetValue(fontName, out var fontSource)) + // return fontSource; + + // Debug.Assert(false, $"An XFontSource with the name '{fontName}' does not exist."); + // return null!; + //} + + ///// + ///// Gets the bytes of a physical font with specified face name. + ///// + //public static OpenTypeFontSource GetFontSourceByTypefaceKey(string typefaceKey) + //{ + // if (OpenTypeGlobals.Global.OTFonts.FontSourcesByName.TryGetValue(typefaceKey, out var fontSource)) + // return fontSource; + + // Debug.Assert(false, $"An XFontSource with the typeface key '{typefaceKey}' does not exist."); + // return null!; + //} + + public bool TryGetFontSourceByKey(ulong key, [MaybeNullWhen(false)] out OpenTypeFontSource fontSource) + { + return _storage.FontSourcesByKey.TryGetValue(key, out fontSource); + } + + /// + /// Gets a value indicating whether at least one font source was created. + /// + public bool HasFontSources => _storage.FontSourcesByName.Count > 0; + + /// + /// Caches a font source under its face name and its key. + /// + public OpenTypeFontSource CacheFontSource(OpenTypeFontSource fontSource) + { + try + { + Locks.EnterFontManagement(); + // Check whether an identical font source with a different face name already exists. + if (_storage.FontSourcesByKey.TryGetValue(fontSource.ChecksumKey, out var existingFontSource)) + { +#if DEBUG + // Fonts have same length and check sum. Now check byte by byte identity. + int length = fontSource.Bytes.Length; + for (int idx = 0; idx < length; idx++) + { + if (existingFontSource.Bytes[idx] != fontSource.Bytes[idx]) + { + //Debug.Assert(false,"Two fonts with identical checksum found."); + break; + //goto FontsAreNotIdentical; + } + } + Debug.Assert(existingFontSource.OTFontFace != null); +#endif + 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(); + } + + OpenTypeFontFace? fontFace = fontSource.OTFontFace; + if (fontFace == null!) + { + // Case: The OpenTypeFontSource has no OpenTypeFontFace. + // The font face cannot be in a cache, because the font source would be in the + // font source cache in the first place. So create and cache a new OpenTypeFontFace. + fontSource.OTFontFace = OpenTypeFontFace.GetOrCreateFrom(fontSource); + } + _storage.FontSourcesByKey.TryAdd(fontSource.ChecksumKey, fontSource); + _storage.FontSourcesByName.TryAdd(fontSource.FontFaceKey, fontSource); + return fontSource; + } + finally { Locks.ExitFontManagement(); } + } + + /// + /// Caches a font source under its face name and its key. + /// + public OpenTypeFontSource CacheNewFontSource(string typefaceKey, OpenTypeFontSource otFontSource) + { + // Debug.Assert(!FontSourcesByFaceName.ContainsKey(otFontSource.FaceName)); + + // Check whether an identical font source with a different face name already exists. + if (_storage.FontSourcesByKey.TryGetValue(otFontSource.ChecksumKey, out var existingFontSource)) + { + //// Fonts have same length and check sum. Now check byte by byte identity. + //int length = otFontSource.Bytes.Length; + //for (int idx = 0; idx < length; idx++) + //{ + // if (existingFontSource.Bytes[idx] != otFontSource.Bytes[idx]) + // { + // goto FontsAreNotIdentical; + // } + //} + return existingFontSource; + + ////// The bytes are really identical. Register font source again with the new face name + ////// but return the existing one to save memory. + ////FontSourcesByFaceName.Add(otFontSource.FaceName, 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(otFontSource.Key)) + // otFontSource.IncrementKey(); + } + + OpenTypeFontFace fontFace = otFontSource.OTFontFace; + if (fontFace == null!) + { + fontFace = OpenTypeFontFace.GetOrCreateFrom(otFontSource); + otFontSource.OTFontFace = fontFace; // Also sets the font name in otFontSource + } + + OtGlobals.Global.OTFonts.FontSourcesByName.TryAdd(typefaceKey, otFontSource); + OtGlobals.Global.OTFonts.FontSourcesByName.TryAdd(otFontSource.FontFaceKey, otFontSource); + OtGlobals.Global.OTFonts.FontSourcesByKey.TryAdd(otFontSource.ChecksumKey, otFontSource); + + return otFontSource; + } + + public void CacheExistingFontSourceWithNewTypefaceKey(string typefaceKey, OpenTypeFontSource fontSource) + { + try + { + Locks.EnterFontManagement(); + _storage.FontSourcesByName.TryAdd(typefaceKey, fontSource); + } + finally { Locks.ExitFontManagement(); } + } + + //internal static void Reset_TODO() + //{ + // try + // { + // OtGlobals.Global.OTFonts.FontSourcesByName.Clear(); + // OtGlobals.Global.OTFonts.FontSourcesByKey.Clear(); + // } + // finally { Locks.ExitFontManagement(); } + //} + + readonly OtGlobals.OtFontStorage _storage; + + internal static string GetFontCachesState() + { + return "Temporarily out of order"; + //var state = new StringBuilder(); + //string[] keys; + //int count; + + //// FontResolverInfo by name. + //state.Append("====================\n"); + //state.Append("Font resolver info by name\n"); + //Dictionary.KeyCollection keyCollection = Globals.Global.Fonts.FontResolverInfosByName.Keys; + //count = keyCollection.Count; + //keys = new string[count]; + //keyCollection.CopyTo(keys, 0); + //Array.Sort(keys, StringComparer.OrdinalIgnoreCase); + //foreach (string key in keys) + // state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontResolverInfosByName[key].DebuggerDisplay); + //state.Append('\n'); + + //// FontSource by key. + //state.Append("Font source by key and name\n"); + //Dictionary.KeyCollection 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; + //count = fontSourceNames.Count; + //keys = new string[count]; + //fontSourceNames.CopyTo(keys, 0); + //Array.Sort(keys, StringComparer.OrdinalIgnoreCase); + //foreach (string key in keys) + // state.AppendFormat(" {0}: {1}\n", key, Globals.Global.Fonts.FontSourcesByName[key].DebuggerDisplay); + //state.Append("--------------------\n\n"); + + //// FontFamilyInternal by name. + //state.Append(FontFamilyCache.GetCacheState()); + //// XGlyphTypeface by name. + //state.Append(GlyphTypefaceCache.GetCacheState()); + //// OpenTypeFontFace by name. + //state.Append(OpenTypeFontFaceCache.GetCacheState()); + //return state.ToString(); + } + } +} + +namespace PdfSharp.Internal.OpenType +{ + partial class OtGlobals + { + partial class OtFontStorage + { + /// + /// Maps typeface key or font name to font source. + /// + 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 ConcurrentDictionary FontSourcesByKey = []; + } + } +} \ No newline at end of file diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontTable.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontTable.cs similarity index 78% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontTable.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontTable.cs index 88a1db95..a753fbfb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontTable.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontTable.cs @@ -1,16 +1,18 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Fonts.OpenType +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Internal.OpenType { /// /// Base class for all OpenType tables used in PDFsharp. /// - class OpenTypeFontTable : ICloneable + public class OpenTypeFontTable : ICloneable { - public OpenTypeFontTable(OpenTypeFontFace? fontData, string tag) + protected OpenTypeFontTable(OpenTypeFontFace? fontData, string tag) { - _fontData = fontData; + FontData = fontData; if (fontData != null && fontData.TableDictionary.ContainsKey(tag)) DirectoryEntry = fontData.TableDictionary[tag]; else @@ -34,9 +36,13 @@ protected virtual OpenTypeFontTable DeepCopy() /// /// Gets the font image the table belongs to. /// - public OpenTypeFontFace? FontData => _fontData; + public OpenTypeFontFace? FontData +#if DEBUG__ + { get; set; } +#endif + = null; // => FontData; - internal OpenTypeFontFace? _fontData = default!; + //internal OpenTypeFontFace? FontData = null!; public TableDirectoryEntry DirectoryEntry; @@ -55,12 +61,11 @@ public virtual void Write(OpenTypeFontWriter writer) /// /// Calculates the checksum of a table represented by its bytes. /// - public static uint CalcChecksum(byte[] bytes) + protected static uint CalcChecksum(byte[] bytes) { Debug.Assert((bytes.Length & 3) == 0); // Cannot use Buffer.BlockCopy because 32-bit values are Big-endian in fonts. - uint byte3, byte2, byte1, byte0; - byte3 = byte2 = byte1 = byte0 = 0; + uint byte3 = 0, byte2 = 0, byte1 = 0, byte0 = 0; int length = bytes.Length; for (int idx = 0; idx < length;) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontTables.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontTables.cs similarity index 59% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontTables.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontTables.cs index 7cbc780f..51d475f2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/OpenTypeFontTables.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontTables.cs @@ -1,9 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -using System.Text; -using PdfSharp.Drawing; +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member +using System.Text; using Fixed = System.Int32; using FWord = System.Int16; using UFWord = System.UInt16; @@ -11,7 +11,7 @@ // ReSharper disable InconsistentNaming // ReSharper disable IdentifierTypo -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { enum PlatformId { @@ -21,7 +21,7 @@ enum PlatformId /// /// Only Symbol and Unicode are used by PDFsharp. /// - enum WinEncodingId + public enum WinEncodingId { Symbol = 0, UnicodeUSC_2 = 1, @@ -33,11 +33,45 @@ enum WinEncodingId UnicodeUSC_4 = 10 } + /// + /// Name IDs of strings in name table. + /// + public static class NameId + { + public const ushort CopyrightNotice = 0; + public const ushort FamilyName = 1; + public const ushort SubfamilyName = 2; + public const ushort UniqueIdentifier = 3; + public const ushort FullName = 4; + public const ushort Version = 5; + public const ushort PostScriptName = 0; + public const ushort Trademark = 7; + public const ushort Manufacturer = 8; + public const ushort Designer = 9; + public const ushort Description = 10; + public const ushort VendorUrl = 11; + public const ushort DesignerUrl = 12; + public const ushort License = 13; + public const ushort LicenseInfoUrl = 14; + public const ushort Reserved = 15; + public const ushort TypographicFamily = 16; + public const ushort TypographicSubfamily = 17; + public const ushort CompatibleFull = 18; + public const ushort SampleText = 19; + public const ushort PostScriptCID = 20; + public const ushort WwsFamily = 21; + public const ushort WwsSubfamily = 22; + public const ushort DarkBackgroundPalette = 24; + public const ushort VariationsPostScriptNamePrefix = 25; + + public const int MaxValue = 25; + } + /// /// CMap format 4: Segment mapping to delta values. /// The Windows standard format. /// - class CMap4 : OpenTypeFontTable + public class CMap4 : OpenTypeFontTable { public WinEncodingId encodingId; // Windows encoding ID. public ushort format; // Format number is set to 4. @@ -47,12 +81,12 @@ class CMap4 : OpenTypeFontTable public ushort searchRange; // 2 x (2**floor(log2(segCount))) public ushort entrySelector; // log2(searchRange/2) public ushort rangeShift; - public ushort[] endCount = null!; // [segCount] / End characterCode for each segment, last=0xFFFF. - public ushort[] startCount = null!; // [segCount] / Start character code for each segment. - public short[] idDelta = null!; // [segCount] / Delta for all character codes in segment. - public ushort[] idRangeOffs = null!; // [segCount] / Offsets into glyphIdArray or 0 - public int glyphCount; // = (length - (16 + 4 * 2 * segCount)) / 2; - public ushort[] glyphIdArray = null!; // Glyph index array (arbitrary length) + public ushort[] endCount = null!; // [segCount] / End characterCode for each segment, last=0xFFFF. + public ushort[] startCount = null!; // [segCount] / Start character code for each segment. + public short[] idDelta = null!; // [segCount] / Delta for all character codes in segment. + public ushort[] idRangeOffs = null!; // [segCount] / Offsets into glyphIdArray or 0 + public int glyphCount; // = (length - (16 + 4 * 2 * segCount)) / 2; + public ushort[] glyphIdArray = null!; // Glyph index array (arbitrary length) public CMap4(OpenTypeFontFace fontData, WinEncodingId encodingId) : base(fontData, "----") @@ -66,14 +100,14 @@ internal void Read() try { // m_EncodingID = encID; - format = _fontData!.ReadUShort(); // NRT + format = FontData!.ReadUShort(); // NRT Debug.Assert(format == 4, "Only format 4 expected."); - length = _fontData.ReadUShort(); - language = _fontData.ReadUShort(); // Always null in Windows - segCountX2 = _fontData.ReadUShort(); - searchRange = _fontData.ReadUShort(); - entrySelector = _fontData.ReadUShort(); - rangeShift = _fontData.ReadUShort(); + length = FontData.ReadUShort(); + language = FontData.ReadUShort(); // Always null in Windows + segCountX2 = FontData.ReadUShort(); + searchRange = FontData.ReadUShort(); + entrySelector = FontData.ReadUShort(); + rangeShift = FontData.ReadUShort(); int segCount = segCountX2 / 2; glyphCount = (length - (16 + 8 * segCount)) / 2; @@ -88,26 +122,26 @@ internal void Read() glyphIdArray = new ushort[glyphCount]; for (int idx = 0; idx < segCount; idx++) - endCount[idx] = _fontData.ReadUShort(); + endCount[idx] = FontData.ReadUShort(); // Read reserved pad. - _fontData.ReadUShort(); + FontData.ReadUShort(); for (int idx = 0; idx < segCount; idx++) - startCount[idx] = _fontData.ReadUShort(); + startCount[idx] = FontData.ReadUShort(); for (int idx = 0; idx < segCount; idx++) - idDelta[idx] = _fontData.ReadShort(); + idDelta[idx] = FontData.ReadShort(); for (int idx = 0; idx < segCount; idx++) - idRangeOffs[idx] = _fontData.ReadUShort(); + idRangeOffs[idx] = FontData.ReadUShort(); for (int idx = 0; idx < glyphCount; idx++) - glyphIdArray[idx] = _fontData.ReadUShort(); + glyphIdArray[idx] = FontData.ReadUShort(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -116,9 +150,9 @@ internal void Read() /// CMap format 12: Segmented coverage. /// The Windows standard format. /// - internal class CMap12 : OpenTypeFontTable + public class CMap12 : OpenTypeFontTable { - internal struct SequentialMapGroup + public struct SequentialMapGroup { public UInt32 startCharCode;// First character code in this group. public UInt32 endCharCode; // Last character code in this group. @@ -145,26 +179,26 @@ internal void Read() try { // m_EncodingID = encID; - format = _fontData!.ReadUShort(); // NRT + format = FontData!.ReadUShort(); // NRT Debug.Assert(format == 12, "Only format 12 expected."); - _fontData.ReadUShort(); // Reserved. - length = _fontData.ReadULong(); - language = _fontData.ReadULong(); // Always null in Windows. - numGroups = _fontData.ReadULong(); + FontData.ReadUShort(); // Reserved. + length = FontData.ReadULong(); + language = FontData.ReadULong(); // Always null in Windows. + numGroups = FontData.ReadULong(); groups = new SequentialMapGroup[numGroups]; for (int i = 0; i < groups.Length; i++) { ref var group = ref groups[i]; - group.startCharCode = _fontData.ReadULong(); - group.endCharCode = _fontData.ReadULong(); - group.startGlyphIndex = _fontData.ReadULong(); + group.startCharCode = FontData.ReadULong(); + group.endCharCode = FontData.ReadULong(); + group.startGlyphIndex = FontData.ReadULong(); } } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -173,7 +207,7 @@ internal void Read() /// This table defines the mapping of character codes to the glyph index values used in the font. /// It may contain more than one subtable, in order to support more than one character encoding scheme. /// - class CMapTable : OpenTypeFontTable + public class CMapTable : OpenTypeFontTable { public const string Tag = TableTagNames.CMap; @@ -201,19 +235,19 @@ internal void Read() { try { - int tableOffset = _fontData!.Position; + int tableOffset = FontData!.Position; - version = _fontData.ReadUShort(); - numTables = _fontData.ReadUShort(); + version = FontData.ReadUShort(); + numTables = FontData.ReadUShort(); bool success = false; for (int idx = 0; idx < numTables; idx++) { - PlatformId platformId = (PlatformId)_fontData.ReadUShort(); - WinEncodingId encodingId = (WinEncodingId)_fontData.ReadUShort(); - int offset = _fontData.ReadLong(); + PlatformId platformId = (PlatformId)FontData.ReadUShort(); + WinEncodingId encodingId = (WinEncodingId)FontData.ReadUShort(); + int offset = FontData.ReadLong(); - int currentPosition = _fontData.Position; + int currentPosition = FontData.Position; // Just read Windows stuff. if (platformId == PlatformId.Win && @@ -221,21 +255,21 @@ internal void Read() { symbol = encodingId == WinEncodingId.Symbol; - _fontData.Position = tableOffset + offset; + FontData.Position = tableOffset + offset; - var format = _fontData.ReadUShort(); - _fontData.Position = tableOffset + offset; + var format = FontData.ReadUShort(); + FontData.Position = tableOffset + offset; if (format == 4) { - cmap4 = new(_fontData, encodingId); + cmap4 = new(FontData, encodingId); } else if (format == 12) { - cmap12 = new(_fontData, encodingId); + cmap12 = new(FontData, encodingId); } - _fontData.Position = currentPosition; + FontData.Position = currentPosition; // We have found what we are looking for, but we do not break as there may be another hit. success = true; @@ -247,7 +281,7 @@ internal void Read() } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -256,18 +290,18 @@ internal void Read() /// This table adds support for multi-colored glyphs in a manner that integrates with the rasterizers /// of existing text engines and that is designed to be easy to support with current OpenType font files. /// - class ColorTable : OpenTypeFontTable + public class ColorTable : OpenTypeFontTable { public const string Tag = TableTagNames.COLR; - internal struct GlyphRecord + public struct GlyphRecord { public ushort glyphId; public ushort firstLayerIndex; public ushort numLayers; } - internal struct LayerRecord + public struct LayerRecord { public ushort glyphId; public ushort paletteIndex; @@ -342,7 +376,7 @@ void Read(OpenTypeFontFace fontData) } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -351,7 +385,7 @@ void Read(OpenTypeFontFace fontData) /// This table is a set of one or more palettes, each containing a predefined number of color records. /// It may also contain 'name' table IDs describing the palettes and their entries. /// - class ColorPalletTable : OpenTypeFontTable + public class ColorPalletTable : OpenTypeFontTable { public const string Tag = TableTagNames.CPAL; @@ -361,7 +395,7 @@ class ColorPalletTable : OpenTypeFontTable public ushort numColorRecords; // Total number of color records, combined for all palettes. public uint colorRecordsArrayOffset; // Offset from the beginning of CPAL table to the first ColorRecord. public ushort[] colorRecordIndices = []; // Index of each palette’s first color record in the combined color record array. - public XColor[] colorRecords = []; + public OTColor[] colorRecords = []; public ColorPalletTable(OpenTypeFontFace fontData) : base(fontData, Tag) @@ -386,19 +420,19 @@ void Read(OpenTypeFontFace fontData) { colorRecordIndices[idx] = fontData.ReadUShort(); } - colorRecords = new XColor[numColorRecords]; + colorRecords = new OTColor[numColorRecords]; for (int idx = 0; idx < numColorRecords; idx++) { var blue = fontData.ReadByte(); var green = fontData.ReadByte(); var red = fontData.ReadByte(); var alpha = fontData.ReadByte(); - colorRecords[idx] = XColor.FromArgb(alpha, red, green, blue); + colorRecords[idx] = OTColor.FromArgb(alpha, red, green, blue); } } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -407,7 +441,7 @@ void Read(OpenTypeFontFace fontData) /// This table gives global information about the font. The bounding box values should be computed using /// only glyphs that have contours. Glyphs with no contours should be ignored for the purposes of these calculations. /// - class FontHeaderTable : OpenTypeFontTable + public class FontHeaderTable : OpenTypeFontTable { public const string Tag = TableTagNames.Head; @@ -437,27 +471,27 @@ public void Read() { try { - version = _fontData!.ReadFixed(); - fontRevision = _fontData.ReadFixed(); - checkSumAdjustment = _fontData.ReadULong(); - magicNumber = _fontData.ReadULong(); - flags = _fontData.ReadUShort(); - unitsPerEm = _fontData.ReadUShort(); - created = _fontData.ReadLongDate(); - modified = _fontData.ReadLongDate(); - xMin = _fontData.ReadShort(); - yMin = _fontData.ReadShort(); - xMax = _fontData.ReadShort(); - yMax = _fontData.ReadShort(); - macStyle = _fontData.ReadUShort(); - lowestRecPPEM = _fontData.ReadUShort(); - fontDirectionHint = _fontData.ReadShort(); - indexToLocFormat = _fontData.ReadShort(); - glyphDataFormat = _fontData.ReadShort(); + version = FontData!.ReadFixed(); + fontRevision = FontData.ReadFixed(); + checkSumAdjustment = FontData.ReadULong(); + magicNumber = FontData.ReadULong(); + flags = FontData.ReadUShort(); + unitsPerEm = FontData.ReadUShort(); + created = FontData.ReadLongDate(); + modified = FontData.ReadLongDate(); + xMin = FontData.ReadShort(); + yMin = FontData.ReadShort(); + xMax = FontData.ReadShort(); + yMax = FontData.ReadShort(); + macStyle = FontData.ReadUShort(); + lowestRecPPEM = FontData.ReadUShort(); + fontDirectionHint = FontData.ReadShort(); + indexToLocFormat = FontData.ReadShort(); + glyphDataFormat = FontData.ReadShort(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -468,7 +502,7 @@ public void Read() /// Glyphs with no contours should be ignored for the purposes of these calculations. /// All reserved areas must be set to 0. /// - class HorizontalHeaderTable : OpenTypeFontTable + public class HorizontalHeaderTable : OpenTypeFontTable { public const string Tag = TableTagNames.HHea; @@ -500,37 +534,37 @@ public void Read() { try { - version = _fontData!.ReadFixed(); - ascender = _fontData.ReadFWord(); - descender = _fontData.ReadFWord(); - lineGap = _fontData.ReadFWord(); - advanceWidthMax = _fontData.ReadUFWord(); - minLeftSideBearing = _fontData.ReadFWord(); - minRightSideBearing = _fontData.ReadFWord(); - xMaxExtent = _fontData.ReadFWord(); - caretSlopeRise = _fontData.ReadShort(); - caretSlopeRun = _fontData.ReadShort(); - reserved1 = _fontData.ReadShort(); - reserved2 = _fontData.ReadShort(); - reserved3 = _fontData.ReadShort(); - reserved4 = _fontData.ReadShort(); - reserved5 = _fontData.ReadShort(); - metricDataFormat = _fontData.ReadShort(); - numberOfHMetrics = _fontData.ReadUShort(); + version = FontData!.ReadFixed(); + ascender = FontData.ReadFWord(); + descender = FontData.ReadFWord(); + lineGap = FontData.ReadFWord(); + advanceWidthMax = FontData.ReadUFWord(); + minLeftSideBearing = FontData.ReadFWord(); + minRightSideBearing = FontData.ReadFWord(); + xMaxExtent = FontData.ReadFWord(); + caretSlopeRise = FontData.ReadShort(); + caretSlopeRun = FontData.ReadShort(); + reserved1 = FontData.ReadShort(); + reserved2 = FontData.ReadShort(); + reserved3 = FontData.ReadShort(); + reserved4 = FontData.ReadShort(); + reserved5 = FontData.ReadShort(); + metricDataFormat = FontData.ReadShort(); + numberOfHMetrics = FontData.ReadUShort(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } - class HorizontalMetrics : OpenTypeFontTable + public class HorizontalMetrics : OpenTypeFontTable { public const string Tag = "----"; public ushort advanceWidth; - public short lsb; + public short leftSideBearing; public HorizontalMetrics(OpenTypeFontFace fontData) : base(fontData, Tag) @@ -542,12 +576,12 @@ public void Read() { try { - advanceWidth = _fontData!.ReadUFWord(); - lsb = _fontData.ReadFWord(); + advanceWidth = FontData!.ReadUFWord(); + leftSideBearing = FontData.ReadFWord(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -557,12 +591,12 @@ public void Read() /// the advance width, which is of type USHORT, and the left side bearing, which is of type SHORT. /// These fields are in font design units. /// - class HorizontalMetricsTable : OpenTypeFontTable + public class HorizontalMetricsTable : OpenTypeFontTable { public const string Tag = TableTagNames.HMtx; - public HorizontalMetrics[] Metrics = default!; - public FWord[] LeftSideBearing = default!; + public HorizontalMetrics[] HorzMetrics = null!; + public FWord[] LeftSideBearing = null!; public HorizontalMetricsTable(OpenTypeFontFace fontData) : base(fontData, Tag) @@ -570,62 +604,89 @@ public HorizontalMetricsTable(OpenTypeFontFace fontData) Read(); } + public (int lsb, int advw) GetLsbAndAdvanceWidth(ushort glyphIndex) + { + int lsb = 0; + int advw = 0; + if (glyphIndex < _numMetrics) + { + var hm = HorzMetrics[glyphIndex]; + lsb = hm.leftSideBearing; + advw = hm.advanceWidth; + } + else + { + lsb = LeftSideBearing[glyphIndex - _numMetrics]; + advw = HorzMetrics[_numMetrics - 1].advanceWidth; + } + return (lsb, advw); + } + public void Read() { try { - var hhea = _fontData!.hhea; - var maxp = _fontData.maxp; + var hhea = FontData!.hhea; + var maxp = FontData.maxp; if (hhea != null! && maxp != null!) { - int numMetrics = hhea.numberOfHMetrics; //->NumberOfHMetrics(); - int numLsbs = maxp.numGlyphs - numMetrics; + _numMetrics = hhea.numberOfHMetrics; + _numLsbs = maxp.numGlyphs - _numMetrics; - Debug.Assert(numMetrics != 0); - Debug.Assert(numLsbs >= 0); + Debug.Assert(_numMetrics != 0); + Debug.Assert(_numLsbs >= 0); - Metrics = new HorizontalMetrics[numMetrics]; - for (int idx = 0; idx < numMetrics; idx++) - Metrics[idx] = new HorizontalMetrics(_fontData); + HorzMetrics = new HorizontalMetrics[_numMetrics]; + for (int idx = 0; idx < _numMetrics; idx++) + HorzMetrics[idx] = new HorizontalMetrics(FontData); - if (numLsbs > 0) + if (_numLsbs > 0) { - LeftSideBearing = new FWord[numLsbs]; - for (int idx = 0; idx < numLsbs; idx++) - LeftSideBearing[idx] = _fontData.ReadFWord(); + LeftSideBearing = new FWord[_numLsbs]; + for (int idx = 0; idx < _numLsbs; idx++) + LeftSideBearing[idx] = FontData.ReadFWord(); } } } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } + + int _numMetrics; + int _numLsbs; } - // Not used in PDFsharp. - class VerticalHeaderTable : OpenTypeFontTable + /// + /// The vertical header table contains information needed for vertical layout of Chinese, Japanese, + /// Korean (CJK) and other ideographic scripts. In vertical layout, these scripts are written either + /// top to bottom or bottom to top. This table contains information that is general to the font as + /// a whole. Information that pertains to specific glyphs is given in the vertical metrics ('vmtx') + /// table. The formats of these tables are similar to those for horizontal metrics, 'hhea' and 'hmtx'. + /// https://learn.microsoft.com/en-us/typography/opentype/spec/vhea + /// + public class VerticalHeaderTable : OpenTypeFontTable { public const string Tag = TableTagNames.VHea; - // Code comes from HorizontalHeaderTable. - public Fixed Version; // 0x00010000 for Version 1.0. - public FWord Ascender; // Typographic ascent. (Distance from baseline of highest Ascender) - public FWord Descender; // Typographic descent. (Distance from baseline of lowest Descender) - public FWord LineGap; // Typographic line gap. Negative LineGap values are treated as zero in Windows 3.1, System 6, and System 7. - public UFWord AdvanceWidthMax; - public FWord MinLeftSideBearing; - public FWord MinRightSideBearing; - public FWord xMaxExtent; + public Fixed version; // 0x00010000 for Version 1.0. + public FWord vertTypoAscender; // Typographic ascent. (Distance from baseline of highest Ascender) + public FWord vertTypoDescender; // Typographic descent. (Distance from baseline of lowest Descender) + public FWord vertTypoLineGap; // Typographic line gap. Negative LineGap values are treated as zero in Windows 3.1, System 6, and System 7. + public UFWord advanceHeightMax; + public FWord minTopSideBearing; + public FWord minBottomSideBearing; + public FWord yMaxExtent; public short caretSlopeRise; public short caretSlopeRun; + public short caretOffset; public short reserved1; public short reserved2; public short reserved3; public short reserved4; - public short reserved5; public short metricDataFormat; - public ushort numberOfHMetrics; + public ushort numOfLongVerMetrics; public VerticalHeaderTable(OpenTypeFontFace fontData) : base(fontData, Tag) @@ -637,38 +698,40 @@ public void Read() { try { - Version = _fontData!.ReadFixed(); - Ascender = _fontData.ReadFWord(); - Descender = _fontData.ReadFWord(); - LineGap = _fontData.ReadFWord(); - AdvanceWidthMax = _fontData.ReadUFWord(); - MinLeftSideBearing = _fontData.ReadFWord(); - MinRightSideBearing = _fontData.ReadFWord(); - xMaxExtent = _fontData.ReadFWord(); - caretSlopeRise = _fontData.ReadShort(); - caretSlopeRun = _fontData.ReadShort(); - reserved1 = _fontData.ReadShort(); - reserved2 = _fontData.ReadShort(); - reserved3 = _fontData.ReadShort(); - reserved4 = _fontData.ReadShort(); - reserved5 = _fontData.ReadShort(); - metricDataFormat = _fontData.ReadShort(); - numberOfHMetrics = _fontData.ReadUShort(); + if (FontData == null) + return; + + version = FontData.ReadFixed(); + vertTypoAscender = FontData.ReadFWord(); + vertTypoDescender = FontData.ReadFWord(); + vertTypoLineGap = FontData.ReadFWord(); + advanceHeightMax = FontData.ReadUFWord(); + minTopSideBearing = FontData.ReadFWord(); + minBottomSideBearing = FontData.ReadFWord(); + yMaxExtent = FontData.ReadFWord(); + caretSlopeRise = FontData.ReadShort(); + caretSlopeRun = FontData.ReadShort(); + caretOffset = FontData.ReadShort(); + reserved1 = FontData.ReadShort(); + reserved2 = FontData.ReadShort(); + reserved3 = FontData.ReadShort(); + reserved4 = FontData.ReadShort(); + metricDataFormat = FontData.ReadShort(); + numOfLongVerMetrics = FontData.ReadUShort(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } - class VerticalMetrics : OpenTypeFontTable + public class VerticalMetrics : OpenTypeFontTable { public const string Tag = "----"; - // code comes from HorizontalMetrics - public ushort advanceWidth; - public short lsb; + public ushort advanceHeight; + public short topSideBearing; public VerticalMetrics(OpenTypeFontFace fontData) : base(fontData, Tag) @@ -680,69 +743,89 @@ public void Read() { try { - advanceWidth = _fontData!.ReadUFWord(); - lsb = _fontData.ReadFWord(); + advanceHeight = FontData!.ReadUFWord(); + topSideBearing = FontData.ReadFWord(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } /// - /// The vertical Metrics table allows you to specify the vertical spacing for each glyph in a - /// vertical font. This table consists of either one or two arrays that contain metric - /// information (the advance heights and top sidebearings) for the vertical layout of each - /// of the glyphs in the font. + /// The vertical metrics table allows you to specify the vertical spacing for each glyph in a + /// vertical font. This table consists of either one or two arrays that contain metric information + /// (the advance heights and top sidebearings) for the vertical layout of each of the glyphs in + /// the font. + /// https://learn.microsoft.com/en-us/typography/opentype/spec/vmtx /// - // Not used in PDFsharp. - class VerticalMetricsTable : OpenTypeFontTable + public class VerticalMetricsTable : OpenTypeFontTable { public const string Tag = TableTagNames.VMtx; - // Code comes from HorizontalMetricsTable. - public HorizontalMetrics[] metrics; - public FWord[] leftSideBearing; + public VerticalMetrics[] VertMetrics = null!; + public FWord[] TopSideBearing = null!; public VerticalMetricsTable(OpenTypeFontFace fontData) : base(fontData, Tag) { Read(); - throw new NotImplementedException("VerticalMetricsTable"); + } + + public (int tsb, int advh) GetTsbAndAdvanceHeight(ushort glyphIndex) + { + int tsb = 0; + int advh = 0; + if (glyphIndex < _numMetrics) + { + var vm = VertMetrics[glyphIndex]; + tsb = vm.topSideBearing; + advh = vm.advanceHeight; + } + else + { + tsb = TopSideBearing[glyphIndex - _numMetrics]; + advh = VertMetrics[_numMetrics - 1].advanceHeight; + } + return (tsb, advh); } public void Read() { try { - var hhea = _fontData!.hhea; - var maxp = _fontData.maxp; - if (hhea != null! && maxp != null!) + var vhea = FontData!.vhea; + var maxp = FontData.maxp; + if (vhea != null! && maxp != null!) { - int numMetrics = hhea.numberOfHMetrics; //->NumberOfHMetrics(); - int numLsbs = maxp.numGlyphs - numMetrics; + _numMetrics = vhea.numOfLongVerMetrics; + _numTsbs = maxp.numGlyphs - _numMetrics; - Debug.Assert(numMetrics != 0); - Debug.Assert(numLsbs >= 0); + Debug.Assert(_numMetrics != 0); + Debug.Assert(_numTsbs >= 0); - metrics = new HorizontalMetrics[numMetrics]; - for (int idx = 0; idx < numMetrics; idx++) - metrics[idx] = new(_fontData); + VertMetrics = new VerticalMetrics[_numMetrics]; + for (int idx = 0; idx < _numMetrics; idx++) + VertMetrics[idx] = new(FontData); - if (numLsbs > 0) + if (_numTsbs > 0) { - leftSideBearing = new FWord[numLsbs]; - for (int idx = 0; idx < numLsbs; idx++) - leftSideBearing[idx] = _fontData.ReadFWord(); + TopSideBearing = new FWord[_numTsbs]; + for (int idx = 0; idx < _numTsbs; idx++) + TopSideBearing[idx] = FontData.ReadFWord(); } } } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } + + int _numMetrics; + int _numTsbs; + } /// @@ -752,7 +835,7 @@ public void Read() /// Both formats of OpenType require a 'maxp' table because a number of applications call the /// Windows GetFontData() API on the 'maxp' table to determine the number of glyphs in the font. /// - class MaximumProfileTable : OpenTypeFontTable + public class MaximumProfileTable : OpenTypeFontTable { public const string Tag = TableTagNames.MaxP; @@ -782,25 +865,25 @@ public void Read() { try { - version = _fontData!.ReadFixed(); - numGlyphs = _fontData.ReadUShort(); - maxPoints = _fontData.ReadUShort(); - maxContours = _fontData.ReadUShort(); - maxCompositePoints = _fontData.ReadUShort(); - maxCompositeContours = _fontData.ReadUShort(); - maxZones = _fontData.ReadUShort(); - maxTwilightPoints = _fontData.ReadUShort(); - maxStorage = _fontData.ReadUShort(); - maxFunctionDefs = _fontData.ReadUShort(); - maxInstructionDefs = _fontData.ReadUShort(); - maxStackElements = _fontData.ReadUShort(); - maxSizeOfInstructions = _fontData.ReadUShort(); - maxComponentElements = _fontData.ReadUShort(); - maxComponentDepth = _fontData.ReadUShort(); + version = FontData!.ReadFixed(); + numGlyphs = FontData.ReadUShort(); + maxPoints = FontData.ReadUShort(); + maxContours = FontData.ReadUShort(); + maxCompositePoints = FontData.ReadUShort(); + maxCompositeContours = FontData.ReadUShort(); + maxZones = FontData.ReadUShort(); + maxTwilightPoints = FontData.ReadUShort(); + maxStorage = FontData.ReadUShort(); + maxFunctionDefs = FontData.ReadUShort(); + maxInstructionDefs = FontData.ReadUShort(); + maxStackElements = FontData.ReadUShort(); + maxSizeOfInstructions = FontData.ReadUShort(); + maxComponentElements = FontData.ReadUShort(); + maxComponentDepth = FontData.ReadUShort(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -820,30 +903,39 @@ public void Read() /// the 'name' table of all fonts include Macintosh platform strings and that the syntax of the Version /// number (name ID 5) follows the guidelines given in this document. /// - class NameTable : OpenTypeFontTable + public class NameTable : OpenTypeFontTable { public const string Tag = TableTagNames.Name; + const int NameArraySize = (int)NameId.MaxValue + 1; + const ushort EnglishUS = 0x0409; /// - /// Get the font family name. + /// Get the OpenType family name. + /// This is the FamilyName in FontFace. /// - public string Name = ""; + public string OTFamilyName = ""; /// - /// Get the font subfamily name. + /// Get the OpenType subfamily name. + /// This is the FaceName in FontFace. /// - public string Style = ""; + public string OTSubfamilyName = ""; /// - /// Get the full font name. + /// Get the OpenType full font name. /// - public string FullFontName = ""; + public string OTFullFontName = ""; + + /// + /// All en-US names of the name table. + /// + public string[] NamesEnUs = new string[NameArraySize]; public ushort format; public ushort count; public ushort stringOffset; - byte[] bytes = default!; + byte[] bytes = null!; public NameTable(OpenTypeFontFace fontData) : base(fontData, Tag) @@ -855,38 +947,48 @@ public void Read() { try { - Debug.Assert(_fontData != null); + Debug.Assert(FontData != null); #if DEBUG || true // TODO_OLD - if (_fontData.Position != DirectoryEntry.Offset) - throw new InvalidOperationException("_fontData!.Position != DirectoryEntry.Offset"); + if (FontData!.Position != DirectoryEntry.Offset) + throw new InvalidOperationException("FontData!.Position != DirectoryEntry.Offset"); - _fontData.Position = DirectoryEntry.Offset; + FontData.Position = DirectoryEntry.Offset; #endif bytes = new byte[DirectoryEntry.PaddedLength]; - Buffer.BlockCopy(_fontData.FontSource.Bytes, DirectoryEntry.Offset, bytes, 0, DirectoryEntry.Length); + Buffer.BlockCopy(FontData.OTFontSource.Bytes, DirectoryEntry.Offset, bytes, 0, DirectoryEntry.Length); + + format = FontData.ReadUShort(); + count = FontData.ReadUShort(); + stringOffset = FontData.ReadUShort(); - format = _fontData.ReadUShort(); - count = _fontData.ReadUShort(); - stringOffset = _fontData.ReadUShort(); + if (format == 1) + { + Debug.Assert(false, "Check this case."); + } for (int idx = 0; idx < count; idx++) { var nrec = ReadNameRecord(); byte[] value = new byte[nrec.length]; - Buffer.BlockCopy(_fontData.FontSource.Bytes, DirectoryEntry.Offset + stringOffset + nrec.offset, value, 0, nrec.length); + Buffer.BlockCopy(FontData.OTFontSource.Bytes, DirectoryEntry.Offset + stringOffset + nrec.offset, value, 0, nrec.length); //De/bug.WriteLine(nrec.platformID.ToString()); // Read font name and style in US English. if (nrec.platformID is 0 or 3) { + if (nrec is { languageID: EnglishUS, nameID: < NameId.MaxValue }) + { + NamesEnUs[nrec.nameID] = Encoding.BigEndianUnicode.GetString(value, 0, value.Length); + } + // Font Family name. Up to four fonts can share the Font Family name, // forming a font style linking group (regular, italic, bold, bold italic - // as defined by OS/2.fsSelection bit settings). - if (nrec.nameID == 1 && nrec.languageID == 0x0409) + if (nrec.nameID == NameId.FamilyName && nrec.languageID == EnglishUS) { - if (String.IsNullOrEmpty(Name)) - Name = Encoding.BigEndianUnicode.GetString(value, 0, value.Length); + if (String.IsNullOrEmpty(OTFamilyName)) + OTFamilyName = Encoding.BigEndianUnicode.GetString(value, 0, value.Length); } // Font Subfamily name. The Font Subfamily name distinguishes the font in a @@ -895,26 +997,26 @@ public void Read() // A font with no particular differences in weight or style (e.g. medium weight, // not italic and fsSelection bit 6 set) should have the string “Regular” stored in // this position. - if (nrec.nameID == 2 && nrec.languageID == 0x0409) + if (nrec.nameID == NameId.SubfamilyName && nrec.languageID == EnglishUS) { - if (String.IsNullOrEmpty(Style)) - Style = Encoding.BigEndianUnicode.GetString(value, 0, value.Length); + if (String.IsNullOrEmpty(OTSubfamilyName)) + OTSubfamilyName = Encoding.BigEndianUnicode.GetString(value, 0, value.Length); } // Full font name; a combination of strings 1 and 2, or a similar human-readable // variant. If string 2 is "Regular", it is sometimes omitted from name ID 4. - if (nrec.nameID == 4 && nrec.languageID == 0x0409) + if (nrec.nameID == NameId.FullName && nrec.languageID == EnglishUS) { - if (String.IsNullOrEmpty(FullFontName)) - FullFontName = Encoding.BigEndianUnicode.GetString(value, 0, value.Length); + if (String.IsNullOrEmpty(OTFullFontName)) + OTFullFontName = Encoding.BigEndianUnicode.GetString(value, 0, value.Length); } } } - Debug.Assert(!String.IsNullOrEmpty(Name)); + Debug.Assert(!String.IsNullOrEmpty(OTFamilyName)); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } @@ -922,12 +1024,12 @@ NameRecord ReadNameRecord() { var nrec = new NameRecord { - platformID = _fontData!.ReadUShort(), - encodingID = _fontData.ReadUShort(), - languageID = _fontData.ReadUShort(), - nameID = _fontData.ReadUShort(), - length = _fontData.ReadUShort(), - offset = _fontData.ReadUShort() + platformID = FontData!.ReadUShort(), + encodingID = FontData.ReadUShort(), + languageID = FontData.ReadUShort(), + nameID = FontData.ReadUShort(), + length = FontData.ReadUShort(), + offset = FontData.ReadUShort() }; return nrec; } @@ -946,16 +1048,16 @@ class NameRecord /// /// The OS/2 table consists of a set of Metrics that are required in OpenType fonts. /// - class OS2Table : OpenTypeFontTable + public class OS2Table : OpenTypeFontTable { public const string Tag = TableTagNames.OS2; - [Flags] - public enum FontSelectionFlags : ushort + public class FontSelectionFlags { - Italic = 1 << 0, - Bold = 1 << 5, - Regular = 1 << 6, + public const ushort Italic = 1 << 0; + public const ushort Bold = 1 << 5; + public const ushort Regular = 1 << 6; + public const ushort Oblique = 1 << 9; } public ushort version; @@ -1008,68 +1110,68 @@ public void Read() { try { - version = _fontData!.ReadUShort(); // NRT - xAvgCharWidth = _fontData.ReadShort(); - usWeightClass = _fontData.ReadUShort(); - usWidthClass = _fontData.ReadUShort(); - fsType = _fontData.ReadUShort(); - ySubscriptXSize = _fontData.ReadShort(); - ySubscriptYSize = _fontData.ReadShort(); - ySubscriptXOffset = _fontData.ReadShort(); - ySubscriptYOffset = _fontData.ReadShort(); - ySuperscriptXSize = _fontData.ReadShort(); - ySuperscriptYSize = _fontData.ReadShort(); - ySuperscriptXOffset = _fontData.ReadShort(); - ySuperscriptYOffset = _fontData.ReadShort(); - yStrikeoutSize = _fontData.ReadShort(); - yStrikeoutPosition = _fontData.ReadShort(); - sFamilyClass = _fontData.ReadShort(); - panose = _fontData.ReadBytes(10); - ulUnicodeRange1 = _fontData.ReadULong(); - ulUnicodeRange2 = _fontData.ReadULong(); - ulUnicodeRange3 = _fontData.ReadULong(); - ulUnicodeRange4 = _fontData.ReadULong(); - achVendID = _fontData.ReadString(4); - fsSelection = _fontData.ReadUShort(); - usFirstCharIndex = _fontData.ReadUShort(); - usLastCharIndex = _fontData.ReadUShort(); - sTypoAscender = _fontData.ReadShort(); - sTypoDescender = _fontData.ReadShort(); - sTypoLineGap = _fontData.ReadShort(); - usWinAscent = _fontData.ReadUShort(); - usWinDescent = _fontData.ReadUShort(); + version = FontData!.ReadUShort(); // NRT + xAvgCharWidth = FontData.ReadShort(); + usWeightClass = FontData.ReadUShort(); + usWidthClass = FontData.ReadUShort(); + fsType = FontData.ReadUShort(); + ySubscriptXSize = FontData.ReadShort(); + ySubscriptYSize = FontData.ReadShort(); + ySubscriptXOffset = FontData.ReadShort(); + ySubscriptYOffset = FontData.ReadShort(); + ySuperscriptXSize = FontData.ReadShort(); + ySuperscriptYSize = FontData.ReadShort(); + ySuperscriptXOffset = FontData.ReadShort(); + ySuperscriptYOffset = FontData.ReadShort(); + yStrikeoutSize = FontData.ReadShort(); + yStrikeoutPosition = FontData.ReadShort(); + sFamilyClass = FontData.ReadShort(); + panose = FontData.ReadBytes(10); + ulUnicodeRange1 = FontData.ReadULong(); + ulUnicodeRange2 = FontData.ReadULong(); + ulUnicodeRange3 = FontData.ReadULong(); + ulUnicodeRange4 = FontData.ReadULong(); + achVendID = FontData.ReadString(4); + fsSelection = FontData.ReadUShort(); + usFirstCharIndex = FontData.ReadUShort(); + usLastCharIndex = FontData.ReadUShort(); + sTypoAscender = FontData.ReadShort(); + sTypoDescender = FontData.ReadShort(); + sTypoLineGap = FontData.ReadShort(); + usWinAscent = FontData.ReadUShort(); + usWinDescent = FontData.ReadUShort(); if (version >= 1) { - ulCodePageRange1 = _fontData.ReadULong(); - ulCodePageRange2 = _fontData.ReadULong(); + ulCodePageRange1 = FontData.ReadULong(); + ulCodePageRange2 = FontData.ReadULong(); if (version >= 2) { - sxHeight = _fontData.ReadShort(); - sCapHeight = _fontData.ReadShort(); - usDefaultChar = _fontData.ReadUShort(); - usBreakChar = _fontData.ReadUShort(); - usMaxContext = _fontData.ReadUShort(); + sxHeight = FontData.ReadShort(); + sCapHeight = FontData.ReadShort(); + usDefaultChar = FontData.ReadUShort(); + usBreakChar = FontData.ReadUShort(); + usMaxContext = FontData.ReadUShort(); } } } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } - public bool IsBold => (fsSelection & (ushort)FontSelectionFlags.Bold) != 0; + public bool IsBold => (fsSelection & FontSelectionFlags.Bold) != 0; - public bool IsItalic => (fsSelection & (ushort)FontSelectionFlags.Italic) != 0; + public bool IsItalic => (fsSelection & FontSelectionFlags.Italic) != 0; } /// /// This table contains additional information needed to use TrueType or OpenType fonts /// on PostScript printers. /// - class PostScriptTable : OpenTypeFontTable + public class PostScriptTable : OpenTypeFontTable { public const string Tag = TableTagNames.Post; @@ -1093,19 +1195,19 @@ public void Read() { try { - formatType = _fontData!.ReadFixed(); // NRT - italicAngle = _fontData.ReadFixed() / 65536f; - underlinePosition = _fontData.ReadFWord(); - underlineThickness = _fontData.ReadFWord(); - isFixedPitch = _fontData.ReadULong(); - minMemType42 = _fontData.ReadULong(); - maxMemType42 = _fontData.ReadULong(); - minMemType1 = _fontData.ReadULong(); - maxMemType1 = _fontData.ReadULong(); + formatType = FontData!.ReadFixed(); // NRT + italicAngle = FontData.ReadFixed() / 65536f; + underlinePosition = FontData.ReadFWord(); + underlineThickness = FontData.ReadFWord(); + isFixedPitch = FontData.ReadULong(); + minMemType42 = FontData.ReadULong(); + maxMemType42 = FontData.ReadULong(); + minMemType1 = FontData.ReadULong(); + maxMemType1 = FontData.ReadULong(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -1115,7 +1217,7 @@ public void Read() /// They can be used, among other things, to control characteristics for different glyphs. /// The length of the table must be an integral number of FWORD units. /// - class ControlValueTable : OpenTypeFontTable + public class ControlValueTable : OpenTypeFontTable { public const string Tag = TableTagNames.Cvt; @@ -1136,11 +1238,11 @@ public void Read() int length = DirectoryEntry.Length / 2; array = new FWord[length]; for (int idx = 0; idx < length; idx++) - array[idx] = _fontData!.ReadFWord(); // NRT + array[idx] = FontData!.ReadFWord(); // NRT } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -1150,7 +1252,7 @@ public void Read() /// It is used only for FDEFs and IDEFs. Thus, the CVT Program need not contain function definitions. /// However, the CVT Program may redefine existing FDEFs or IDEFs. /// - class FontProgram : OpenTypeFontTable + public class FontProgram : OpenTypeFontTable { public const string Tag = TableTagNames.Fpgm; @@ -1171,11 +1273,11 @@ public void Read() int length = DirectoryEntry.Length; bytes = new byte[length]; for (int idx = 0; idx < length; idx++) - bytes[idx] = _fontData!.ReadByte(); + bytes[idx] = FontData!.ReadByte(); } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -1186,7 +1288,7 @@ public void Read() /// CVT Program but since no glyph is associated with it, instructions intended to move points within a particular /// glyph outline cannot be used in the CVT Program. The name 'prep' is anachronistic. /// - class ControlValueProgram : OpenTypeFontTable + public class ControlValueProgram : OpenTypeFontTable { public const string Tag = TableTagNames.Prep; @@ -1207,11 +1309,11 @@ public void Read() int length = DirectoryEntry.Length; bytes = new byte[length]; for (int idx = 0; idx < length; idx++) - bytes[idx] = _fontData!.ReadByte(); // NRT + bytes[idx] = FontData!.ReadByte(); // NRT } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } @@ -1220,7 +1322,7 @@ public void Read() /// This table contains information that describes the glyphs in the font in the TrueType outline format. /// Information regarding the rasterizer (scaler) refers to the TrueType rasterizer. /// - class GlyphSubstitutionTable : OpenTypeFontTable + public class GlyphSubstitutionTable : OpenTypeFontTable { public const string Tag = TableTagNames.GSUB; @@ -1239,7 +1341,7 @@ public void Read() } catch (Exception ex) { - throw new InvalidOperationException(PsMsgs.ErrorReadingFontData, ex); + throw new InvalidOperationException(OtMsgs.ErrorReadingFontData, ex); } } } diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontWriter.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontWriter.cs new file mode 100644 index 00000000..4216d706 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeFontWriter.cs @@ -0,0 +1,169 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Internal.OpenType +{ + /// + /// Represents a writer for True Type font files. + /// + public class OpenTypeFontWriter : FontWriter + { + /// + /// Initializes a new instance of the class. + /// + public OpenTypeFontWriter(Stream stream) + : base(stream) + { } + + /// + /// Writes a table name. + /// + public void WriteTag(string tag) + { + WriteByte((byte)(tag[0])); + WriteByte((byte)(tag[1])); + WriteByte((byte)(tag[2])); + WriteByte((byte)(tag[3])); + } + } + + /// + /// Represents a writer for generation of font file streams. + /// + public class FontWriter + { + /// + /// Initializes a new instance of the class. + /// Data is written in Motorola format (big-endian). + /// + public FontWriter(Stream stream) + { + _stream = stream; + } + + /// + /// Closes the writer and, if specified, the underlying stream. + /// + public void Close(bool closeUnderlyingStream) + { + if (_stream != null! && closeUnderlyingStream) + { + _stream.Close(); + _stream.Dispose(); + } + _stream = null!; + } + + /// + /// Closes the writer and the underlying stream. + /// + public void Close() + => Close(true); + + /// + /// Gets or sets the position within the stream. + /// + public int Position + { + get => (int)_stream.Position; + set => _stream.Position = value; + } + + /// + /// Writes the specified value to the font stream. + /// + public void WriteByte(byte value) + { + _stream.WriteByte(value); + } + + /// + /// Writes the specified value to the font stream. + /// + public void WriteByte(int value) + { + _stream.WriteByte((byte)value); + } + + /// + /// Writes the specified value to the font stream using big-endian. + /// + public void WriteShort(short value) + { + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)value); + } + + /// + /// Writes the specified value to the font stream using big-endian. + /// + public void WriteShort(int value) + { + WriteShort((short)value); + } + + /// + /// Writes the specified value to the font stream using big-endian. + /// + public void WriteUShort(ushort value) + { + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)value); + } + + /// + /// Writes the specified value to the font stream using big-endian. + /// + public void WriteUShort(int value) + { + WriteUShort((ushort)value); + } + + /// + /// Writes the specified value to the font stream using big-endian. + /// + public void WriteInt(int value) + { + _stream.WriteByte((byte)(value >> 24)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)value); + } + + /// + /// Writes the specified value to the font stream using big-endian. + /// + public void WriteUInt(uint value) + { + _stream.WriteByte((byte)(value >> 24)); + _stream.WriteByte((byte)(value >> 16)); + _stream.WriteByte((byte)(value >> 8)); + _stream.WriteByte((byte)value); + } + + //public short ReadFWord() + //public ushort ReadUFWord() + //public long ReadLongDate() + //public string ReadString(int size) + + public void Write(byte[] buffer) + { + _stream.Write(buffer, 0, buffer.Length); + } + + public void Write(byte[] buffer, int offset, int count) + { + _stream.Write(buffer, offset, count); + } + + /// + /// Gets the underlying stream. + /// + internal Stream Stream => _stream; + + Stream _stream; + } + +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlobals.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlobals.cs new file mode 100644 index 00000000..ce2a8608 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlobals.cs @@ -0,0 +1,89 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using Microsoft.Extensions.Logging; +using PdfSharp.Logging; + +namespace PdfSharp.Internal.OpenType +{ + /// + /// The class that holds all OpenType global stuff in a singleton. + /// + public partial class OtGlobals + { + OtGlobals() + { + IncrementVersion(); + } + + public static OtGlobals Global => _global; + + public static void RecreateOtGlobals() + { + _global = new(); + } + + internal void IncrementVersion() => _version = _globalVersionCount++; + + /// + /// Gets the version of this instance. + /// + public int Version => _version; + int _version; + + public readonly OtFontStorage OTFonts = new(); + + /// + /// The global version count gives every new instance of OtGlobals a new unique + /// version number. + /// + static int _globalVersionCount; + + /// + /// The container of all global stuff in PDFsharp.OpenType. + /// By creating a new instance of OtGlobals all caches are reset + /// as if an application starts in a new process. + /// + static OtGlobals _global = new(); + + /// + /// Stores font information from PdfSharp.OpenType assembly. + /// + public partial class OtFontStorage + { + internal OtFontStorage() + { + OpenTypeFontSourceCache = new(this); + OpenTypeFontFamilyCache = new(this); + OpenTypeFontFaceCache = new(this); + OpenTypeGlyphTypefaceCache = new(this); + + // Each instance gets a unique version number. + Version = _openTypeFontStorageVersionCount++; + } + + public readonly OpenTypeFontSourceCache OpenTypeFontSourceCache; + public readonly OpenTypeFontFamilyCache OpenTypeFontFamilyCache; + public readonly OpenTypeFontFaceCache OpenTypeFontFaceCache; + public readonly OpenTypeGlyphTypefaceCache OpenTypeGlyphTypefaceCache; + + public void CheckVersion(int version) + { + if (_openTypeFontStorageVersionCount != version) + { + LogHost.Logger.LogCritical("Your XFont object is outdated because you have reset the font management."); + throw new InvalidOperationException("Old instance of an object is used in a newer instance of Globals."); + } + } + + /// + /// Gets the version of this instance. + /// + public int Version { get; } + } + + static int _openTypeFontStorageVersionCount; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphMetrics.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphMetrics.cs new file mode 100644 index 00000000..0357f026 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphMetrics.cs @@ -0,0 +1,189 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +//using PdfSharp.Drawing; +//using PdfSharp.Fonts; +//using PdfSharp.Internal; +//using PdfSharp.Pdf.Internal; +//using System.Drawing; + +#pragma warning disable IDE0290 + +namespace PdfSharp.Internal.OpenType +{ + public struct OpenTypeGlyphMetrics //: IEquatable + { + public OpenTypeGlyphMetrics( + int leftSideBearing, int advanceWidth, int rightSideBearing, + int topSideBearing, int advanceHeight, int bottomSideBearing, + int verticalOrigin, int distancesFromHorizontalBaselineToBlackBoxBottom, + OpenTypeRect drawBounds) + { + LeftSideBearing = leftSideBearing; + AdvanceWidth = advanceWidth; + RightSideBearing = rightSideBearing; + TopSideBearing = topSideBearing; + AdvanceHeight = advanceHeight; + BottomSideBearing = bottomSideBearing; + VerticalOrigin = verticalOrigin; + DistancesFromHorizontalBaselineToBlackBoxBottom = distancesFromHorizontalBaselineToBlackBoxBottom; + DrawBounds = drawBounds; + } + + public int LeftSideBearing; + public int AdvanceWidth; + public int RightSideBearing; + public int TopSideBearing; + public int AdvanceHeight; + public int BottomSideBearing; + public int VerticalOrigin; + public int DistancesFromHorizontalBaselineToBlackBoxBottom; + public OpenTypeRect DrawBounds; + + //public static bool operator ==(CanvasGlyphMetrics x, CanvasGlyphMetrics y); + //public static bool operator !=(CanvasGlyphMetrics x, CanvasGlyphMetrics y); + //public bool Equals(CanvasGlyphMetrics other); + //public override bool Equals(object obj); + //public override int GetHashCode(); + } + + public struct OpenTypeRect //: IFormattable + { + public OpenTypeRect(int x, int y, int width, int height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + //public Rect(double x, double y, double width, double height); + + //public Rect(Point point1, Point point2); + + //public Rect(Point location, Size size); + + + public int X; + + public int Y; + + public int Width; + + public int Height; + + public int Left => X; + + public int Top => Y; + + public int Right => X + Width; + + public int Bottom => Y + Height; + + //public static Rect Empty { get; } + + //public bool IsEmpty { get; } + + //public bool Contains(Point point); + //public void Intersect(Rect rect); + //public void Union(Rect rect); + //public void Union(Point point); + //public override string ToString(); + //public string ToString(IFormatProvider provider); + //string IFormattable.ToString(string format, IFormatProvider provider); + //public bool Equals(Rect value); + //public static bool operator ==(Rect rect1, Rect rect2); + //public static bool operator !=(Rect rect1, Rect rect2); + //public override bool Equals(object o); + //public override int GetHashCode(); + } + + public struct RenderingGlyphMetrics //: IEquatable + { + public RenderingGlyphMetrics( + float leftSideBearing, float advanceWidth, float rightSideBearing, + float topSideBearing, float advanceHeight, float bottomSideBearing, + float verticalOrigin, float distancesFromHorizontalBaselineToBlackBoxBottom, + RenderingRect drawBounds) + { + LeftSideBearing = leftSideBearing; + AdvanceWidth = advanceWidth; + RightSideBearing = rightSideBearing; + TopSideBearing = topSideBearing; + AdvanceHeight = advanceHeight; + BottomSideBearing = bottomSideBearing; + VerticalOrigin = verticalOrigin; + DistancesFromHorizontalBaselineToBlackBoxBottom = distancesFromHorizontalBaselineToBlackBoxBottom; + DrawBounds = drawBounds; + } + + public float LeftSideBearing; + public float AdvanceWidth; + public float RightSideBearing; + public float TopSideBearing; + public float AdvanceHeight; + public float BottomSideBearing; + public float VerticalOrigin; + public float DistancesFromHorizontalBaselineToBlackBoxBottom; + public RenderingRect DrawBounds; + + //public static bool operator ==(CanvasGlyphMetrics x, CanvasGlyphMetrics y); + //public static bool operator !=(CanvasGlyphMetrics x, CanvasGlyphMetrics y); + //public bool Equals(CanvasGlyphMetrics other); + //public override bool Equals(object obj); + //public override int GetHashCode(); + } + + public struct RenderingRect //: IFormattable + { + public RenderingRect(float x, float y, float width, float height) + { + X = x; + Y = y; + Width = width; + Height = height; + } + + //public Rect(double x, double y, double width, double height); + + //public Rect(Point point1, Point point2); + + //public Rect(Point location, Size size); + + + public float X; + + public float Y; + + public float Width; + + public float Height; + + public float Left => X; + + public float Top => Y; + + public float Right => X + Width; + + public float Bottom => Y + Height; + + //public static Rect Empty { get; } + + //public bool IsEmpty { get; } + + //public bool Contains(Point point); + //public void Intersect(Rect rect); + //public void Union(Rect rect); + //public void Union(Point point); + //public override string ToString(); + //public string ToString(IFormatProvider provider); + //string IFormattable.ToString(string format, IFormatProvider provider); + //public bool Equals(Rect value); + //public static bool operator ==(Rect rect1, Rect rect2); + //public static bool operator !=(Rect rect1, Rect rect2); + //public override bool Equals(object o); + //public override int GetHashCode(); + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphTypeface.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphTypeface.cs new file mode 100644 index 00000000..279c5f69 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphTypeface.cs @@ -0,0 +1,83 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Globalization; +using Microsoft.Extensions.Logging; +using PdfSharp.Internal; +using PdfSharp.Logging; +using Fixed = System.Int32; +//using FWord = System.Int16; +//using UFWord = System.UInt16; + +#pragma warning disable 0649 + +namespace PdfSharp.Internal.OpenType +{ + /// + /// Represents an OpenType glyph typeface. + /// + //[DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + public sealed class OpenTypeGlyphTypeface + { + /// + /// + public OpenTypeGlyphTypeface(OpenTypeFontFace otFontFace, OpenTypeFontDescriptor? otDescriptor = null, + bool isBoldSimulated = false, bool isObliqueSimulated = false, string? key = null) + { + otDescriptor ??= otFontFace.OTDescriptor; + key ??= KeyHelper.CalcGlyphTypefaceKey(otDescriptor.FamilyName, otDescriptor.FaceName, + otDescriptor.OTFontStyle, otDescriptor.OTFontWeight, otDescriptor.OTFontStretch, + isBoldSimulated, isObliqueSimulated); + + Key = key; + OTFontFace = otFontFace; + OTDescriptor = otDescriptor; + IsBoldSimulated = isBoldSimulated; + IsObliqueSimulated = isObliqueSimulated; + } + + public static OpenTypeGlyphTypeface GetOrCreate(OpenTypeFontSource otFontSource, + bool isBoldSimulated, bool isObliqueSimulated) + { + var otFontFace = otFontSource.OTFontFace; + var otDescriptor = otFontFace.OTDescriptor; + + var key = KeyHelper.CalcGlyphTypefaceKey(otDescriptor.FamilyName, otDescriptor.FaceName, + otDescriptor.OTFontStyle, otDescriptor.OTFontWeight, otDescriptor.OTFontStretch, + isBoldSimulated, isObliqueSimulated); + + var openTypeGlyphTypefaceCache = OtGlobals.Global.OTFonts.OpenTypeGlyphTypefaceCache; + if (!openTypeGlyphTypefaceCache.TryGetGlyphTypeface(key, out var otGlyphTypeface)) + { + otGlyphTypeface = new(otFontFace, otFontFace.OTDescriptor, isBoldSimulated, isObliqueSimulated, key); + openTypeGlyphTypefaceCache.TryAddGlyphTypeface(otGlyphTypeface); + } + return otGlyphTypeface; + } + + public OpenTypeGlyphTypeface CreateVariant(bool isBoldSimulated, bool isObliqueSimulated) + { + IsBoldSimulated = true; + var variant = (OpenTypeGlyphTypeface)MemberwiseClone(); + variant.IsBoldSimulated = isBoldSimulated; + variant.IsObliqueSimulated = isObliqueSimulated; + var descr = variant.OTDescriptor; + variant.Key = KeyHelper.CalcGlyphTypefaceKey(descr.FamilyName, descr.FaceName, + descr.OTFontStyle, descr.OTFontWeight, descr.OTFontStretch, + isBoldSimulated, isObliqueSimulated); + return variant; + } + + public string Key { get; private set; } + + public OpenTypeFontFace OTFontFace { get; private set; } + + public OpenTypeFontDescriptor OTDescriptor { get; private set; } + + public bool IsBoldSimulated { get; private set; } + + public bool IsObliqueSimulated { get; private set; } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphTypefaceCache.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphTypefaceCache.cs new file mode 100644 index 00000000..ea2f2fa1 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/OpenTypeGlyphTypefaceCache.cs @@ -0,0 +1,103 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Collections.Concurrent; +using System.Globalization; +using PdfSharp.Internal.Threading; + +namespace PdfSharp.Internal.OpenType +{ + /// + /// Global table of all OpenType glyph typefaces cached by their face name and check sum. + /// + [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + public class OpenTypeGlyphTypefaceCache + { + internal OpenTypeGlyphTypefaceCache(OtGlobals.OtFontStorage storage) + { + _storage = storage; + } + + /// + /// Tries to get an OpenTypeGlyphTypeface by its typeface key. + /// + public bool TryGetGlyphTypeface(string key, + [MaybeNullWhen(false)] + out OpenTypeGlyphTypeface otGlyphTypeface) + { + try + { + Locks.EnterFontManagement(); + var result = _storage.GlyphTypefaceByKey.TryGetValue(key, out otGlyphTypeface); + return result; + } + finally { Locks.ExitFontManagement(); } + } + + public bool TryAddGlyphTypeface(OpenTypeGlyphTypeface otGlyphTypeface) + { + try + { + Locks.EnterFontManagement(); + + return _storage.GlyphTypefaceByKey.TryAdd(otGlyphTypeface.Key, otGlyphTypeface); + } + finally { Locks.ExitFontManagement(); } + } + + public static void Reset_todo() + { + try + { + var fontStorage = OtGlobals.Global.OTFonts; + + Locks.EnterFontManagement(); + fontStorage.FontFaceCache.Clear(); + fontStorage.FontFacesByCheckSum.Clear(); + } + finally { Locks.ExitFontManagement(); } + } + + readonly OtGlobals.OtFontStorage _storage; + + //public static string GetCacheState() + //{ + // StringBuilder state = new StringBuilder(); + // state.Append("====================\n"); + // state.Append("OpenType font faces by name\n"); + // var familyKeys = OpenTypeGlobals.Global.OTFonts.FontFaceCache.Keys; + // int count = familyKeys.Count; + // string[] keys = new string[count]; + // familyKeys.CopyTo(keys, 0); + // Array.Sort(keys, StringComparer.OrdinalIgnoreCase); + // foreach (string key in keys) + // state.AppendFormat(" {0}: {1}\n", key, OpenTypeGlobals.Global.OTFonts.FontFaceCache[key].DebuggerDisplay); + // state.Append("\n"); + // return state.ToString(); + //} + + /// + /// Gets the DebuggerDisplayAttribute text. + /// + // ReSharper disable UnusedMember.Local + static string DebuggerDisplay + // ReSharper restore UnusedMember.Local + => String.Format(CultureInfo.InvariantCulture, "Font faces: {0}", OtGlobals.Global.OTFonts.FontFaceCache.Count); + } +} + +namespace PdfSharp.Internal.OpenType +{ + partial class OtGlobals + { + partial class OtFontStorage + { + /// + /// Maps typeface key to OpenType glyph typeface. + /// + public readonly ConcurrentDictionary GlyphTypefaceByKey = []; + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/PdfSharp.OpenType.csproj b/src/foundation/src/shared/src/PdfSharp.OpenType/PdfSharp.OpenType.csproj new file mode 100644 index 00000000..88b84c24 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/PdfSharp.OpenType.csproj @@ -0,0 +1,21 @@ + + + + $(PDFsharpTargetFrameworks_Library) + PdfSharp.Internal.OpenType + True + ..\..\..\..\..\StrongnameKey.snk + + + + true + + + + + + + + + + diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/Properties/GlobalDeclarations.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/Properties/GlobalDeclarations.cs new file mode 100644 index 00000000..0bd4f110 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/Properties/GlobalDeclarations.cs @@ -0,0 +1,15 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +global using System.Diagnostics; +global using System.Diagnostics.CodeAnalysis; +global using static System.FormattableString; +#if !NET8_0_OR_GREATER +global using PdfSharp.DotNetFrameworkExtensions; +#endif + +//#if USE_LONG_SIZE +//global using SizeType = System.Int64; +//#else +//global using SizeType = System.Int32; +//#endif diff --git a/src/foundation/src/shared/src/PdfSharp.OpenType/README.md b/src/foundation/src/shared/src/PdfSharp.OpenType/README.md new file mode 100644 index 00000000..016b820b --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/README.md @@ -0,0 +1,7 @@ +# PDFsharp.OpenType + +This is the PDFsharp assembly that handles OpenType fonts. +It it used by both XGraphics and the new PDFsharp Graphics. + +The API is public, but in the **PdfSharp.Internal.OpenType** namespace. +It is not intended to be used from PDFsharp customers and may be changed without notice. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/TableDirectoryEntry.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/TableDirectoryEntry.cs similarity index 92% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/TableDirectoryEntry.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/TableDirectoryEntry.cs index 6ffe306f..b1ae8c52 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/TableDirectoryEntry.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/TableDirectoryEntry.cs @@ -1,12 +1,14 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Fonts.OpenType +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Internal.OpenType { /// /// Represents an entry in the fonts table dictionary. /// - class TableDirectoryEntry + public class TableDirectoryEntry { /// /// Initializes a new instance of the class. @@ -24,7 +26,7 @@ public TableDirectoryEntry(string tag) } /// - /// 4 -byte identifier. + /// 4-byte identifier. /// public string Tag = null!; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/IndexToLocationTable.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/Tables/IndexToLocationTable.cs similarity index 84% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/IndexToLocationTable.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/Tables/IndexToLocationTable.cs index af5a130b..e2c681de 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/IndexToLocationTable.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/Tables/IndexToLocationTable.cs @@ -1,20 +1,22 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + #define VERBOSE_ //using Fixed = System.Int32; //using FWord = System.Int16; //using UFWord = System.UInt16; -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { /// /// The indexToLoc table stores the offsets to the locations of the glyphs in the font, /// relative to the beginning of the glyphData table. In order to compute the length of /// the last glyph element, there is an extra entry after the last valid index. /// - class IndexToLocationTable : OpenTypeFontTable + public class IndexToLocationTable : OpenTypeFontTable { public const string Tag = TableTagNames.Loca; @@ -29,7 +31,7 @@ public IndexToLocationTable() public IndexToLocationTable(OpenTypeFontFace fontData) : base(fontData, Tag) { - DirectoryEntry = _fontData!.TableDictionary[TableTagNames.Loca]; + DirectoryEntry = FontData!.TableDictionary[TableTagNames.Loca]; Read(); } @@ -42,25 +44,25 @@ public void Read() { try { - ShortIndex = _fontData!.head!.indexToLocFormat == 0; - _fontData.Position = DirectoryEntry.Offset; + ShortIndex = FontData!.head!.indexToLocFormat == 0; + FontData.Position = DirectoryEntry.Offset; if (ShortIndex) { int entries = DirectoryEntry.Length / 2; - Debug.Assert(_fontData.maxp.numGlyphs + 1 == entries, + Debug.Assert(FontData.maxp.numGlyphs + 1 == entries, "For your information only: Number of glyphs mismatch in font. You can ignore this assertion."); LocaTable = new int[entries]; for (int idx = 0; idx < entries; idx++) - LocaTable[idx] = 2 * _fontData.ReadUFWord(); + LocaTable[idx] = 2 * FontData.ReadUFWord(); } else { int entries = DirectoryEntry.Length / 4; - Debug.Assert(_fontData.maxp.numGlyphs + 1 == entries, + Debug.Assert(FontData.maxp.numGlyphs + 1 == entries, "For your information only: Number of glyphs mismatch in font. You can ignore this assertion."); LocaTable = new int[entries]; for (int idx = 0; idx < entries; idx++) - LocaTable[idx] = _fontData.ReadLong(); + LocaTable[idx] = FontData.ReadLong(); } } catch (Exception) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/UnicodeHelper.cs similarity index 83% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/UnicodeHelper.cs index c0ea21a6..adb239b7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.Internal/UnicodeHelper.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/UnicodeHelper.cs @@ -1,25 +1,25 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + using Microsoft.Extensions.Logging; -using PdfSharp.Drawing; -using PdfSharp.Fonts.OpenType; using PdfSharp.Logging; -using PdfSharp.Pdf.Internal; +using PdfSharp.Internal; -namespace PdfSharp.Fonts.Internal +namespace PdfSharp.Internal.OpenType { /// /// A bunch of internal functions to handle Unicode. /// - static class UnicodeHelper + public static class UnicodeHelper { - internal const char HighSurrogateStart = '\uD800'; - internal const char HighSurrogateEnd = '\uDBFF'; - internal const char LowSurrogateStart = '\uDC00'; - internal const char LowSurrogateEnd = '\uDFFF'; - internal const int HighSurrogateRange = 0x3FF; - internal const int UnicodePlane01Start = 0x1_0000; + public const char HighSurrogateStart = '\uD800'; + public const char HighSurrogateEnd = '\uDBFF'; + public const char LowSurrogateStart = '\uDC00'; + public const char LowSurrogateEnd = '\uDFFF'; + public const int HighSurrogateRange = 0x3FF; + public const int UnicodePlane01Start = 0x1_0000; /// /// Converts a UTF-16 string into an array of Unicode code points. @@ -61,7 +61,7 @@ public static int[] Utf32FromString(string s, bool coerceAnsi = false, byte nonA else { // Case: Unpaired high surrogate. - PdfSharpLogHost.FontManagementLogger.LogDebug("High surrogate 0x{Char:X2} not followed by a low surrogate.", ch); + LogHost.Logger./*FontManagementLogger.*/LogDebug("High surrogate 0x{Char:X2} not followed by a low surrogate.", ch); idx--; continue; } @@ -69,7 +69,7 @@ public static int[] Utf32FromString(string s, bool coerceAnsi = false, byte nonA else { // Case: High surrogate at string end. - PdfSharpLogHost.FontManagementLogger.LogDebug("High surrogate 0x{Char:X2} found at end of string.", ch); + LogHost.Logger./*FontManagementLogger.*/LogDebug("High surrogate 0x{Char:X2} found at end of string.", ch); break; } } @@ -83,7 +83,7 @@ public static int[] Utf32FromString(string s, bool coerceAnsi = false, byte nonA // Case: unpaired low surrogate. // We only come here when the text contains a low surrogate not preceded by a high surrogate. // This is an error in the UTF-32 text. - PdfSharpLogHost.FontManagementLogger.LogDebug("Unexpected low surrogate found: 0x{Char:X2}", ch); + LogHost.Logger./*FontManagementLogger.*/LogDebug("Unexpected low surrogate found: 0x{Char:X2}", ch); continue; } @@ -99,7 +99,7 @@ public static int[] Utf32FromString(string s, bool coerceAnsi = false, byte nonA /// /// Converts a UTF-16 string into an array of code points of a symbol font. /// - public static int[] SymbolCodePointsFromString(string s, OpenTypeDescriptor openTypeDescriptor) + public static int[] SymbolCodePointsFromString(string s, OpenTypeFontDescriptor openTypeDescriptor) { if (String.IsNullOrEmpty(s)) return []; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/enums/FontTechnology.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/enums/FontTechnology.cs similarity index 94% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/enums/FontTechnology.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/enums/FontTechnology.cs index 051d5687..c1e74584 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/enums/FontTechnology.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/enums/FontTechnology.cs @@ -1,7 +1,7 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { /// /// Identifies the technology of an OpenType font file. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/enums/TableTagNames.cs b/src/foundation/src/shared/src/PdfSharp.OpenType/enums/TableTagNames.cs similarity index 99% rename from src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/enums/TableTagNames.cs rename to src/foundation/src/shared/src/PdfSharp.OpenType/enums/TableTagNames.cs index 6313cfda..1a2ccd69 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Fonts.OpenType/enums/TableTagNames.cs +++ b/src/foundation/src/shared/src/PdfSharp.OpenType/enums/TableTagNames.cs @@ -3,7 +3,7 @@ // ReSharper disable InconsistentNaming -namespace PdfSharp.Fonts.OpenType +namespace PdfSharp.Internal.OpenType { /// /// Case-sensitive TrueType font table names. diff --git a/src/foundation/src/shared/src/PdfSharp.Quality-gdi/PdfSharp.Quality-gdi.csproj b/src/foundation/src/shared/src/PdfSharp.Quality-gdi/PdfSharp.Quality-gdi.csproj index 3a0fa119..02273f24 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality-gdi/PdfSharp.Quality-gdi.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Quality-gdi/PdfSharp.Quality-gdi.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp.Quality true @@ -31,16 +31,19 @@ - + - - + + + + + @@ -50,4 +53,8 @@ + + + + diff --git a/src/foundation/src/shared/src/PdfSharp.Quality-wpf/PdfSharp.Quality-wpf.csproj b/src/foundation/src/shared/src/PdfSharp.Quality-wpf/PdfSharp.Quality-wpf.csproj index 102c063a..39794018 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality-wpf/PdfSharp.Quality-wpf.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Quality-wpf/PdfSharp.Quality-wpf.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp.Quality true @@ -30,16 +30,19 @@ - + - - + + + + + diff --git a/src/foundation/src/shared/src/PdfSharp.Quality-wpf/Properties/Settings.Designer.cs b/src/foundation/src/shared/src/PdfSharp.Quality-wpf/Properties/Settings.Designer.cs index 9c622de1..522c554d 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality-wpf/Properties/Settings.Designer.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality-wpf/Properties/Settings.Designer.cs @@ -12,10 +12,10 @@ namespace PdfSharp.Quality.Properties { [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] - [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.8.0.0")] + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("Microsoft.VisualStudio.Editors.SettingsDesigner.SettingsSingleFileGenerator", "17.14.0.0")] internal sealed partial class Settings : global::System.Configuration.ApplicationSettingsBase { - static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); + private static Settings defaultInstance = ((Settings)(global::System.Configuration.ApplicationSettingsBase.Synchronized(new Settings()))); public static Settings Default { get { diff --git a/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/PdfSharp.Quality.Ghostscript.csproj b/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/PdfSharp.Quality.Ghostscript.csproj new file mode 100644 index 00000000..bd832113 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/PdfSharp.Quality.Ghostscript.csproj @@ -0,0 +1,31 @@ + + + + Library + $(PDFsharpTargetFrameworks_Library) + CORE + True + ..\..\..\..\..\StrongnameKey.snk + + + + true + + + + 0 + + + + 0 + + + + + + + + + diff --git a/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/PdfToPngConverter.cs b/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/PdfToPngConverter.cs new file mode 100644 index 00000000..b98203aa --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/PdfToPngConverter.cs @@ -0,0 +1,195 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Diagnostics; +using System.Runtime.InteropServices; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Quality.Ghostscript +{ + /// + /// + public class PdfToPngConverter + { + // Add support for Linux when needed. + // Add more options when needed. + +#if false + // Sample code: + + var pdf = @"D:\PDFsharp\Specs\ISO_32000-2_2020(en).pdf"; + var png = @"D:\page34.png"; + var png2 = @"D:\page%d.png"; + var png3 = @"D:\page%04d.png"; + + var pdfConvert = new PdfToPngConverter(pdf, 150, true); + pdfConvert.ConvertPages(png, 33); + pdfConvert.ConvertPages(png2, 34, 3); + pdfConvert.ConvertPages(png3, 37, 3); +#endif + + /// + /// Initializes a new instance of the class. + /// + /// The PDF file to be rendered. + /// The DPI setting of the generated image files. + /// The font folder used to supply TTF files for the conversion. + public PdfToPngConverter(string pdf, int dpi, string? fontFolder = null) + { + Pdf = pdf; + Dpi = dpi; + FontFolder = fontFolder; + } + + /// + /// Initializes a new instance of the class. + /// + /// The PDF file to be rendered. + /// The DPI setting of the generated image files. + /// If set to true the Windows fonts folder will be + /// used to supply TTF fonts if running under Windows. If false or not running under Windows, + /// no font folder will be used. + public PdfToPngConverter(string pdf, int dpi, bool useWindowsFontFolder) + { + Pdf = pdf; + Dpi = dpi; + if (useWindowsFontFolder && RuntimeInformation.IsOSPlatform(OSPlatform.Windows)) + { + FontFolder = Environment.ExpandEnvironmentVariables("%windir%") + "/fonts"; + } + } + + public bool IsInstalled() + { + var toolExe = FindTool(false); + return !String.IsNullOrEmpty(toolExe); + } + + /// + /// Sets the timeout in seconds. + /// + /// Timeout must be in the range 1 to {Int32.MaxValue / 1000} seconds. + public void SetTimeout(int timeout) + { + if (timeout is < 1 or > Int32.MaxValue / 1000) + throw new ArgumentOutOfRangeException($"Timeout must be in the range 1 to {Int32.MaxValue / 1000} seconds."); + Timeout = timeout; + } + + /// + /// Converts a range of pages. If more than 1 page is to be converted, include a placeholder + /// in the filename. + /// "%d" is replaced by the page number without leading zeros. + /// "%3d" is replaced by the page number padded with leading blanks to always get 3 characters. + /// "%03d" is replaced by the page number padded with leading zeros to always get 3 digits. + /// Note that automatic page numbering always begins at 1. When rendering pages 10 through 12, + /// the files will be numbered 1, 2, and 3. + /// + /// Name of the output file. + /// The index of the start page, zero-based. + /// The page count. + /// Processing failed with exit code {result.ExitCode}.\n" + + /// $"Output: {result.Output}\nError: {result.Error} + /// Processing fails if conversion tool is + /// not installed + public void ConvertPages(string outputFileName, int startPage = 0, int pageCount = 1) + { + var result = PdfPagesToPng(Pdf, outputFileName, startPage + 1, pageCount, Dpi, FontFolder, Timeout); + if (result.ExitCode != 0) + { + throw new Exception($"Processing failed with exit code {result.ExitCode}.\n" + + $"Output: {result.Output}\nError: {result.Error}"); + } + } + + static ProcessResult PdfPagesToPng(string pdf, string outputFile, int page, int pageCount, int dpi, string? fontFolder, int timeout) + { + var toolExe = FindTool(); + + // Optional parameters. + // -sFONTPATH: Path to folder with TTF files. + var options = String.IsNullOrEmpty(fontFolder) ? + "" : + $"-sFONTPATH={fontFolder}"; + + // List of pages to be printed. We support a single page or a single range. + var pages = pageCount > 1 ? + $"{page}-{page + pageCount - 1}" : + page.ToString(); + + var si = new ProcessStartInfo(toolExe); + si.Arguments = $"-dNOPAUSE -dBATCH -r{dpi} -sDEVICE=png16m -sOutputFile=\"{outputFile}\" -sPageList={pages} {options} \"{pdf}\""; + si.UseShellExecute = false; + si.CreateNoWindow = true; + si.RedirectStandardError = true; + si.RedirectStandardOutput = true; + var proc = Process.Start(si); + if (proc is not null) + { + proc.WaitForExit(timeout * 1000); // Use Async when we encounter problems. + var result = new ProcessResult(proc); + return result; + } + + return default; + } + + private static string FindTool(bool throwOnError = true) + { +#if true + var toolFolder = IOUtility.GetAssetsPath("tools/pdf-to-png"); + if (toolFolder == null) + throw new InvalidOperationException("Copy tools to asset folder \"tools/pdf-to-png\". See README.md for further information."); + var toolExe = toolFolder + "/gswin64c.exe"; // Linux: different application name. +#else + var toolFolder = AssetsHelper.GetAssetsFolder(); + if (toolFolder == null) + throw new InvalidOperationException("Copy tools to asset folder."); + toolFolder = Path.Combine(toolFolder, "tools\\pdf-to-png"); + var toolExe = toolFolder + "\\gswin64c.exe"; // Linux: different application name. +#endif + if (!File.Exists(toolExe)) + { + if (throwOnError) + throw new InvalidOperationException("Copy tools to asset folder."); + return null!; + } + + return toolExe; + } + + struct ProcessResult + { + internal ProcessResult(Process process) + { + ExitCode = process.ExitCode; + Output = process.StandardOutput.ReadToEnd(); + Error = process.StandardError.ReadToEnd(); + } + + public int ExitCode; + public string Output; + public string Error; + } + /// + /// Gets the PDF file name. + /// + public string Pdf { get; } + + /// + /// Gets the DPI. + /// + public int Dpi { get; } + + /// + /// Gets the timeout for conversion processes in seconds. + /// + public int Timeout { get; private set; } = 30; + + /// + /// Gets the font folder. + /// + public string? FontFolder { get; } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/README.md b/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/README.md new file mode 100644 index 00000000..9e4189f5 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality.Ghostscript/README.md @@ -0,0 +1,22 @@ +# PdfSharp.Quality.Ghostscript + +This is the PDFsharp.Quality.Ghostscript project. +It provides functionality by using Ghostscript from Artifex Software Inc. +Mainly to convert PDF files into PNG images. + +--- +**NOTE** + +We use Ghostscript only for testing and quality control of PDFsharp during development. +PDFsharp neither uses nor is based on Ghostscript. + +--- +**Providing the required tools** + +The following files should be copied to the `assets/tools/pdf-to-png` folder: + +* gsdll64.dll +* gsdll64.lib +* gswin64.exe +* gswin64c.exe + diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/AssetsHelper.cs b/src/foundation/src/shared/src/PdfSharp.Quality/AssetsHelper.cs index 681c1ee4..fcf41fba 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/AssetsHelper.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/AssetsHelper.cs @@ -27,6 +27,9 @@ public static class AssetsHelper var dirInfo = Directory.GetParent(path); if (dirInfo == null) break; + + // Is an endless loop possible? Yes. + path = dirInfo.FullName; // Update path to prevent endless loop. } return null; } diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/CompareHelper.cs b/src/foundation/src/shared/src/PdfSharp.Quality/CompareHelper.cs new file mode 100644 index 00000000..b8f77d61 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality/CompareHelper.cs @@ -0,0 +1,41 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using Microsoft.Extensions.Logging; +using PdfSharp.Drawing; +using PdfSharp.Logging; +using PdfSharp.Pdf; +using PdfSharp.Pdf.IO; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Quality +{ + /// + /// + public static class CompareHelper + { + public static void RenderA4Page(PdfPage page, CompareOptions left, CompareOptions right) + { + page.Size = PageSize.A4; + page.Orientation = PageOrientation.Landscape; + + var leftImage = XImage.FromFile(left.Filename); + var rightImage = XImage.FromFile(right.Filename); + + var gfx = XGraphics.FromPdfPage(page); + var rcLeft = new XRect(30, 30, 360, 480); + gfx.DrawImage(leftImage, rcLeft); + gfx.DrawRectangle(XPens.LightGray, rcLeft); + + var rcRight = new XRect(451, 30, 360, 480); + gfx.DrawImage(rightImage, rcRight); + gfx.DrawRectangle(XPens.LightGray, rcRight); + } + } + + public class CompareOptions + { + public string Filename { get; set; } = ""; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/FeatureAndSnippetBase.cs b/src/foundation/src/shared/src/PdfSharp.Quality/FeatureAndSnippetBase.cs index 8e8c7b09..d8348f51 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/FeatureAndSnippetBase.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/FeatureAndSnippetBase.cs @@ -318,7 +318,7 @@ void DrawImageToBox(XGraphics gfx, XRect box) return; } - var stream = new MemoryStream(PngBytes); + var stream = new MemoryStream(PngBytes, 0, PngBytes.Length, false, true); var image = XImage.FromStream(stream); image.Interpolate = false; gfx.DrawImage(image, box); @@ -432,8 +432,8 @@ public void SaveStreamToFile(Stream stream, string path) { int length = (int)stream.Length; var bytes = new byte[length]; - _ = stream.Read(bytes, 0, length); - + var read = stream.Read(bytes, 0, length); + Debug.Assert(read == length); using var fs = new FileStream(path, FileMode.Create); fs.Write(bytes, 0, length); } diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/FontHelper.cs b/src/foundation/src/shared/src/PdfSharp.Quality/FontHelper.cs index 3765baff..74099b89 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/FontHelper.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/FontHelper.cs @@ -60,7 +60,9 @@ public static byte[] LoadFontDataPack(Uri uri) { int count = (int)stream.Length; byte[] data = new byte[count]; - _ = stream.Read(data, 0, count); + var read = stream.Read(data, 0, count); + if (read != count) + throw new InvalidOperationException($"Reading the font failed. Stream has {count} bytes, but only {read} bytes were returned."); return data; } } diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/IOUtility.cs b/src/foundation/src/shared/src/PdfSharp.Quality/IOUtility.cs index 33a0ce0e..57c29891 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/IOUtility.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/IOUtility.cs @@ -9,6 +9,7 @@ #endif using System.Reflection; using System.Runtime.CompilerServices; +using System.Text; using PdfSharp.Internal; namespace PdfSharp.Quality @@ -17,6 +18,7 @@ namespace PdfSharp.Quality /// Static utility functions for file IO. /// These functions are intended for unit tests and samples in solution code only. /// + // ReSharper disable once InconsistentNaming public static class IOUtility { internal const char DirectorySeparatorChar = '\\'; @@ -28,17 +30,15 @@ public static class IOUtility /// True if the given character is a directory separator. /// [MethodImpl(MethodImplOptions.AggressiveInlining)] - public static bool IsDirectorySeparator(char ch) => ch is DirectorySeparatorChar or AltDirectorySeparatorChar; + public static bool IsDirectorySeparator(char ch) + => ch is DirectorySeparatorChar or AltDirectorySeparatorChar; /// /// Replaces all back-slashes with forward slashes in the specified path. - /// The resulting path works under Windows and Linux if no drive names are - /// included. + /// The resulting path works under Windows and Linux if no drive names are included. /// - public static void NormalizeDirectorySeparators(ref string? path) - { - path = path?.Replace(DirectorySeparatorChar, AltDirectorySeparatorChar); - } + public static void NormalizeDirectorySeparators(ref string path) + => path = path?.Replace(DirectorySeparatorChar, AltDirectorySeparatorChar) ?? null!; /// /// Gets the root path of the current solution, or null, if no parent @@ -66,7 +66,53 @@ public static void NormalizeDirectorySeparators(ref string? path) } static string? _solutionRoot; - // R eShar per disable once GrammarMistakeInComment because assets is plural + /// + /// By searching from the startDirectory to the root + /// gets the full path of the first directory above the given directory that contains + /// the specified file, or null, if no such directory exists. + /// If no startDirectory is specified the parent directory of the current directory is taken. + /// + /// Name of the file to search for. + /// The directory the search starts. + public static string? GetPathOfFileAbove(string fileName, string? startDirectory = null) + { + var directory = startDirectory; + directory ??= Directory.GetParent(Directory.GetCurrentDirectory())?.FullName ?? null; + + while (directory != null) + { + var trial = Path.Combine(directory, fileName); + if (File.Exists(trial)) + return directory; // The path to the directory that contains fileName. + directory = Directory.GetParent(directory)?.FullName; + } + return null; + } + + /// + /// By searching from the startDirectory to the root + /// gets the full path of the first directory above the given directory that matches + /// the specified name, or null, if no such directory exists. + /// If no startDirectory is specified the parent directory of the current directory is taken. + /// + /// Name of the directory to search for + /// The directory the search starts. + /// + public static string? GetPathOfDirectoryAbove(string directoryName, string? startDirectory = null) + { + var directory = startDirectory; + directory ??= Directory.GetParent(Directory.GetCurrentDirectory())?.FullName ?? null; + + while (directory != null) + { + var trial = Path.Combine(directory, directoryName); + if (Directory.Exists(trial)) + return trial; // The path to the directory directoryName. + directory = Directory.GetParent(directory)?.FullName; + } + return null; + } + /// /// Gets the root path of the current assets directory if no parameter is specified, /// or null, if no assets directory exists in the solution root directory. @@ -100,11 +146,10 @@ public static void NormalizeDirectorySeparators(ref string? path) /// The directory is created if it does not exist. /// If a valid path is returned it always ends with the current directory separator. /// - public static string? GetTempPath(string? relativeDirectoryInTemp = null) + public static string GetTempPath(string? relativeDirectoryInTemp = null) { if (_tempPath is null) { - // ??? // '/' is important to work correctly under Linux. _tempPath = GetAssetsPath() + @"temp" + Path.DirectorySeparatorChar; if (!Directory.Exists(_tempPath)) Directory.CreateDirectory(_tempPath); @@ -203,7 +248,18 @@ public static string GetTempFullFileName(string? namePrefix, string? extension, int length = namePrefix?.Length ?? 0; if (length > 0 && namePrefix != null) // Interesting: System cannot deduce that namePrefix cannot be null if length is greater than 0. { - int idx = length - 1; + var sb = new StringBuilder(); + int idx = 0; + for (; idx < length; idx++) + { + var ch = namePrefix[idx]; + if (ch is <= (char)31 or '\"' or '<' or '>' or '|' or ':' or '*' or '?' /*or '\\' or '/'*/) + ch = '_'; + sb.Append(ch); + } + namePrefix = sb.ToString(); + + idx = length - 1; while (idx >= 0 && !IsDirectorySeparator(namePrefix[idx])) idx--; @@ -213,7 +269,7 @@ public static string GetTempFullFileName(string? namePrefix, string? extension, namePart = namePrefix[(idx + 1)..]; } - var tempPath = GetTempPath() ?? throw new IOException("Cannot localize temp directory. Your current directory may not be part of a solution."); + var tempPath = GetTempPath() ?? throw new InvalidOperationException("Cannot localize temp directory. Your current directory may not be part of a solution."); if (pathPart != null) { @@ -235,13 +291,14 @@ public static string GetTempFullFileName(string? namePrefix, string? extension, } catch { - // Prevent endless loop in case of access violation. + // Prevent endless loop in case of a general access violation. // Try sometimes is easier than tearing apart the seven possible exception types. if (retryCount-- > 0) goto Retry; throw; } - return tempFile; + NormalizeDirectorySeparators(ref tempFile); + return tempFile!; // Cannot be null here. } /// @@ -259,11 +316,11 @@ public static string GetTempFullFileName(string? namePrefix, string? extension, ? $"*_temp.{extension}" : $"{namePrefix}*_temp.{extension}"; - (String? FileName, DateTime? LastWrite) result = default; + (String? FileName, DateTimeOffset? LastWrite) result = default; FindFile(searchPattern, ref result, path, recursive); return result.FileName; - static void FindFile(string searchPattern, ref (string? FileName, DateTime? LastWrite) result, + static void FindFile(string searchPattern, ref (string? FileName, DateTimeOffset? LastWrite) result, string directory, bool recursive) { var files = Directory.GetFiles(directory, searchPattern); @@ -326,14 +383,14 @@ public static void EnsureAssets(string? relativeFileOrDirectory = null, int? req pathType = "directory"; if (Directory.Exists(relativeFileOrDirectory)) return; - throw new IOException( + throw new InvalidOperationException( $"The {pathType} '{relativeFileOrDirectory}' does not exist in the assets folder. " + AssetsInfo); } else if (File.Exists(relativeFileOrDirectory)) return; - throw new IOException( + throw new InvalidOperationException( $"The {pathType} '{relativeFileOrDirectory}' does not exist in the assets folder. " + AssetsInfo); } @@ -342,11 +399,11 @@ public static void EnsureAssets(string? relativeFileOrDirectory = null, int? req var files = Directory.GetDirectories(assetsPath); if (files.Length > 0 && File.Exists(assetsPath + AssetsVersionFileName)) return; - throw new IOException("The assets folder is not yet downloaded. " + AssetsInfo); + throw new InvalidOperationException("The assets folder is not yet downloaded. " + AssetsInfo); } } - throw new IOException(@"The assets folder does not exist. " + AssetsInfo); + throw new InvalidOperationException(@"The assets folder does not exist. " + AssetsInfo); } /// @@ -366,16 +423,16 @@ public static void EnsureAssetsVersion(int requiredAssetsVersion) { if (assetsVersion >= requiredAssetsVersion) return; - throw new IOException( + throw new InvalidOperationException( Invariant($"The required assets version is {requiredAssetsVersion}, but the current version is just {assetsVersion}. ") + AssetsInfo); } } - throw new IOException($"The assets version file '{AssetsVersionFileName}' does not exist in the assets folder. " + AssetsInfo); + throw new InvalidOperationException($"The assets version file '{AssetsVersionFileName}' does not exist in the assets folder. " + AssetsInfo); } - throw new IOException(@"The assets folder does not exist. " + AssetsInfo); + throw new InvalidOperationException(@"The assets folder does not exist. " + AssetsInfo); } /// @@ -405,34 +462,8 @@ public static string GetCachedWebFile(string assetsFilename, string url) inputStream.CopyTo(fs); fs.Flush(); } - return fullAssetsFilename; } - #endregion - -#if true_ // Not needed anymore. - // Path.Join does not exist in .NET Standard 2.0 - static string Join(string path1, string path2) - { - if (path1.Length == 0) - return path2.ToString(); - if (path2.Length == 0) - return path1.ToString(); - - return JoinInternal(path1, path2); - - static string JoinInternal(string first, string second) - { - Debug.Assert(first.Length > 0 && second.Length > 0, "Should have dealt with empty paths."); - - bool hasSeparator = IsDirectorySeparator(first[^1]) || IsDirectorySeparator(second[0]); - - return hasSeparator - ? String.Concat(first, second) - : String.Concat(first, Path.DirectorySeparatorChar.ToString(), second); - } - } -#endif } } diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/PdfDocUtility.cs b/src/foundation/src/shared/src/PdfSharp.Quality/PdfDocUtility.cs index 66003af7..ba150b14 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/PdfDocUtility.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/PdfDocUtility.cs @@ -5,6 +5,7 @@ using System.IO; #endif using System.Reflection; +using System.Runtime.InteropServices; using PdfSharp.Pdf; namespace PdfSharp.Quality @@ -21,7 +22,7 @@ public class DocTag /// The title. /// public string Title { get; set; } = ""; - + // PageSize, Orientation, ... } diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/PdfFileUtility.cs b/src/foundation/src/shared/src/PdfSharp.Quality/PdfFileUtility.cs index 5c109882..c5ed0328 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/PdfFileUtility.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/PdfFileUtility.cs @@ -6,6 +6,8 @@ #endif using PdfSharp.Pdf; +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + namespace PdfSharp.Quality { /// @@ -14,6 +16,11 @@ namespace PdfSharp.Quality /// public static class PdfFileUtility { + public static string GetUnitTestPath(Type type) + { + return $"unit-tests/{type.Namespace}/{type.Name}/"; + } + /// /// Creates a temporary name of a PDF file with the pattern '{namePrefix}-{WIN|WSL|LNX|...}-{...uuid...}_temp.pdf'. /// The name ends with '_temp.pdf' to make it easy to delete it using the pattern '*_temp.pdf'. diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/PdfPngComparer.cs b/src/foundation/src/shared/src/PdfSharp.Quality/PdfPngComparer.cs new file mode 100644 index 00000000..b6a6a11b --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality/PdfPngComparer.cs @@ -0,0 +1,97 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; +using PdfSharp.Pdf; +using PdfSharp.Pdf.IO; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Quality +{ + /// + /// Static helper functions for file IO. + /// These functions are intended for unit tests and samples in solution code only. + /// + public static class PdfPngComparer + { + static readonly XPoint LeftTopLeft = new(XUnit.FromMillimeter(10.75).Point, XUnit.FromMillimeter(25).Point); + static readonly XPoint RightTopLeft = new(XUnit.FromMillimeter(159.25).Point, XUnit.FromMillimeter(25).Point); + + static readonly XRect TitleRect = new(LeftTopLeft.X, XUnit.FromMillimeter(10).Point, + XUnit.FromMillimeter(297 - 21.5).Point, XUnit.FromMillimeter(12).Point); + + static readonly XRect LeftSubTitleRect = new(LeftTopLeft.X, XUnit.FromMillimeter(200).Point, + XUnit.FromMillimeter(127).Point, XUnit.FromMillimeter(8).Point); + + static readonly XRect RightSubTitleRect = new(RightTopLeft.X, LeftSubTitleRect.Y, + LeftSubTitleRect.Width, LeftSubTitleRect.Height); + + public static void AppendPdfPngComparisonPage(PdfPngComparerParameters parms) + { + PdfDocument document; + // Note that PdfFileUtility creates an empty temp file. + if (File.Exists(parms.TargetPdfFilePath) && new FileInfo(parms.TargetPdfFilePath).Length > 0) + { + document = PdfReader.Open(parms.TargetPdfFilePath, PdfDocumentOpenMode.Modify); + } + else + { + document = new PdfDocument(); + document.ViewerPreferences.FitWindow = true; + document.PageLayout = PdfPageLayout.SinglePage; + //document.PageMode= + } + + var page = document.AddPage(); + page.Size = PageSize.A4; + page.Orientation = PageOrientation.Landscape; + + var gfx = XGraphics.FromPdfPage(page); + var titleFont = new XFont("Arial", 22, XFontStyleEx.Bold); + var subTitleFont = new XFont("Arial", 9, XFontStyleEx.Bold); + + var docLeft = parms.LeftFileStream != null + ? XPdfForm.FromStream(parms.LeftFileStream) + : XPdfForm.FromFile(parms.LeftFilePath); + + var docRight = parms.RightFileStream != null + ? XImage.FromStream(parms.RightFileStream) + : XImage.FromFile(parms.RightFilePath); + + gfx.DrawImage(docLeft, XUnit.FromMillimeter(10.75).Point, XUnit.FromMillimeter(25).Point); + gfx.DrawImage(docRight, XUnit.FromMillimeter(159.25).Point, XUnit.FromMillimeter(25).Point); + + //360, 480); + //gfx.DrawImage(docRight, XUnit.FromMillimeter(21.5).Point, XUnit.FromMillimeter(25).Point); + + //gfx.DrawRectangle(XPens.Yellow, TitleRect); + //gfx.DrawRectangle(XPens.Yellow, LeftSubTitleRect); + //gfx.DrawRectangle(XPens.Yellow, RightSubTitleRect); + gfx.DrawString(parms.Title, titleFont, XBrushes.Black, TitleRect, XStringFormats.Center); + gfx.DrawString(parms.LeftSubTitle, subTitleFont, XBrushes.Black, LeftSubTitleRect, XStringFormats.Center); + gfx.DrawString(parms.RightSubTitle, subTitleFont, XBrushes.Black, RightSubTitleRect, XStringFormats.Center); + + document.Save(parms.TargetPdfFilePath); + } + } + + public class PdfPngComparerParameters + { + public string Title { get; set; } = ""; + + public string TargetPdfFilePath { get; set; } = ""; + + public Stream? LeftFileStream { get; set; } = null!; + + public string LeftFilePath { get; set; } = ""; + + public string LeftSubTitle { get; set; } = ""; + + public Stream? RightFileStream { get; set; } = null!; + + public string RightFilePath { get; set; } = ""; + + public string RightSubTitle { get; set; } = ""; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/PdfSharp.Quality.csproj b/src/foundation/src/shared/src/PdfSharp.Quality/PdfSharp.Quality.csproj index f46af9bf..538688ec 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/PdfSharp.Quality.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Quality/PdfSharp.Quality.csproj @@ -1,7 +1,8 @@  - net8.0;net9.0;net10.0;netstandard2.0 + Library + $(PDFsharpTargetFrameworks_Library) CORE True ..\..\..\..\..\StrongnameKey.snk @@ -20,7 +21,8 @@ - + + diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/GlobalUsings.cs b/src/foundation/src/shared/src/PdfSharp.Quality/Properties/GlobalUsings.cs similarity index 100% rename from src/foundation/src/shared/src/PdfSharp.Quality/GlobalUsings.cs rename to src/foundation/src/shared/src/PdfSharp.Quality/Properties/GlobalUsings.cs diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/Snippet.cs b/src/foundation/src/shared/src/PdfSharp.Quality/Snippet.cs index e353f77c..0e3223fa 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/Snippet.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/Snippet.cs @@ -347,7 +347,7 @@ protected void DrawTiledBox(XGraphics gfx, double x, double y, double width, dou points.Add(new(x + width, yy - 2 * delta)); } path.AddPolygon(points.ToArray()); - gfx.DrawPath(Gray, path); + gfx.DrawPath(Gray, path); } /// diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/Testing.TestModel/ObjectModel.cs b/src/foundation/src/shared/src/PdfSharp.Quality/Testing.TestModel/ObjectModel.cs new file mode 100644 index 00000000..a4aac70a --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality/Testing.TestModel/ObjectModel.cs @@ -0,0 +1,366 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; + +#pragma warning disable CS1591 + +namespace PdfSharp.Quality.Testing.TestModel +{ + public class TestDict1 : PdfDictionary + { + public TestDict1() + { + Elements.Add(Keys.IAmA, new PdfName('/' + nameof(TestDict1))); + Elements.Add(Name.Empty, new PdfName('/' + nameof(TestDict1))); + } + + public TestDict1(PdfDocument doc, bool createIndirect) : base(doc, createIndirect) + { } + + protected TestDict1(PdfDictionary dict) : base(dict) + { } + + public T? Foo(int x) => default(T); + + public sealed class Keys : KeysBase + { + // ReSharper disable InconsistentNaming + + [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict1))] + public const string IAmA = "/I_am_a"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class TestDict2 : PdfDictionary + { + public TestDict2() + { + Elements.Add("/I_am_a", new PdfName('/' + nameof(TestDict2))); + } + + /// + /// Initializes a new instance of this class using the elements of the specified dictionary. + /// After this type transformation the specified dictionary is dead and cannot be used anymore. + /// + internal TestDict2(PdfDictionary dict) : base(dict) + { } + + public sealed class Keys : KeysBase + { + // ReSharper disable InconsistentNaming + + /// + /// + [KeyInfo(KeyType.Name | KeyType.Optional, FixedValue = "Outlines")] + public const string Type = "/Type"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class TestDict3 : PdfDictionary + { + public TestDict3(PdfDocument doc) : base(doc) + { + Elements.Add(Keys.IAmA, new PdfName('/' + nameof(TestDict3))); + + var dict1 = new TestDict1(); + Elements.Add(Keys.TestDict1, dict1); + + var dict2 = new TestDict1(); + doc.Internals.AddObject(dict2); + Elements.Add(Keys.TestDict1Ref, dict2); + } + + TestDict3(PdfDictionary dict) : base(dict) + { } + + public sealed class Keys : KeysBase + { + // ReSharper disable InconsistentNaming + + [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict3))] + public const string IAmA = "/I_am_a"; + + [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] + public const string TestDict1 = "/TestDict1"; + + [KeyInfo(KeyType.String | KeyType.Optional, typeof(TestDict1))] + public const string TestDict1Ref = "/TestDict1Ref"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class TestBaseDict : PdfDictionary + { + public TestBaseDict(PdfDocument doc) : base(doc) + { + Elements.Add(Keys.IAmA, new PdfName('/' + nameof(TestDict3))); + + var dict1 = new TestDict1(); + Elements.Add(Keys.TestDict1, dict1); + + var dict2 = new TestDict1(); + doc.Internals.AddObject(dict2); + Elements.Add(Keys.TestDict1Ref, dict2); + } + + protected TestBaseDict(PdfDictionary dict) : base(dict) + { } + + public /*sealed*/ class Keys : KeysBase + { + // ReSharper disable InconsistentNaming + + [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict3))] + public const string IAmA = "/I_am_a"; + + [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] + public const string TestDict1 = "/TestDict1"; + + [KeyInfo(KeyType.String | KeyType.Optional, typeof(TestDict1))] + public const string TestDict1Ref = "/TestDict1Ref"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class TestDerivedDict : TestBaseDict + { + public TestDerivedDict(PdfDocument doc) : base(doc) + { + Elements.Add(Keys.IAmA2, new PdfName('/' + nameof(TestDict3))); + + var dict1 = new TestDict1(); + Elements.Add(Keys.TestDict12, dict1); + //Elements.Add(TestBaseDict.Keys.TestDict1, dict1); + + var dict2 = new TestDict1(); + doc.Internals.AddObject(dict2); + Elements.Add(Keys.TestDict1Ref2, dict2); + } + + TestDerivedDict(PdfDictionary dict) : base(dict) + { } + + public new sealed class Keys : TestBaseDict.Keys + { + // ReSharper disable InconsistentNaming + + [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict3))] + public const string IAmA2 = "/I_am_a2"; + + [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] + public const string TestDict12 = "/TestDict12"; + + [KeyInfo(KeyType.String | KeyType.Optional, typeof(TestDict1))] + public const string TestDict1Ref2 = "/TestDict1Ref2"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal new static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class ApiTestDict1 : PdfDictionary + { + public ApiTestDict1() + { + Elements.Add(Keys.IAmA, new PdfName('/' + nameof(ApiTestDict1))); + } + + protected ApiTestDict1(PdfDocument doc, bool createIndirect) : base(doc, createIndirect) + { } + + protected ApiTestDict1(PdfDictionary dict) : base(dict) + { } + + public sealed class Keys : KeysBase + { + // ReSharper disable InconsistentNaming + + [KeyInfo(KeyType.String | KeyType.Optional, FixedValue = nameof(TestDict1))] + public const string IAmA = "/I_am_a"; + + [KeyInfo(KeyType.Boolean | KeyType.Optional)] + public const string SomeBoolean = "/SomeBoolean"; + + [KeyInfo(KeyType.Integer | KeyType.Optional)] + public const string SomeInteger = "/SomeInteger"; + + // TODO: double, long int, unsigned int?, + + [KeyInfo(KeyType.String | KeyType.Optional)] + public const string SomeString = "/SomeString"; + + // TODO: PdfRectangle, etc. + + [KeyInfo(KeyType.Name | KeyType.Optional)] + public const string SomeName = "/SomeName"; + + [KeyInfo(KeyType.Array | KeyType.Optional, typeof(TestArray1))] + public const string SomeDirectArray = "/SomeDirectDict"; + + [KeyInfo(KeyType.Array | KeyType.Optional, typeof(TestArray1))] + public const string SomeIndirectArray = "/SomeIndirectDict "; + + [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] + public const string SomeDirectDict = "/SomeDirectDict"; + + [KeyInfo(KeyType.Dictionary | KeyType.Optional, typeof(TestDict1))] + public const string SomeIndirectDict = "/SomeIndirectDict"; + + /// + /// Gets the KeysMeta for these keys. + /// + internal static DictionaryMeta Meta => _meta ??= CreateMeta(typeof(Keys)); + static DictionaryMeta? _meta; + + // ReSharper restore InconsistentNaming + } + + /// + /// Gets the KeysMeta of this dictionary type. + /// + internal override DictionaryMeta Meta => Keys.Meta; + } + + public class TestArray1 : PdfArray + { + public TestArray1() + { } + + public TestArray1(PdfDocument doc, bool createIndirect) : base(doc, createIndirect) + { } + + } + + public class TestBaseArray : PdfArray + { } + + public class TestDerivedArray : TestBaseArray + { } + + /// + /// Test class for class ArrayElementsTests. + /// + public class TestArrayElements : PdfArray + { + public enum Index + { + TestArray1, + TestDict1, + Integer, + IntegerObject + } + + public TestArrayElements() + { + Init(); + } + + public TestArrayElements(PdfDocument document, bool createIndirect = false) + : base(document, createIndirect) + { + Init(); + } + + void Init() + { + Elements.Add(new TestArray1()); + Elements.Add(new TestDict1()); + Elements.Add(new PdfInteger(-42)); + + if (IsIndirect) + { + + } + } + } + + /// + /// Test class for class DictionaryElementsTests. + /// + public class TestDictionaryElements : PdfDictionary + { + public TestDictionaryElements() + { + Init(); + } + + public TestDictionaryElements(PdfDocument document, bool createIndirect = false) + : base(document, createIndirect) + { + Init(); + } + + void Init() + { + Elements.Add("/TestArray1", new TestArray1()); + Elements.Add("/TestDict1", new TestDict1()); + Elements.Add("/Integer", new PdfInteger(-42)); + + if (IsIndirect) + { + + } + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/Testing.TestModel/ObjectModelTestHelper.cs b/src/foundation/src/shared/src/PdfSharp.Quality/Testing.TestModel/ObjectModelTestHelper.cs new file mode 100644 index 00000000..df100ad4 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality/Testing.TestModel/ObjectModelTestHelper.cs @@ -0,0 +1,181 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Drawing; +using PdfSharp.Pdf; +using PdfSharp.Pdf.Internal; + +#pragma warning disable CS1591 + +namespace PdfSharp.Quality.Testing.TestModel +{ + public class ObjectModelTestHelper + { + public ObjectModelTestHelper(PdfDocument document) + { + Document = document; + } + + PdfDocument Document { get; set; } + + public void PopulateDictionary(PdfDictionary dict) + { + // Primitives + dict.Elements.Add("/null", PdfNull.Value); + dict.Elements.Add("/array", new PdfArray(Document, new PdfReal(5), new PdfReal(6), new PdfReal(7), new PdfReal(8))); // For GetRectangle test. + dict.Elements.Add("/bool", new PdfBoolean(true)); + dict.Elements.Add("/int", new PdfInteger(42)); + dict.Elements.Add("/int-min", new PdfInteger(Int32.MinValue)); + dict.Elements.Add("/int-max", new PdfInteger(Int32.MaxValue)); + dict.Elements.Add("/long-min", new PdfLongInteger(Int64.MinValue)); + dict.Elements.Add("/long-max", new PdfLongInteger(Int64.MaxValue)); + dict.Elements.Add("/real", new PdfReal(Math.PI)); + dict.Elements.Add("/real-min", new PdfReal(Single.MinValue)); + dict.Elements.Add("/real-max", new PdfReal(Single.MaxValue)); + dict.Elements.Add("/string", new PdfString("Hello")); + dict.Elements.Add("/string-hex", new PdfString("HelloHex", true)); + dict.Elements.Add("/string-date1", new PdfString("D:19991231235959")); + dict.Elements.Add("/string-date2", new PdfString("D:19991231235959+02'00'")); + dict.Elements.Add("/string-date3", new PdfString("D:19991231235959-03'00'")); + dict.Elements.Add("/name", new PdfName("/Mambo #5")); + dict.Elements.Add("/rect", new PdfRectangle(new XRect(1, 2, 3, 4))); + dict.Elements.Add("/-.999", new PdfDebugItem("-.99999")); + dict.Elements.Add("/", new PdfNameObject(Document, "/")); + dict.Elements.Add("/int-default", new PdfInteger()); + dict.Elements.Add("/date1", new PdfDate(new DateTimeOffset(1999, 12, 31, 23, 59, 59, TimeSpan.Zero))); + dict.Elements.Add("/date2", new PdfDate(new DateTimeOffset(1999, 12, 31, 23, 59, 59, new TimeSpan(4, 0, 0)))); + dict.Elements.Add("/date3", new PdfDate(new DateTimeOffset(1999, 12, 31, 23, 59, 59, -new TimeSpan(3, 34, 0)))); + + // TODO PdfSignaturePlaceHolder + + // Primitive objects + dict.Elements.Add("/null-obj", new PdfNullObject(Document, true)); + dict.Elements.Add("/bool-obj", new PdfBooleanObject(Document, true)); + dict.Elements.Add("/int-obj", new PdfIntegerObject(Document, 42)); + dict.Elements.Add("/int-min-obj", new PdfIntegerObject(Document, Int32.MinValue)); + dict.Elements.Add("/int-max-obj", new PdfIntegerObject(Document, Int32.MaxValue)); + dict.Elements.Add("/long-min-obj", new PdfLongIntegerObject(Document, Int64.MinValue)); + dict.Elements.Add("/long-max-obj", new PdfLongIntegerObject(Document, Int64.MaxValue)); + dict.Elements.Add("/real-obj", new PdfRealObject(Document, Math.PI)); + dict.Elements.Add("/real-min-obj", new PdfRealObject(Document, Single.MinValue)); + dict.Elements.Add("/real-max-obj", new PdfRealObject(Document, Single.MaxValue)); + dict.Elements.Add("/string-obj", new PdfStringObject(Document, "Hello")); + dict.Elements.Add("/string-obj-hex", new PdfStringObject(Document, "HelloHex") { HexLiteral = true }); + dict.Elements.Add("/name-obj", new PdfNameObject(Document, "/Mambo #5")); + //dict.Elements.Add("/rect-obj", new PdfRectangleObject(new XRect(1, 2, 3, 4))); + dict.Elements.Add("/-.999", new PdfDebugObject(Document, "-.99999")); + dict.Elements.Add("/", new PdfNameObject(Document, "/")); + + // Direct and indirect array + dict.Elements.Add("/array-direct", new TestArray1()); + dict.Elements.Add("/array-indirect", new TestArray1(Document, true)); + + // Direct and indirect dictionary + dict.Elements.Add("/dict1-direct", new TestDict1()); + dict.Elements.Add("/dict1-indirect", new TestDict1(Document, true)); + + // Some direct array + dict.Elements.Add("/some-array", CreateSomeArray()); + + // Some direct dictionary + dict.Elements.Add("/some-dictionary", CreateSomeDictionary()); + + // Some enum test data + dict.Elements.Add("/pagelayout-test", new PdfInteger((int)PdfPageLayout.TwoColumnRight)); + dict.Elements.Add("/pagemode-test", new PdfInteger((int)PdfPageMode.FullScreen)); + dict.Elements.Add("/pagelayout-test-string", new PdfName("/" + PdfPageLayout.TwoColumnRight)); + dict.Elements.Add("/pagemode-test-string", new PdfName("/" + PdfPageMode.FullScreen)); + } + + public void PopulateArray(PdfArray array) + { + // Primitives + array.Elements.Add(PdfNull.Value); + // 1 + array.Elements.Add(new PdfBoolean(true)); + array.Elements.Add(new PdfInteger(42)); + array.Elements.Add(new PdfInteger(Int32.MinValue)); + array.Elements.Add(new PdfInteger(Int32.MaxValue)); + array.Elements.Add(new PdfLongInteger(Int64.MinValue)); + // 6 + array.Elements.Add(new PdfLongInteger(Int64.MaxValue)); + array.Elements.Add(new PdfReal(Math.PI)); + array.Elements.Add(new PdfReal(Single.MinValue)); + array.Elements.Add(new PdfReal(Single.MaxValue)); + array.Elements.Add(new PdfString("Hello")); + // 11 + array.Elements.Add(new PdfName("/Mambo #5")); + array.Elements.Add(new PdfDebugItem("-.99999")); + array.Elements.Add(new PdfName("/")); + array.Elements.Add(new PdfInteger(-1)); + + // Primitive objects + // 15 + array.Elements.Add(new PdfNullObject(Document, true)); + array.Elements.Add(new PdfBooleanObject(Document, true)); + array.Elements.Add(new PdfIntegerObject(Document, 42)); + array.Elements.Add(new PdfIntegerObject(Document, Int32.MinValue)); + array.Elements.Add(new PdfIntegerObject(Document, Int32.MaxValue)); + // 20 + array.Elements.Add(new PdfLongIntegerObject(Document, Int64.MinValue)); + array.Elements.Add(new PdfLongIntegerObject(Document, Int64.MaxValue)); + array.Elements.Add(new PdfRealObject(Document, Math.PI)); + array.Elements.Add(new PdfRealObject(Document, Single.MinValue)); + array.Elements.Add(new PdfRealObject(Document, Single.MaxValue)); + // 25 + array.Elements.Add(new PdfStringObject(Document, "Hello")); + array.Elements.Add(new PdfNameObject(Document, "/Mambo #5")); + array.Elements.Add(new PdfDebugObject(Document, "-.99999")); + array.Elements.Add(new PdfNameObject(Document, "/")); + + // Direct and indirect array + // 29 + array.Elements.Add(new TestArray1()); + array.Elements.Add(new TestArray1(Document, true)); + + // Direct and indirect dictionary + // 31 + array.Elements.Add(new TestDict1()); + array.Elements.Add(new TestDict1(Document, true)); + + // Some direct array + array.Elements.Add(CreateSomeArray()); + + // Some direct dictionary + array.Elements.Add(CreateSomeDictionary()); + } + + public PdfArray CreateSomeArray() + { + var array = new PdfArray(); + array.Elements.Add(new PdfInteger(123)); + array.Elements.Add(PdfNull.Value); + array.Elements.Add(new PdfBoolean(true)); + array.Elements.Add(new PdfBoolean(false)); + array.Elements.Add(new PdfInteger(42)); + array.Elements.Add(new PdfArray()); + array.Elements.Add(new PdfInteger(43)); + array.Elements.Add(new PdfArray(Document, new PdfDictionary())); + array.Elements.Add(new PdfInteger(44)); + array.Elements.Add(new PdfLiteral("123456 0 R")); + + return array; + } + + public PdfDictionary CreateSomeDictionary() + { + var dict = new PdfDictionary(); + dict.Elements.Add("/null", PdfNull.Value); + dict.Elements.Add("/true", new PdfBoolean(true)); + dict.Elements.Add("/false", new PdfBoolean(false)); + dict.Elements.Add("/42", new PdfInteger(42)); + dict.Elements.Add("/arrEmpty", new PdfArray()); + dict.Elements.Add("/43", new PdfInteger(43)); + dict.Elements.Add("/arr2", new PdfArray(Document, new PdfDictionary())); + dict.Elements.Add("/44", new PdfInteger(44)); + dict.Elements.Add("/invalid-iref", new PdfLiteral("123456 0 R")); + + return dict; + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/Testing/PdfSharpTestBase.cs b/src/foundation/src/shared/src/PdfSharp.Quality/Testing/PdfSharpTestBase.cs new file mode 100644 index 00000000..cb283a46 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality/Testing/PdfSharpTestBase.cs @@ -0,0 +1,22 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Quality.Testing +{ + public abstract class PdfSharpTestBase : IDisposable + { + protected PdfSharpTestBase() + { } + + public void Dispose() => Dispose(true); + + protected virtual void Dispose(bool disposing) + { } + + protected static string GetTempRoot(Type type) => $"unit-tests/{type.Namespace}/{type.Name}/"; + + protected const string WindowsFontsPath = "C:/Windows/Fonts/"; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/Testing/ReadWriteHelper.cs b/src/foundation/src/shared/src/PdfSharp.Quality/Testing/ReadWriteHelper.cs new file mode 100644 index 00000000..705ce1b9 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality/Testing/ReadWriteHelper.cs @@ -0,0 +1,176 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Pdf; +using PdfSharp.Pdf.Advanced; +using PdfSharp.Pdf.IO; + +#pragma warning disable CS8321 // Local function is declared but never used +#pragma warning disable CS1591 + +namespace PdfSharp.Quality.Testing +{ + public class ReadWriteHelper + { + public ReadWriteHelper() + { + _document = null!; + _reloadedDocument = null!; + _testClass = null!; + _filename = null!; + _fullFilename = null!; + _fullFilename2 = null!; + _page = null!; + } + + public PdfDocument CreateDocument(Type testClass, string name) + { + _testClass = testClass; + _document = PdfDocUtility.CreateNewPdfDocument(name); + _document.Info.Title = name; + // Add empty page to make it savable. + _page = _document.AddPage(); + + return _document; + } + + public PdfDocument ImportDocument(string fileName, string passWord, PdfDocumentOpenMode mode = PdfDocumentOpenMode.Modify) + { + var name = Path.GetFileName(fileName); + _document = PdfReader.Open(fileName, passWord, mode); + + return _document; + } + + public string Save(string? filename = null, PdfWriterLayout layout = PdfWriterLayout.Compact) + { + _filename = filename ?? Document.Info.Title; + //_fullFilename = PdfFileUtility.GetTempPdfFullFileName("Pdf-ObjectModel/" + filename); + _fullFilename = PdfFileUtility.GetTempPdfFullFileName(PdfFileUtility.GetUnitTestPath(_testClass) + _filename); + Document.Options.Layout = layout; + Document.Save(_fullFilename); + return _fullFilename; + } + + public PdfDocument Reload() + { + _reloadedDocument = PdfReader.Open(_fullFilename, PdfDocumentOpenMode.Modify); + return _reloadedDocument; + } + + public string Resave(PdfWriterLayout layout = PdfWriterLayout.Compact) + { + var filename = _filename + "_#2"; + //_fullFilename2 = PdfFileUtility.GetTempPdfFullFileName("Pdf-ObjectModel/" + filename); + _fullFilename2 = PdfFileUtility.GetTempPdfFullFileName(PdfFileUtility.GetUnitTestPath(_testClass) + _filename); + + _reloadedDocument.Options.Layout = layout; + _reloadedDocument.Save(_fullFilename2); + return _fullFilename2; + } + + PdfDocument Load(string path) + { + var document = PdfReader.Open(path, PdfDocumentOpenMode.Modify); + return document; + } + + PdfDocument CreateDocument() + { + var doc = new PdfDocument(); + var page = new PdfPage(); + doc.AddPage(page); + return doc; + } + + public void SetDocument(PdfDocument doc) + { + _document = doc; + } + + public void SetTestClass(Type testClass) + { + _testClass = testClass; + } + + public PdfDocument Document => _document; + + public PdfDocument ReloadedDocument => _reloadedDocument; + + public PdfCatalog Catalog => _document.Internals.Catalog; + + public PdfPage Page => _page; + + PdfDocument _document; + PdfDocument _reloadedDocument; + Type _testClass; + string _filename; + string _fullFilename; + string _fullFilename2; + PdfPage _page; + + // TODO + static void CheckParentInfoConsistency(PdfObject root) + { + Debug.Assert(root.IsIndirect); + var owner = root.Owner; + var closure = owner.IrefTable.TransitiveClosure(root); + + + foreach (var reference in closure) + { + var obj = reference.Value; + } + return; + + // TODO + static void CheckArray(PdfArray array) + { + foreach (var item in array.Elements) + { + //var item = elements[name]; + if (item is PdfObject obj) + { + if (obj.Reference != null) + { + if (obj.ParentInfo != null) + throw new InvalidOperationException("ParentInfo must be null."); + } + else + { + if (obj.ParentInfo == null) + throw new InvalidOperationException("ParentInfo must not be null."); + + if (obj.ParentInfo.OwningArray != array) + throw new InvalidOperationException("ParentInfo must be owning array."); + } + } + } + } + + // TODO + static void CheckDictionary(PdfDictionary dict) + { + foreach (var item in dict.Elements.Values) + { + if (item is PdfObject obj) + { + if (obj.Reference != null) + { + if (obj.ParentInfo != null) + throw new InvalidOperationException("ParentInfo must be null."); + } + else + { + if (obj.ParentInfo == null) + throw new InvalidOperationException("ParentInfo must not be null."); + + if (obj.ParentInfo.OwningDictionary != dict) + throw new InvalidOperationException("ParentInfo must be owning dictionary."); + } + } + } + } + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/TestClassBase.cs b/src/foundation/src/shared/src/PdfSharp.Quality/Testing/TestClassBase.cs similarity index 95% rename from src/foundation/src/shared/src/PdfSharp.Quality/TestClassBase.cs rename to src/foundation/src/shared/src/PdfSharp.Quality/Testing/TestClassBase.cs index 832fe8f6..013de4bd 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/TestClassBase.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/Testing/TestClassBase.cs @@ -43,21 +43,21 @@ // _testContextInstance = value; // } // } -// private TestContext _testContextInstance; +// TestContext _testContextInstance; // protected class TestInstance // { // static TestInstance _current; -// private readonly List _openedDirectories = new List(); +// readonly List _openedDirectories = new List(); // public static TestInstance Current() // { // return _current ?? (_current = new TestInstance()); // } -// private TestInstance() +// TestInstance() // { -// StartTime = DateTime.Now; +// StartTime = Date/Time.Now; // } // public void OpenDirectoryOnce(string directory) @@ -71,7 +71,7 @@ // public IFontResolver FontResolver; -// public DateTime StartTime; +// public Date/Time StartTime; // public string TestDocumentsDirectorySL // { @@ -232,6 +232,6 @@ // return result; // } -// private readonly List _currentFilepaths = new List(); +// readonly List _currentFilepaths = new List(); // } //} \ No newline at end of file diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/Testing/TrueOrFalse.cs b/src/foundation/src/shared/src/PdfSharp.Quality/Testing/TrueOrFalse.cs new file mode 100644 index 00000000..0f702db4 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality/Testing/TrueOrFalse.cs @@ -0,0 +1,31 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System; +using System.Collections.Generic; +using System.IO; +using System.Text; + +namespace PdfSharp.Quality +{ + /// + /// Helper class for getting constant values without + /// getting warnings or unwanted code optimizations. + /// + public static class TrueOrFalse + { + /// + /// Returns true. + /// Used in tests for statements like 'if (true)...' without getting + /// a warning from Visual Studio or ReSharper. + /// + public static bool True => true; + + /// + /// Returns false. + /// Used in tests for statements like 'if (false)...' without getting + /// a warning from Visual Studio or ReSharper. + /// + public static bool False => false; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/WindowsFonts.cs b/src/foundation/src/shared/src/PdfSharp.Quality/WindowsFonts.cs new file mode 100644 index 00000000..476b1f52 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Quality/WindowsFonts.cs @@ -0,0 +1,154 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Collections; +using PdfSharp.Internal.OpenType; + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Quality +{ + public static class WindowsFonts + { + public const string ArialRegular = "arial.ttf"; + public const string ArialBold = "arialbd.ttf"; + public const string ArialItalic = "ariali.ttf"; + public const string ArialBoldItalic = "arialbi.ttf"; + public const string ArialNarrowRegular = "ARIALN.TTF"; + public const string ArialNarrowBold = "ARIALNB.TTF"; + public const string ArialNarrowItalic = "ARIALNI.TTF"; + public const string ArialNarrowBoldItalic = "ARIALNBI.TTF"; + public const string AriaBlack = "ariblk.ttf"; + public const string ArialRoundedBold = "ARLRDBD.TTF"; + + public const string TimesRegular = "times.ttf"; + public const string TimesBold = "timesbd.ttf"; + public const string TimesItalic = "timesi.ttf"; + public const string TimesBoldItalic = "timesbi.ttf"; + + public const string CourierRegular = "cour.ttf"; + public const string CourierBold = "courbd.ttf"; + public const string CourierItalic = "couri.ttf"; + public const string CourierBoldItalic = "courbi.ttf"; + + //public IEnumerator ArialFonts : IEnumerator, IEnumerable + //{ + // yield return Ari + //} + + public class ArialFonts : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return ArialRegular; + yield return ArialBold; + yield return ArialItalic; + yield return ArialBoldItalic; + yield return ArialNarrowRegular; + yield return ArialNarrowBold; + yield return ArialNarrowItalic; + yield return ArialNarrowBoldItalic; + yield return AriaBlack; + yield return ArialRoundedBold; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + public class TimesFonts : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return TimesRegular; + yield return TimesBold; + yield return TimesItalic; + yield return TimesBoldItalic; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + public class CourierFonts : IEnumerable + { + public IEnumerator GetEnumerator() + { + yield return CourierRegular; + yield return CourierBold; + yield return CourierItalic; + yield return CourierBoldItalic; + } + + IEnumerator IEnumerable.GetEnumerator() => GetEnumerator(); + } + + public static void RegisterArialTimesCourier(ref OpenTypeFontRegistry registry) + { + const string windowsFontsPath = "C:/Windows/Fonts/"; + + // ----- Arial fonts ----- + + // Keep original values for Arial + var fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialRegular))!; + registry.RegisterFont(fontSource); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialBold))!; + registry.RegisterFont(fontSource); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialItalic))!; + registry.RegisterFont(fontSource); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialBoldItalic))!; + registry.RegisterFont(fontSource); + + // Adjust family name for Arial Narrow. + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialNarrowRegular))!; + registry.RegisterFont(fontSource.OTFontFace, "Arial", "Narrow"); + registry.RegisterFont(fontSource.OTFontFace, "Arial", "Narrow"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialNarrowBold))!; + registry.RegisterFont(fontSource.OTFontFace, "Arial", "Narrow Bold"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialNarrowItalic))!; + registry.RegisterFont(fontSource.OTFontFace, "Arial", "Narrow Italic"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialNarrowBoldItalic))!; + registry.RegisterFont(fontSource.OTFontFace, "Arial", "Narrow Bold Italic"); + + // Adjust family name and weight for Arial Black. + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + AriaBlack))!; + registry.RegisterFont(fontSource.OTFontFace, "Arial", "Black", null, OpenTypeFontWeight.Black); + + // Adjust family name for Arial Rounded. + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + ArialRoundedBold))!; + registry.RegisterFont(fontSource.OTFontFace, "Arial Rounded"); + + // ----- Times New Roman fonts ----- + + // Adjust family name for Times New Roman to Times. + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + TimesRegular))!; + registry.RegisterFont(fontSource.OTFontFace, "Times"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + TimesBold))!; + registry.RegisterFont(fontSource.OTFontFace, "Times"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + TimesItalic))!; + registry.RegisterFont(fontSource.OTFontFace, "Times"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + TimesBoldItalic))!; + registry.RegisterFont(fontSource.OTFontFace, "Times"); + + // ----- Courier New fonts ----- + + // Adjust family name for Courier New to Courier. + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + CourierRegular))!; + registry.RegisterFont(fontSource.OTFontFace, "Courier"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + CourierBold))!; + registry.RegisterFont(fontSource.OTFontFace, "Courier"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + CourierItalic))!; + registry.RegisterFont(fontSource.OTFontFace, "Courier"); + + fontSource = OpenTypeFontSource.GetOrCreateFrom(new(windowsFontsPath + CourierBoldItalic))!; + registry.RegisterFont(fontSource.OTFontFace, "Courier"); + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Quality/fontresolver/UnitTestFontResolver.cs b/src/foundation/src/shared/src/PdfSharp.Quality/fontresolver/UnitTestFontResolver.cs index 6334ef1a..66767699 100644 --- a/src/foundation/src/shared/src/PdfSharp.Quality/fontresolver/UnitTestFontResolver.cs +++ b/src/foundation/src/shared/src/PdfSharp.Quality/fontresolver/UnitTestFontResolver.cs @@ -1,7 +1,9 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +#if WPF using System.IO; +#endif using PdfSharp.Fonts; namespace PdfSharp.Quality @@ -9,6 +11,8 @@ namespace PdfSharp.Quality /// /// The font resolver that provides fonts needed by the unit tests on any platform. /// The typeface names are case-sensitive by design. + /// The font files are expected in the assets folder to ensure consistent test result + /// not only under Windows. /// public class UnitTestFontResolver : IFontResolver { @@ -76,7 +80,7 @@ public class UnitTestFontResolver : IFontResolver const string VerdanaBI = "verdanaz"; /// - /// Creates a new instance of UnitTestFontResolver. + /// Initializes a new instance of the class. /// public UnitTestFontResolver() { diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/.gitignore b/src/foundation/src/shared/src/PdfSharp.Shared/.gitignore new file mode 100644 index 00000000..e3a0225f --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Shared/.gitignore @@ -0,0 +1,3 @@ +# Ignore generated C# file +GitVersion.json +SemVersionInformation-generated.cs diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/BuildVersion.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/BuildVersion.cs index f3cb8037..0abf07bd 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/BuildVersion.cs +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/BuildVersion.cs @@ -18,7 +18,7 @@ class BuildInformation /// The PDFsharp build version used as fourth value in AssemblyFileVersion. /// Must be set in gitversion.yml. /// - public static int BuildVersionNumber = (DateTime.Now - new DateTime(2005, 1, 1)).Days; + public static int BuildVersionNumber = (DateTimeOffset.Now - new DateTimeOffset(2005, 1, 1, 0, 0, 0, TimeSpan.Zero)).Days; } #endif } diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/CS13.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/CS13.cs new file mode 100644 index 00000000..d9b1bd62 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/CS13.cs @@ -0,0 +1,56 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#if DEBUG + +namespace PdfSharp.Internal +{ + // Test how CS 13 features works in all PDFsharp builds. + + public class TimerRemaining + { + const string Dummy = "abc\edef"; + + //public string Name { get => field; set => field = value; } + + public int[] Buffer { get; set; } = new int[10]; + + void Foo() + { + var countdown = new TimerRemaining() + { + Buffer = + { + [^1] = 0, + [^2] = 1, + [^3] = 2, + [^4] = 3, + [^5] = 4, + [^6] = 5, + [^7] = 6, + [^8] = 7, + [^9] = 8, + [^10] = 9 + } + }; + } + } + + public partial class C + { + // Declaring declaration. + public partial string Name { get; set; } + } + + public partial class C + { + // Implementation declaration. + public partial string Name + { + get => _name; + set => _name = value; + } + string _name = ""; + } +} +#endif diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/ChecksumHelper.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/ChecksumHelper.cs new file mode 100644 index 00000000..a62f65c9 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/ChecksumHelper.cs @@ -0,0 +1,75 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +namespace PdfSharp.Internal +{ + public static class ChecksumHelper + { + // Both sums (s1 and s2) are done modulo 65521. + const int AdlerModulus = 65521; + + /// + /// Calculate the Adler-32 checksum for some data. + /// + public static int Calculate(IEnumerable data, int length = -1) + { + // s1 is the sum of all bytes. + var s1 = 1; + + // s2 is the sum of all s1 values. + var s2 = 0; + + var count = 0; + foreach (var b in data) + { + if (length > 0 && count == length) + break; + + s1 = (s1 + b) % AdlerModulus; + s2 = (s1 + s2) % AdlerModulus; + count++; + } + + // The Adler-32 checksum is stored as s2*65536 + s1. + return (s2 << 16) + s1; + } + + + /// + /// Calculates an Adler32 checksum combined with the buffer length + /// in a 64-bit unsigned integer. + /// + public static ulong CalcChecksum(byte[] buffer) + { + if (buffer == null) + throw new ArgumentNullException(nameof(buffer)); + + const uint prime = 65521; // largest prime smaller than 65536 + uint s1 = 0; + uint s2 = 0; + int length = buffer.Length; + int offset = 0; + while (length > 0) + { + int n = 3800; + if (n > length) + n = length; + length -= n; + while (--n >= 0) + { + s1 += buffer[offset++]; + s2 += s1; + } + s1 %= prime; + s2 %= prime; + } + //return ((ulong)((ulong)(((ulong)s2 << 16) | (ulong)s1)) << 32) | (ulong)buffer.Length; + ulong ul1 = ((ulong)s2 << 16) | s1; + //ul1 |= s1; + uint ui2 = (uint)buffer.Length; + return (ul1 << 32) | ui2; + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/GfxMsg.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/GfxMsg.cs new file mode 100644 index 00000000..a2804caa --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/GfxMsg.cs @@ -0,0 +1,28 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable 1591 // Because this is preview code. +// It should not be public. + +using Microsoft.Extensions.Logging; + +namespace PdfSharp.Internal +{ + /// + /// PDFsharp Graphics message. + /// + readonly struct GfxMsg(SyMsgId id, string message) : IErrorMessageInfo + { + int IErrorMessageInfo.Id => (int)Id; + + public string Name => "Gfx-" + Id; + + public SyMsgId Id { get; init; } = id; + + public string Message { get; init; } = message; + + public EventId EventId => new((int)Id, EventName); + + public string EventName => Id.ToString(); + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/GfxMsgs.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/GfxMsgs.cs new file mode 100644 index 00000000..f254b6b4 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/GfxMsgs.cs @@ -0,0 +1,64 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// ReSharper disable InconsistentNaming +#pragma warning disable 1591 // Because this is preview code. + +namespace PdfSharp.Internal +{ + /// + /// PDFsharp Graphics messages. + /// + // ReSharper disable once InconsistentNaming + // ReSharper disable once IdentifierTypo + static class GfxMsgs + { + // ----- Generic messages ------------------------------------------------------------------ + + /// + /// Use as a generic hack during development. + /// + public static SyMsg Generic(string message) + => new(SyMsgId.None, message); + + // Example: + static SyMsg Some_Example(string? hint = null) + => new(SyMsgId.UnexpectedNullValueRetrieved, + "A function returns null, but a value was expected." + + (hint != null ? " " + hint : "") + + " " + "[[Link to pdfsharp.com]]"); + + // ----- General messages ------------------------------------------------------------------ + + public static SyMsg General_NotImplemented(string member) + => new(SyMsgId.ToDo, $"The member {member} is not (yet) implemented."); + + public static SyMsg General_BadType(string target) + => new(SyMsgId.ToDo, $"The object passed to '{target}' is not a valid type."); + + public static SyMsg General_Expected_Type(Type expectedType) + => new(SyMsgId.ToDo, $"Expected object of type '{expectedType.FullName}'."); + + public static SyMsg General_ObjectIsReadOnly + => new(SyMsgId.ToDo, "The object is marked 'read-only'."); + + public static SyMsg General_PlatformMismatch(string name) + => new(SyMsgId.ToDo, $"The object '{name}' was created for another platform. " + + "You cannot mix objects from different platforms."); + + // ----- Geometry messages ----------------------------------------------------------------- + + + // ----- Imaging messages ------------------------------------------------------------------ + + public static SyMsg Imaging_NoFileUri(Uri uri) + => new(SyMsgId.None, "Only file URIs are allowed for bitmap images. " + + $"Invalid URI: '{uri.ToString()}'."); + + // ----- Font messages --------------------------------------------------------------------- + + public static SyMsg Font_NoFileUri(Uri uri) + => new(SyMsgId.None, "Only file URIs are allowed for font files. " + + $"Invalid URI: '{uri.ToString()}'."); + } +} \ No newline at end of file diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs deleted file mode 100644 index 4a52820a..00000000 --- a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/PdfSharpGitVersionInformation.cs +++ /dev/null @@ -1,56 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -namespace PdfSharp.Internal -{ - /// - /// Product version information from git version tool. - /// - public static class PdfSharpGitVersionInformation - { - /// - /// The major version number of the product. - /// - public static string Major = global::GitVersionInformation.Major; - - /// - /// The minor version number of the product. - /// - public static string Minor = global::GitVersionInformation.Minor; - - /// - /// The patch number of the product. - /// - public static string Patch = global::GitVersionInformation.Patch; - - /// - /// The Version pre-release string for NuGet. - /// - public static string PreReleaseLabel = global::GitVersionInformation.PreReleaseLabel; - - /// - /// The full version number. - /// - public static string MajorMinorPatch = global::GitVersionInformation.MajorMinorPatch; - - /// - /// The full semantic version number created by GitVersion. - /// - public static string SemVer = global::GitVersionInformation.SemVer; - - /// - /// The full informational version number created by GitVersion. - /// - public static string InformationalVersion = global::GitVersionInformation.InformationalVersion; - - /// - /// The branch name of the product. - /// - public static string BranchName = global::GitVersionInformation.BranchName; - - /// - /// The commit date of the product. - /// - public static string CommitDate = global::GitVersionInformation.CommitDate; - } -} diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SemVersionInformation.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SemVersionInformation.cs new file mode 100644 index 00000000..78350370 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SemVersionInformation.cs @@ -0,0 +1,76 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace PdfSharp.Internal +{ + /// + /// Internal public fields with semantic versioning information. + /// + public static partial class SemVersionInformation + { + // Visual Studio is very smart while building a solution. + // It can compile projects before its dependencies are compiled. + // I assume this is possible because it has the source code of + // the projects. Therefore, we write code that depends on the + // static fields below, while the fields are initialized when + // this project is compiled. + + /// + /// The major version number. + /// + //public static string Major = SemMajor; + public const string Major = SemMajor; + + /// + /// The minor version number. + /// + public const string Minor = SemMinor; + + /// + /// The patch number. + /// + public static readonly string Patch = SemPatch; + + /// + /// The full semantic version number. + /// + public static readonly string Version = SemVersion; + + /// + /// The assembly file version number. + /// + public static readonly string FileVersion = SemFileVersion; + + /// + /// The pre-release label. + /// + public static readonly string PreReleaseLabel = SemPreReleaseLabel; + + //public static string SemVer = SemSemVer; + + /// + /// The assembly informational version. + /// + public static readonly string InformationalVersion = SemInformationalVersion; + + /// + /// The name of the current Git branch. + /// + public static readonly string BranchName = SemBranchName; + + /// + /// The date of the last commit. + /// + public static readonly string CommitDate = SemCommitDate; + + /// + /// The SHA of the last commit. + /// + public static readonly string Sha = SemSha; + + /// + /// The short SHA of the last commit. + /// + public static readonly string ShortSha = SemShortSha; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SyMsg.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SyMsg.cs index c6ec16a7..ca1bde44 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SyMsg.cs +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SyMsg.cs @@ -1,19 +1,53 @@ // PDFsharp - A .NET library for processing PDF -// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. #pragma warning disable 1591 // Because this is preview code. - // It should not be public. +// It should not be public. using Microsoft.Extensions.Logging; namespace PdfSharp.Internal { + /// + /// Provides information about an error. + /// + public interface IErrorMessageInfo + { + /// + /// The integer value of the error enum. + /// + public int Id { get; } + + /// + /// The name of the error enum. + /// + public string Name { get; } + + /// + /// The error message. + /// + public string Message { get; } + + /// + /// The event ID for logging. + /// + public EventId EventId { get; } + + /// + /// The name of the event. + /// + public string EventName { get; } + } + /// /// (PDFsharp) System message. /// - readonly struct SyMsg(SyMsgId id, string message) + readonly struct SyMsg(SyMsgId id, string message) : IErrorMessageInfo { + int IErrorMessageInfo.Id => (int)Id; + + public string Name => "Sys-" + Id; + public SyMsgId Id { get; init; } = id; public string Message { get; init; } = message; diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SyMsgs.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SyMsgs.cs index 93d03e3e..8ae23635 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SyMsgs.cs +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/SyMsgs.cs @@ -1,20 +1,29 @@ // PDFsharp - A .NET library for processing PDF -// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. #pragma warning disable 1591 // Because this is preview code. -using System.Diagnostics.Contracts; - namespace PdfSharp.Internal { /// - /// (PDFsharp) System message. + /// (PDFsharp) System messages. /// // ReSharper disable once InconsistentNaming // ReSharper disable once IdentifierTypo static class SyMsgs { + // ----- Generic messages ------------------------------------------------------------------ + + /// + /// Use as a generic hack during development. + /// + public static SyMsg Generic(string message) + => new(SyMsgId.None, message); + + + + + public static string IndexOutOfRange3 => "Index out of range."; @@ -22,5 +31,56 @@ public static SyMsg IndexOutOfRange2(string parameter, T lowerBound, T upperB => new(SyMsgId.IndexOutOfRange, $"The value of '{parameter}' is out of range. " + Invariant($"The value must be between '{lowerBound}' and '{upperBound}'.")); + + // ----- General messages ------------------------------------------------------------------ + + + public static SyMsg UnexpectedNullValueRetrieved(string? hint = null) + => new(SyMsgId.UnexpectedNullValueRetrieved, + "A function returns null, but a value was expected." + + (hint != null ? " " + hint : "") + + " " + "[[Link to pdfsharp.com]]"); + + // ----- PDF object model ------------------------------------------------------------------ + + public static SyMsg IndirectReferenceMustNotBeNull + => new(SyMsgId.IndirectReferenceMustNotBeNull, + "The indirect reference (PdfReference) is null, but is expected to be defined."); + + public static SyMsg ObjectWithoutOwner + => new(SyMsgId.ObjectWithoutOwner, + "The direct or indirect object must be owned by a PdfDocument to execute the expected operation."); + + public static SyMsg ArrayEntryIsOfWrongType(int index, Type expected, Type found) + => new(SyMsgId.ArrayEntryIsOfWrongType, + $"The array entry '{index}' was expected to be of type '{expected.FullName}', but is of type '{found.FullName}'."); + + public static SyMsg DictionaryEntryDoesNotExist(string key) + => new(SyMsgId.DictionaryEntryDoesNotExist, + $"The dictionary entry with key '{key}' does not exist."); + + public static SyMsg DictionaryEntryDoesNotExistAndNoDefaultSpecified(string key) + => new(SyMsgId.DictionaryEntryDoesNotExistAndNoDefaultSpecified, + $"The dictionary entry with key '{key}' does not exist and no default value was specified."); + + public static SyMsg DictionaryEntryIsOfWrongType(string key, Type expected, Type found) + => new(SyMsgId.DictionaryEntryIsOfWrongType, + $"The dictionary entry with key '{key}' was expected to be of type '{expected.FullName}', but is of type '{found.FullName}'."); + + public static SyMsg IndirectObjectExpected + => new(SyMsgId.IndirectObjectExpected, + $"An indirect object was expected."); + + public static SyMsg IndirectObjectExpectedButDirectObjectFound + => new(SyMsgId.IndirectObjectExpectedButDirectObjectFound, + $"An indirect object was expected, but a direct object was found."); + + public static SyMsg DirectObjectExpected + => new(SyMsgId.DirectObjectExpected, + $"A direct object was expected."); + + public static SyMsg DirectObjectExpectedButIndirectObjectFound + => new(SyMsgId.DirectObjectExpectedButIndirectObjectFound, + $"A direct object was expected, but an indirect object was found."); } } \ No newline at end of file diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/enum/GfxMsgId.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/enum/GfxMsgId.cs new file mode 100644 index 00000000..28e60a11 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/enum/GfxMsgId.cs @@ -0,0 +1,32 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#pragma warning disable 1591 // Because this is preview code. + +namespace PdfSharp.Internal +{ + /// + /// System message ID. + /// + enum GfxMsgId + { + None = 0, + + // ----- General messages ------------------------------------------------------------------ + + IndexOutOfRange = MessageIdOffset.Gfx, + IndexOutOfRange2, + + + UnexpectedNullValueRetrieved = MessageIdOffset.Sy + 10, + + // ----- PDF object model ------------------------------------------------------------------ + + #region new message ids, TODO review before release + + ToBeDone = MessageIdOffset.Om + 1, + + + #endregion + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/enum/SyMsgId.cs b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/enum/SyMsgId.cs index dc0e4a3d..a8297d7e 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/Internal/enum/SyMsgId.cs +++ b/src/foundation/src/shared/src/PdfSharp.Shared/Internal/enum/SyMsgId.cs @@ -10,14 +10,53 @@ enum FooBarEnum3 { xxx = 843 } /// /// System message ID. /// + /// + /// The enum value should be fixed over time, but the enum name + /// must be stable in the future. This is because the name is + /// the link to any kind of outer resources, e.g. an entry in + /// the PDFsharp technical reference. + /// enum SyMsgId { None = 0, + ToDo = 0, - // ----- General Messages --------------------------------------------------------------------- + // ----- General messages ------------------------------------------------------------------ IndexOutOfRange = MessageIdOffset.Sy, IndexOutOfRange2, + + + UnexpectedNullValueRetrieved = MessageIdOffset.Sy + 10, + + // ----- PDF object model ------------------------------------------------------------------ + + #region new message ids, TODO review before release + + IndirectReferenceMustNotBeNull = MessageIdOffset.Om + 1, + + ObjectWithoutOwner, + + ArrayEntryIsOfWrongType, + + DictionaryEntryDoesNotExist, + + DictionaryEntryDoesNotExistAndNoDefaultSpecified, + + DictionaryEntryIsOfWrongType, + + IndirectObjectExpected, + IndirectObjectExpectedButDirectObjectFound, + + DirectObjectExpected, + DirectObjectExpectedButIndirectObjectFound, + + + CannotConvertArrayIntoDictionary = MessageIdOffset.Om + 100, + + CannotConvertDictionaryIntoArray, + + #endregion } /// @@ -26,11 +65,44 @@ enum SyMsgId /// public enum MessageIdOffset { + /// + /// General system messages. + /// Sy = 1000, + + /// + /// PDFsharp object model messages + /// + Om = 2000, + + /// + /// General PDFsharp messages. + /// Ps = 3000, + + /// + /// PDFsharp cryptography messages. + /// PsCrypto = 4000, + + /// + /// MigraDoc document object model messages. + /// MdDom = 5000, + + /// + /// MigraDoc PDF renderer messages. + /// MdPdf = 6000, + + /// + /// MigraDoc RTF renderer messages. + /// MdRtf = 7000, + + /// + /// PDFsharp Graphics messages. + /// + Gfx = 10_000, } } diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/PdfSharp.Shared.csproj b/src/foundation/src/shared/src/PdfSharp.Shared/PdfSharp.Shared.csproj index a7bd61c5..58a61467 100644 --- a/src/foundation/src/shared/src/PdfSharp.Shared/PdfSharp.Shared.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Shared/PdfSharp.Shared.csproj @@ -1,58 +1,82 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp True ..\..\..\..\..\StrongnameKey.snk - true - true + + + + true + + + + + + + + + - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets-wpf/PdfSharp.Snippets-wpf.csproj b/src/foundation/src/shared/src/PdfSharp.Snippets-wpf/PdfSharp.Snippets-wpf.csproj index 69e98b77..1407abfa 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets-wpf/PdfSharp.Snippets-wpf.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Snippets-wpf/PdfSharp.Snippets-wpf.csproj @@ -1,7 +1,7 @@  - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true PdfSharp.Snippets true @@ -77,7 +77,7 @@ - + diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/clipping/ClippingMisc.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/clipping/ClippingMisc.cs index d51db2eb..e2b12898 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/clipping/ClippingMisc.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/clipping/ClippingMisc.cs @@ -6,13 +6,22 @@ namespace PdfSharp.Snippets.Drawing { + /// + /// Snippet that demonstrates clipping. + /// public class ClippingMisc : Snippet { + /// + /// Creates a new ClippingMisc instance. + /// public ClippingMisc() { Title = "Clipping Miscellaneous"; } + /// + /// Renders the snippet. + /// public override void RenderSnippet(XGraphics gfx) { // Clipping only. @@ -20,7 +29,7 @@ public override void RenderSnippet(XGraphics gfx) { var path = new XGraphicsPath(); path.AddPolygon(GetPentagram(70, BoxCenter)); - + gfx.IntersectClip(path); const int offset = 50; @@ -49,7 +58,7 @@ public override void RenderSnippet(XGraphics gfx) } gfx.EndContainer(cont); - for (var x = offset; x < BoxWidth -offset; x += 10) + for (var x = offset; x < BoxWidth - offset; x += 10) gfx.DrawLine(XPens.DarkRed, new XPoint(x, 0.75 * BoxHeight), new XPoint(x + 0.25 * offset, BoxHeight)); } EndBox(gfx); diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/colors/ColorsCmyk.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/colors/ColorsCmyk.cs index b777e617..5e5acb12 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/colors/ColorsCmyk.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/colors/ColorsCmyk.cs @@ -6,20 +6,29 @@ namespace PdfSharp.Snippets.Drawing { + /// + /// Snippet that demonstrates CMYK colors. + /// public class ColorsCmyk : Snippet { + /// + /// Creates a new ColorsCmyk instance. + /// public ColorsCmyk() { Title = "CMYK Colors"; } - public XRect GetDeciRect(int i) + XRect GetDeciRect(int i) { var left = i * BoxWidth / 10; var right = (i + 1) * BoxWidth / 10; return new XRect(left, 0, right - left, BoxHeight); } + /// + /// Renders the snippet. + /// public override void RenderSnippet(XGraphics gfx) { // White. diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/colors/ColorsRgb.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/colors/ColorsRgb.cs index c9130f4d..242df932 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/colors/ColorsRgb.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/colors/ColorsRgb.cs @@ -6,20 +6,29 @@ namespace PdfSharp.Snippets.Drawing { + /// + /// Snippet that demonstrates RGB colors. + /// public class ColorsRgb : Snippet { + /// + /// Creates a new ColorsRgb instance. + /// public ColorsRgb() { Title = "RGB Colors"; } - public XRect GetDeciRect(int i) + XRect GetDeciRect(int i) { var left = i * BoxWidth / 10; var right = (i + 1) * BoxWidth / 10; return new XRect(left, 0, right - left, BoxHeight); } + /// + /// Renders the snippet. + /// public override void RenderSnippet(XGraphics gfx) { // White. @@ -29,7 +38,7 @@ public override void RenderSnippet(XGraphics gfx) for (int idx = 0; idx < 10; idx++) { var rect = GetDeciRect(idx); - gfx.DrawRectangle(new XSolidBrush(XColor.FromArgb(idx * 255 / 9, 255, 255, 255)), rect); + gfx.DrawRectangle(new XSolidBrush(XColor.FromArgb(idx * 255 / 9, 255, 255, 255)), rect); } } EndBox(gfx); diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsFromImage.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsFromImage.cs index 3247c143..bee10d49 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsFromImage.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsFromImage.cs @@ -6,20 +6,29 @@ namespace PdfSharp.Snippets.Drawing { + /// + /// Snippet that demonstrates graphics from image. + /// public class GraphicsFromImage : Snippet { + /// + /// Creates a new GraphicsFromImage instance. + /// public GraphicsFromImage() { - Title = "CMYK Colors"; + Title = "Graphics from image"; } - public XRect GetDeciRect(int i) + XRect GetDeciRect(int i) { var left = i * BoxWidth / 10; var right = (i + 1) * BoxWidth / 10; return new XRect(left, 0, right - left, BoxHeight); } + /// + /// Renders the snippet. + /// public override void RenderSnippet(XGraphics gfx) { // White. diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitBase.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitBase.cs index d5d4aac0..a0a7529b 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitBase.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitBase.cs @@ -7,13 +7,34 @@ namespace PdfSharp.Snippets.Drawing { + /// + /// Baseclass for graphics units snippets. + /// public abstract class GraphicsUnitBase : Snippet { + /// + /// 10 pt font using Presentation units. + /// protected XFont font10ptinPP = new("Arial", XUnit.FromPoint(10).Presentation); + + /// + /// 10 pt font. + /// protected XFont font10pt = new("Arial", 10); + + /// + /// 10 pt font using millimeters. + /// protected XFont font10ptInMM = new("Arial", XUnit.FromPoint(10).Millimeter); + + /// + /// 10 pt font centimeters. + /// protected XFont font10ptInCM = new("Arial", XUnit.FromPoint(10).Centimeter); + /// + /// Converts the specified size and unit to PageUnit. + /// protected double CalcSizeForGraphics(double size, XGraphicsUnit graphicsUnit) { var x = new XUnit(size, graphicsUnit); diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitDownwards.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitDownwards.cs index f13497e2..aca6b96b 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitDownwards.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitDownwards.cs @@ -7,14 +7,23 @@ namespace PdfSharp.Snippets.Drawing { + /// + /// Snippet that demonstrates graphics units. + /// public class GraphicsUnitDownwards : GraphicsUnitBase { + /// + /// Creates a new GraphicsUnitDownwards instance. + /// public GraphicsUnitDownwards() { Title = "Graphics Unit"; PathName = "snippets/drawing/graphics/GraphicsUnit-Downwards"; } + /// + /// Renders the snippet. + /// public override void RenderSnippet(XGraphics gfx) { // Page 1 - PU diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitUpwards.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitUpwards.cs index 531ab0a3..ce0d87a9 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitUpwards.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/graphics/GraphicsUnitUpwards.cs @@ -6,16 +6,22 @@ namespace PdfSharp.Snippets.Drawing { /// - /// + /// Snippet that demonstrates graphics units. /// public class GraphicsUnitUpwards : GraphicsUnitBase { + /// + /// Creates a new GraphicsUnitUpwards instance. + /// public GraphicsUnitUpwards() { Title = "Graphics Unit"; PathName = "snippets/drawing/graphics/GraphicsUnit-Upwards"; } + /// + /// Renders the snippet. + /// public override void RenderSnippet(XGraphics gfx) { #pragma warning disable CS0618 // Type or member is obsolete diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/images/ImageHelper.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/images/ImageHelper.cs index 4c61bf61..3e23cff9 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/images/ImageHelper.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Drawing/images/ImageHelper.cs @@ -13,7 +13,7 @@ namespace PdfSharp.Snippets.Drawing { - public static class ImageHelper + internal static class ImageHelper { public enum BmpImages { diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Font/encoding/FontAnsiEncoding.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Font/encoding/FontAnsiEncoding.cs index 02590334..bce18e21 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Font/encoding/FontAnsiEncoding.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Font/encoding/FontAnsiEncoding.cs @@ -3,6 +3,7 @@ using System.Globalization; using PdfSharp.Drawing; +using PdfSharp.Internal; using PdfSharp.Quality; #if GDI using System.Drawing; @@ -37,7 +38,7 @@ public override void RenderSnippet(XGraphics gfx) const int dx = 140; const int dy = 15; - var encoder = new PdfSharp.Pdf.Internal.AnsiEncoding(); + var encoder = new AnsiEncoding(); var ansi = new byte[1]; diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Font/fontresolving/ExoticFontsFontResolver.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Font/fontresolving/ExoticFontsFontResolver.cs index fc77ae1a..3955f63d 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Font/fontresolving/ExoticFontsFontResolver.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Font/fontresolving/ExoticFontsFontResolver.cs @@ -92,7 +92,7 @@ static class FaceNames /// public byte[]? GetFont(string faceName) { - // Note: PDFsharp never calls GetFont twice with the same face name. + // Note that PDFsharp never calls GetFont twice with the same face name. // Return the bytes of a font. return faceName switch diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Font/fontresolving/SegoeWpFontResolver.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Font/fontresolving/SegoeWpFontResolver.cs index 6d2c31fe..ad5688ba 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Font/fontresolving/SegoeWpFontResolver.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Font/fontresolving/SegoeWpFontResolver.cs @@ -61,7 +61,7 @@ static class FaceNames /// public FontResolverInfo? ResolveTypeface(string familyName, bool isBold, bool isItalic) { - // Note: PDFsharp calls ResolveTypeface only once for each unique combination + // Note that PDFsharp calls ResolveTypeface only once for each unique combination // of familyName, isBold, and isItalic. string lowercaseFamilyName = familyName.ToLowerInvariant(); @@ -140,9 +140,9 @@ static class FaceNames /// public byte[]? GetFont(string faceName) { - // Note: PDFsharp never calls GetFont twice with the same face name. - // Note: If a typeface is resolved by the PlatformFontResolver.ResolveTypeface - // you never come here. + // Note that PDFsharp never calls GetFont twice with the same face name. + // If a typeface is resolved by the PlatformFontResolver.ResolveTypeface + // you never come here. // Return the bytes of a font. return faceName switch diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Font/selection/DefaultConstructionSnippets.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Font/selection/DefaultConstructionSnippets.cs index 52ea9e2d..a06f5e49 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Font/selection/DefaultConstructionSnippets.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Font/selection/DefaultConstructionSnippets.cs @@ -746,7 +746,7 @@ static PrivateFontCollectionSnippet() public override void RenderSnippet(XGraphics gfx) { var brush = new XSolidBrush(XColor.FromArgb(0x20, 0x20, 0x20)); - var brushSimulation = new XSolidBrush(XColors.DarkOrange); + var brushSimulation = XBrushes.DarkOrange; //Typeface typeface = new Typeface("segoe ui semibold"); var xfiles = new XFont("x-files", EmSize, XFontStyleEx.Regular, _fontOptions); @@ -862,7 +862,7 @@ static FrutigerFontsSnippet() public override void RenderSnippet(XGraphics gfx) { var brush = new XSolidBrush(XColor.FromArgb(0x20, 0x20, 0x20)); - var brushSimulation = new XSolidBrush(XColors.DarkOrange); + var brushSimulation = XBrushes.DarkOrange; var frStd = new XFont("Frutiger", EmSize); Debug.WriteLine(PdfSharp.Internal.FontsDevHelper.GetFontCachesState()); diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Fonts.Text/TextSamples.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Fonts.Text/TextSamples.cs new file mode 100644 index 00000000..0d67b39b --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Fonts.Text/TextSamples.cs @@ -0,0 +1,37 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +//using System.IO; +//using System.Threading.Tasks; +//using PdfSharp.Drawing; +//using PdfSharp.Pdf; +//using PdfSharp.Quality; + +// ReSharper disable StringLiteralTypo +// ReSharper disable InconsistentNaming + +namespace PdfSharp.Snippets.Fonts.Text +{ + public static class ShortTestTexts + { + public static readonly string GermanUmlautsAndEszett = "ÄÖÜäöüß"; + + public static readonly string GoodMorning_English = "Good morning"; + public static readonly string GoodMorning_German = "Guten Morgen"; + public static readonly string GoodMorning_French = "Bonjour"; + public static readonly string GoodMorning_Italian = "Buongiorno"; + public static readonly string GoodMorning_Turkish = "Günaydın"; + + // Non-western glyphs + public static readonly string GoodMorning_Chinese = "早安"; + public static readonly string GoodMorning_SimplifiedChinese = "早上好"; + public static readonly string GoodMorning_Korean = "좋은 아침이에요"; + public static readonly string GoodMorning_Japanese = "おはよう"; + public static readonly string GoodMorning_Malayalam = "സുപ്രഭാതം"; + public static readonly string GoodMorning_Telugu = "శుభోదయం"; + public static readonly string GoodMorning_Thai = "สวัสดีตอนเช้า"; + + // Right to left + public static readonly string GoodMorning_Arabic = "صباح الخير"; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Pangrams.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Pangrams.cs index d19aa8d2..f2d3df48 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Pangrams.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Pangrams.cs @@ -16,7 +16,7 @@ public class Pangrams { public static string QuickBrownFox = "The quick brown fox jumps over the lazy dog. 1234567890 +-*/"; public static string SphinxOfQuarz = "Sphinx of black quartz, judge my vow. 1234567890 +-*/"; - public static string TwelveBoxer = "Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich. 1234567890 +-*/"; + public static string TwelveBoxers = "Zwölf Boxkämpfer jagen Viktor quer über den großen Sylter Deich. 1234567890 +-*/"; } public class LeftToRightText diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Pdf/annotations/LinkAnnotations.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Pdf/annotations/LinkAnnotations.cs index e596e377..66f220d3 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Pdf/annotations/LinkAnnotations.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Pdf/annotations/LinkAnnotations.cs @@ -89,7 +89,7 @@ public void RenderSnippet(PdfPage page, XGraphics gfx) BeginBox(gfx, 5, BoxOptions.Box); { - var rsa = new PdfRubberStampAnnotation { Icon = PdfRubberStampAnnotationIcon.Approved, Flags = PdfAnnotationFlags.ReadOnly }; + var rsa = new PdfStampAnnotation(page.Owner) { Icon = PdfStampAnnotationIcons.Approved, Flags = PdfAnnotationFlags.ReadOnly }; var rect = gfx.Transformer.WorldToDefaultPage(new XRect(new XPoint(20, 20), new XSize(160, 50))); rsa.Rectangle = new PdfRectangle(rect); @@ -101,9 +101,9 @@ public void RenderSnippet(PdfPage page, XGraphics gfx) BeginBox(gfx, 6, BoxOptions.Box); { - var rsa = new PdfRubberStampAnnotation + var rsa = new PdfStampAnnotation(page.Owner) { - Icon = PdfRubberStampAnnotationIcon.TopSecret, + Icon = PdfStampAnnotationIcons.TopSecret, Flags = PdfAnnotationFlags.ReadOnly, Title = "Annotation (title)", Subject = "Annotation (subject)", @@ -123,12 +123,12 @@ public void RenderSnippet(PdfPage page, XGraphics gfx) BeginBox(gfx, 7, BoxOptions.Fill); { // Create a PDF text annotation - var textAnnot = new PdfTextAnnotation + var textAnnot = new PdfTextAnnotation(page.Owner) { Title = "This is the title", Subject = "This is the subject", Contents = "This is the contents of the annotation.\rThis is the 2nd line.", - Icon = PdfTextAnnotationIcon.Note + Icon = PdfTextAnnotationIcons.Note }; gfx.DrawString("A text annotation.", font, XBrushes.Black, 20, 60, XStringFormats.Default); @@ -146,12 +146,12 @@ public void RenderSnippet(PdfPage page, XGraphics gfx) BeginBox(gfx, 8, BoxOptions.Fill); { // Create another PDF text annotation which is open and transparent - var textAnnot = new PdfTextAnnotation + var textAnnot = new PdfTextAnnotation(page.Owner) { Title = "Annotation 2 (title)", Subject = "Annotation 2 (subject)", Contents = "This is the contents of the 2nd annotation.", - Icon = PdfTextAnnotationIcon.Help, + Icon = PdfTextAnnotationIcons.Help, Color = XColors.LimeGreen, Opacity = 0.5, Open = true diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/Pdf/signatures/BouncyCastleSigner.cs b/src/foundation/src/shared/src/PdfSharp.Snippets/Pdf/signatures/BouncyCastleSigner.cs index 1d76b3fa..9b7d005b 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/Pdf/signatures/BouncyCastleSigner.cs +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/Pdf/signatures/BouncyCastleSigner.cs @@ -48,7 +48,7 @@ public async Task GetSignatureAsync(Stream stream) var cert = DotNetUtilities.FromX509Certificate(Certificate); var key = DotNetUtilities.GetKeyPair(GetAsymmetricAlgorithm(Certificate)); -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var allCerts = CertificateChain.Select(DotNetUtilities.FromX509Certificate); #else var allCerts = CertificateChain.OfType().Select(DotNetUtilities.FromX509Certificate); @@ -110,7 +110,7 @@ string GetProperDigestAlgorithm(PdfMessageDigestType digestType) AsymmetricAlgorithm? GetAsymmetricAlgorithm(X509Certificate2 cert) { const string RSA = "1.2.840.113549.1.1.1"; -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER const string DSA = "1.2.840.10040.4.1"; #endif const string ECC = "1.2.840.10045.2.1"; @@ -118,7 +118,7 @@ string GetProperDigestAlgorithm(PdfMessageDigestType digestType) return cert.PublicKey.Oid.Value switch { RSA => cert.GetRSAPrivateKey(), -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER DSA => cert.GetDSAPrivateKey(), #endif ECC => cert.GetECDsaPrivateKey(), diff --git a/src/foundation/src/shared/src/PdfSharp.Snippets/PdfSharp.Snippets.csproj b/src/foundation/src/shared/src/PdfSharp.Snippets/PdfSharp.Snippets.csproj index 4b446a6f..19285a64 100644 --- a/src/foundation/src/shared/src/PdfSharp.Snippets/PdfSharp.Snippets.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Snippets/PdfSharp.Snippets.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp.Snippets PdfSharp.Snippets TRACE;CORE @@ -27,13 +27,8 @@ - - - - - diff --git a/src/foundation/src/shared/src/PdfSharp.System/Diagnostics/DebuggerDisplayHelper.cs b/src/foundation/src/shared/src/PdfSharp.System/Diagnostics/DebuggerDisplayHelper.cs new file mode 100644 index 00000000..5d5155dc --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/Diagnostics/DebuggerDisplayHelper.cs @@ -0,0 +1,58 @@ +//// PDFsharp - A .NET library for processing PDF +//// See the LICENSE file in the solution root for more information. + +using System.Globalization; + +#if DEBUG || true // I don’t compile this effin’ crap into a Release build. + +namespace PdfSharp.Diagnostics +{ + /// + /// Try to fix a ReSharper issue. + /// + public static class DebuggerDisplayHelper + { + // As a German I use a German version of Windows, Office, etc. Visual Studio, all developer + // tools etc. are en-US. During debugging, I want to see decimal points and not decimal commas + // as common in Germany. Therefore, I write e.g. the struct Point like this: + // + // [DebuggerDisplay("{" + nameof(DebuggerDisplay) + "}")] + // public struct Point : ... + // { + // ... + // string DebuggerDisplay => Invariant($"Point=({X} {Y})"); + // } + // + // So far, so good. But during a debug session ReSharper says: + // + // "Evaluation of method PdfSharp.Graphics.Point.get_DebuggerDisplay requires reading field System.Globalization.CultureInfo._numInfo, which is not available in this context." + // + // What??? + + static DebuggerDisplayHelper() + { + // Initialize InvariantCulture with CultureInfo.InvariantCulture. + _ = InvariantCulture; + } + + /// + /// Prevent ReSharper from saying: + /// Evaluation of method {MyType}.get_DebuggerDisplay requires reading field System.Globalization.CultureInfo._numInfo, which is not available in this context. + /// + public static void FixReSharperBugInConnectionWithCultureInfo_numInfo() + { + // Just ensures that the class constructor is executed. + } + + /// + /// Number format DebuggerDisplay. + /// + public const string DebugNumberFormat = "0.########;-0.########;0"; + + /// + /// Get my own copy of InvariantCulture that ReSharper can use. + /// + public static readonly CultureInfo InvariantCulture = CultureInfo.InvariantCulture; + } +} +#endif diff --git a/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/BigIntegerExtensions.cs b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/BigIntegerExtensions.cs new file mode 100644 index 00000000..651e2d9d --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/BigIntegerExtensions.cs @@ -0,0 +1,58 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#if !NET8_0_OR_GREATER + +using System.Numerics; + +namespace PdfSharp.DotNetFrameworkExtensions +{ + /// + /// PDFsharp extensions for .NET types. + /// + public static class BigIntegerExtensions + { + extension(BigInteger _) + { + /// + /// Implements the .NET 6 BigInteger constructor missing in .NET framework 4.6.2. + /// Initializes a new instance of the BigInteger structure using the values in a read-only span of bytes, and optionally indicating the signing encoding and the endianness byte order. + /// + /// + /// + /// + public static BigInteger CreateBigInteger(ReadOnlySpan value, bool isUnsigned = false, bool isBigEndian = false) + { + var bytes = value.ToArray(); + + // Convert to little endian, which is expected by BigInteger constructor. + if (isBigEndian) + { + //bytes = bytes.Reverse().ToArray(); + // In .NET 10 is for 'bytes.Reverse()' 'MemoryExtensions.Reverse(this Span)' resolved + // instead of 'Enumerable.Reverse(this IEnumerable)'. + // See https://learn.microsoft.com/en-us/dotnet/csharp/whats-new/breaking-changes/compiler%20breaking%20changes%20-%20dotnet%2010#enumerablereverse + // ReSharper disable once InvokeAsExtensionMethod because we must call the extension method explicitly in .NET 10. + bytes = Enumerable.Reverse(bytes).ToArray(); + } + + // A leading bit of 1 defines a negative number. If the input should be interpreted as unsigned, prepend a new zero byte, if there’s a leading 1. + // As bytes is in little endian order, check the most significant bit of the last byte. If it is 1, append the zero byte. + if (isUnsigned && bytes.Length > 0 && (bytes.Last() & 0x80) != 0) + { +#if NET462 + var len = bytes.Length; + var bytes2 = new byte[len + 1]; + bytes.CopyTo(bytes2, 0); + bytes2[len] = 0; + bytes = bytes2; +#else + bytes = bytes.Append((byte)0).ToArray(); +#endif + } + return new BigInteger(bytes); + } + } + } +} +#endif diff --git a/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/ConcurrentDictionaryExtensions.cs b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/ConcurrentDictionaryExtensions.cs new file mode 100644 index 00000000..2fb48dda --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/ConcurrentDictionaryExtensions.cs @@ -0,0 +1,30 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#if !NET8_0_OR_GREATER + +#pragma warning disable CS1591 // TODO_DOC: Missing XML comment for publicly visible type or member + +using System.Collections.Concurrent; + +namespace PdfSharp.DotNetFrameworkExtensions +{ + + /// + /// PDFsharp extensions for .NET types. + /// + public static class ConcurrentDictionaryExtensions + { + /// + /// Extensions for ConcurrentDictionary. + /// + extension(ConcurrentDictionary dict) + { + // Extensions for .NET functionality missing in .NET Standard/Framework. + + public TValue? GetValueOrDefault(TKey key) + => dict.TryGetValue(key, out var value) ? value : default(TValue); + } + } +} +#endif diff --git a/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/DictionaryExtensions.cs b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/DictionaryExtensions.cs new file mode 100644 index 00000000..e410de87 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/DictionaryExtensions.cs @@ -0,0 +1,37 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +#if !NET8_0_OR_GREATER + +using System.Runtime.CompilerServices; + +#pragma warning disable IDE0130 + +// Extensions for .NET functionality missing in .NET Standard/Framework. + +namespace PdfSharp.DotNetFrameworkExtensions +{ + /// + /// Extensions for Dictionary. + /// + public static class DictionaryExtensions + { + extension(Dictionary dict) where TKey : notnull + { + /// + /// Implementation of TryAdd. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool TryAdd(TKey key, TValue value) + { + if (!dict.ContainsKey(key)) + { + dict.Add(key, value); + return true; + } + return false; + } + } + } +} +#endif diff --git a/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/EnumExtensions.cs b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/EnumExtensions.cs new file mode 100644 index 00000000..e66321e6 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/EnumExtensions.cs @@ -0,0 +1,36 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Runtime.CompilerServices; + +#pragma warning disable IDE0130 + +namespace PdfSharp.DotNetFrameworkExtensions +{ + /// + /// PDFsharp extensions for .NET types. + /// + public static class EnumExtensions + { + /// + /// Extensions for Enum. + /// + extension(Enum) + { +#if !NET8_0_OR_GREATER + // Extensions for .NET functionality missing in .NET Standard/Framework. + + /// + /// Brings "Parse<T>(string)" to Enum. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public static T Parse(string name, bool ignoreCase) where T : struct, Enum + { + var enumValue = (int)Enum.Parse(typeof(T), name, ignoreCase); + T result = (T)(enumValue as object); + return result; + } +#endif + } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/StringExtensions.cs b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/StringExtensions.cs new file mode 100644 index 00000000..da7434bb --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/DotNetFrameworkExtensions/StringExtensions.cs @@ -0,0 +1,62 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Runtime.CompilerServices; + +#pragma warning disable IDE0130 + +namespace PdfSharp.DotNetFrameworkExtensions +{ + //#if !NET8_0_OR_GREATER + // /// + // /// Extension methods for functionality missing in .NET Framework. + // /// + // public static class SystemStringExtensions + // { + // /// + // /// Brings "bool StartsWith(char value)" to String class. + // /// + // public static bool StartsWith(this string @string, char value) => @string.Length != 0 && @string[0] == value; + + // /// + // /// Brings "bool EndsWith(char value)" to String class. + // /// + // public static bool EndsWith(this string @string, char value) => @string.Length != 0 && @string[^1] == value; + // } + //#endif + + /// + /// PDFsharp extensions for .NET types. + /// + public static class StringExtensions + { + /// + /// Extensions for String. + /// + extension(String? s) + { + /// + /// The functionality I missed since C# 1.0. + /// + //[MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool NullOrEmpty => String.IsNullOrEmpty(s); + +#if !NET8_0_OR_GREATER + + // Extensions for .NET functionality missing in .NET Standard/Framework. + + /// + /// Brings "bool StartsWith(char value)" to String class. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool StartsWith(char ch) => s?.Length != 0 && s?[0] == ch; + + /// + /// Brings "bool EndsWith(char value)" to String class. + /// + [MethodImpl(MethodImplOptions.AggressiveInlining)] + public bool EndsWith(char ch) => s?.Length != 0 && s?[^1] == ch; +#endif + } + } +} diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Locks.cs b/src/foundation/src/shared/src/PdfSharp.System/Internal.Threading/Locks.cs similarity index 52% rename from src/foundation/src/PDFsharp/src/PdfSharp/Internal/Locks.cs rename to src/foundation/src/shared/src/PdfSharp.System/Internal.Threading/Locks.cs index 56f94938..a439ee1b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Internal/Locks.cs +++ b/src/foundation/src/shared/src/PdfSharp.System/Internal.Threading/Locks.cs @@ -1,28 +1,29 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +#pragma warning disable CS1591 // ...because this is internal stuff. + +using System.Diagnostics; using Microsoft.Extensions.Logging; using PdfSharp.Logging; -namespace PdfSharp.Internal +namespace PdfSharp.Internal.Threading { /// /// Static locking functions to make PDFsharp thread save. - /// POSSIBLE BUG_OLD: Having more than one lock can lead to a deadlock. /// - static class Locks + public static class Locks // Renamed from Lock because of the new Lock class in .NET 9. { public static void EnterGdiPlus() { - Monitor.Enter(GdiPlusLock); //Interlocked.Increment(ref _gdiPlusLockCount); _gdiPlusLockCount++; // Is atomic because we have the lock. - - if (IsFontFactoryLockedByCurrentThread) + + if (IsFontManagementLockedByCurrentThread) { // This should not happen by design. - PdfSharpLogHost.Logger.LogCritical("Entered GDI+ lock while FontFactory lock is also taken."); + LogHost.Logger.LogCritical("Entered GDI+ lock while font management lock is also taken."); } } @@ -33,38 +34,38 @@ public static void ExitGdiPlus() } public static bool IsGdiPlusLockedByCurrentThread => Monitor.IsEntered(GdiPlusLock); - - public static bool IsGdiPlusLookTaken() => _gdiPlusLockCount > 0; + public static bool IsGdiPlusLookTaken() => _gdiPlusLockCount > 0; static readonly object GdiPlusLock = new(); static int _gdiPlusLockCount; - + // ------------------------------------------------------------ - public static void EnterFontFactory() + public static void EnterFontManagement() { - Monitor.Enter(FontFactoryLock); - _fontFactoryLockCount++; // Is atomic because we have the lock. + Monitor.Enter(FontManagementLock); + _fontManagementLockCount++; // Is atomic because we have the lock. if (IsGdiPlusLockedByCurrentThread) { // This should not happen by design. - PdfSharpLogHost.Logger.LogCritical("Entered FontFactory lock while GDI+ lock is also taken."); + LogHost.Logger.LogCritical("Entered font management lock while GDI+ lock is also taken."); } } - public static void ExitFontFactory() + public static void ExitFontManagement() { - _fontFactoryLockCount--; - Monitor.Exit(FontFactoryLock); + _fontManagementLockCount--; + //Debug.Assert(_fontManagementLockCount == 0); + Monitor.Exit(FontManagementLock); } - public static bool IsFontFactoryLockedByCurrentThread => Monitor.IsEntered(FontFactoryLock); + public static bool IsFontManagementLockedByCurrentThread => Monitor.IsEntered(FontManagementLock); + + public static bool IsFontManagementLookTaken() => _fontManagementLockCount > 0; - public static bool IsFontFactoryLookTaken() => _fontFactoryLockCount > 0; - - static readonly object FontFactoryLock = new(); - static int _fontFactoryLockCount; + static readonly object FontManagementLock = new(); + static int _fontManagementLockCount; } } diff --git a/src/foundation/src/shared/src/PdfSharp.System/Internal/AnsiEncoding.cs b/src/foundation/src/shared/src/PdfSharp.System/Internal/AnsiEncoding.cs new file mode 100644 index 00000000..d58384d9 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/Internal/AnsiEncoding.cs @@ -0,0 +1,309 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using System.Text; + +namespace PdfSharp.Internal +{ + /// + /// An encoder use for PDF WinAnsi encoding. + /// It is by design not to use CodePagesEncodingProvider.Instance.GetEncoding(1252). + /// However, AnsiEncoding is equivalent to Windows-1252 (CP-1252), + /// see https://en.wikipedia.org/wiki/Windows-1252 + /// + public sealed class AnsiEncoding : Encoding + { + /// + /// Gets the byte count. + /// + public override int GetByteCount(char[] chars, int index, int count) => count; + + /// + /// Gets the bytes. + /// + public override int GetBytes(char[] chars, int charIndex, int charCount, byte[] bytes, int byteIndex) + { + int count = charCount; + for (; charCount > 0; byteIndex++, charIndex++, charCount--) + { + var ch = chars[charIndex]; + bytes[byteIndex] = (byte)UnicodeToAnsi(ch/*, ch*/); + } + return count; + } + + /// + /// Gets the character count. + /// + public override int GetCharCount(byte[] bytes, int index, int count) => count; + + /// + /// Gets the chars. + /// + public override int GetChars(byte[] bytes, int byteIndex, int byteCount, char[] chars, int charIndex) + { + for (int idx = byteCount; idx > 0; byteIndex++, charIndex++, idx--) + chars[charIndex] = AnsiToUnicode[bytes[byteIndex]]; + return byteCount; + } + + /// + /// When overridden in a derived class, calculates the maximum number of bytes produced by encoding the specified number of characters. + /// + /// The number of characters to encode. + /// + /// The maximum number of bytes produced by encoding the specified number of characters. + /// + public override int GetMaxByteCount(int charCount) => charCount; + + /// + /// When overridden in a derived class, calculates the maximum number of characters produced by decoding the specified number of bytes. + /// + /// The number of bytes to decode. + /// + /// The maximum number of characters produced by decoding the specified number of bytes. + /// + public override int GetMaxCharCount(int byteCount) => byteCount; + + /// + /// Gets the ANSI encoder singleton. + /// + public static AnsiEncoding Encoder => field ??= new AnsiEncoding(); + + /// + /// Indicates whether the specified Unicode BMP character is available in the ANSI code page 1252. + /// + public static bool IsAnsi(char ch) + { + if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') + return true; + + return ch switch + { + '\u20AC' => true, + '\u0081' => false, + '\u201A' => true, + '\u0192' => true, + '\u201E' => true, + '\u2026' => true, + '\u2020' => true, + '\u2021' => true, + '\u02C6' => true, + '\u2030' => true, + '\u0160' => true, + '\u2039' => true, + '\u0152' => true, + '\u008D' => false, + '\u017D' => true, + '\u008F' => false, + '\u0090' => false, + '\u2018' => true, + '\u2019' => true, + '\u201C' => true, + '\u201D' => true, + '\u2022' => true, + '\u2013' => true, + '\u2014' => true, + '\u02DC' => true, + '\u2122' => true, + '\u0161' => true, + '\u203A' => true, + '\u0153' => true, + '\u009D' => false, + '\u017E' => true, + '\u0178' => true, + _ => false + }; + } + + /// + /// Indicates whether the specified string is available in the ANSI code page 1252. + /// + public static bool IsAnsi(string s) + { + var length = s.Length; + for (int idx = 0; idx < length; idx++) + { + char ch = s[idx]; + + if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') + continue; + + if (ch switch + { + '\u20AC' => true, + '\u0081' => false, // undefined + '\u201A' => true, + '\u0192' => true, + '\u201E' => true, + '\u2026' => true, + '\u2020' => true, + '\u2021' => true, + '\u02C6' => true, + '\u2030' => true, + '\u0160' => true, + '\u2039' => true, + '\u0152' => true, + '\u008D' => false, // undefined + '\u017D' => true, + '\u008F' => false, // undefined + '\u0090' => false, // undefined + '\u2018' => true, + '\u2019' => true, + '\u201C' => true, + '\u201D' => true, + '\u2022' => true, + '\u2013' => true, + '\u2014' => true, + '\u02DC' => true, + '\u2122' => true, + '\u0161' => true, + '\u203A' => true, + '\u0153' => true, + '\u009D' => false, // undefined + '\u017E' => true, + '\u0178' => true, + _ => false + } is false) + return false; + } + return true; + } + + /// + /// Indicates whether all code points in the specified array are available in the ANSI code page 1252. + /// + public static bool IsAnsi(int[] codePoints) + { + var length = codePoints.Length; + for (int idx = 0; idx < length; idx++) + { + int ch = codePoints[idx]; + + if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') + continue; + + // There are 6 values between 128 and 255 that are not part of the original ANSI character set. + // U+00AD was later added for the soft hyphen. The remaining 5 undefined values (see below) are + // no valid ANSI characters. All of them are C1 control characters (from U+0080 to U+009F) in + // Unicode. Therefore, we return false here. + if (ch switch + { + '\u20AC' => true, + '\u0081' => false, // undefined + '\u201A' => true, + '\u0192' => true, + '\u201E' => true, + '\u2026' => true, + '\u2020' => true, + '\u2021' => true, + '\u02C6' => true, + '\u2030' => true, + '\u0160' => true, + '\u2039' => true, + '\u0152' => true, + '\u008D' => false, // undefined + '\u017D' => true, + '\u008F' => false, // undefined + '\u0090' => false, // undefined + '\u2018' => true, + '\u2019' => true, + '\u201C' => true, + '\u201D' => true, + '\u2022' => true, + '\u2013' => true, + '\u2014' => true, + '\u02DC' => true, + '\u2122' => true, + '\u0161' => true, + '\u203A' => true, + '\u0153' => true, + '\u009D' => false, // undefined + '\u017E' => true, + '\u0178' => true, + _ => false + } is false) + return false; + } + return true; + } + + /// + /// Maps Unicode to ANSI (CP-1252). + /// Return an ANSI code in a char or the value of parameter nonAnsi + /// if Unicode value has no ANSI counterpart. + /// + public static char UnicodeToAnsi(char ch, char nonAnsi = '\u003F' /* '?' */) + { + if (ch is < '\u0080' or >= '\u00A0' and <= '\u00FF') + return ch; + + // Unicode code points from U-0080 to U-009F are no + // valid ANSI characters in a PDF file. + // But the 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to + // themselves. This is the same as .NET handles them. + return ch switch + { + '\u20AC' => '\u0080', + '\u0081' => '\u0081', // undefined, but in ANSI range. + '\u201A' => '\u0082', + '\u0192' => '\u0083', + '\u201E' => '\u0084', + '\u2026' => '\u0085', + '\u2020' => '\u0086', + '\u2021' => '\u0087', + '\u02C6' => '\u0088', + '\u2030' => '\u0089', + '\u0160' => '\u008A', + '\u2039' => '\u008B', + '\u0152' => '\u008C', + '\u008D' => '\u008D', // undefined, but in ANSI range. + '\u017D' => '\u008E', + '\u008F' => '\u008F', // undefined, but in ANSI range. + '\u0090' => '\u0090', // undefined, but in ANSI range. + '\u2018' => '\u0091', + '\u2019' => '\u0092', + '\u201C' => '\u0093', + '\u201D' => '\u0094', + '\u2022' => '\u0095', + '\u2013' => '\u0096', + '\u2014' => '\u0097', + '\u02DC' => '\u0098', + '\u2122' => '\u0099', + '\u0161' => '\u009A', + '\u203A' => '\u009B', + '\u0153' => '\u009C', + '\u009D' => '\u009D', // undefined, but in ANSI range. + '\u017E' => '\u009E', + '\u0178' => '\u009F', + _ => nonAnsi + }; + } + + /// + /// Maps WinAnsi to Unicode characters. + /// The 5 undefined ANSI value 81, 8D, 8F, 90, and 9D are mapped to + /// the C1 control code. This is the same as .NET handles them. + /// + static readonly char[] AnsiToUnicode = + [ + // 00 01 02 03 04 05 06 07 08 09 0A 0B 0C 0D 0E 0F + /* 00 */ '\u0000', '\u0001', '\u0002', '\u0003', '\u0004', '\u0005', '\u0006', '\u0007', '\u0008', '\u0009', '\u000A', '\u000B', '\u000C', '\u000D', '\u000E', '\u000F', + /* 10 */ '\u0010', '\u0011', '\u0012', '\u0013', '\u0014', '\u0015', '\u0016', '\u0017', '\u0018', '\u0019', '\u001A', '\u001B', '\u001C', '\u001D', '\u001E', '\u001F', + /* 20 */ '\u0020', '\u0021', '\u0022', '\u0023', '\u0024', '\u0025', '\u0026', '\u0027', '\u0028', '\u0029', '\u002A', '\u002B', '\u002C', '\u002D', '\u002E', '\u002F', + /* 30 */ '\u0030', '\u0031', '\u0032', '\u0033', '\u0034', '\u0035', '\u0036', '\u0037', '\u0038', '\u0039', '\u003A', '\u003B', '\u003C', '\u003D', '\u003E', '\u003F', + /* 40 */ '\u0040', '\u0041', '\u0042', '\u0043', '\u0044', '\u0045', '\u0046', '\u0047', '\u0048', '\u0049', '\u004A', '\u004B', '\u004C', '\u004D', '\u004E', '\u004F', + /* 50 */ '\u0050', '\u0051', '\u0052', '\u0053', '\u0054', '\u0055', '\u0056', '\u0057', '\u0058', '\u0059', '\u005A', '\u005B', '\u005C', '\u005D', '\u005E', '\u005F', + /* 60 */ '\u0060', '\u0061', '\u0062', '\u0063', '\u0064', '\u0065', '\u0066', '\u0067', '\u0068', '\u0069', '\u006A', '\u006B', '\u006C', '\u006D', '\u006E', '\u006F', + /* 70 */ '\u0070', '\u0071', '\u0072', '\u0073', '\u0074', '\u0075', '\u0076', '\u0077', '\u0078', '\u0079', '\u007A', '\u007B', '\u007C', '\u007D', '\u007E', '\u007F', + /* 80 */ '\u20AC', '\u0081', '\u201A', '\u0192', '\u201E', '\u2026', '\u2020', '\u2021', '\u02C6', '\u2030', '\u0160', '\u2039', '\u0152', '\u008D', '\u017D', '\u008F', + /* 90 */ '\u0090', '\u2018', '\u2019', '\u201C', '\u201D', '\u2022', '\u2013', '\u2014', '\u02DC', '\u2122', '\u0161', '\u203A', '\u0153', '\u009D', '\u017E', '\u0178', + /* A0 */ '\u00A0', '\u00A1', '\u00A2', '\u00A3', '\u00A4', '\u00A5', '\u00A6', '\u00A7', '\u00A8', '\u00A9', '\u00AA', '\u00AB', '\u00AC', '\u00AD', '\u00AE', '\u00AF', + /* B0 */ '\u00B0', '\u00B1', '\u00B2', '\u00B3', '\u00B4', '\u00B5', '\u00B6', '\u00B7', '\u00B8', '\u00B9', '\u00BA', '\u00BB', '\u00BC', '\u00BD', '\u00BE', '\u00BF', + /* C0 */ '\u00C0', '\u00C1', '\u00C2', '\u00C3', '\u00C4', '\u00C5', '\u00C6', '\u00C7', '\u00C8', '\u00C9', '\u00CA', '\u00CB', '\u00CC', '\u00CD', '\u00CE', '\u00CF', + /* D0 */ '\u00D0', '\u00D1', '\u00D2', '\u00D3', '\u00D4', '\u00D5', '\u00D6', '\u00D7', '\u00D8', '\u00D9', '\u00DA', '\u00DB', '\u00DC', '\u00DD', '\u00DE', '\u00DF', + /* E0 */ '\u00E0', '\u00E1', '\u00E2', '\u00E3', '\u00E4', '\u00E5', '\u00E6', '\u00E7', '\u00E8', '\u00E9', '\u00EA', '\u00EB', '\u00EC', '\u00ED', '\u00EE', '\u00EF', + /* F0 */ '\u00F0', '\u00F1', '\u00F2', '\u00F3', '\u00F4', '\u00F5', '\u00F6', '\u00F7', '\u00F8', '\u00F9', '\u00FA', '\u00FB', '\u00FC', '\u00FD', '\u00FE', '\u00FF' + ]; + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.System/Internal/BuildVersion.cs b/src/foundation/src/shared/src/PdfSharp.System/Internal/BuildVersion.cs index 39eccbb7..891d4be5 100644 --- a/src/foundation/src/shared/src/PdfSharp.System/Internal/BuildVersion.cs +++ b/src/foundation/src/shared/src/PdfSharp.System/Internal/BuildVersion.cs @@ -18,7 +18,7 @@ // /// The PDFsharp build version used as fourth value in AssemblyFileVersion. // /// Must be set in gitversion.yml. // /// -// public static int BuildVersionNumber = (DateTime.Now - new DateTime(2005, 1, 1)).Days; +// public static int BuildVersionNumber = (Date/Time.Now - new Date/Time(2005, 1, 1)).Days; // } //#endif //} diff --git a/src/foundation/src/shared/src/PdfSharp.System/Logging/LogMessages.cs b/src/foundation/src/shared/src/PdfSharp.System/Logging/LogMessages.cs new file mode 100644 index 00000000..80b905f2 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/Logging/LogMessages.cs @@ -0,0 +1,171 @@ +////// PDFsharp - A .NET library for processing PDF +////// See the LICENSE file in the solution root for more information. + +////using Microsoft.Extensions.Logging; +////using PdfSharp.Pdf; + +////#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member because it is for internal use only. + +////namespace PdfSharp.Logging +////{ +//// /// +//// /// Defines the logging event IDs of PDFsharp. +//// /// +//// public static class PdfSharpEventId // draft... +//// { +//// public const int DocumentCreated = StartId + 1; +//// public const int DocumentSaved = StartId + 2; +//// public const int PageCreated = StartId + 3; +//// public const int PageAdded = StartId + 4; +//// public const int GraphicsCreated = StartId + 5; +//// public const int FontCreated = StartId + 6; + +//// // Reading PDFs +//// public const int PdfReaderIssue = StartId + 10; +//// public const int StreamIssue = StartId + 11; +//// public const int EndOfStreamReached = StartId + 12; +//// public const int SkippedIllegalBlanksAfterStreamKeyword = StartId + 13; +//// public const int StreamKeywordFollowedBySingleCR = StartId + 14; +//// public const int StreamKeywordFollowedByIllegalBytes = StartId + 15; + +//// internal const int Placeholder = StartId + 1234; +//// const int StartId = 50000; +//// }; + +//// public static class PdfSharpEventName +//// { +//// public const string DocumentCreated = "Document created"; +//// public const string DocumentSaved = "Document saved"; +//// public const string PageCreated = "Page created"; +//// public const string PageAdded = "Page creation2"; +//// public const string GraphicsCreated = "Graphics created"; +//// public const string FontCreated = "Font created"; + +//// public const string PdfReaderIssue = "PDF reader issue"; +//// public const string StreamIssue = "Stream issue"; +//// public const string EndOfStreamReached = "End of stream reached"; +//// public const string SkippedIllegalBlanksAfterStreamKeyword = "Skipped illegal blanks after stream keyword"; +//// public const string StreamKeywordFollowedBySingleCR = "Stream keyword followed by single CR"; +//// public const string StreamKeywordFollowedByIllegalBytes = "Stream keyword followed by illegal bytes"; +//// } + +//// public static class PdfSharpEvent +//// { +//// public static EventId DocumentCreate = new(PdfSharpEventId.DocumentCreated, PdfSharpEventName.DocumentCreated); +//// public static EventId DocumentSaved = new(PdfSharpEventId.DocumentSaved, PdfSharpEventName.DocumentSaved); +//// public static EventId PageCreate = new(PdfSharpEventId.PageCreated, PdfSharpEventName.PageCreated); +//// public static EventId PageAdded = new(PdfSharpEventId.PageAdded, PdfSharpEventName.PageAdded); +//// public static EventId FontCreate = new(PdfSharpEventId.FontCreated, PdfSharpEventName.FontCreated); + +//// public static EventId PdfReaderIssue = new(PdfSharpEventId.PdfReaderIssue, PdfSharpEventName.PdfReaderIssue); + +//// public static EventId Placeholder = new(999999, "Placeholder"); +//// } + +//// /// +//// /// Defines the logging high performance messages of PDFsharp. +//// /// +//// public static partial class LogMessages +//// { +////#pragma warning disable SYSLIB1006 + +//// [LoggerMessage( +//// Level = LogLevel.Information, +//// EventId = PdfSharpEventId.DocumentCreated, +//// EventName = PdfSharpEventName.DocumentCreated, +//// Message = "New PDF document '{DocumentName}' created.")] +//// public static partial void PdfDocumentCreated(this ILogger logger, +//// string? documentName); + +//// [LoggerMessage( +//// Level = LogLevel.Information, +//// EventId = PdfSharpEventId.DocumentSaved, +//// EventName = PdfSharpEventName.DocumentSaved, +//// Message = "PDF document '{documentName}' saved.")] +//// public static partial void PdfDocumentSaved(this ILogger logger, +//// string? documentName); + +//// [LoggerMessage( +//// Level = LogLevel.Information, +//// EventId = PdfSharpEventId.PageCreated, +//// EventName = PdfSharpEventName.PageCreated, +//// Message = "New PDF page added to document '{documentName}'.")] +//// public static partial void NewPdfPageCreated(this ILogger logger, +//// string? documentName); + +//// [LoggerMessage( +//// Level = LogLevel.Information, +//// EventId = PdfSharpEventId.PageAdded, +//// EventName = PdfSharpEventName.PageAdded, +//// Message = "Existing PDF page added to document '{documentName}'.")] +//// public static partial void ExistingPdfPageAdded(this ILogger logger, +//// string? documentName); + +//// [LoggerMessage( +//// Level = LogLevel.Information, +//// EventId = PdfSharpEventId.GraphicsCreated, +//// EventName = PdfSharpEventName.GraphicsCreated, +//// Message = "New XGraphics created from '{source}'.")] +//// public static partial void XGraphicsCreated(this ILogger logger, +//// string? source); + +//// // Reading PDFs + +//// [LoggerMessage( +//// Level = LogLevel.Error, +//// EventId = PdfSharpEventId.StreamIssue, +//// EventName = PdfSharpEventName.StreamIssue, +//// Message = "{Status} {BytesRead} of {Length} bytes were received. " + +//// "We strongly recommend using streams with PdfReader whose content is fully available. " + +//// "Copy the stream containing the file to a MemoryStream for example.")] +//// public static partial void StreamIssue(this ILogger logger, +//// string status, int bytesRead, int length); + +//// [LoggerMessage( +//// Level = LogLevel.Warning, +//// EventId = PdfSharpEventId.EndOfStreamReached, +//// EventName = PdfSharpEventName.EndOfStreamReached, +//// Message = "End of stream reached while reading {Length} bytes at position {Position}, but got only {BytesRead} bytes.")] +//// public static partial void EndOfStreamReached(this ILogger logger, +//// int length, SizeType position, int bytesRead); + +//// [LoggerMessage( +//// Level = LogLevel.Warning, +//// EventId = PdfSharpEventId.SkippedIllegalBlanksAfterStreamKeyword, +//// EventName = PdfSharpEventName.SkippedIllegalBlanksAfterStreamKeyword, +//// Message = "Skipped {BlankCount} illegal blanks behind keyword 'stream' at position {Position} in object {ObjectId}.")] +//// public static partial void SkippedIllegalBlanksAfterStreamKeyword(this ILogger logger, +//// int blankCount, SizeType position, PdfObjectID objectId); + +//// [LoggerMessage( +//// Level = LogLevel.Warning, +//// EventId = PdfSharpEventId.StreamKeywordFollowedBySingleCR, +//// EventName = PdfSharpEventName.StreamKeywordFollowedBySingleCR, +//// Message = "Keyword 'stream' followed by single CR is illegal at position {Position} in object {ObjectId}.")] +//// public static partial void StreamKeywordFollowedBySingleCR(this ILogger logger, +//// SizeType position, PdfObjectID objectId); + +//// [LoggerMessage( +//// Level = LogLevel.Warning, +//// EventId = PdfSharpEventId.StreamKeywordFollowedByIllegalBytes, +//// EventName = PdfSharpEventName.StreamKeywordFollowedByIllegalBytes, +//// Message = "Keyword 'stream' followed by illegal bytes at position {Position} in object {ObjectId}.")] +//// public static partial void StreamKeywordFollowedByIllegalBytes(this ILogger logger, +//// SizeType position, PdfObjectID objectId); + +//// //[LoggerMessage(EventId = 23, EventName = "hallo", Level = LogLevel.Warning, Message = "This is a warning: `{someText}`")] +//// //public static partial void WarningMessage(this ILogger logger, string someText); +//// } + +////#if true_ +//// class LogTestCode +//// { +//// void FooBar() +//// { +//// //var ss = PSEventId.Test1; +//// //PdfSharpLogHost.Logger.LogError(ss, "message"); +//// LoggerMessage.Define(LogLevel.Critical, ) +//// } +//// } +////#endif +////} diff --git a/src/foundation/src/shared/src/PdfSharp.System/Logging/PdfSharpLogHost.cs b/src/foundation/src/shared/src/PdfSharp.System/Logging/PdfSharpLogHost.cs new file mode 100644 index 00000000..b2dbb506 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/Logging/PdfSharpLogHost.cs @@ -0,0 +1,138 @@ +////// PDFsharp - A .NET library for processing PDF +////// See the LICENSE file in the solution root for more information. + +////using Microsoft.Extensions.Logging; + +////namespace PdfSharp.Logging +////{ +//// /// +//// /// Defines the logging categories of PDFsharp. +//// /// +//// public static class PdfSharpLogCategory +//// { +//// /// +//// /// Logger category for creating or saving documents, adding or removing pages, +//// /// and other document level specific action.s +//// /// +//// public const string DocumentProcessing = "Document processing"; + +//// /// +//// /// Logger category for processing bitmap images. +//// /// +//// public const string ImageProcessing = "Image processing"; + +//// /// +//// /// Logger category for creating XFont objects. +//// /// +//// public const string FontManagement = "Font management"; + +//// /// +//// /// Logger category for reading PDF documents. +//// /// +//// public const string PdfReading = "PDF reading"; +//// } + +//// /// +//// /// Provides a single host for logging in PDFsharp. +//// /// The logger factory is taken from LogHost. +//// /// +//// public static class PdfSharpLogHost +//// { +//// /// +//// /// Gets the general PDFsharp logger. +//// /// This the same you get from LogHost.Logger. +//// /// +//// public static ILogger Logger => LogHost.Logger; + +//// #region Specific logger + +//// /// +//// /// Gets the global PDFsharp font management logger. +//// /// +//// public static ILogger DocumentProcessingLogger +//// { +//// get +//// { +//// // We do not need lock, because even creating two loggers has no negative effects. +//// //lock (typeof(MigraDocLogHost)) // Keep for documenting that it is by design not to lock. +//// { +//// CheckFactoryHasChanged(); +//// return _documentProcessingLogger ??= LogHost.CreateLogger(PdfSharpLogCategory.DocumentProcessing); +//// } +//// } +//// } +//// static ILogger? _documentProcessingLogger; + +//// /// +//// /// Gets the global PDFsharp image processing logger. +//// /// +//// public static ILogger ImageProcessingLogger +//// { +//// get +//// { +//// //lock (typeof(MigraDocLogHost)) // Keep for documenting that it is by design not to lock. +//// { +//// CheckFactoryHasChanged(); +//// return _imageProcessingLogger ??= LogHost.CreateLogger(PdfSharpLogCategory.ImageProcessing); +//// } +//// } +//// } +//// static ILogger? _imageProcessingLogger; + +//// /// +//// /// Gets the global PDFsharp font management logger. +//// /// +//// public static ILogger FontManagementLogger +//// { +//// get +//// { +//// //lock (typeof(MigraDocLogHost)) // Keep for documenting that it is by design not to lock. +//// { +//// CheckFactoryHasChanged(); +//// return _fontManagementLogger ??= LogHost.CreateLogger(PdfSharpLogCategory.FontManagement); +//// } +//// } +//// } +//// static ILogger? _fontManagementLogger; + +//// /// +//// /// Gets the global PDFsharp document reading logger. +//// /// +//// public static ILogger PdfReadingLogger +//// { +//// get +//// { +//// //lock (typeof(MigraDocLogHost)) // Keep for documenting that it is by design not to lock. +//// { +//// CheckFactoryHasChanged(); +//// return _pdfReadingLogger ??= LogHost.CreateLogger(PdfSharpLogCategory.PdfReading); +//// } +//// } +//// } +//// static ILogger? _pdfReadingLogger; +//// #endregion + +//// static void CheckFactoryHasChanged() +//// { +//// // Sync with LogHost factory. +//// if (!ReferenceEquals(_factory, LogHost.Factory)) +//// { +//// ResetLogging(); +//// _factory = LogHost.Factory; +//// } +//// } +//// static ILoggerFactory? _factory; + +//// /// +//// /// Resets all loggers after an update of global logging factory. +//// /// +//// internal static void ResetLogging() +//// { +//// _factory = default; +//// _documentProcessingLogger = default; +//// _imageProcessingLogger = default; +//// _fontManagementLogger = default; +//// _pdfReadingLogger = default; +//// } +//// } +////} diff --git a/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj b/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj index a280d2b1..f49975c7 100644 --- a/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj +++ b/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj @@ -1,13 +1,15 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp True ..\..\..\..\..\StrongnameKey.snk - true - true + + + + diff --git a/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj.DotSettings b/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj.DotSettings index 2d7c7bb8..24666374 100644 --- a/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj.DotSettings +++ b/src/foundation/src/shared/src/PdfSharp.System/PdfSharp.System.csproj.DotSettings @@ -1,4 +1,5 @@  + False True True True \ No newline at end of file diff --git a/src/foundation/src/shared/src/PdfSharp.System/PlugIn/IPdfSharpPlugIn.cs b/src/foundation/src/shared/src/PdfSharp.System/PlugIn/IPdfSharpPlugIn.cs new file mode 100644 index 00000000..5104f69d --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/PlugIn/IPdfSharpPlugIn.cs @@ -0,0 +1,28 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +//using System; +//using System.Collections.Generic; +//using System.Linq; +//using System.Text; +//using System.Threading.Tasks; + +namespace PdfSharp.PlugIn +{ + /// + /// Provisionally plug-in interface. + /// Internal use only. + /// + public interface IPdfSharpPlugInV0 + { + /// + /// Gets the GUID of the plug-in. + /// + Guid ID { get; } + + /// + /// Gets the name of the plug-in. + /// + string Name { get; } + } +} diff --git a/src/foundation/src/shared/src/PdfSharp.System/Properties/FloatOrDouble.cs b/src/foundation/src/shared/src/PdfSharp.System/Properties/FloatOrDouble.cs new file mode 100644 index 00000000..d117d335 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/Properties/FloatOrDouble.cs @@ -0,0 +1,22 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// WPF, XGraphics, SVG and other libraries use double for numbers, +// while DirectWrite, DirectDraw, Win2D, and other libraries use float_. +// I don’t want to tie me to one of these and keep the code switchable. +// I use float_ over nmBr, float_Δ, floaτ, and other ideas of unique type names. + +////// Move to Directory.Create.targets +////#define USE_64BIT_FLOATS_XXX // Does not yet compile with double. + +// ReSharper disable once IdentifierTypo +#pragma warning disable CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. +// Use a unique name that can be globally switched or permanently replaced with search and replace if some time wanted. +#if USE_64BIT_NUMBERS +global using float_ = dobule; +global using FLOAT_ = double; +#else +global using float_ = float; // Use for keyword float_ or double in the context of PDFsharp Graphics. +global using FLOAT_ = float; // Use for Type Single or Double in the context of PDFsharp Graphics. +#endif +#pragma warning restore CS8981 // The type name only contains lower-cased ascii characters. Such names may become reserved for the language. \ No newline at end of file diff --git a/src/foundation/src/shared/src/PdfSharp.System/Properties/GlobalDeclarations.cs b/src/foundation/src/shared/src/PdfSharp.System/Properties/GlobalDeclarations.cs index 1f8cc8db..67e38bea 100644 --- a/src/foundation/src/shared/src/PdfSharp.System/Properties/GlobalDeclarations.cs +++ b/src/foundation/src/shared/src/PdfSharp.System/Properties/GlobalDeclarations.cs @@ -1,9 +1,6 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. -//global using System.IO; - - #if USE_LONG_SIZE global using SizeType = System.Int64; #else @@ -17,5 +14,3 @@ using System.Runtime.InteropServices; [assembly: ComVisible(false)] -//[assembly: SuppressMessage("LoggingGenerator", "SYSLIB1006:Multiple logging methods cannot use the same event ID within a class", -// Justification = "We use logging event IDs as documented, i.e. multiple times", Scope = "member"/*, Target = "~M:PdfSharp.Internal.Logging.LogMessages.XGraphicsCreated(Microsoft.Extensions.Logging.ILogger,System.String)"*/)] diff --git a/src/foundation/src/shared/src/PdfSharp.System/dotnet/CodeAnalysis.cs b/src/foundation/src/shared/src/PdfSharp.System/dotnet/CodeAnalysis.cs new file mode 100644 index 00000000..6220650b --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/dotnet/CodeAnalysis.cs @@ -0,0 +1,143 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +namespace System.Diagnostics.CodeAnalysis +{ +#if false //!NET8_0_OR_GREATER + /// + /// Indicates that certain members on a specified are accessed dynamically, + /// for example through . + /// + /// + /// This allows tools to understand which members are being accessed during the execution + /// of a program. + /// + /// This attribute is valid on members whose type is or . + /// + /// When this attribute is applied to a location of type , the assumption is + /// that the string represents a fully qualified type name. + /// + /// When this attribute is applied to a class, interface, or struct, the members specified + /// can be accessed dynamically on instances returned from calling + /// on instances of that class, interface, or struct. + /// + /// If the attribute is applied to a method it's treated as a special case and it implies + /// the attribute should be applied to the "this" parameter of the method. As such the attribute + /// should only be used on instance methods of types assignable to System.Type (or string, but no methods + /// will use it there). + /// + [AttributeUsage( + AttributeTargets.Field | AttributeTargets.ReturnValue | AttributeTargets.GenericParameter | + AttributeTargets.Parameter | AttributeTargets.Property | AttributeTargets.Method | + AttributeTargets.Class | AttributeTargets.Interface | AttributeTargets.Struct, + Inherited = false)] + internal sealed class DynamicallyAccessedMembersAttribute : Attribute + { + /// + /// Initializes a new instance of the class + /// with the specified member types. + /// + /// The types of members dynamically accessed. + public DynamicallyAccessedMembersAttribute(DynamicallyAccessedMemberTypes memberTypes) + { + MemberTypes = memberTypes; + } + + /// + /// Gets the which specifies the type + /// of members dynamically accessed. + /// + public DynamicallyAccessedMemberTypes MemberTypes { get; } + } + + /// + /// Specifies the types of members that are dynamically accessed. + /// + /// This enumeration has a attribute that allows a + /// bitwise combination of its member values. + /// + [Flags] + internal enum DynamicallyAccessedMemberTypes + { + /// + /// Specifies no members. + /// + None = 0, + + /// + /// Specifies the default, parameterless public constructor. + /// + PublicParameterlessConstructor = 0x0001, + + /// + /// Specifies all public constructors. + /// + PublicConstructors = 0x0002 | PublicParameterlessConstructor, + + /// + /// Specifies all non-public constructors. + /// + NonPublicConstructors = 0x0004, + + /// + /// Specifies all public methods. + /// + PublicMethods = 0x0008, + + /// + /// Specifies all non-public methods. + /// + NonPublicMethods = 0x0010, + + /// + /// Specifies all public fields. + /// + PublicFields = 0x0020, + + /// + /// Specifies all non-public fields. + /// + NonPublicFields = 0x0040, + + /// + /// Specifies all public nested types. + /// + PublicNestedTypes = 0x0080, + + /// + /// Specifies all non-public nested types. + /// + NonPublicNestedTypes = 0x0100, + + /// + /// Specifies all public properties. + /// + PublicProperties = 0x0200, + + /// + /// Specifies all non-public properties. + /// + NonPublicProperties = 0x0400, + + /// + /// Specifies all public events. + /// + PublicEvents = 0x0800, + + /// + /// Specifies all non-public events. + /// + NonPublicEvents = 0x1000, + + /// + /// Specifies all interfaces implemented by the type. + /// + Interfaces = 0x2000, + + /// + /// Specifies all members. + /// + All = ~None + } +#endif +} diff --git a/src/foundation/src/shared/src/PdfSharp.System/dotnet/CompilerServices2.cs b/src/foundation/src/shared/src/PdfSharp.System/dotnet/CompilerServices2.cs new file mode 100644 index 00000000..e865d352 --- /dev/null +++ b/src/foundation/src/shared/src/PdfSharp.System/dotnet/CompilerServices2.cs @@ -0,0 +1,99 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +// This file contains code from the .NET source to use some newer C# features in .NET Standard / Framework. +// All classes are internal, i.e. using PDFsharp packages does not make this functionality visible in +// your projects. + +#pragma warning disable IDE0130 // Namespace does not match folder structure +namespace System.Runtime.CompilerServices +#pragma warning restore IDE0130 // Namespace does not match folder structure +{ +#if false //!NET8_0_OR_GREATER + /// + /// Extension method GetSubArray required for the built-in range operator (e.g.'[1..9]'). + /// Fun fact: This class must be compiled into each assembly. If it is only visible through + /// InternalsVisibleTo code will not compile with .NET Framework 4.6.2 and .NET Standard 2.0. + /// + /*public*/ + static class RuntimeHelpers xxx + { + /// + /// Slices the specified array using the specified range. + /// + public static T[] GetSubArray(T[] array, Range range) + { + if (array == null) + throw new ArgumentNullException(nameof(array)); + + (int offset, int length) = range.GetOffsetAndLength(array.Length); + + if (default(T) != null || typeof(T[]) == array.GetType()) + { + // We know the type of the array to be exactly T[]. + if (length == 0) + return []; + + var dest = new T[length]; + Array.Copy(array, offset, dest, 0, length); + return dest; + } + else + { + // The array is actually a U[] where U:T. + var dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); + Array.Copy(array, offset, dest, 0, length); + return dest; + } + } + } + + /// + /// Specifies that a type has required members or that a member is required. + /// + [AttributeUsage(AttributeTargets.Class | AttributeTargets.Struct | AttributeTargets.Field | AttributeTargets.Property, + AllowMultiple = false, Inherited = false)] + [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal sealed class RequiredMemberAttribute : global::System.Attribute + { + public RequiredMemberAttribute() { } + } + + /// + /// Indicates that compiler support for a particular feature is required for the location where this attribute is applied. + /// + [AttributeUsage(global::System.AttributeTargets.All, AllowMultiple = true, Inherited = false)] + [Diagnostics.CodeAnalysis.ExcludeFromCodeCoverage] + internal sealed class CompilerFeatureRequiredAttribute : global::System.Attribute + { + /// + /// Creates a new instance of the type. + /// + /// The name of the feature to indicate. + public CompilerFeatureRequiredAttribute(string featureName) + { + FeatureName = featureName; + } + + /// + /// The name of the compiler feature. + /// + public string FeatureName { get; } + + /// + /// If true, the compiler can choose to allow access to the location where this attribute is applied if it does not understand . + /// + public bool IsOptional { get; set; } + + /// + /// The used for the ref structs C# feature. + /// + public const string RefStructs = nameof(RefStructs); + + /// + /// The used for the required members C# feature. + /// + public const string RequiredMembers = nameof(RequiredMembers); + } +#endif +} diff --git a/src/foundation/src/shared/src/PdfSharp.System/extensions/SystemStringExtensions.cs b/src/foundation/src/shared/src/PdfSharp.System/extensions/SystemStringExtensions.cs deleted file mode 100644 index 685ce093..00000000 --- a/src/foundation/src/shared/src/PdfSharp.System/extensions/SystemStringExtensions.cs +++ /dev/null @@ -1,24 +0,0 @@ -// PDFsharp - A .NET library for processing PDF -// See the LICENSE file in the solution root for more information. - -using System; -using System.Collections.Generic; -using System.Linq; -using System.Text; -using System.Threading.Tasks; - -namespace PdfSharp -{ -#if !NET6_0_OR_GREATER - /// - /// Extension methods for functionality missing in .NET Framework. - /// - public static class SystemStringExtensions - { - /// - /// Brings "bool StartsWith(char value)" to String class. - /// - public static bool StartsWith(this string @string, char value) => @string.Length != 0 && @string[0] == value; - } -#endif -} diff --git a/src/foundation/src/shared/src/PdfSharp.Testing-gdi/PdfSharp.Testing-gdi.csproj b/src/foundation/src/shared/src/PdfSharp.Testing-gdi/PdfSharp.Testing-gdi.csproj index d3e2a67b..2ed22dd2 100644 --- a/src/foundation/src/shared/src/PdfSharp.Testing-gdi/PdfSharp.Testing-gdi.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Testing-gdi/PdfSharp.Testing-gdi.csproj @@ -1,7 +1,7 @@ - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) PdfSharp.Testing_gdi enable enable diff --git a/src/foundation/src/shared/src/PdfSharp.Testing-wpf/PdfSharp.Testing-wpf.csproj b/src/foundation/src/shared/src/PdfSharp.Testing-wpf/PdfSharp.Testing-wpf.csproj index f255f9cd..28e2fda1 100644 --- a/src/foundation/src/shared/src/PdfSharp.Testing-wpf/PdfSharp.Testing-wpf.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Testing-wpf/PdfSharp.Testing-wpf.csproj @@ -1,7 +1,7 @@ - net8.0-windows;net9.0-windows;net10.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) PdfSharp.Testing_wpf enable enable diff --git a/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj b/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj index 808424f0..7815a3b4 100644 --- a/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj +++ b/src/foundation/src/shared/src/PdfSharp.Testing/PdfSharp.Testing.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) PdfSharp True diff --git a/src/foundation/src/shared/src/PdfSharp.WPFonts/PdfSharp.WPFonts.csproj b/src/foundation/src/shared/src/PdfSharp.WPFonts/PdfSharp.WPFonts.csproj index 901b466e..e45b7dba 100644 --- a/src/foundation/src/shared/src/PdfSharp.WPFonts/PdfSharp.WPFonts.csproj +++ b/src/foundation/src/shared/src/PdfSharp.WPFonts/PdfSharp.WPFonts.csproj @@ -1,7 +1,7 @@  - net8.0;net9.0;net10.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) disable True ..\..\..\..\..\StrongnameKey.snk @@ -23,5 +23,4 @@ - diff --git a/src/foundation/src/shared/testapps/PdfSharp.Fonts.TestApp/PdfSharp.Fonts.TestApp.csproj b/src/foundation/src/shared/testapps/PdfSharp.Fonts.TestApp/PdfSharp.Fonts.TestApp.csproj index b074f6f5..ecac4098 100644 --- a/src/foundation/src/shared/testapps/PdfSharp.Fonts.TestApp/PdfSharp.Fonts.TestApp.csproj +++ b/src/foundation/src/shared/testapps/PdfSharp.Fonts.TestApp/PdfSharp.Fonts.TestApp.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net9.0;net10.0;net462 + $(PDFsharpTargetFrameworks_Exe) true diff --git a/src/foundation/src/shared/src/PdfSharp.Shared/System/CompilerServices.cs b/src/foundation/src/shared/testapps/Shared.TestApp/CompilerServices.cs similarity index 100% rename from src/foundation/src/shared/src/PdfSharp.Shared/System/CompilerServices.cs rename to src/foundation/src/shared/testapps/Shared.TestApp/CompilerServices.cs diff --git a/src/foundation/src/shared/testapps/Shared.TestApp/CompilerServices2.cs b/src/foundation/src/shared/testapps/Shared.TestApp/CompilerServices2.cs new file mode 100644 index 00000000..f2065ae2 --- /dev/null +++ b/src/foundation/src/shared/testapps/Shared.TestApp/CompilerServices2.cs @@ -0,0 +1,50 @@ +//// PDFsharp - A .NET library for processing PDF +//// See the LICENSE file in the solution root for more information. + +//// This file contains code from the .NET source to use some newer C# features in .NET Standard / Framework. +//// All classes are internal, i.e. using PDFsharp packages does not make this functionality visible in +//// your projects. + +//namespace System.Runtime.CompilerServices +//{ +//#if !NET8_0_OR_GREATER +// /// +// /// Extension method GetSubArray required for the built-in range operator (e.g.'[1..9]'). +// /// Fun fact: This class must be compiled into each assembly. If it is only visible through +// /// InternalsVisibleTo code will not compile with .NET Framework 4.6.2 and .NET Standard 2.0. +// /// +// /*public*/ +// static class RuntimeHelpers +// { +// /// +// /// Slices the specified array using the specified range. +// /// +// public static T[] GetSubArray(T[] array, Range range) +// { +// if (array == null) +// throw new ArgumentNullException(nameof(array)); + +// (int offset, int length) = range.GetOffsetAndLength(array.Length); + +// if (default(T) != null || typeof(T[]) == array.GetType()) +// { +// // We know the type of the array to be exactly T[]. +// if (length == 0) +// //return []; +// return []; + +// var dest = new T[length]; +// Array.Copy(array, offset, dest, 0, length); +// return dest; +// } +// else +// { +// // The array is actually a U[] where U:T. +// var dest = (T[])Array.CreateInstance(array.GetType().GetElementType()!, length); +// Array.Copy(array, offset, dest, 0, length); +// return dest; +// } +// } +// } +//#endif +//} diff --git a/src/foundation/src/shared/testapps/Shared.TestApp/LogMessages.cs b/src/foundation/src/shared/testapps/Shared.TestApp/LogMessages.cs index cedc80cc..21b4c3d4 100644 --- a/src/foundation/src/shared/testapps/Shared.TestApp/LogMessages.cs +++ b/src/foundation/src/shared/testapps/Shared.TestApp/LogMessages.cs @@ -14,7 +14,6 @@ public class Ids { public const int IdXxxx = 7000; public const int IdYyyy = 7010; - }; public static partial class TestAppLogMessages diff --git a/src/foundation/src/shared/testapps/Shared.TestApp/Program.cs b/src/foundation/src/shared/testapps/Shared.TestApp/Program.cs index 4946fb8c..c91f71ff 100644 --- a/src/foundation/src/shared/testapps/Shared.TestApp/Program.cs +++ b/src/foundation/src/shared/testapps/Shared.TestApp/Program.cs @@ -35,17 +35,9 @@ static void Main( /*string[] args*/) ILogger logger = loggerFactory.CreateLogger(); - //LogHost.Logger.LogError("Something went wrong."); - //LogHost.Logger.TestMessage(LogLevel.Critical, "blah"); - //LogHost.Logger.TestMessage("di-blub"); LogHost.Logger.TestMessage("------------------------------------------------------------------------------"); - - var tempFileName = PdfFileUtility.GetTempPdfFullFileName("tests"); - - //document.Save(tempFileName); - // Call some developer specific test code from a file not in the repo. // Implement your code in ProgramEx.cs in partial class Program. var test = typeof(Program).GetMethod("Test", BindingFlags.Static | BindingFlags.NonPublic); @@ -53,8 +45,6 @@ static void Main( /*string[] args*/) { test.Invoke(null, null); } - - //PdfFileUtility.ShowDocumentIfDebugging(tempFileName); } } } diff --git a/src/foundation/src/shared/testapps/Shared.TestApp/Shared.TestApp.csproj b/src/foundation/src/shared/testapps/Shared.TestApp/Shared.TestApp.csproj index aa742563..6f1d98bc 100644 --- a/src/foundation/src/shared/testapps/Shared.TestApp/Shared.TestApp.csproj +++ b/src/foundation/src/shared/testapps/Shared.TestApp/Shared.TestApp.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net9.0;net10.0;net462 + $(PDFsharpTargetFrameworks_Exe) true @@ -20,9 +20,16 @@ + + + + diff --git a/src/foundation/src/shared/tests/PdfSharp.Fonts.Test/BasicTests.cs b/src/foundation/src/shared/tests/PdfSharp.Fonts.Tests/BasicTests.cs similarity index 100% rename from src/foundation/src/shared/tests/PdfSharp.Fonts.Test/BasicTests.cs rename to src/foundation/src/shared/tests/PdfSharp.Fonts.Tests/BasicTests.cs diff --git a/src/foundation/src/shared/tests/PdfSharp.Fonts.Test/PdfSharp.Fonts.Test.csproj b/src/foundation/src/shared/tests/PdfSharp.Fonts.Tests/PdfSharp.Fonts.Test.csproj similarity index 88% rename from src/foundation/src/shared/tests/PdfSharp.Fonts.Test/PdfSharp.Fonts.Test.csproj rename to src/foundation/src/shared/tests/PdfSharp.Fonts.Tests/PdfSharp.Fonts.Test.csproj index cc6d7c22..23c66905 100644 --- a/src/foundation/src/shared/tests/PdfSharp.Fonts.Test/PdfSharp.Fonts.Test.csproj +++ b/src/foundation/src/shared/tests/PdfSharp.Fonts.Tests/PdfSharp.Fonts.Test.csproj @@ -1,7 +1,7 @@  - net8.0;net462 + $(PDFsharpTargetFrameworks_Tests_Exe) diff --git a/src/foundation/src/shared/tests/PdfSharp.Fonts.Test/README.md b/src/foundation/src/shared/tests/PdfSharp.Fonts.Tests/README.md similarity index 100% rename from src/foundation/src/shared/tests/PdfSharp.Fonts.Test/README.md rename to src/foundation/src/shared/tests/PdfSharp.Fonts.Tests/README.md diff --git a/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/FontFaces/BasicTests.cs b/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/FontFaces/BasicTests.cs new file mode 100644 index 00000000..56d39e98 --- /dev/null +++ b/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/FontFaces/BasicTests.cs @@ -0,0 +1,16 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.OpenType.Tests; +using Xunit; + +namespace PdfSharp.OpenType.Test.FontFaces +{ + public class BasicTests : OpenTypeTestBase + { + [Fact] + public void PlaceHolder() + { + } + } +} diff --git a/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/PdfSharp.OpenType.Test.csproj b/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/PdfSharp.OpenType.Test.csproj new file mode 100644 index 00000000..02d017f3 --- /dev/null +++ b/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/PdfSharp.OpenType.Test.csproj @@ -0,0 +1,25 @@ + + + + $(PDFsharpTargetFrameworks_Tests_Exe) + + + + + + + + + + + + + + + + + + + + + diff --git a/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/README.md b/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/README.md new file mode 100644 index 00000000..5150eb64 --- /dev/null +++ b/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/README.md @@ -0,0 +1,3 @@ +# PDFsharp.OpenType.Tests + +This is the PDFsharp.OpenType.Tests project. diff --git a/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/WpfTestBase.cs b/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/WpfTestBase.cs new file mode 100644 index 00000000..ddc6a2cc --- /dev/null +++ b/src/foundation/src/shared/tests/PdfSharp.OpenType.Tests/WpfTestBase.cs @@ -0,0 +1,65 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using PdfSharp.Quality.Testing; +using Xunit; + +namespace PdfSharp.OpenType.Tests +{ + [Collection("PDFsharp")] + public class OpenTypeTestBase : PdfSharpTestBase + { + public OpenTypeTestBase() + { } + + //public void Dispose() + //{ } + + //WpfBitmapSource render() + //{ + + // var content = ContentReader.ReadContent(page); + + // var pageSize = new Size((float)page.Width.Point, (float)page.Height.Point); + + // //var device = CanvasDevice.GetSharedDevice(); + // //var renderTarget = new CanvasRenderTarget(device, pageSize.Width, pageSize.Height, 4*72); + + // int cx = (int)pageSize.Width; + // int cy = (int)pageSize.Height; + // int dpi = 600; + // double scaling = dpi / 72d; + // WpfRenderTargetBitmap renderBitmap = + // new WpfRenderTargetBitmap((int)(cx * scaling), (int)(cy * scaling), dpi, dpi, System.Windows.Media.PixelFormats.Pbgra32); + + // WpfDrawingVisual drawingVisual = new WpfDrawingVisual(); + // using (WpfDrawingContext context = drawingVisual.RenderOpen()) + // { + // //var matrix = new WpfMatrix(1 / scaling, 0, 0, 1 / scaling, 0, 0); + // //context.PushTransform(new WpfMatrixTransform(matrix)); + // //var group = new System.Windows.Media.GeometryGroup(); + // //group.Children.Add(new System.Windows.Media.LineGeometry(new WpfPoint(0, 0), new WpfPoint(cx, cy))); + // //group.Children.Add(new System.Windows.Media.LineGeometry(new WpfPoint(0, cx), new WpfPoint(cx, 0))); + // //context.DrawGeometry(null, Colors.Red.CreateWpfPen(), group); + // ////VisualBrush visualBrush = new VisualBrush(target); + // //context.DrawRectangle(System.Windows.Media.Brushes.Red, null, new WpfRect(10, 10, 50, 50)); + + // XPdfProcessor processor = new XPdfProcessor(); + // var target = new PdfSharp.Content.WPF.WpfRenderTarget(processor.PdfGraphicState, context, page); + // processor.SetRenderingTarget(target); + // processor.Apply(content, page); + // } + + // renderBitmap.Render(drawingVisual); + // { + // var encoder = new PngBitmapEncoder(); + // BitmapFrame frame = BitmapFrame.Create(renderBitmap); + // encoder.Frames.Add(frame); + // var fullName = IOUtility.GetTempFullFileName("unit-tests/" + typeof(PdfContentHelper).Namespace + "/" + nameof(PdfContentHelper) + "/" + "Test_TD" + Capabilities.Create.BuildTag, "png"); + // using var stream = System.IO.File.Create(fullName); + // encoder.Save(stream); + // } + // return renderBitmap; + //} + } +} diff --git a/src/foundation/src/shared/tests/Shared.Tests/BasicTests.cs b/src/foundation/src/shared/tests/Shared.Tests/BasicTests.cs index d580fb48..fd163874 100644 --- a/src/foundation/src/shared/tests/Shared.Tests/BasicTests.cs +++ b/src/foundation/src/shared/tests/Shared.Tests/BasicTests.cs @@ -1,4 +1,4 @@ -// PDFsharp - A .NET library for processing PDF +// PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. using PdfSharp.Diagnostics; @@ -6,16 +6,15 @@ using PdfSharp.Fonts; using PdfSharp.Pdf; using PdfSharp.Quality; -using PdfSharp.Snippets.Font; -using PdfSharp.TestHelper; -#if CORE -#endif +using PdfSharp.Quality.Testing; using Xunit; -namespace PdfSharp.Tests +namespace Shared.Tests { - public class BasicTests : IDisposable + public class BasicTests : PdfSharpTestBase { + readonly string _tempRoot = GetTempRoot(typeof(BasicTests)); + public BasicTests() { PdfSharpCore.ResetAll(); @@ -24,7 +23,7 @@ public BasicTests() #endif } - public void Dispose() + protected override void Dispose(bool disposing) { PdfSharpCore.ResetAll(); } @@ -55,7 +54,9 @@ public void Create_Hello_World_BasicTests() // Create a font. var font = new XFont("Times New Roman", 20, XFontStyleEx.BoldItalic); -#if NET6_0_OR_GREATER + //var font2 = new XFont("Times New Roman", 20, XFontStyleEx.BoldItalic); + +#if NET8_0_OR_GREATER gfx.DrawString($"Hello, dotnet {Environment.Version.Major}.{Environment.Version.Minor}!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); #else @@ -63,10 +64,8 @@ public void Create_Hello_World_BasicTests() new XRect(0, 0, width, height), XStringFormats.Center); #endif - // Save the document... - string filename = PdfFileUtility.GetTempPdfFileName("HelloWorld"); + string filename = PdfFileUtility.GetTempPdfFullFileName(_tempRoot + nameof(Create_Hello_World_BasicTests)); document.Save(filename); - // ...and start a viewer. PdfFileUtility.ShowDocumentIfDebugging(filename); } } diff --git a/src/foundation/src/shared/tests/Shared.Tests/Quality/IOUtilityTests.cs b/src/foundation/src/shared/tests/Shared.Tests/Quality/IOUtilityTests.cs index c099ea28..b3d5e846 100644 --- a/src/foundation/src/shared/tests/Shared.Tests/Quality/IOUtilityTests.cs +++ b/src/foundation/src/shared/tests/Shared.Tests/Quality/IOUtilityTests.cs @@ -24,15 +24,15 @@ public void IsDirectorySeparator_Test() [Fact] public void NormalizeDirectorySeparators_Test() { - string? path1 = null; + string path1 = null!; IOUtility.NormalizeDirectorySeparators(ref path1); path1.Should().BeNull(); - string? path2 = ""; + string path2 = ""; IOUtility.NormalizeDirectorySeparators(ref path2); path2.Should().Be(""); - string? path3 = @"Foo\bar\"; + string path3 = @"Foo\bar\"; IOUtility.NormalizeDirectorySeparators(ref path3); path3.Should().Be("Foo/bar/"); } @@ -47,30 +47,51 @@ public void GetSolutionRoot_Test() files.Length.Should().Be(1); } + [Fact] + public void GetPathOfFileAbove_Test() + { + var result = IOUtility.GetPathOfFileAbove("SemVersion.props"); + result.Should().NotBeNull(); + + var files = Directory.GetFiles(result!, "Directory.Build.targets"); // Case-sensitive under Linux. + files.Length.Should().Be(1); + } + + [Fact] + public void GetPathOfDirectoryAbove_Test() + { + var result = IOUtility.GetPathOfDirectoryAbove("dev"); + result.Should().NotBeNull(); + //Debug.Assert(result != null); + result = Directory.GetParent(result!)?.FullName ?? ""; + var files = Directory.GetFiles(result!, "PDFsharp.sln"); // Case-sensitive under Linux. + files.Length.Should().Be(1); + } + [Fact] public void GetAssetsPath_Test() { // The solution may not be in directory PDFsharp. var solutionRoot = IOUtility.GetSolutionRoot(); if (IOUtility.IsDirectorySeparator(solutionRoot![solutionRoot.Length - 1])) - solutionRoot = solutionRoot.Substring(0, solutionRoot.Length - 1); + solutionRoot = solutionRoot[..^1]; var solutionPath = Path.GetFileName(solutionRoot); - var path1 = IOUtility.GetAssetsPath(); + var path1 = IOUtility.GetAssetsPath() ?? null!; path1.Should().NotBeNull(); - IOUtility.NormalizeDirectorySeparators(ref path1); + IOUtility.NormalizeDirectorySeparators(ref path1!); path1.Should().EndWith($"{solutionPath}/assets/"); // Get path to directory. - var path2 = IOUtility.GetAssetsPath("pdfsharp/"); + var path2 = IOUtility.GetAssetsPath("pdfsharp/") ?? null!; path2.Should().NotBeNull(); - IOUtility.NormalizeDirectorySeparators(ref path2); + IOUtility.NormalizeDirectorySeparators(ref path2!); path2.Should().EndWith($"{solutionPath}/assets/pdfsharp/"); // Get path to file. - var path3 = IOUtility.GetAssetsPath("pdfsharp/LICENSE"); + var path3 = IOUtility.GetAssetsPath("pdfsharp/LICENSE") ?? null!; path3.Should().NotBeNull(); - IOUtility.NormalizeDirectorySeparators(ref path3); + IOUtility.NormalizeDirectorySeparators(ref path3!); path3.Should().EndWith($"{solutionPath}/assets/pdfsharp/LICENSE"); } @@ -145,7 +166,7 @@ public void EnsureAssets_Test() var assetsPath2 = Path.Combine(root, "assets_"); Directory.Move(assetsPath, assetsPath2); var action = IOHelper.EnsureAssetsAreDownloaded; - action.Should().Throw(); + action.Should().Throw(); Directory.Move(assetsPath2, assetsPath); #endif } @@ -160,7 +181,7 @@ public void EnsureAssetsVersion_Test() action1.Should().NotThrow("Version 1 should always exist."); var action2 = () => IOUtility.EnsureAssetsVersion(Int32.MaxValue); - action2.Should().Throw(); + action2.Should().Throw(); } } } diff --git a/src/foundation/src/shared/tests/Shared.Tests/Quality/PdfToPngConverterTests.cs b/src/foundation/src/shared/tests/Shared.Tests/Quality/PdfToPngConverterTests.cs new file mode 100644 index 00000000..a4b07635 --- /dev/null +++ b/src/foundation/src/shared/tests/Shared.Tests/Quality/PdfToPngConverterTests.cs @@ -0,0 +1,30 @@ +// PDFsharp - A .NET library for processing PDF +// See the LICENSE file in the solution root for more information. + +using FluentAssertions; +using PdfSharp.Quality; +using PdfSharp.Quality.Ghostscript; +using Xunit; + +namespace Shared.Tests.Quality +{ + public class PdfToPngConverterTests + { + [SkippableFact] + public void Convert_PDF_to_PNG_Test() + { + var testFile = IOUtility.GetAssetsPath("archives/samples-1.5/PDFs/SomeLayout.pdf")!; + var filename = IOUtility.GetTempFullFileName("unittests/pdfsharp/quality/PdfToPngConverterTest1", "png"); + + var pdfConvert = new PdfToPngConverter(testFile, 150, true); + + Skip.If(!pdfConvert.IsInstalled()); + + // Page index is 0-based. + pdfConvert.ConvertPages(filename); + + // Test was reviewed manually. + true.Should().BeTrue(); + } + } +} diff --git a/src/foundation/src/shared/tests/Shared.Tests/Shared.Tests.csproj b/src/foundation/src/shared/tests/Shared.Tests/Shared.Tests.csproj index 7df23636..37b67d39 100644 --- a/src/foundation/src/shared/tests/Shared.Tests/Shared.Tests.csproj +++ b/src/foundation/src/shared/tests/Shared.Tests/Shared.Tests.csproj @@ -1,7 +1,7 @@  - net8.0;net462 + $(PDFsharpTargetFrameworks_Tests_Exe) True ..\..\..\..\..\StrongnameKey.snk $(DefineConstants);CORE @@ -18,6 +18,7 @@ + @@ -27,4 +28,7 @@ + + + diff --git a/src/samples/src/MigraDoc/src/HelloWorld-gdi/HelloWorld,MigraDoc-gdi.csproj b/src/samples/src/MigraDoc/src/HelloWorld-gdi/HelloWorld,MigraDoc-gdi.csproj index cd2c3ed0..74252e0a 100644 --- a/src/samples/src/MigraDoc/src/HelloWorld-gdi/HelloWorld,MigraDoc-gdi.csproj +++ b/src/samples/src/MigraDoc/src/HelloWorld-gdi/HelloWorld,MigraDoc-gdi.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true true HelloWorld_gdi diff --git a/src/samples/src/MigraDoc/src/HelloWorld-wpf/HelloWorld,MigraDoc-wpf.csproj b/src/samples/src/MigraDoc/src/HelloWorld-wpf/HelloWorld,MigraDoc-wpf.csproj index a6d0e050..8a88826f 100644 --- a/src/samples/src/MigraDoc/src/HelloWorld-wpf/HelloWorld,MigraDoc-wpf.csproj +++ b/src/samples/src/MigraDoc/src/HelloWorld-wpf/HelloWorld,MigraDoc-wpf.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true true HelloWorld_wpf diff --git a/src/samples/src/MigraDoc/src/HelloWorld/HelloWorld,MigraDoc.csproj b/src/samples/src/MigraDoc/src/HelloWorld/HelloWorld,MigraDoc.csproj index b81b60f9..31fbdf30 100644 --- a/src/samples/src/MigraDoc/src/HelloWorld/HelloWorld,MigraDoc.csproj +++ b/src/samples/src/MigraDoc/src/HelloWorld/HelloWorld,MigraDoc.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net462 + $(PDFsharpTargetFrameworks_Exe) diff --git a/src/samples/src/MigraDoc/src/HelloWorld/Program.cs b/src/samples/src/MigraDoc/src/HelloWorld/Program.cs index aaf10dca..31c9e9e5 100644 --- a/src/samples/src/MigraDoc/src/HelloWorld/Program.cs +++ b/src/samples/src/MigraDoc/src/HelloWorld/Program.cs @@ -10,6 +10,7 @@ using MigraDoc.DocumentObjectModel.Fields; using MigraDoc.DocumentObjectModel.Shapes; using MigraDoc.Rendering; +using PdfSharp.Pdf.PdfA; // For clarity, we do not use var keyword. // ReSharper disable SuggestVarOrType_SimpleTypes @@ -49,14 +50,15 @@ static void Main() }; PdfDocument pdfDocument = pdfRenderer.PdfDocument; + PdfAManager.ForDocument(pdfDocument).SetFormat(PdfAFormats.PdfA_3a); // Layout and render document to PDF. pdfRenderer.RenderDocument(); - // Save the document... - var fullName = PdfFileUtility.GetTempPdfFullFileName("MigraDocSamples/HelloWorld/HelloWorld" + Capabilities.Build.BuildTag); + // Save the document… + var fullName = PdfFileUtility.GetTempPdfFullFileName("samples/MigraDoc/HelloWorld/HelloWorld" + Capabilities.Build.BuildTag); pdfRenderer.PdfDocument.Save(fullName); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocument(fullName); } diff --git a/src/samples/src/PDFsharp/src/HelloWorld-gdi/HelloWorld-gdi,PDFsharp.csproj b/src/samples/src/PDFsharp/src/HelloWorld-gdi/HelloWorld-gdi,PDFsharp.csproj index f4954fa0..6f6a0b81 100644 --- a/src/samples/src/PDFsharp/src/HelloWorld-gdi/HelloWorld-gdi,PDFsharp.csproj +++ b/src/samples/src/PDFsharp/src/HelloWorld-gdi/HelloWorld-gdi,PDFsharp.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true true diff --git a/src/samples/src/PDFsharp/src/HelloWorld-wpf/HelloWorld-wpf,PDFsharp.csproj b/src/samples/src/PDFsharp/src/HelloWorld-wpf/HelloWorld-wpf,PDFsharp.csproj index 13ce5bb7..f56cdd49 100644 --- a/src/samples/src/PDFsharp/src/HelloWorld-wpf/HelloWorld-wpf,PDFsharp.csproj +++ b/src/samples/src/PDFsharp/src/HelloWorld-wpf/HelloWorld-wpf,PDFsharp.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true true diff --git a/src/samples/src/PDFsharp/src/HelloWorld/HelloWorld,PDFsharp.csproj b/src/samples/src/PDFsharp/src/HelloWorld/HelloWorld,PDFsharp.csproj index 3d7e7994..9b08f830 100644 --- a/src/samples/src/PDFsharp/src/HelloWorld/HelloWorld,PDFsharp.csproj +++ b/src/samples/src/PDFsharp/src/HelloWorld/HelloWorld,PDFsharp.csproj @@ -2,7 +2,7 @@ Exe - net8.0;net462 + $(PDFsharpTargetFrameworks_Exe) HelloWorld,PDFsharp diff --git a/src/samples/src/PDFsharp/src/HelloWorld/Program.cs b/src/samples/src/PDFsharp/src/HelloWorld/Program.cs index 67db34c3..d276089c 100644 --- a/src/samples/src/PDFsharp/src/HelloWorld/Program.cs +++ b/src/samples/src/PDFsharp/src/HelloWorld/Program.cs @@ -3,10 +3,10 @@ using Microsoft.Extensions.Logging; using PdfSharp; -using PdfSharp.Drawing; -using PdfSharp.Fonts; using PdfSharp.Logging; using PdfSharp.Pdf; +using PdfSharp.Fonts; +using PdfSharp.Drawing; using PdfSharp.Quality; namespace HelloWorld @@ -31,12 +31,15 @@ static void Main(string[] _) LogHost.Factory = loggerFactory; GlobalFontSettings.UseWindowsFontsUnderWindows = true; + GlobalFontSettings.UseWindowsFontsUnderWsl2 = true; // Create a new PDF document. var document = new PdfDocument(); + document.ViewerPreferences.FitWindow = true; document.Info.Title = "Created with PDFsharp"; document.Info.Author = "PDFsharp team"; document.Info.Subject = "Hello, World!"; + document.ViewerPreferences.FitWindow = true; // Create an empty page in this document. var page = document.AddPage(); @@ -61,11 +64,11 @@ static void Main(string[] _) gfx.DrawString("Hello, PDFsharp!", font, XBrushes.Black, new XRect(0, 0, width, height), XStringFormats.Center); - // Save the document... - var fullName = PdfFileUtility.GetTempPdfFullFileName("PdfSharpSamples/HelloWorld/HelloWorld" + Capabilities.Build.BuildTag); + // Save the document… + var fullName = PdfFileUtility.GetTempPdfFullFileName("samples/PDFsharp/HelloWorld/HelloWorld" + Capabilities.Build.BuildTag); document.Save(fullName); - // ...and start a viewer. + // … and start a viewer. PdfFileUtility.ShowDocument(fullName); } } diff --git a/src/tools/src/CopyAsLink/CopyAsLink.csproj b/src/tools/src/CopyAsLink/CopyAsLink.csproj index 43264bf1..35abd921 100644 --- a/src/tools/src/CopyAsLink/CopyAsLink.csproj +++ b/src/tools/src/CopyAsLink/CopyAsLink.csproj @@ -2,7 +2,7 @@ Exe - net8.0-windows + net10.0-windows CopyAsLink true diff --git a/src/tools/src/CopyAsLink/Program.cs b/src/tools/src/CopyAsLink/Program.cs index 3af34114..01ffbe7b 100644 --- a/src/tools/src/CopyAsLink/Program.cs +++ b/src/tools/src/CopyAsLink/Program.cs @@ -29,11 +29,15 @@ static void Main(string[] _) var pattern = "*.cs;…"; // The files to be included, separated by ';'. // Root on your PC. - var root = @"D:\repos\emp\PDFsharp\src\foundation\src\"; + var root = @"D:\repos\emp\PDFsharp\src\foundation\src\"; //D:\repos\emp\PDFsharp\src\foundation\src\PDFsharp\src\PdfSharp\Pdf.AcroForms - // PdfSharp.Charting - source = root + @"PDFsharp\src\PdfSharp.Charting\"; - prefix = @"..\PdfSharp.Charting\"; + // PdfSharp - attention, requires extra work because of UI elements (exclusive for GDI) and PNG library (exclusive for CORE). + //source = root + @"PDFsharp\src\PdfSharp\"; + //prefix = @"..\PdfSharp\"; + + // PdfSharp + source = root + @"PDFsharp\src\PdfSharp\Pdf.AcroForms"; + prefix = @"..\PdfSharp\"; // MigraDoc.Rendering //source = root + @"MigraDoc\src\MigraDoc.Rendering\"; @@ -50,7 +54,7 @@ static void Main(string[] _) if (info.Contains(@"\obj\")) continue; - var link = info[source.Length..]; + var link = info[(source.Length -13)..]; var include = prefix + link; var res = $" "; diff --git a/src/tools/src/NRT-Tests/NRT-Tests.csproj b/src/tools/src/NRT-Tests/NRT-Tests.csproj index 432f1983..d1afecbe 100644 --- a/src/tools/src/NRT-Tests/NRT-Tests.csproj +++ b/src/tools/src/NRT-Tests/NRT-Tests.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 NRT_Tests diff --git a/src/tools/src/PdfFileViewer/PdfFileViewer.csproj b/src/tools/src/PdfFileViewer/PdfFileViewer.csproj index 9f950e67..04b18666 100644 --- a/src/tools/src/PdfFileViewer/PdfFileViewer.csproj +++ b/src/tools/src/PdfFileViewer/PdfFileViewer.csproj @@ -2,7 +2,7 @@ Exe - net8.0 + net10.0 true true win-x64 diff --git a/src/tools/src/PdfSharp.TestHelper-gdi/PdfSharp.TestHelper-gdi.csproj b/src/tools/src/PdfSharp.TestHelper-gdi/PdfSharp.TestHelper-gdi.csproj index 3061816e..0fc7befa 100644 --- a/src/tools/src/PdfSharp.TestHelper-gdi/PdfSharp.TestHelper-gdi.csproj +++ b/src/tools/src/PdfSharp.TestHelper-gdi/PdfSharp.TestHelper-gdi.csproj @@ -1,14 +1,14 @@  - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true true GDI - CS1685 + $(NoWarn);1685 True ..\..\..\StrongnameKey.snk diff --git a/src/tools/src/PdfSharp.TestHelper-wpf/PdfSharp.TestHelper-wpf.csproj b/src/tools/src/PdfSharp.TestHelper-wpf/PdfSharp.TestHelper-wpf.csproj index c7a3d959..f1280dfc 100644 --- a/src/tools/src/PdfSharp.TestHelper-wpf/PdfSharp.TestHelper-wpf.csproj +++ b/src/tools/src/PdfSharp.TestHelper-wpf/PdfSharp.TestHelper-wpf.csproj @@ -1,14 +1,14 @@  - net8.0-windows;net462 + $(PDFsharpTargetFrameworks_Windows) true true WPF - CS1685 + $(NoWarn);1685 True ..\..\..\StrongnameKey.snk diff --git a/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/ContentStreamEnumerator.cs b/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/ContentStreamEnumerator.cs index 9d4eb3a1..fa2140b1 100644 --- a/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/ContentStreamEnumerator.cs +++ b/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/ContentStreamEnumerator.cs @@ -32,7 +32,7 @@ public ContentStreamEnumerator(string contentStream, PdfDocument pdfDocument) public string? Current => _state.Current; /// - /// Moves to the next white space separated element inside the content stream. + /// Moves to the next white-space separated element inside the content stream. /// public bool MoveNext() { @@ -45,7 +45,7 @@ public bool MoveNext() if (!_elements.TryGetValue(elementIdx, out var elementBase)) { var position = _state.CurrentPosition + _state.CurrentLength; // Start after the current element. - var skipOneNecessaryWhiteSpace = position > 0; // For the first element, there must not be a separating white space before. + var skipOneNecessaryWhiteSpace = position > 0; // For the first element, there must not be a separating white-space before. // Try to move to the beginning of the next element. var hasNextElement = MoveAfterWhiteSpaces(ref position, skipOneNecessaryWhiteSpace); @@ -69,7 +69,7 @@ public bool MoveNext() } /// - /// Moves to the previous white space separated element inside the content stream. + /// Moves to the previous white-space separated element inside the content stream. /// public bool MovePrevious() { @@ -90,7 +90,7 @@ public bool MovePrevious() } /// - /// Moves to the next white space separated element inside the content stream. + /// Moves to the next white-space separated element inside the content stream. /// /// Moves this count of elements. public bool MoveNext(int steps) @@ -99,7 +99,7 @@ public bool MoveNext(int steps) } /// - /// Moves to the next white space separated element inside the content stream. + /// Moves to the next white-space separated element inside the content stream. /// /// The element must satisfy this check. /// The element must not be a direct neighbor. @@ -109,7 +109,7 @@ public bool MoveNext(Func check, bool nextMatch) } /// - /// Moves to the next white space separated element inside the content stream. + /// Moves to the next white-space separated element inside the content stream. /// /// The element must satisfy this check. /// Moves this count of elements. @@ -120,7 +120,7 @@ public bool MoveNext(Func check, int steps, bool countMatchesOnly) } /// - /// Moves to the previous white space separated element inside the content stream. + /// Moves to the previous white-space separated element inside the content stream. /// /// Moves this count of elements. public bool MovePrevious(int steps) @@ -129,7 +129,7 @@ public bool MovePrevious(int steps) } /// - /// Moves to the previous white space separated element inside the content stream. + /// Moves to the previous white-space separated element inside the content stream. /// /// The element must satisfy this check. /// The element must not be a direct neighbor. @@ -139,7 +139,7 @@ public bool MovePrevious(Func check, bool previousMatch) } /// - /// Moves to the previous white space separated element inside the content stream. + /// Moves to the previous white-space separated element inside the content stream. /// /// The element must satisfy this check. /// Moves this count of elements. @@ -150,7 +150,7 @@ public bool MovePrevious(Func check, int steps, bool countMatchesO } /// - /// Moves to the previous or next white space separated element inside the content stream and returns it. + /// Moves to the previous or next white-space separated element inside the content stream and returns it. /// /// The element must satisfy this check. /// Moves this count of elements. @@ -214,7 +214,7 @@ public bool Move(Func check, int steps, bool countMatchesOnly, boo } /// - /// Moves to the previous or next white space separated element inside the content stream and returns it. + /// Moves to the previous or next white-space separated element inside the content stream and returns it. /// /// The element must satisfy this check. /// Moves this count of elements. @@ -284,10 +284,10 @@ public void Dispose() bool MoveAfterWhiteSpaces(ref int position, bool moveByOneNecessaryWhiteSpace) { - // If we have to move by one white space, return false, if it was not found. + // If we have to move by one white-space, return false, if it was not found. if (moveByOneNecessaryWhiteSpace) { - // There is no white space after the last character. + // There is no white-space after the last character. if (position >= _contentStreamLength) return false; @@ -298,7 +298,7 @@ bool MoveAfterWhiteSpaces(ref int position, bool moveByOneNecessaryWhiteSpace) return false; } - // Move after the last white space. + // Move after the last white-space. while (true) { if (position >= _contentStreamLength) @@ -311,7 +311,7 @@ bool MoveAfterWhiteSpaces(ref int position, bool moveByOneNecessaryWhiteSpace) return true; } - // There is no element after these white spaces. + // There is no element after these white-spaces. return false; } @@ -379,11 +379,11 @@ int GetLengthOfCurrentElement(int position) if (position >= _contentStreamLength) return position - startPosition; - // Other elements are separated by white spaces. + // Other elements are separated by white-spaces. if (IsWhiteSpace(c)) return position - startPosition; - // Reached the end of the string. There is no white space after the last character. + // Reached the end of the string. There is no white-space after the last character. if (position + 1 >= _contentStreamLength) return position - startPosition + 1; diff --git a/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/ObjectGetterBase.cs b/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/ObjectGetterBase.cs index 54fb230c..ae23e76a 100644 --- a/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/ObjectGetterBase.cs +++ b/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/ObjectGetterBase.cs @@ -25,7 +25,7 @@ public bool MoveAndGet(int steps, bool countThisTypeOnly, bool backwards, out T? /// /// The element must not be a direct neighbor. /// The loaded object. - public Boolean MoveAndGetNext(bool nextOfThisType, out T? obj) + public bool MoveAndGetNext(bool nextOfThisType, out T? obj) { return MoveAndGet(1, nextOfThisType, false, out obj); } @@ -35,7 +35,7 @@ public Boolean MoveAndGetNext(bool nextOfThisType, out T? obj) /// /// The element must not be a direct neighbor. /// The loaded object. - public Boolean MoveAndGetPrevious(bool previousOfThisType, out T? obj) + public bool MoveAndGetPrevious(bool previousOfThisType, out T? obj) { return MoveAndGet(1, previousOfThisType, true, out obj); } @@ -166,7 +166,7 @@ bool MoveAndGet(Func check, int steps, bool countMatchesOnly, bool back protected (bool, T?) GetObjectFailed() { - return new ValueTuple(false, default); + return new ValueTuple(false, default); } protected ContentStreamEnumerator ContentStreamEnumerator { get; init; } = contentStreamEnumerator; diff --git a/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/TextGetter.cs b/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/TextGetter.cs index ef93051a..38bd53e9 100644 --- a/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/TextGetter.cs +++ b/src/tools/src/PdfSharp.TestHelper/Analysis/ContentStream/TextGetter.cs @@ -1,9 +1,11 @@ // PDFsharp - A .NET library for processing PDF // See the LICENSE file in the solution root for more information. +using PdfSharp.Internal; +using PdfSharp.Fonts.Internal; using PdfSharp.Drawing; using PdfSharp.Events; -using PdfSharp.Fonts.Internal; +using PdfSharp.Internal.OpenType; using PdfSharp.Pdf.Advanced; using PdfSharp.Pdf.Internal; diff --git a/src/tools/src/PdfSharp.TestHelper/MemoryLogger.cs b/src/tools/src/PdfSharp.TestHelper/MemoryLogger.cs index 427e2751..2edb2976 100644 --- a/src/tools/src/PdfSharp.TestHelper/MemoryLogger.cs +++ b/src/tools/src/PdfSharp.TestHelper/MemoryLogger.cs @@ -42,7 +42,7 @@ public void Log(LogLevel logLevel, EventId eventId, TState state, Except /// public void Clear() => _logEntries.Clear(); - public Boolean IsEnabled(LogLevel logLevel) + public bool IsEnabled(LogLevel logLevel) { var level = _config.LogLevel; return level != LogLevel.None && logLevel >= level; diff --git a/src/tools/src/PdfSharp.TestHelper/PdfFileHelper.cs b/src/tools/src/PdfSharp.TestHelper/PdfFileHelper.cs index 36d357eb..f9b4e76b 100644 --- a/src/tools/src/PdfSharp.TestHelper/PdfFileHelper.cs +++ b/src/tools/src/PdfSharp.TestHelper/PdfFileHelper.cs @@ -18,7 +18,7 @@ public static string GetPageContentStream(PdfDocument pdfDocument, int pageIdx) var pdfPage = pdfDocument.Pages[pageIdx]; var contentReference = (PdfReference)pdfPage.Contents.Elements.Items[0]; var content = (PdfDictionary)contentReference.Value; - var contentStream = content.Stream.ToString(); + var contentStream = content.Stream?.ToString() ?? ""; return contentStream; } @@ -90,14 +90,13 @@ Exception CreateCouldNotDetermineFontException(Exception? innerException = null) var page = pdfDocument.Pages[0]; // Get the font dictionary. - var fontDict = (PdfDictionary)page.Resources.Elements[PdfResources.Keys.Font]!; + var fontDict = page.Resources.Elements.GetDictionary(PdfResources.Keys.Font)!; // Get the font object; - var fontObject = fontDict.Elements[fontId]!; - PdfReference.Dereference(ref fontObject); + var fontObject = fontDict.Elements.GetObject(fontId)!; // Get the font descriptor. - var fontDescriptor = ((PdfFont)fontObject).FontDescriptor; + var fontDescriptor = fontObject.FontDescriptor; // Get the font name. var fontName = fontDescriptor.FontName; diff --git a/src/tools/src/PdfSharp.TestHelper/PdfSharp.TestHelper.csproj b/src/tools/src/PdfSharp.TestHelper/PdfSharp.TestHelper.csproj index 6f5be2a8..4680f566 100644 --- a/src/tools/src/PdfSharp.TestHelper/PdfSharp.TestHelper.csproj +++ b/src/tools/src/PdfSharp.TestHelper/PdfSharp.TestHelper.csproj @@ -1,11 +1,11 @@  - net8.0;netstandard2.0 + $(PDFsharpTargetFrameworks_Library) - CS1685 + $(NoWarn);1685 True ..\..\..\StrongnameKey.snk diff --git a/src/tools/src/PdfSharp.TestHelper/SecurityTestHelper.cs b/src/tools/src/PdfSharp.TestHelper/SecurityTestHelper.cs index 575a748c..33864e66 100644 --- a/src/tools/src/PdfSharp.TestHelper/SecurityTestHelper.cs +++ b/src/tools/src/PdfSharp.TestHelper/SecurityTestHelper.cs @@ -111,7 +111,7 @@ public class V4 : TestDataBase public V4() : this(false) { } -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER protected V4(bool getSkipped) : base(x => Enum.GetName(x)!.StartsWith("V4", StringComparison.OrdinalIgnoreCase), getSkipped) { } #else @@ -130,7 +130,7 @@ public class V5 : TestDataBase public V5() : this(false) { } -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER protected V5(bool getSkipped) : base(x => Enum.GetName(x)!.StartsWith("V5", StringComparison.OrdinalIgnoreCase), getSkipped) { } #else @@ -153,7 +153,7 @@ public abstract class TestDataBase : IEnumerable protected TestDataBase(Func? condition = null, bool getSkipped = false) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER _data = Enum.GetValues() .Where(x => SkippedTestOptions.Contains(x) == getSkipped // Get Skipped or not skipped encryption configurations, like desired. @@ -270,11 +270,10 @@ public static void SecureDocument(PdfDocument pdfDoc, TestOptions options) /// Adds a prefix to the filename, depending on the options Encryption and EncryptMetadata properties. /// Other information must be added manually to the filename parameter (this applies also to the use of user and/or owner password). /// - public static string AddPrefixToFilename(string filename, TestOptions? options = null) + public static Stream AddPrefixToFilename(string filename, TestOptions? options = null) { var prefix = GetFilenamePrefix(options); - - return $"{prefix} {filename}"; + return CreateTempStream($"{prefix} {filename}"); } /// @@ -287,7 +286,7 @@ public static string AddSuffixToFilename(string filename, TestOptions? options = var extension = Path.GetExtension(filename); var filenameWithoutExtension = Path.GetFileNameWithoutExtension(filename); - return $"{filenameWithoutExtension} {suffix}{extension}"; + return PdfFileUtility.GetTempPdfFullFileName($"{filenameWithoutExtension} {suffix}{extension}"); } static string GetFilenamePrefix(TestOptions? options) @@ -301,7 +300,7 @@ static string GetFilenamePrefix(TestOptions? options) // Prefix for encrypted file. else { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER prefixSuffix += $"{Enum.GetName(options.Encryption)}" .Replace("Default", "Def") .Replace("Using", "_") @@ -358,5 +357,19 @@ public static void GetAssetsTestFile(TestOptions options, out string filename) throw new InvalidOperationException("There are no test files to cache for this encryption configuration."); } + + public static Stream CreateTempStream(string nameTag) + { +#if true + // Create MemoryStream. + return new MemoryStream(); +#else + // Create FileStream for debugging. + string filename = PdfFileUtility.GetTempPdfFullFileName(nameTag); + var stream = new FileStream(filename, FileMode.Create, FileAccess.ReadWrite, FileShare.None); + return stream; +#endif + } + } } diff --git a/src/tools/src/PdfSharp.TestHelper/TestHelperExtensions.cs b/src/tools/src/PdfSharp.TestHelper/TestHelperExtensions.cs index 97cd00e1..c08cfa5e 100644 --- a/src/tools/src/PdfSharp.TestHelper/TestHelperExtensions.cs +++ b/src/tools/src/PdfSharp.TestHelper/TestHelperExtensions.cs @@ -28,7 +28,7 @@ public static string[] Splitter(this string text, string sep) public static string GetOriginalLocation(this Assembly assembly) { -#if NET6_0_OR_GREATER +#if NET8_0_OR_GREATER var dllPath = assembly.Location; #else // In net 4.6.2 assembly.Location returns a temporary folder, when executed via Test Explorer. @@ -40,7 +40,7 @@ public static string GetOriginalLocation(this Assembly assembly) return dllPath; } -#if !NET6_0_OR_GREATER +#if !NET8_0_OR_GREATER /// /// Split the elements of a sequence into chunks of size at most . /// From 5a40ef6e8961e4096cd6fa81f45951b3ca087814 Mon Sep 17 00:00:00 2001 From: Sabin Pop Date: Mon, 20 Apr 2026 13:27:15 +0300 Subject: [PATCH 7/7] Refactor enumerator to use PdfFormField abstraction Replaces PdfAcroFieldEnumerator and related types with PdfFormFieldEnumerator and PdfFormFieldCollection. Updates all references, method signatures, and documentation to enumerate PdfFormField objects instead of PdfAcroField, reflecting a broader abstraction in the codebase. --- .../PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs index 4c78b093..90ea8f3b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.OldAcroForms/PdfAcroField.cs @@ -404,9 +404,9 @@ PdfFormField CreateAcroField(PdfDictionary dict) } } - public new PdfAcroFieldEnumerator GetEnumerator() + public new PdfFormFieldEnumerator GetEnumerator() { - return new PdfAcroFieldEnumerator(this); + return new PdfFormFieldEnumerator(this); } IEnumerator IEnumerable.GetEnumerator() @@ -416,26 +416,26 @@ IEnumerator IEnumerable.GetEnumerator() IEnumerator IEnumerable.GetEnumerator() { - return new PdfAcroFieldEnumerator(this); + return new PdfFormFieldEnumerator(this); } /// - /// Enumerates the elements of a . + /// Enumerates the elements of a . /// - public struct PdfAcroFieldEnumerator : IEnumerator, IEnumerator, IDisposable + public struct PdfFormFieldEnumerator : IEnumerator, IEnumerator, IDisposable { - private readonly PdfAcroFieldCollection _collection; + private readonly PdfFormFieldCollection _collection; private int _currentIndex; - private PdfAcroField? _currentItem; + private PdfFormField? _currentItem; - internal PdfAcroFieldEnumerator(PdfAcroFieldCollection fieldCollection) + internal PdfFormFieldEnumerator(PdfFormFieldCollection fieldCollection) { _collection = fieldCollection; _currentIndex = -1; _currentItem = default; } - public readonly PdfAcroField Current => _currentItem!; + public readonly PdfFormField Current => _currentItem!; readonly Object IEnumerator.Current => Current;