From 5c315b4b5818cbfc9882fbe021942b4f7d7aca0f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Wed, 18 Jun 2025 11:41:10 +0200 Subject: [PATCH 01/11] Add ref preserve --- .gitignore | 1 + .../Pdf.Advanced/PdfCrossReferenceTable.cs | 10 ++++ .../PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs | 7 +-- .../src/PdfSharp/Pdf.IO/PdfReaderOptions.cs | 4 ++ .../PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 4 ++ .../tests/PdfSharp.Tests/IO/WriterTests.cs | 50 +++++++++++++++++++ 6 files changed, 73 insertions(+), 3 deletions(-) diff --git a/.gitignore b/.gitignore index e136e4a4..101a6dc8 100644 --- a/.gitignore +++ b/.gitignore @@ -53,3 +53,4 @@ InternalAgentState.json *tempfile.* launchsettings.json testEnvironments.json +.cr/ 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..bd6430ee 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs @@ -29,6 +29,10 @@ namespace PdfSharp.Pdf.Advanced /// sealed class PdfCrossReferenceTable(PdfDocument document) // Must not be derived from PdfObject. { + public bool EnableCompaction => document.EnableReferenceCompaction; + + public bool EnableRenumbering => document.EnableReferenceRenumbering; + #if TEST_CODE readonly Stopwatch _stopwatch = new(); #endif @@ -237,6 +241,9 @@ internal void HandleOrphanedReferences() /// internal int Compact() { + if (!EnableCompaction) + return 0; + // IMPROVE: remove PdfBooleanObject, PdfIntegerObject etc. int removed = _objectTable.Count; PdfReference[] irefs = TransitiveClosure(document.Trailer); @@ -313,6 +320,9 @@ internal int Compact() /// internal void Renumber() { + if (!EnableRenumbering) + return; + //CheckConsistence(); PdfReference[] irefs = AllReferences; _objectTable.Clear(); 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..4061e370 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs @@ -267,8 +267,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) + PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode openMode, PdfPasswordProvider? passwordProvider) { try { @@ -279,6 +278,8 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode var lexer = new Lexer(stream, _logger); _document = new PdfDocument(lexer); + _document.EnableReferenceCompaction = _options.EnableReferenceCompaction; + _document.EnableReferenceRenumbering = _options.EnableReferenceRenumbering; _document._state |= DocumentState.Imported; _document._openMode = openMode; @@ -310,7 +311,7 @@ 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(); 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..0c035f05 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs @@ -99,6 +99,10 @@ public class PdfReaderOptions public ReaderProblemDelegate? ReaderProblemCallback { get; set; } + public bool EnableReferenceRenumbering { get; set; } = true; + + public bool EnableReferenceCompaction { get; set; } = true; + // Testing only //public bool UseOldCode { get; set; } = false; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index 68cd0250..ad2451a5 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -138,6 +138,10 @@ void Dispose(bool disposing) } _state = DocumentState.Disposed | DocumentState.Saved; } + + public bool EnableReferenceCompaction { get; set; } = true; + + public bool EnableReferenceRenumbering { get; set; } = true; /// /// Gets or sets a user-defined object that contains arbitrary information associated with this document. 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..aec29993 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.Advanced; using PdfSharp.Pdf.IO; using PdfSharp.Quality; using PdfSharp.Snippets.Font; @@ -29,5 +30,54 @@ public void Write_import_file() Action save = () => doc.Save(filename); save.Should().Throw(); } + + [Fact] + public void Memory_Write_import_file() + { + var testFile = IOUtility.GetAssetsPath("archives/samples-1.5/PDFs/SomeLayout.pdf")!; + + var doc = PdfReader.Open(testFile, PdfDocumentOpenMode.Import); + + Action save = () => + { + using var mem = new MemoryStream(); + doc.Save(mem); + }; + save.Should().Throw(); + } + + [Fact] + public void Memory_Write_modify_file() + { + var testFile = IOUtility.GetAssetsPath("archives/samples-1.5/PDFs/SomeLayout.pdf")!; + + var doc = PdfReader.Open(testFile, PdfDocumentOpenMode.Modify); + + Action save = () => + { + using var mem = new MemoryStream(); + doc.Save(mem); + }; + save.Should().NotThrow(); + } + + [Fact] + public void Memory_Write_modify_file2() + { + var testFile = @"C:\Projekty\PDForge\ValidatorTests\in.pdf"; + + var doc = PdfReader.Open(testFile, PdfDocumentOpenMode.Modify, new PdfReaderOptions() + { + EnableReferenceCompaction = false, + EnableReferenceRenumbering = false, + }); + + Action save = () => + { + using var mem = new MemoryStream(); + doc.Save(mem); + }; + save.Should().NotThrow(); + } } } From 7af59612dafa4d072c27d9cfc21f45477006bfa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Mon, 23 Jun 2025 12:18:42 +0200 Subject: [PATCH 02/11] prevent implicit transparency group --- .../src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs | 8 ++------ .../src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs | 5 +++-- .../src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs | 2 ++ .../src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 4 ---- .../src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs | 6 ++++++ src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs | 2 +- 6 files changed, 14 insertions(+), 13 deletions(-) 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 bd6430ee..a870fbfb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs @@ -29,10 +29,6 @@ namespace PdfSharp.Pdf.Advanced /// sealed class PdfCrossReferenceTable(PdfDocument document) // Must not be derived from PdfObject. { - public bool EnableCompaction => document.EnableReferenceCompaction; - - public bool EnableRenumbering => document.EnableReferenceRenumbering; - #if TEST_CODE readonly Stopwatch _stopwatch = new(); #endif @@ -241,7 +237,7 @@ internal void HandleOrphanedReferences() /// internal int Compact() { - if (!EnableCompaction) + if (!document.Options.EnableReferenceCompaction) return 0; // IMPROVE: remove PdfBooleanObject, PdfIntegerObject etc. @@ -320,7 +316,7 @@ internal int Compact() /// internal void Renumber() { - if (!EnableRenumbering) + if (!document.Options.EnableReferenceRenumbering) return; //CheckConsistence(); 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 4061e370..08552150 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs @@ -278,8 +278,9 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode var lexer = new Lexer(stream, _logger); _document = new PdfDocument(lexer); - _document.EnableReferenceCompaction = _options.EnableReferenceCompaction; - _document.EnableReferenceRenumbering = _options.EnableReferenceRenumbering; + _document.Options.EnableReferenceCompaction = _options.EnableReferenceCompaction; + _document.Options.EnableReferenceRenumbering = _options.EnableReferenceRenumbering; + _document.Options.EnableImplicitTransparencyGroup = _options.EnableImplicitTransparencyGroup; _document._state |= DocumentState.Imported; _document._openMode = openMode; 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 0c035f05..4980e034 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs @@ -103,6 +103,8 @@ public class PdfReaderOptions public bool EnableReferenceCompaction { get; set; } = true; + public bool EnableImplicitTransparencyGroup { get; set; } = true; + // Testing only //public bool UseOldCode { get; set; } = false; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index ad2451a5..68cd0250 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -138,10 +138,6 @@ void Dispose(bool disposing) } _state = DocumentState.Disposed | DocumentState.Saved; } - - public bool EnableReferenceCompaction { get; set; } = true; - - public bool EnableReferenceRenumbering { get; set; } = true; /// /// Gets or sets a user-defined object that contains arbitrary information associated with this document. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs index fcc8df71..2669266b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs @@ -81,5 +81,11 @@ public PdfUseFlateDecoderForJpegImages UseFlateDecoderForJpegImages set => _useFlateDecoderForJpegImages = value; } PdfUseFlateDecoderForJpegImages _useFlateDecoderForJpegImages = PdfUseFlateDecoderForJpegImages.Never; + + public bool EnableReferenceCompaction { get; set; } = true; + + public bool EnableReferenceRenumbering { get; set; } = true; + + public bool EnableImplicitTransparencyGroup { get; set; } = true; } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs index 9d451f45..604980a0 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfPage.cs @@ -815,7 +815,7 @@ 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 && _document.Options.EnableImplicitTransparencyGroup) { var group = new PdfDictionary(); Elements["/Group"] = group; From 9465293e595240b1c919cbe19aeeab5d5348160a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Mon, 23 Jun 2025 12:36:50 +0200 Subject: [PATCH 03/11] prevent implicit metadata --- src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs | 1 + .../src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs | 2 ++ src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 3 ++- .../src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) 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 08552150..1388bce4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs @@ -281,6 +281,7 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode _document.Options.EnableReferenceCompaction = _options.EnableReferenceCompaction; _document.Options.EnableReferenceRenumbering = _options.EnableReferenceRenumbering; _document.Options.EnableImplicitTransparencyGroup = _options.EnableImplicitTransparencyGroup; + _document.Options.EnableImplicitMetadata = _options.EnableImplicitMetadata; _document._state |= DocumentState.Imported; _document._openMode = openMode; 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 4980e034..14fb417d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs @@ -105,6 +105,8 @@ public class PdfReaderOptions public bool EnableImplicitTransparencyGroup { get; set; } = true; + public bool EnableImplicitMetadata { get; set; } = true; + // Testing only //public bool UseOldCode { get; set; } = false; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index 68cd0250..62fdf25b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -502,7 +502,8 @@ internal override void PrepareForSave() // #PDF-UA // Create PdfMetadata now to include the final document information in XMP generation. - Catalog.Elements.SetReference(PdfCatalog.Keys.Metadata, new PdfMetadata(this)); + if (Options.EnableImplicitMetadata) + Catalog.Elements.SetReference(PdfCatalog.Keys.Metadata, new PdfMetadata(this)); } /// diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs index 2669266b..7bfdbf3f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs @@ -87,5 +87,7 @@ public PdfUseFlateDecoderForJpegImages UseFlateDecoderForJpegImages public bool EnableReferenceRenumbering { get; set; } = true; public bool EnableImplicitTransparencyGroup { get; set; } = true; + + public bool EnableImplicitMetadata { get; set; } = true; } } From a2e5d3f41920f847dca3a1763e8e81dfc931ccb9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Mon, 23 Jun 2025 13:01:45 +0200 Subject: [PATCH 04/11] prevent comment in trailer --- src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs | 1 + .../src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs | 2 ++ src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs | 3 ++- .../src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs | 2 ++ 4 files changed, 7 insertions(+), 1 deletion(-) 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 1388bce4..85211069 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs @@ -282,6 +282,7 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode _document.Options.EnableReferenceRenumbering = _options.EnableReferenceRenumbering; _document.Options.EnableImplicitTransparencyGroup = _options.EnableImplicitTransparencyGroup; _document.Options.EnableImplicitMetadata = _options.EnableImplicitMetadata; + _document.Options.EnableWriterCommentInTrailer = _options.EnableWriterCommentInTrailer; _document._state |= DocumentState.Imported; _document._openMode = openMode; 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 14fb417d..d42f9001 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs @@ -107,6 +107,8 @@ public class PdfReaderOptions public bool EnableImplicitMetadata { get; set; } = true; + public bool EnableWriterCommentInTrailer { get; set; } = true; + // Testing only //public bool UseOldCode { get; set; } = false; 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..e3242470 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs @@ -548,7 +548,8 @@ public void WriteFileHeader(PdfDocument document) public void WriteEof(PdfDocument document, SizeType startxref) { - WriteRaw($"% Created with PDFsharp {PdfSharpProductVersionInformation.SemanticVersion} ({Capabilities.Build.BuildName}) under .NET {Capabilities.Build.Framework}\n"); + if(document.Options.EnableWriterCommentInTrailer) + WriteRaw($"% Created with PDFsharp {PdfSharpProductVersionInformation.SemanticVersion} ({Capabilities.Build.BuildName}) under .NET {Capabilities.Build.Framework}\n"); WriteRaw("startxref\n"); WriteRaw(startxref.ToString(CultureInfo.InvariantCulture)); WriteRaw("\n%%EOF\n"); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs index 7bfdbf3f..4a708850 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs @@ -89,5 +89,7 @@ public PdfUseFlateDecoderForJpegImages UseFlateDecoderForJpegImages public bool EnableImplicitTransparencyGroup { get; set; } = true; public bool EnableImplicitMetadata { get; set; } = true; + + public bool EnableWriterCommentInTrailer { get; set; } = true; } } From 36f4b8b8f51ba244a886f8ad2551a0d7149bc086 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Tue, 24 Jun 2025 16:46:44 +0200 Subject: [PATCH 05/11] configure line endings --- .../PdfSharp/Drawing.Pdf/PdfGraphicsState.cs | 101 +++--- .../Drawing.Pdf/XGraphicsPdfRenderer.cs | 337 ++++++++++++------ .../PdfSharp/Pdf.AcroForms/PdfTextField.cs | 2 +- .../src/PdfSharp/Pdf.Advanced/PdfContent.cs | 38 +- .../src/PdfSharp/Pdf.Advanced/PdfContents.cs | 84 +++-- .../Pdf.Advanced/PdfCrossReferenceTable.cs | 24 +- .../src/PdfSharp/Pdf.Advanced/PdfReference.cs | 8 +- .../PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs | 10 +- .../src/PdfSharp/Pdf.Content/ContentWriter.cs | 2 + .../src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs | 2 +- .../PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs | 1 + .../src/PdfSharp/Pdf.IO/PdfReaderOptions.cs | 2 + .../PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs | 133 ++++--- .../PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 3 +- .../src/PdfSharp/Pdf/PdfDocumentOptions.cs | 8 + .../PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs | 64 ++-- .../PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs | 2 +- .../StructureElementStack.cs | 12 +- 18 files changed, 552 insertions(+), 281 deletions(-) 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..49a7c208 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/PdfGraphicsState.cs @@ -42,13 +42,15 @@ public PdfGraphicsState Clone() public void PushState() { // BeginGraphic - renderer.Append("q/n"); + renderer.Append("q"); + renderer.Append(renderer.Owner.Options.LineEnding); } public void PopState() { //BeginGraphic - renderer.Append("Q/n"); + renderer.Append("Q"); + renderer.Append(renderer.Owner.Options.LineEnding); } #region Stroke @@ -64,8 +66,8 @@ public void PopState() public void RealizePen(XPen pen, PdfColorMode colorMode) { - const string format = Config.SignificantDecimalPlaces3; const string format2 = Config.SignificantDecimalPlaces2; + const string format3 = Config.SignificantDecimalPlaces3; XColor color = pen.Color; bool overPrint = pen.Overprint; @@ -73,19 +75,22 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) if (_realizedLineWith != pen.Width) { - renderer.AppendFormatArgs("{0:" + format + "} w\n", pen.Width); + renderer.AppendFormatArgs("{0:" + format3 + "} w", pen.Width); + renderer.Append(renderer.Owner.Options.LineEnding); _realizedLineWith = pen.Width; } if (_realizedLineCap != (int)pen.LineCap) { - renderer.AppendFormatArgs("{0} J\n", (int)pen.LineCap); + renderer.AppendFormatArgs("{0} J", (int)pen.LineCap); + renderer.Append(renderer.Owner.Options.LineEnding); _realizedLineCap = (int)pen.LineCap; } if (_realizedLineJoin != (int)pen.LineJoin) { - renderer.AppendFormatArgs("{0} j\n", (int)pen.LineJoin); + renderer.AppendFormatArgs("{0} j", (int)pen.LineJoin); + renderer.Append(renderer.Owner.Options.LineEnding); _realizedLineJoin = (int)pen.LineJoin; } @@ -93,7 +98,8 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) { if (_realizedMiterLimit != (int)pen.MiterLimit && (int)pen.MiterLimit != 0) { - renderer.AppendFormatInt("{0} M\n", (int)pen.MiterLimit); + renderer.AppendFormatInt("{0} M", (int)pen.MiterLimit); + renderer.Append(renderer.Owner.Options.LineEnding); _realizedMiterLimit = (int)pen.MiterLimit; } } @@ -111,23 +117,28 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) switch (dashStyle) { case XDashStyle.Solid: - renderer.Append("[]0 d\n"); + renderer.Append("[]0 d"); + renderer.Append(renderer.Owner.Options.LineEnding); break; case XDashStyle.Dash: - renderer.AppendFormatArgs("[{0:" + format2 + "} {1:" + format2 + "}]0 d\n", dash, dot); + renderer.AppendFormatArgs("[{0:" + format2 + "} {1:" + format2 + "}]0 d", dash, dot); + renderer.Append(renderer.Owner.Options.LineEnding); break; case XDashStyle.Dot: - renderer.AppendFormatArgs("[{0:" + format2 + "}]0 d\n", dot); + renderer.AppendFormatArgs("[{0:" + format2 + "}]0 d", dot); + renderer.Append(renderer.Owner.Options.LineEnding); break; case XDashStyle.DashDot: - renderer.AppendFormatArgs("[{0:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "}]0 d\n", dash, dot); + renderer.AppendFormatArgs("[{0:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "}]0 d", dash, dot); + renderer.Append(renderer.Owner.Options.LineEnding); break; case XDashStyle.DashDotDot: - renderer.AppendFormatArgs("[{0:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "}]0 d\n", dash, dot); + renderer.AppendFormatArgs("[{0:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "} {1:" + format2 + "}]0 d", dash, dot); + renderer.Append(renderer.Owner.Options.LineEnding); break; case XDashStyle.Custom: @@ -146,7 +157,8 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) pdf.Append(' '); pdf.Append(PdfEncoders.ToString(0.2 * pen.Width)); } - pdf.AppendFormat(CultureInfo.InvariantCulture, "]{0:" + format + "} d\n", pen.DashOffset * pen.Width); + pdf.AppendFormat(CultureInfo.InvariantCulture, "]{0:" + format3 + "} d", pen.DashOffset * pen.Width); + pdf.Append(renderer.Owner.Options.LineEnding); string pattern = pdf.ToString(); // IMPROVE @@ -168,7 +180,8 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) if (_realizedStrokeColor.Rgb != color.Rgb) { renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb)); - renderer.Append(" RG\n"); + renderer.Append(" RG"); + renderer.Append(renderer.Owner.Options.LineEnding); } } else @@ -176,7 +189,8 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) if (!ColorSpaceHelper.IsEqualCmyk(_realizedStrokeColor, color)) { renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk)); - renderer.Append(" K\n"); + renderer.Append(" K"); + renderer.Append(renderer.Owner.Options.LineEnding); } } @@ -184,7 +198,8 @@ public void RealizePen(XPen pen, PdfColorMode colorMode) { PdfExtGState extGState = renderer.Owner.ExtGStateTable.GetExtGStateStroke(color.A, overPrint); string gs = renderer.Resources.AddExtGState(extGState); - renderer.AppendFormatString("{0} gs\n", gs); + renderer.AppendFormatString("{0} gs", gs); + renderer.Append(renderer.Owner.Options.LineEnding); // Must create transparency group. if (renderer._page != null! && color.A < 1) @@ -238,8 +253,10 @@ public void RealizeBrush(XBrush brush, PdfColorMode colorMode, int renderingMode PdfShadingPattern pattern = new PdfShadingPattern(renderer.Owner); pattern.SetupFromBrush(gradientBrush, matrix, renderer); string name = renderer.Resources.AddPattern(pattern); - renderer.AppendFormatString("/Pattern cs\n", name); - renderer.AppendFormatString("{0} scn\n", name); + renderer.AppendFormatString("/Pattern cs", name); + renderer.Append(renderer.Owner.Options.LineEnding); + renderer.AppendFormatString("{0} scn", name); + renderer.Append(renderer.Owner.Options.LineEnding); // Invalidate fill color. _realizedFillColor = XColor.Empty; @@ -256,7 +273,8 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode) if (_realizedFillColor.IsEmpty || _realizedFillColor.Rgb != color.Rgb) { renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Rgb)); - renderer.Append(" rg\n"); + renderer.Append(" rg"); + renderer.Append(renderer.Owner.Options.LineEnding); } } else @@ -266,7 +284,8 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode) if (_realizedFillColor.IsEmpty || !ColorSpaceHelper.IsEqualCmyk(_realizedFillColor, color)) { renderer.Append(PdfEncoders.ToString(color, PdfColorMode.Cmyk)); - renderer.Append(" k\n"); + renderer.Append(" k"); + renderer.Append(renderer.Owner.Options.LineEnding); } } @@ -275,7 +294,8 @@ void RealizeFillColor(XColor color, bool overPrint, PdfColorMode colorMode) PdfExtGState extGState = renderer.Owner.ExtGStateTable.GetExtGStateNonStroke(color.A, overPrint); string gs = renderer.Resources.AddExtGState(extGState); - renderer.AppendFormatString("{0} gs\n", gs); + renderer.AppendFormatString("{0} gs", gs); + renderer.Append(renderer.Owner.Options.LineEnding); // Must create transparency group. // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract @@ -307,7 +327,8 @@ internal void RealizeNonStrokeTransparency(double transparency, PdfColorMode col public void RealizeFont(XGlyphTypeface glyphTypeface, double emSize, XBrush brush, int renderingMode, FontType fontType) { - const string format = Config.SignificantDecimalPlaces3; + const string format3 = Config.SignificantDecimalPlaces3; + const string format = "{0} {1:" + format3 + "} Tf"; // 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); @@ -315,7 +336,8 @@ public void RealizeFont(XGlyphTypeface glyphTypeface, double emSize, XBrush brus // Realize rendering mode. if (_realizedRenderingMode != renderingMode) { - renderer.AppendFormatInt("{0} Tr\n", renderingMode); + renderer.AppendFormatInt("{0} Tr", renderingMode); + renderer.Append(renderer.Owner.Options.LineEnding); _realizedRenderingMode = renderingMode; } @@ -324,7 +346,8 @@ public void RealizeFont(XGlyphTypeface glyphTypeface, double emSize, XBrush brus { if (_realizedCharSpace != 0) { - renderer.Append("0 Tc\n"); + renderer.Append("0 Tc"); + renderer.Append(renderer.Owner.Options.LineEnding); _realizedCharSpace = 0; } } @@ -333,7 +356,8 @@ public void RealizeFont(XGlyphTypeface glyphTypeface, double emSize, XBrush brus double charSpace = emSize * Const.BoldEmphasis; if (_realizedCharSpace != charSpace) { - renderer.AppendFormatDouble("{0:" + format + "} Tc\n", charSpace); + renderer.AppendFormatDouble("{0:" + format3 + "} Tc", charSpace); + renderer.Append(renderer.Owner.Options.LineEnding); _realizedCharSpace = charSpace; } } @@ -342,26 +366,24 @@ 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"; if (renderer.Gfx.PageDirection == XPageDirection.Downwards) { // earlier: // renderer.AppendFormatFont("{0} {1:" + format + "} Tf\n", fontName, emSize); - renderer.AppendFormatFont(s_formatTf, fontName, emSize); + renderer.AppendFormatFont(format, fontName, emSize); + renderer.Append(renderer.Owner.Options.LineEnding); } else { // earlier: // renderer.AppendFormatFont("{0} {1:" + format + "} Tf\n", fontName, emSize); - renderer.AppendFormatFont(s_formatTf, fontName, emSize); + renderer.AppendFormatFont(format, fontName, emSize); + renderer.Append(renderer.Owner.Options.LineEnding); } _realizedFontName = fontName; _realizedFontSize = emSize; } } - // ReSharper disable InconsistentNaming - static string? s_formatTf; - // ReSharper restore InconsistentNaming public XPoint RealizedTextPosition; @@ -445,21 +467,22 @@ public void AddTransform(XMatrix value, XMatrixOrder matrixOrder) /// public void RealizeCtm() { + const string format7 = Config.SignificantDecimalPlaces7; + const string format = "{0:" + format7 + "} {1:" + format7 + "} {2:" + format7 + "} {3:" + format7 + "} {4:" + + format7 + "} {5:" + format7 + "} cm"; + //if (MustRealizeCtm) if (!UnrealizedCtm.IsIdentity) { 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"; - 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(format, matrix[0], matrix[1], matrix[2], matrix[3], matrix[4], matrix[5]); + renderer.Append(renderer.Owner.Options.LineEnding); RealizedCtm.Prepend(UnrealizedCtm); UnrealizedCtm = new XMatrix(); @@ -468,9 +491,6 @@ public void RealizeCtm() InverseEffectiveCtm.Invert(); } } - // ReSharper disable InconsistentNaming - static string? s_formatCtm; - // ReSharper restore InconsistentNaming #endregion #region Clip Path @@ -519,7 +539,8 @@ void RealizeClipPath(XGraphicsPath clipPath) else renderer.AppendPath(clipPath._pathGeometry); #endif - renderer.Append(clipPath.FillMode == XFillMode.Winding ? "W n\n" : "W* n\n"); + renderer.Append(clipPath.FillMode == XFillMode.Winding ? "W n" : "W* n"); + renderer.Append(renderer.Owner.Options.LineEnding); } #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..39120213 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Drawing.Pdf/XGraphicsPdfRenderer.cs @@ -131,10 +131,15 @@ public void DrawLines(XPen pen, XPoint[] points) Realize(pen); const string format = Config.SignificantDecimalPlaces4; - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", points[0].X, points[0].Y); + _content.Append(Owner.Options.LineEnding); for (int idx = 1; idx < count; idx++) - AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y); - _content.Append("S\n"); + { + AppendFormatPoint("{0:" + format + "} {1:" + format + "} l", points[idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); + } + _content.Append("S"); + _content.Append(Owner.Options.LineEnding); } // ----- DrawBezier --------------------------------------------------------------------------- @@ -167,12 +172,16 @@ public void DrawBeziers(XPen pen, XPoint[] points) Realize(pen); const string format = Config.SignificantDecimalPlaces4; - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", points[0].X, points[0].Y); + _content.Append(Owner.Options.LineEnding); for (int idx = 1; idx < count; idx += 3) - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + { + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", points[idx].X, points[idx].Y, points[idx + 1].X, points[idx + 1].Y, points[idx + 2].X, points[idx + 2].Y); + _content.Append(Owner.Options.LineEnding); + } AppendStrokeFill(pen, null, XFillMode.Alternate, false); } @@ -202,7 +211,8 @@ public void DrawCurve(XPen pen, XPoint[] points, double tension) Realize(pen); const string format = Config.SignificantDecimalPlaces4; - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", points[0].X, points[0].Y); + _content.Append(Owner.Options.LineEnding); if (count == 2) { // Just draws a line. @@ -252,14 +262,16 @@ public void DrawRectangle(XPen? pen, XBrush? brush, double x, double y, double w Realize(pen, brush); //AppendFormat123("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} re\n", x, y, width, -height); - AppendFormatRect("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} re\n", x, y + height, width, height); + AppendFormatRect("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} re", x, y + height, width, height); + _content.Append(Owner.Options.LineEnding); if (pen != null && brush != null) - _content.Append("B\n"); + _content.Append("B"); else if (pen != null) - _content.Append("S\n"); + _content.Append("S"); else - _content.Append("f\n"); + _content.Append("f"); + _content.Append(Owner.Options.LineEnding); } // ----- DrawRectangles ----------------------------------------------------------------------- @@ -310,15 +322,20 @@ public void DrawEllipse(XPen? pen, XBrush? brush, double x, double y, double wid // Approximate an ellipse by drawing four cubic splines. const string format = Config.SignificantDecimalPlaces4; - 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", + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", x0 + δx, y0); + _content.Append(Owner.Options.LineEnding); + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", x0 + δx, y0 + fy, x0 + fx, y0 + δy, x0, y0 + δy); - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + _content.Append(Owner.Options.LineEnding); + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", x0 - fx, y0 + δy, x0 - δx, y0 + fy, x0 - δx, y0); - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + _content.Append(Owner.Options.LineEnding); + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", x0 - δx, y0 - fy, x0 - fx, y0 - δy, x0, y0 - δy); - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + _content.Append(Owner.Options.LineEnding); + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", x0 + fx, y0 - δy, x0 + δx, y0 - fy, x0 + δx, y0); + _content.Append(Owner.Options.LineEnding); AppendStrokeFill(pen, brush, XFillMode.Winding, true); } @@ -337,9 +354,13 @@ public void DrawPolygon(XPen? pen, XBrush? brush, XPoint[] points, XFillMode fil throw new ArgumentException(PsMsgs.PointArrayAtLeast(2), nameof(points)); const string format = Config.SignificantDecimalPlaces4; - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", points[0].X, points[0].Y); + _content.Append(Owner.Options.LineEnding); for (int idx = 1; idx < count; idx++) - AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y); + { + AppendFormatPoint("{0:" + format + "} {1:" + format + "} l", points[idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); + } AppendStrokeFill(pen, brush, fillmode, true); } @@ -356,7 +377,8 @@ public void DrawPie(XPen? pen, XBrush? brush, double x, double y, double width, Realize(pen, brush); const string format = Config.SignificantDecimalPlaces4; - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", x + width / 2, y + height / 2); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", x + width / 2, y + height / 2); + _content.Append(Owner.Options.LineEnding); AppendPartialArc(x, y, width, height, startAngle, sweepAngle, PathStart.LineTo1st, new XMatrix()); AppendStrokeFill(pen, brush, XFillMode.Alternate, true); } @@ -381,7 +403,8 @@ public void DrawClosedCurve(XPen? pen, XBrush? brush, XPoint[] points, double te Realize(pen, brush); const string format = Config.SignificantDecimalPlaces4; - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", points[0].X, points[0].Y); + _content.Append(Owner.Options.LineEnding); if (count == 2) { // Just draw a line. @@ -687,12 +710,14 @@ public void DrawString(string s, XFont font, XBrush brush, XRect rect, XStringFo var pos = new XPoint(x, y); pos = WorldToView(pos); AdjustTdOffset(ref pos, 0, false); - AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text); + AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj", pos.X, pos.Y, text); + _content.Append(Owner.Options.LineEnding); } else { // Rest of the layers are rendered on top of the first layer. - AppendFormatArgs("0 0 Td {0} Tj\n", text); + AppendFormatArgs("0 0 Td {0} Tj", text); + _content.Append(Owner.Options.LineEnding); } } x += chunkWidth; @@ -744,7 +769,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 format4 = Config.SignificantDecimalPlaces4; #if ITALIC_SIMULATION if (italicSimulation) { @@ -752,25 +777,32 @@ void RenderText(string text, XFont font, XBrush brush, double x, double y, doubl { // Case: Simulate Italic and simulation is already on. // Build format string only once. - s_format1 ??= "{0:" + formatTj + "} {1:" + formatTj + "} Td\n{2} Tj\n"; + const string s_format1 = "{0:" + format4 + "} {1:" + format4 + "} Td"; + const string s_format2 = "{0} Tj"; AdjustTdOffset(ref pos, verticalOffset, true); // earlier: //AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} Td\n{2} Tj\n", pos.X, pos.Y, text); - AppendFormatArgs(s_format1, pos.X, pos.Y, text); + AppendFormatArgs(s_format1, pos.X, pos.Y); + _content.Append(Owner.Options.LineEnding); + AppendFormatArgs(s_format2, text); + _content.Append(Owner.Options.LineEnding); } else { // Case: Simulate Italic and turn simulation on. - s_format2 ??= "{0:" + formatTj + "} {1:" + formatTj + "} {2:" + formatTj + "} {3:" + formatTj + "} {4:" - + formatTj + "} {5:" + formatTj + "} Tm\n{6} Tj\n"; + const string s_format1 = "{0:" + format4 + "} {1:" + format4 + "} {2:" + format4 + "} {3:" + format4 + "} {4:" + format4 + "} {5:" + format4 + "} Tm"; + const string s_format2 = "{0} Tj"; // Italic simulation is done by skewing characters 20° to the right. var m = new XMatrix(1, 0, Const.ItalicSkewAngleSinus, 1, pos.X, pos.Y); // earlier: //AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} {2:" + format2 + "} {3:" + format2 + "} {4:" + format2 + "} {5:" + format2 + "} Tm\n{6} Tj\n", // m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY, text); - AppendFormatArgs(s_format2, m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY, text); + AppendFormatArgs(s_format1, m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY); + _content.Append(Owner.Options.LineEnding); + AppendFormatArgs(s_format2, text); + _content.Append(Owner.Options.LineEnding); _gfxState.ItalicSimulationOn = true; AdjustTdOffset(ref pos, verticalOffset, false); } @@ -781,14 +813,17 @@ void RenderText(string text, XFont font, XBrush brush, double x, double y, doubl { // Case: Do not simulate Italic but simulation is currently on. // Same as format2, but keep code clear. - s_format3 ??= "{0:" + formatTj + "} {1:" + formatTj + "} {2:" + formatTj + "} {3:" + formatTj + "} {4:" - + formatTj + "} {5:" + formatTj + "} Tm\n{6} Tj\n"; + const string s_format1 = "{0:" + format4 + "} {1:" + format4 + "} {2:" + format4 + "} {3:" + format4 + "} {4:" + format4 + "} {5:" + format4 + "} Tm"; + const string s_format2 = "{0} Tj"; var m = new XMatrix(1, 0, 0, 1, pos.X, pos.Y); // earlier: //AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} {2:" + format2 + "} {3:" + format2 + "} {4:" + format2 + "} {5:" + format2 + "} Tm\n{6} Tj\n", // m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY, text); - AppendFormatArgs(s_format3, m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY, text); + AppendFormatArgs(s_format1, m.M11, m.M12, m.M21, m.M22, m.OffsetX, m.OffsetY); + _content.Append(Owner.Options.LineEnding); + AppendFormatArgs(s_format2, text); + _content.Append(Owner.Options.LineEnding); _gfxState.ItalicSimulationOn = false; AdjustTdOffset(ref pos, verticalOffset, false); } @@ -796,17 +831,22 @@ void RenderText(string text, XFont font, XBrush brush, double x, double y, doubl { // Case: Do not simulate Italic and simulation is already off. // Same as format1, but keep code clear. - s_format4 ??= "{0:" + formatTj + "} {1:" + formatTj + "} Td\n{2} Tj\n"; + const string s_format1 = "{0:" + format4 + "} {1:" + format4 + "} Td"; + const string s_format2 = "{0} Tj"; AdjustTdOffset(ref pos, verticalOffset, false); // earlier: //AppendFormatArgs("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text); - AppendFormatArgs(s_format4, pos.X, pos.Y, text); + AppendFormatArgs(s_format1, pos.X, pos.Y); + _content.Append(Owner.Options.LineEnding); + AppendFormatArgs(s_format2, text); + _content.Append(Owner.Options.LineEnding); } } #else AdjustTextMatrix(ref pos); - AppendFormat2("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj\n", pos.X, pos.Y, text); + AppendFormat2("{0:" + format2 + "} {1:" + format2 + "} Td {2} Tj", pos.X, pos.Y, text); + _content.Append(Owner.Options.LineEnding); #endif if (underline) { @@ -831,13 +871,6 @@ void RenderText(string text, XFont font, XBrush brush, double x, double y, doubl } } - // ReSharper disable InconsistentNaming - static string? s_format1; - static string? s_format2; - static string? s_format3; - static string? s_format4; - // ReSharper restore InconsistentNaming - public void DrawString(string s, XGlyphTypeface typeface, XBrush brush, XRect rect, XStringFormat format) { // Placeholder @@ -890,13 +923,13 @@ public void DrawImage(XImage image, double x, double y, double width, double hei { if (_gfx.PageDirection == XPageDirection.Downwards) { - AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", - x, y + height, width, height, name); + AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q", x, y + height, width, height, name); + _content.Append(Owner.Options.LineEnding); } else { - AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", - x, y, width, height, name); + AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q", x, y, width, height, name); + _content.Append(Owner.Options.LineEnding); } } else @@ -915,7 +948,7 @@ public void DrawImage(XImage image, double x, double y, double width, double hei var xForm = form as XPdfForm; // Reset colors in this graphics state. Usually PDF images should set them. // But in rare cases they aren’t which may result in changed colors inside the image. - var resetColor = xForm != null ? "\n0 g\n0 G\n" : " "; + var resetColor = xForm != null ? $" 0 g 0 G " : " "; if (_gfx.PageDirection == XPageDirection.Downwards) { @@ -928,14 +961,14 @@ public void DrawImage(XImage image, double x, double y, double width, double hei xDraw -= xForm.Page!.MediaBox.X1; yDraw += xForm.Page.MediaBox.Y1; } - AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm 100 Tz {4} Do Q\n", - xDraw, yDraw + height, cx, cy, name); + AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm 100 Tz {4} Do Q", xDraw, yDraw + height, cx, cy, name); + _content.Append(Owner.Options.LineEnding); } else { // TODO_OLD Translation for MediaBox. - AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", - x, y, cx, cy, name); + AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q", x, y, cx, cy, name); + _content.Append(Owner.Options.LineEnding); } } } @@ -960,13 +993,13 @@ 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", - x, y + height, width, height, name); + AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q", x, y + height, width, height, name); + _content.Append(Owner.Options.LineEnding); } else { - AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", - x, y, width, height, name); + AppendFormatImage("q {2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q", x, y, width, height, name); + _content.Append(Owner.Options.LineEnding); } } else @@ -984,7 +1017,7 @@ 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. - var resetColor = xForm != null ? "\n0 g\n0 G\n" : " "; + var resetColor = xForm != null ? " 0 g 0 G " : " "; if (_gfx.PageDirection == XPageDirection.Downwards) { @@ -996,14 +1029,14 @@ public void DrawImage(XImage image, XRect destRect, XRect srcRect, XGraphicsUnit xDraw -= xForm.Page!.MediaBox.X1; yDraw += xForm.Page.MediaBox.Y1; } - AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", - xDraw, yDraw + height, cx, cy, name); + AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q", xDraw, yDraw + height, cx, cy, name); + _content.Append(Owner.Options.LineEnding); } else { // TODO_OLD Translation for MediaBox. - AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q\n", - x, y, cx, cy, name); + AppendFormatImage("q" + resetColor + "{2:" + format + "} 0 0 {3:" + format + "} {0:" + format + "} {1:" + format + "} cm {4} Do Q", x, y, cx, cy, name); + _content.Append(Owner.Options.LineEnding); } } } @@ -1165,9 +1198,10 @@ public void ResetClip() /// public void WriteComment(string comment) { - comment = comment.Replace("\n", "\n% "); + comment = comment.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", Owner.Options.LineEnding + "% "); // Nothing right of '% ' can break a PDF file. - Append("% " + comment + "\n"); + Append(Invariant($"% {comment}")); + Append(Owner.Options.LineEnding); } #endregion @@ -1344,12 +1378,14 @@ void AppendPartialArcQuadrant(double x, double y, double width, double height, d { case PathStart.MoveTo1st: pt1 = matrix.Transform(new XPoint(x0 + δx * cosα, y0 + δy * sinα)); - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", pt1.X, pt1.Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", pt1.X, pt1.Y); + _content.Append(Owner.Options.LineEnding); break; case PathStart.LineTo1st: pt1 = matrix.Transform(new XPoint(x0 + δx * cosα, y0 + δy * sinα)); - AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", pt1.X, pt1.Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} l", pt1.X, pt1.Y); + _content.Append(Owner.Options.LineEnding); break; case PathStart.Ignore1st: @@ -1358,8 +1394,9 @@ void AppendPartialArcQuadrant(double x, double y, double width, double height, d pt1 = matrix.Transform(new XPoint(x0 + δx * (cosα - κ * sinα), y0 + δy * (sinα + κ * cosα))); pt2 = matrix.Transform(new XPoint(x0 + δx * (cosβ + κ * sinβ), y0 + δy * (sinβ - κ * cosβ))); pt3 = matrix.Transform(new XPoint(x0 + δx * cosβ, y0 + δy * sinβ)); - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", pt1.X, pt1.Y, pt2.X, pt2.Y, pt3.X, pt3.Y); + _content.Append(Owner.Options.LineEnding); } else { @@ -1368,12 +1405,14 @@ void AppendPartialArcQuadrant(double x, double y, double width, double height, d { case PathStart.MoveTo1st: pt1 = matrix.Transform(new XPoint(x0 - δx * cosα, y0 - δy * sinα)); - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", pt1.X, pt1.Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", pt1.X, pt1.Y); + _content.Append(Owner.Options.LineEnding); break; case PathStart.LineTo1st: pt1 = matrix.Transform(new XPoint(x0 - δx * cosα, y0 - δy * sinα)); - AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", pt1.X, pt1.Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} l", pt1.X, pt1.Y); + _content.Append(Owner.Options.LineEnding); break; case PathStart.Ignore1st: @@ -1382,8 +1421,9 @@ void AppendPartialArcQuadrant(double x, double y, double width, double height, d pt1 = matrix.Transform(new XPoint(x0 - δx * (cosα - κ * sinα), y0 - δy * (sinα + κ * cosα))); pt2 = matrix.Transform(new XPoint(x0 - δx * (cosβ + κ * sinβ), y0 - δy * (sinβ - κ * cosβ))); pt3 = matrix.Transform(new XPoint(x0 - δx * cosβ, y0 - δy * sinβ)); - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", pt1.X, pt1.Y, pt2.X, pt2.Y, pt3.X, pt3.Y); + _content.Append(Owner.Options.LineEnding); } } @@ -1403,12 +1443,19 @@ void AppendPartialArc(SysPoint point1, SysPoint point2, double rotationAngle, int count = points.Count; int start = count % 3 == 1 ? 1 : 0; if (start == 1) - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[0].X, points[0].Y); + { + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", points[0].X, points[0].Y); + _content.Append(Owner.Options.LineEnding); + } + for (int idx = start; idx < count; idx += 3) - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + { + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", points[idx].X, points[idx].Y, points[idx + 1].X, points[idx + 1].Y, points[idx + 2].X, points[idx + 2].Y); + _content.Append(Owner.Options.LineEnding); + } } #endif @@ -1418,10 +1465,11 @@ 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; - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", 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), pt2.X, pt2.Y); + _content.Append(Owner.Options.LineEnding); } #if _CORE_ @@ -1452,14 +1500,19 @@ internal void AppendPath(GraphicsPath path) { case PathPointTypeStart: //PDF_moveto(pdf, points[idx].X, points[idx].Y); - AppendFormat("{0:" + format + "} {1:" + format + "} m\n", points[idx].X, points[idx].Y); + AppendFormat("{0:" + format + "} {1:" + format + "} m", points[idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); break; case PathPointTypeLine: //PDF_lineto(pdf, points[idx].X, points[idx].Y); - AppendFormat("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y); + AppendFormat("{0:" + format + "} {1:" + format + "} l", points[idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); if ((type & PathPointTypeCloseSubpath) != 0) - Append("h\n"); + { + Append("h"); + _content.Append(Owner.Options.LineEnding); + } break; case PathPointTypeBezier: @@ -1467,10 +1520,15 @@ internal void AppendPath(GraphicsPath path) //PDF_curveto(pdf, points[idx].X, points[idx].Y, // points[idx + 1].X, points[idx + 1].Y, // points[idx + 2].X, points[idx + 2].Y); - AppendFormat("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", points[idx].X, points[idx].Y, + AppendFormat("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", points[idx].X, points[idx].Y, points[++idx].X, points[idx].Y, points[++idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); if ((types[idx] & PathPointTypeCloseSubpath) != 0) - Append("h\n"); + { + Append("h"); + _content.Append(Owner.Options.LineEnding); + } + break; } } @@ -1566,14 +1624,19 @@ internal void AppendPath(GraphicsPath path) { case PathPointTypeStart: //PDF_moveto(pdf, points[idx].X, points[idx].Y); - AppendFormat("{0:" + format + "} {1:" + format + "} m\n", points[idx].X, points[idx].Y); + AppendFormat("{0:" + format + "} {1:" + format + "} m", points[idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); break; case PathPointTypeLine: //PDF_lineto(pdf, points[idx].X, points[idx].Y); - AppendFormat("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y); + AppendFormat("{0:" + format + "} {1:" + format + "} l", points[idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); if ((type & PathPointTypeCloseSubpath) != 0) - Append("h\n"); + { + Append("h"); + _content.Append(Owner.Options.LineEnding); + } break; case PathPointTypeBezier: @@ -1581,10 +1644,14 @@ internal void AppendPath(GraphicsPath path) //PDF_curveto(pdf, points[idx].X, points[idx].Y, // points[idx + 1].X, points[idx + 1].Y, // points[idx + 2].X, points[idx + 2].Y); - AppendFormat("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", points[idx].X, points[idx].Y, + AppendFormat("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", points[idx].X, points[idx].Y, points[++idx].X, points[idx].Y, points[++idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); if ((types[idx] & PathPointTypeCloseSubpath) != 0) - Append("h\n"); + { + Append("h"); + _content.Append(Owner.Options.LineEnding); + } break; } } @@ -1618,14 +1685,19 @@ void AppendPath(XPoint[] points, byte[] types) { case PathPointTypeStart: //PDF_moveto(pdf, points[idx].X, points[idx].Y); - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", points[idx].X, points[idx].Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", points[idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); break; case PathPointTypeLine: //PDF_lineto(pdf, points[idx].X, points[idx].Y); - AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", points[idx].X, points[idx].Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} l", points[idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); if ((type & PathPointTypeCloseSubpath) != 0) - Append("h\n"); + { + Append("h"); + _content.Append(Owner.Options.LineEnding); + } break; case PathPointTypeBezier: @@ -1633,10 +1705,14 @@ void AppendPath(XPoint[] points, byte[] types) //PDF_curveto(pdf, points[idx].X, points[idx].Y, // points[idx + 1].X, points[idx + 1].Y, // points[idx + 2].X, points[idx + 2].Y); - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", points[idx].X, points[idx].Y, + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", points[idx].X, points[idx].Y, points[++idx].X, points[idx].Y, points[++idx].X, points[idx].Y); + _content.Append(Owner.Options.LineEnding); if ((types[idx] & PathPointTypeCloseSubpath) != 0) - Append("h\n"); + { + Append("h"); + _content.Append(Owner.Options.LineEnding); + } break; } } @@ -1673,7 +1749,8 @@ internal void AppendPath(PathGeometry geometry) { // Move to start point. var currentPoint = figure.StartPoint; - AppendFormatPoint("{0:" + format + "} {1:" + format + "} m\n", currentPoint.X, currentPoint.Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} m", currentPoint.X, currentPoint.Y); + _content.Append(Owner.Options.LineEnding); foreach (PathSegment segment in figure.Segments) { @@ -1683,7 +1760,8 @@ internal void AppendPath(PathGeometry geometry) { // Draw a single line. var point = lineSegment.Point; - AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", point.X, point.Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} l", point.X, point.Y); + _content.Append(Owner.Options.LineEnding); currentPoint = point; } break; @@ -1694,7 +1772,8 @@ internal void AppendPath(PathGeometry geometry) var points = polyLineSegment.Points; foreach (SysPoint point in points) { - AppendFormatPoint("{0:" + format + "} {1:" + format + "} l\n", point.X, point.Y); + AppendFormatPoint("{0:" + format + "} {1:" + format + "} l", point.X, point.Y); + _content.Append(Owner.Options.LineEnding); } currentPoint = points[^1]; } @@ -1706,8 +1785,9 @@ internal void AppendPath(PathGeometry geometry) var point1 = bezierSegment.Point1; var point2 = bezierSegment.Point2; var point3 = bezierSegment.Point3; - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", point1.X, point1.Y, point2.X, point2.Y, point3.X, point3.Y); + _content.Append(Owner.Options.LineEnding); currentPoint = point3; } break; @@ -1725,8 +1805,9 @@ internal void AppendPath(PathGeometry geometry) var point1 = points[idx]; var point2 = points[idx + 1]; var point3 = points[idx + 2]; - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", point1.X, point1.Y, point2.X, point2.Y, point3.X, point3.Y); + _content.Append(Owner.Options.LineEnding); } currentPoint = points[count - 1]; } @@ -1771,8 +1852,9 @@ These calculations position the control points C1 and C2 so that the cubic curve var point1 = quadraticBezierSegment.Point1; var point2 = quadraticBezierSegment.Point2; var controlPoints = QuadraticToCubic(currentPoint.X, currentPoint.Y, point1.X, point1.Y, point2.X, point2.Y); - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", controlPoints.C1X, controlPoints.C1Y, controlPoints.C2X, controlPoints.C2Y, point2.X, point2.Y); + _content.Append(Owner.Options.LineEnding); currentPoint = point2; } break; @@ -1792,8 +1874,9 @@ These calculations position the control points C1 and C2 so that the cubic curve var point2 = points[idx + 1]; var controlPoints = QuadraticToCubic(currentPoint.X, currentPoint.Y, point1.X, point1.Y, point2.X, point2.Y); - AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c\n", + AppendFormat3Points("{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "} {4:" + format + "} {5:" + format + "} c", controlPoints.C1X, controlPoints.C1Y, controlPoints.C2X, controlPoints.C2Y, point2.X, point2.Y); + _content.Append(Owner.Options.LineEnding); currentPoint = point2; } } @@ -1811,7 +1894,10 @@ These calculations position the control points C1 and C2 so that the cubic curve } } if (figure.IsClosed) - Append("h\n"); + { + Append("h"); + _content.Append(Owner.Options.LineEnding); + } } } } @@ -1822,6 +1908,36 @@ internal void Append(string value) _content.Append(value); } + internal void AppendFormatArgs(string format, object arg1) + { + _content.AppendFormat(CultureInfo.InvariantCulture, format, arg1); +#if DEBUG_ + string dummy = _content.ToString(); + dummy = dummy.Substring(Math.Max(0, dummy.Length - 100)); + dummy.GetType(); +#endif + } + + internal void AppendFormatArgs(string format, object arg1, object arg2) + { + _content.AppendFormat(CultureInfo.InvariantCulture, format, arg1, arg2); +#if DEBUG_ + string dummy = _content.ToString(); + dummy = dummy.Substring(Math.Max(0, dummy.Length - 100)); + dummy.GetType(); +#endif + } + + internal void AppendFormatArgs(string format, object arg1, object arg2, object arg3) + { + _content.AppendFormat(CultureInfo.InvariantCulture, format, arg1, arg2, arg3); +#if DEBUG_ + string dummy = _content.ToString(); + dummy = dummy.Substring(Math.Max(0, dummy.Length - 100)); + dummy.GetType(); +#endif + } + internal void AppendFormatArgs(string format, params object[] args) { _content.AppendFormat(CultureInfo.InvariantCulture, format, args); @@ -1898,20 +2014,22 @@ void AppendStrokeFill(XPen? pen, XBrush? brush, XFillMode fillMode, bool closePa if (fillMode == XFillMode.Winding) { if (pen != null && brush != null) - _content.Append("B\n"); + _content.Append("B"); else if (pen != null) - _content.Append("S\n"); + _content.Append("S"); else - _content.Append("f\n"); + _content.Append("f"); + _content.Append(Owner.Options.LineEnding); } else { if (pen != null && brush != null) - _content.Append("B*\n"); + _content.Append("B*"); else if (pen != null) - _content.Append("S\n"); + _content.Append("S"); else - _content.Append("f*\n"); + _content.Append("f*"); + _content.Append(Owner.Options.LineEnding); } } #endregion @@ -2050,7 +2168,8 @@ void EndPage() { if (_streamMode == StreamMode.Text) { - _content.Append("ET\n"); + _content.Append("ET"); + _content.Append(Owner.Options.LineEnding); _streamMode = StreamMode.Graphic; } @@ -2079,7 +2198,10 @@ internal void BeginGraphicMode() // Why the check? if (_streamMode == StreamMode.Text) - _content.Append("ET\n"); + { + _content.Append("ET"); + _content.Append(Owner.Options.LineEnding); + } _streamMode = StreamMode.Graphic; } @@ -2095,7 +2217,8 @@ internal void BeginTextMode() Debug.Assert(_streamMode == StreamMode.Graphic, "Undefined stream mode. Check what happened."); _streamMode = StreamMode.Text; - _content.Append("BT\n"); + _content.Append("BT"); + _content.Append(Owner.Options.LineEnding); // Text matrix is empty after BT. _gfxState.RealizedTextPosition = new XPoint(); _gfxState.ItalicSimulationOn = false; @@ -2380,7 +2503,8 @@ void SaveState() _gfxStateStack.Push(_gfxState); _gfxState = _gfxState.Clone(); _gfxState.Level = _gfxStateStack.Count; - Append("q\n"); + Append("q"); + _content.Append(Owner.Options.LineEnding); } /// @@ -2391,7 +2515,8 @@ void RestoreState() Debug.Assert(_streamMode == StreamMode.Graphic, "Cannot restore state in text mode."); _gfxState = _gfxStateStack.Pop(); - Append("Q\n"); + Append("Q"); + _content.Append(Owner.Options.LineEnding); } PdfGraphicsState RestoreState(InternalGraphicsState state) @@ -2400,11 +2525,13 @@ PdfGraphicsState RestoreState(InternalGraphicsState state) var top = _gfxStateStack.Pop(); while (top.InternalState != state) { - Append("Q\n"); + Append("Q"); + _content.Append(Owner.Options.LineEnding); count++; top = _gfxStateStack.Pop(); } - Append("Q\n"); + Append("Q"); + _content.Append(Owner.Options.LineEnding); _gfxState = top; return top; } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs index cefef326..09ea50ec 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.AcroForms/PdfTextField.cs @@ -217,7 +217,7 @@ void RenderAppearance() string s = xobj.Stream?.ToString() ?? ""; // Thank you Adobe: Without putting the content in 'EMC brackets' // the text is not rendered by PDF Reader 9 or higher. - s = "/Tx BMC\n" + s + "\nEMC"; + s = "/Tx BMC" + _document.Options.LineEnding + s + _document.Options.LineEnding + "EMC"; if (xobj.Stream != null) xobj.Stream.Value = new RawEncoding().GetBytes(s); #endif 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..08bb15c7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContent.cs @@ -93,23 +93,39 @@ internal void PreserveGraphicsState() // prepended or appended. Some nasty PDF tools do not preserve the graphical state correctly. // Therefore, we try to relieve the problem by surrounding the content stream with push/restore // graphic state operation. - if (Stream != null!) + if (Stream != null) { var value = Stream.Value; - if (value != null!) // NRT + if (value != null) // NRT { int length = value.Length; - if (length > 1 && ((value[0] != (byte)'q' || value[1] != (byte)'\n'))) + if (length > 1 && (value[0] != (byte)'q' || value[1] is not (byte)'\r' and not (byte)'\n' and not (byte)' ')) { - var newValue = new byte[length + 2 + 3]; - newValue[0] = (byte)'q'; - newValue[1] = (byte)'\n'; - Array.Copy(value, 0, newValue, 2, length); - newValue[length + 2] = (byte)' '; - newValue[length + 3] = (byte)'Q'; - newValue[length + 4] = (byte)'\n'; + var lineEnding = _document.Options.LineEndingBytes; + var newValue = new byte[length + 2 * lineEnding.Length + 3]; + int written = 0; + + newValue[written] = (byte)'q'; + written++; + Array.Copy(lineEnding, 0, newValue, written, lineEnding.Length); + written += lineEnding.Length; + + Array.Copy(value, 0, newValue, written, length); + written += length; + + newValue[written] = (byte)' '; + written++; + newValue[written] = (byte)'Q'; + written++; + Array.Copy(lineEnding, 0, newValue, written, lineEnding.Length); + written += lineEnding.Length; + +#if DEBUG + Debug.Assert(written == newValue.Length); +#endif + Stream.Value = newValue; - Elements.SetInteger("/Length", Stream.Length); + Elements.SetInteger("/Length", newValue.Length); } } } 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..9bfb52da 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfContents.cs @@ -76,23 +76,26 @@ public PdfContent PrependContent() /// Creates a single content stream with the bytes from the array of the content streams. /// This operation does not modify any of the content streams in this array. /// + /// This operation actually concats multiple streams by new line public PdfContent CreateSingleContent() { - byte[] bytes = []; - byte[] bytes1; - byte[] bytes2; - foreach (PdfItem iref in Elements) + var content = new PdfContent(Owner); + var lineEnding = _document.Options.LineEndingBytes; + using (var stream = new MemoryStream()) { - PdfDictionary cont = (PdfDictionary)((PdfReference)iref).Value; - bytes1 = bytes; - 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); + foreach (PdfItem iref in Elements) + { + var cont = (PdfDictionary)((PdfReference)iref).Value; + var data = cont.Stream!.UnfilteredValue; + stream.Write(data, 0, data.Length); + stream.Write(lineEnding, 0, lineEnding.Length); + } + + if (stream.Length > 0) + stream.SetLength(stream.Length - lineEnding.Length); + + content.Stream = new PdfStream(stream.ToArray(), content); } - PdfContent content = new PdfContent(Owner); - content.Stream = new PdfDictionary.PdfStream(bytes, content); return content; } @@ -140,30 +143,53 @@ void SetModified() else if (count > 1) { // Surround content streams with q/Q operations - byte[] value; - int length; - PdfContent content = (PdfContent)((PdfReference)Elements[0]).Value; + var lineEnding = _document.Options.LineEndingBytes; + + var content = (PdfContent)((PdfReference)Elements[0]).Value; if (content != null && content.Stream != null) { - length = content.Stream.Length; - value = new byte[length + 2]; - value[0] = (byte)'q'; - value[1] = (byte)'\n'; - Array.Copy(content.Stream.Value, 0, value, 2, length); + int written = 0; + var length = content.Stream.Length; + var value = new byte[length + 1 + lineEnding.Length]; + + value[written] = (byte)'q'; + written++; + + Array.Copy(lineEnding, 0, value, written, lineEnding.Length); + written += lineEnding.Length; + + Array.Copy(content.Stream.Value, 0, value, written, length); + written += length; +#if DEBUG + Debug.Assert(written == value.Length); +#endif content.Stream.Value = value; - content.Elements.SetInteger("/Length", length + 2); + content.Elements.SetInteger("/Length", value.Length); } + content = (PdfContent)((PdfReference)Elements[count - 1]).Value; if (content != null && content.Stream != null) { - length = content.Stream.Length; - value = new byte[length + 3]; - Array.Copy(content.Stream.Value, 0, value, 0, length); - value[length] = (byte)' '; - value[length + 1] = (byte)'Q'; - value[length + 2] = (byte)'\n'; + int written = 0; + var length = content.Stream.Length; + var value = new byte[length + 2 + lineEnding.Length]; + + Array.Copy(content.Stream.Value, 0, value, written, length); + written += length; + + value[written] = (byte)' '; + written++; + + value[written] = (byte)'Q'; + written++; + + Array.Copy(lineEnding, 0, value, written, lineEnding.Length); + written += lineEnding.Length; +#if DEBUG + Debug.Assert(written == value.Length); +#endif content.Stream.Value = value; - content.Elements.SetInteger("/Length", length + 3); + content.Elements.SetInteger("/Length", value.Length); } } } 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 a870fbfb..5963400d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfCrossReferenceTable.cs @@ -181,20 +181,36 @@ public int GetNewObjectNumber() /// internal void WriteObject(PdfWriter writer) { - writer.WriteRaw("xref\n"); + writer.WriteRaw("xref"); + writer.WriteRaw(document.Options.LineEnding); var iRefs = AllReferences; int count = iRefs.Length; - writer.WriteRaw(Invariant($"0 {count + 1}\n")); - writer.WriteRaw(Invariant($"{0:0000000000} {65535:00000} f \n")); + writer.WriteRaw(Invariant($"0 {count + 1}")); + writer.WriteRaw(document.Options.LineEnding); + + // Acrobat is very pedantic; it must be exactly 20 bytes per line. + switch (document.Options.LineEnding.Length) + { + case 1: writer.WriteRaw("0000000000 65535 f "); break; + case 2: writer.WriteRaw("0000000000 65535 f"); break; + default: throw new ArgumentOutOfRangeException(nameof(document.Options.LineEnding), document.Options.LineEnding.Length, "Line ending length is invalid"); + } + writer.WriteRaw(document.Options.LineEnding); for (int idx = 0; idx < count; idx++) { var iref = iRefs[idx]; // Acrobat is very pedantic; it must be exactly 20 bytes per line. - writer.WriteRaw(Invariant($"{iref.Position:0000000000} {iref.GenerationNumber:00000} n \n")); + switch (document.Options.LineEnding.Length) + { + case 1: writer.WriteRaw(Invariant($"{iref.Position:0000000000} {iref.GenerationNumber:00000} n ")); break; + case 2: writer.WriteRaw(Invariant($"{iref.Position:0000000000} {iref.GenerationNumber:00000} n")); break; + default: throw new ArgumentOutOfRangeException(nameof(document.Options.LineEnding), document.Options.LineEnding.Length, "Line ending length is invalid"); + } + writer.WriteRaw(document.Options.LineEnding); } } 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..dfced52c 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfReference.cs @@ -100,7 +100,13 @@ 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. - writer.WriteRaw(Invariant($"{_position:0000000000} {_objectID.GenerationNumber:00000} n \n")); + switch (Document.Options.LineEnding.Length) + { + case 1: writer.WriteRaw(Invariant($"{_position:0000000000} {_objectID.GenerationNumber:00000} n ")); break; + case 2: writer.WriteRaw(Invariant($"{_position:0000000000} {_objectID.GenerationNumber:00000} n")); break; + default: throw new ArgumentOutOfRangeException(nameof(Document.Options.LineEnding), Document.Options.LineEnding.Length, "Line ending length is invalid"); + } + writer.WriteRaw(Document.Options.LineEnding); } /// 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..3ab898cb 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Advanced/PdfToUnicodeMap.cs @@ -38,11 +38,11 @@ internal override void PrepareForSave() // This code comes literally from PDF Reference. string prefix = - "/CIDInit /ProcSet findresource begin\n" + - "12 dict begin\n" + - "begincmap\n" + - "/CIDSystemInfo << /Registry (Adobe)/Ordering (UCS)/Supplement 0>> def\n" + - "/CMapName /Adobe-Identity-UCS def /CMapType 2 def\n"; + "/CIDInit /ProcSet findresource begin" + _document.Options.LineEnding + + "12 dict begin" + _document.Options.LineEnding + + "begincmap" + _document.Options.LineEnding + + "/CIDSystemInfo << /Registry (Adobe)/Ordering (UCS)/Supplement 0>> def" + _document.Options.LineEnding + + "/CMapName /Adobe-Identity-UCS def /CMapType 2 def" + _document.Options.LineEnding; string suffix = "endcmap CMapName currentdict /CMap defineresource pop end end"; //var glyphIndexToCharacter = new Dictionary(); 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..dd427233 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.Content/ContentWriter.cs @@ -68,6 +68,7 @@ public void WriteRaw(string rawString) _lastCat = GetCategory((char)bytes[bytes.Length - 1]); } + [Obsolete("Does not honor document line endings")] public void WriteLineRaw(string rawString) { if (String.IsNullOrEmpty(rawString)) @@ -148,6 +149,7 @@ void WriteSeparator(CharCat cat) WriteSeparator(cat, '\0'); } + [Obsolete("Does not honor document line endings")] public void NewLine() { if (_lastCat != CharCat.NewLine) 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..2a119ae4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/Lexer.cs @@ -1180,7 +1180,7 @@ public string DumpNeighborhoodOfPosition(SizeType position = -1, bool hex = fals //Chars.SP => "・", // U+30FB //<= ' ' => $"⁌((int)ch).ToString(\"X2\")⁍", - <= ' ' => $"""⟬{(((int)ch).ToString("X2"))}⟭""", + <= ' ' => $"⟬{(int)ch:X2}⟭", _ => ch.ToString() }; 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 85211069..b1759683 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs @@ -283,6 +283,7 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode _document.Options.EnableImplicitTransparencyGroup = _options.EnableImplicitTransparencyGroup; _document.Options.EnableImplicitMetadata = _options.EnableImplicitMetadata; _document.Options.EnableWriterCommentInTrailer = _options.EnableWriterCommentInTrailer; + _document.Options.EnableLfLineEndings = _options.EnableLfLineEndings; _document._state |= DocumentState.Imported; _document._openMode = openMode; 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 d42f9001..cb7e2cb2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs @@ -109,6 +109,8 @@ public class PdfReaderOptions public bool EnableWriterCommentInTrailer { get; set; } = true; + public bool EnableLfLineEndings { get; set; } = true; + // Testing only //public bool UseOldCode { get; set; } = false; 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 e3242470..09452d77 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs @@ -355,9 +355,15 @@ public void WriteBeginObject(PdfObject obj) if (indirect) { if (obj is PdfArray) - WriteRaw("[\n"); + { + WriteRaw("["); + WriteRaw(_document.Options.LineEnding); + } else if (obj is PdfDictionary) - WriteRaw("<<\n"); + { + WriteRaw("<<"); + WriteRaw(_document.Options.LineEnding); + } _lastCat = CharCat.NewLine; } else @@ -372,7 +378,8 @@ public void WriteBeginObject(PdfObject obj) { NewLine(); WriteSeparator(CharCat.Delimiter); - WriteRaw("<<\n"); + WriteRaw("<<"); + WriteRaw(_document.Options.LineEnding); _lastCat = CharCat.NewLine; } } @@ -401,7 +408,9 @@ public void WriteEndObject() { if (indirect) { - WriteRaw("\n]\n"); + WriteRaw(_document.Options.LineEnding); + WriteRaw("]"); + WriteRaw(_document.Options.LineEnding); _lastCat = CharCat.NewLine; } else @@ -415,22 +424,30 @@ public void WriteEndObject() if (indirect) { if (!stackItem.HasStream) - WriteRaw(_lastCat == CharCat.NewLine ? ">>\n" : " >>\n"); + { + WriteRaw(_lastCat == CharCat.NewLine ? ">>" : " >>"); + WriteRaw(_document.Options.LineEnding); + } } else { Debug.Assert(!stackItem.HasStream, "Direct object with stream??"); WriteSeparator(CharCat.NewLine); - WriteRaw(">>\n"); + WriteRaw(">>"); + WriteRaw(_document.Options.LineEnding); _lastCat = CharCat.NewLine; } } if (indirect) { NewLine(); - WriteRaw("endobj\n"); + WriteRaw("endobj"); + WriteRaw(_document.Options.LineEnding); if (Layout == PdfWriterLayout.Verbose) - WriteRaw("%--------------------------------------------------------------------------------------------------\n"); + { + WriteRaw("%--------------------------------------------------------------------------------------------------"); + WriteRaw(_document.Options.LineEnding); + } } } @@ -444,11 +461,15 @@ public void WriteStream(PdfDictionary value, bool omitStream) Debug.Assert(stackItem.Object.IsIndirect); stackItem.HasStream = true; - WriteRaw(_lastCat == CharCat.NewLine ? ">>\nstream\n" : " >>\nstream\n"); + WriteRaw(_lastCat == CharCat.NewLine ? ">>" : " >>"); + WriteRaw(_document.Options.LineEnding); + WriteRaw("stream"); + WriteRaw(_document.Options.LineEnding); if (omitStream) { - WriteRaw(" «…stream content omitted…»\n"); // useful for debugging only + WriteRaw(" «…stream content omitted…»"); // useful for debugging only + WriteRaw(_document.Options.LineEnding); } else { @@ -458,7 +479,9 @@ public void WriteStream(PdfDictionary value, bool omitStream) if (bytes.Length != 0) Write(bytes); } - WriteRaw("\nendstream\n"); + WriteRaw(_document.Options.LineEnding); + WriteRaw("endstream"); + WriteRaw(_document.Options.LineEnding); } public void WriteRaw(string rawString) @@ -493,83 +516,103 @@ void WriteObjectAddress(PdfObject value) if (Layout == PdfWriterLayout.Verbose) { string comment = value.Comment; - if (!String.IsNullOrEmpty(comment)) - comment = $" -- {value.Comment}"; + if (String.IsNullOrEmpty(comment)) + comment = value.GetType().FullName!; + else + comment = $"{value.GetType().FullName} -- {value.Comment}"; + comment = comment.Replace("\r\n", "\n").Replace("\r", "\n").Replace("\n", _document.Options.LineEnding + "% "); + + // Nothing right of '% ' can break a PDF file. #if DEBUG_ if (_document is null) _ = typeof(int); #endif // #PDF-A - if (_document.IsPdfA) + //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")); - } - 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($"% {comment}")); + WriteRaw(_document.Options.LineEnding); + WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj")); + WriteRaw(_document.Options.LineEnding); } + //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}")); + // WriteRaw(_document.Options.LineEnding); + //} } else { // Write object number only. - WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj\n")); + WriteRaw(Invariant($"{value.ObjectID.ObjectNumber} {value.ObjectID.GenerationNumber} obj")); + WriteRaw(_document.Options.LineEnding); } } + const string blank = "% "; + public void WriteFileHeader(PdfDocument document) { - var header = new StringBuilder("%PDF-"); 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()); + WriteRaw(Invariant($"%PDF-{version / 10}.{version % 10}")); + WriteRaw(document.Options.LineEnding); + WriteRaw("%\xD3\xF4\xCC\xE1"); + WriteRaw(document.Options.LineEnding); if (Layout == PdfWriterLayout.Verbose) { - WriteRaw($"% PDFsharp Version {PdfSharpProductVersionInformation.Version} (verbose mode)\n"); + WriteRaw($"% PDFsharp Version {PdfSharpProductVersionInformation.Version} (verbose mode)"); + WriteRaw(_document.Options.LineEnding); // 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 + + //Should match lineLen below + WriteRaw(blank); WriteRaw(document.Options.LineEnding); // Creation date placeholder + WriteRaw(blank); WriteRaw(document.Options.LineEnding); // Creation time placeholder + WriteRaw(blank); WriteRaw(document.Options.LineEnding); // File size placeholder + WriteRaw(blank); WriteRaw(document.Options.LineEnding); // Pages placeholder + WriteRaw(blank); WriteRaw(document.Options.LineEnding); // Objects placeholder #if DEBUG - WriteRaw("% This document is created from a DEBUG build. Do not use a DEBUG build of PDFsharp for production.\n"); + WriteRaw("% This document is created from a DEBUG build. Do not use a DEBUG build of PDFsharp for production."); + WriteRaw(document.Options.LineEnding); #endif - WriteRaw("%--------------------------------------------------------------------------------------------------\n"); } } public void WriteEof(PdfDocument document, SizeType startxref) { if(document.Options.EnableWriterCommentInTrailer) - WriteRaw($"% Created with PDFsharp {PdfSharpProductVersionInformation.SemanticVersion} ({Capabilities.Build.BuildName}) under .NET {Capabilities.Build.Framework}\n"); - WriteRaw("startxref\n"); - WriteRaw(startxref.ToString(CultureInfo.InvariantCulture)); - WriteRaw("\n%%EOF\n"); - SizeType fileSize = (SizeType)_stream.Position; + { + WriteRaw($"% Created with PDFsharp {PdfSharpProductVersionInformation.SemanticVersion} ({Capabilities.Build.BuildName}) under .NET {Capabilities.Build.Framework}"); + WriteRaw(document.Options.LineEnding); + } + WriteRaw("startxref"); + WriteRaw(document.Options.LineEnding); + WriteRaw(startxref.ToString(CultureInfo.InvariantCulture)); + WriteRaw(document.Options.LineEnding); + WriteRaw("%%EOF"); + WriteRaw(document.Options.LineEnding); if (Layout == PdfWriterLayout.Verbose) { + var fileSize = _stream.Position; TimeSpan duration = DateTime.Now - document._creation; + var lineLen = blank.Length + document.Options.LineEnding.Length; _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; + _stream.Position = _commentPosition + lineLen; WriteRaw(Invariant($"Creation time: {duration.TotalSeconds:0.000} seconds")); - _stream.Position = _commentPosition + 100; + _stream.Position = _commentPosition + 2* lineLen; WriteRaw(Invariant($"File size: {fileSize:#,###} bytes")); - _stream.Position = _commentPosition + 150; + _stream.Position = _commentPosition + 3* lineLen; WriteRaw(Invariant($"Pages: {document.Pages.Count:#}")); // No thousands separator here. - _stream.Position = _commentPosition + 200; + _stream.Position = _commentPosition + 4* lineLen; WriteRaw(Invariant($"Objects: {document.IrefTable.Count:#,###}")); } } @@ -641,7 +684,7 @@ void WriteSeparator(CharCat cat /*, char ch = '\0'*/) public void NewLine() { if (_lastCat != CharCat.NewLine) - WriteRaw('\n'); + WriteRaw(_document.Options.LineEnding); } static CharCat GetCategory(char ch) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index 62fdf25b..d16eba73 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -374,7 +374,8 @@ async Task DoSaveAsync(PdfWriter writer) // ReSharper disable once RedundantCast. Redundant only if 64 bit. var startXRef = (SizeType)writer.Position; IrefTable.WriteObject(writer); - writer.WriteRaw("trailer\n"); + writer.WriteRaw("trailer"); + writer.WriteRaw(Options.LineEnding); Trailer.Elements.SetInteger("/Size", count + 1); Trailer.WriteObject(writer); writer.WriteEof(this, startXRef); diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs index 4a708850..48bbc26f 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 System.Text; + namespace PdfSharp.Pdf { /// @@ -91,5 +93,11 @@ public PdfUseFlateDecoderForJpegImages UseFlateDecoderForJpegImages public bool EnableImplicitMetadata { get; set; } = true; public bool EnableWriterCommentInTrailer { get; set; } = true; + + public bool EnableLfLineEndings { get; set; } = true; + + public string LineEnding => EnableLfLineEndings ? "\n" : "\r\n"; + + public byte[] LineEndingBytes => Encoding.ASCII.GetBytes(LineEnding); } } diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs index 9bbef518..dac194a2 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfMetadata.cs @@ -76,45 +76,43 @@ static DateTime SpecifyLocalDateTimeKindIfUnspecified(DateTime value) string? pdfA = null; if (_document.IsPdfA) { + pdfA = + " " + _document.Options.LineEnding + + " 1" + _document.Options.LineEnding + + " A" + _document.Options.LineEnding + + " "; // #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} - - - - """; + var str = + "" + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + $" {producer}{keywords}" + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + $" {title}" + _document.Options.LineEnding + + $" {author}" + _document.Options.LineEnding + + $" {subject}" + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + $" {creator}" + _document.Options.LineEnding + + $" {creationDate}" + _document.Options.LineEnding + + $" {modificationDate}" + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + $" uuid:{documentId}" + _document.Options.LineEnding + + $" uuid:{instanceId}" + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + pdfA + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + " " + _document.Options.LineEnding + + ""; #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 + // XMP Documentation: http://www.images.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. diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs index c5989ff1..9a1f563d 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfOutline.cs @@ -610,7 +610,7 @@ static string Fd(double? value) internal override void WriteObject(PdfWriter writer) { #if DEBUG - writer.WriteRaw("% Title = " + FilterUnicode(Title) + "\n"); + writer.WriteRaw("% Title = " + FilterUnicode(Title) + Owner.Options.LineEnding);// #endif // TODO_OLD: Proof that there is nothing to do here. bool hasKids = HasChildren; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/StructureElementStack.cs b/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/StructureElementStack.cs index 58f85943..c346f7ae 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/StructureElementStack.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/UniversalAccessibility/StructureElementStack.cs @@ -110,13 +110,15 @@ public override void BeginItem() { base.BeginItem(); // Begin marked content. - StructureBuilder.Content.Write($"{Tag}<>BDC\n"); + StructureBuilder.Content.Write($"{Tag}<>BDC"); + StructureBuilder.Content.Write(StructureBuilder.UaManager.Owner.Options.LineEnding); } public override void EndItem() { // End marked content. - StructureBuilder.Content.Write("EMC\n"); + StructureBuilder.Content.Write("EMC"); + StructureBuilder.Content.Write(StructureBuilder.UaManager.Owner.Options.LineEnding); base.EndItem(); } } @@ -134,13 +136,15 @@ public override void BeginItem() { base.BeginItem(); // Begin artifact. - StructureBuilder.Content.Write("/Artifact BMC\n"); + StructureBuilder.Content.Write("/Artifact BMC"); + StructureBuilder.Content.Write(StructureBuilder.UaManager.Owner.Options.LineEnding); } public override void EndItem() { // End artifact. - StructureBuilder.Content.Write("EMC\n"); + StructureBuilder.Content.Write("EMC"); + StructureBuilder.Content.Write(StructureBuilder.UaManager.Owner.Options.LineEnding); base.EndItem(); } } From 835ec20b9424262f3a88532beb20910ad54022bd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Wed, 25 Jun 2025 10:42:55 +0200 Subject: [PATCH 06/11] replace binary tag --- src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) 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 09452d77..ecd4c1ec 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs @@ -559,7 +559,8 @@ public void WriteFileHeader(PdfDocument document) int version = document._version; WriteRaw(Invariant($"%PDF-{version / 10}.{version % 10}")); WriteRaw(document.Options.LineEnding); - WriteRaw("%\xD3\xF4\xCC\xE1"); + //WriteRaw("%\xD3\xF4\xCC\xE1"); + WriteRaw("%\xE2\xE3\xCF\xD3"); WriteRaw(document.Options.LineEnding); if (Layout == PdfWriterLayout.Verbose) From 2d72b49a24f1ff3392ebc84103ca62ab729d6318 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Wed, 25 Jun 2025 12:48:25 +0200 Subject: [PATCH 07/11] put pdf pages at end --- .../src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index d16eba73..14a4eaf4 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -361,13 +361,13 @@ async Task DoSaveAsync(PdfWriter writer) writer.WriteFileHeader(this); var irefs = IrefTable.AllReferences; int count = irefs.Length; - for (int idx = 0; idx < count; idx++) + foreach(var iref in irefs.Where(x=>x.Value is not PdfPages)) + { + iref.Position = writer.Position; + iref.Value.WriteObject(writer); + } + foreach (var iref in irefs.Where(x => x.Value is PdfPages)) { - PdfReference iref = irefs[idx]; -#if DEBUG_ - if (iref.ObjectNumber == 378) - _ = typeof(int); -#endif iref.Position = writer.Position; iref.Value.WriteObject(writer); } From 629e2e0c4daca38eb5a8000651051bc53bc2c743 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Wed, 25 Jun 2025 13:14:23 +0200 Subject: [PATCH 08/11] put pdf catalog at end --- .../PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 26 ++++++++++++++----- 1 file changed, 19 insertions(+), 7 deletions(-) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index 14a4eaf4..185021f9 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -359,14 +359,26 @@ async Task DoSaveAsync(PdfWriter writer) effectiveSecurityHandler?.PrepareForWriting(); writer.WriteFileHeader(this); - var irefs = IrefTable.AllReferences; - int count = irefs.Length; - foreach(var iref in irefs.Where(x=>x.Value is not PdfPages)) + var irefs = IrefTable.AllReferences.ToList(); + irefs.Sort((a, b) => { - iref.Position = writer.Position; - iref.Value.WriteObject(writer); - } - foreach (var iref in irefs.Where(x => x.Value is PdfPages)) + int getPrio(PdfReference r) => r.Value switch + { + PdfPages => 1, + PdfCatalog => 2, + _ => 0, + }; + var cmp = getPrio(a).CompareTo(getPrio(b)); + if (cmp != 0) + return cmp; + cmp = a.GenerationNumber.CompareTo(b.GenerationNumber); + if (cmp != 0) + return cmp; + cmp = a.ObjectNumber.CompareTo(b.ObjectNumber); + return cmp; + }); + int count = irefs.Count; + foreach(var iref in irefs) { iref.Position = writer.Position; iref.Value.WriteObject(writer); From 8dc9190b29ac3a44ea01ed0f382580f35d717310 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Wed, 25 Jun 2025 13:51:02 +0200 Subject: [PATCH 09/11] flatten array objects --- src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) 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 ecd4c1ec..014032a3 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs @@ -357,7 +357,7 @@ public void WriteBeginObject(PdfObject obj) if (obj is PdfArray) { WriteRaw("["); - WriteRaw(_document.Options.LineEnding); + //WriteRaw(_document.Options.LineEnding); } else if (obj is PdfDictionary) { @@ -408,7 +408,7 @@ public void WriteEndObject() { if (indirect) { - WriteRaw(_document.Options.LineEnding); + //WriteRaw(_document.Options.LineEnding); WriteRaw("]"); WriteRaw(_document.Options.LineEnding); _lastCat = CharCat.NewLine; From 9c9fa545af5aeed201724acbd9c73905b18601f2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Wed, 25 Jun 2025 14:26:26 +0200 Subject: [PATCH 10/11] add flags --- .../PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs | 7 +++- .../src/PdfSharp/Pdf.IO/PdfReaderOptions.cs | 30 ++++++++++++++++ .../PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs | 12 ++++--- .../PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs | 31 +++++++++-------- .../src/PdfSharp/Pdf/PdfDocumentOptions.cs | 34 +++++++++++++++++-- 5 files changed, 93 insertions(+), 21 deletions(-) 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 b1759683..c3042211 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs @@ -278,12 +278,17 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode var lexer = new Lexer(stream, _logger); _document = new PdfDocument(lexer); - _document.Options.EnableReferenceCompaction = _options.EnableReferenceCompaction; + _document.Options.EnableReferenceRenumbering = _options.EnableReferenceRenumbering; + _document.Options.EnableReferenceCompaction = _options.EnableReferenceCompaction; _document.Options.EnableImplicitTransparencyGroup = _options.EnableImplicitTransparencyGroup; _document.Options.EnableImplicitMetadata = _options.EnableImplicitMetadata; _document.Options.EnableWriterCommentInTrailer = _options.EnableWriterCommentInTrailer; _document.Options.EnableLfLineEndings = _options.EnableLfLineEndings; + _document.Options.EnableOwnBinaryHeader = _options.EnableOwnBinaryHeader; + _document.Options.EnableLineBreakInArrayObjects = _options.EnableLineBreakInArrayObjects; + _document.Options.DisablePagesAndCatalogAtEnd = _options.DisablePagesAndCatalogAtEnd; + _document._state |= DocumentState.Imported; _document._openMode = openMode; 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 cb7e2cb2..4a9824b7 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs @@ -99,18 +99,48 @@ public class PdfReaderOptions public ReaderProblemDelegate? ReaderProblemCallback { get; set; } + /// + /// Set to to enable greater compatibility + /// public bool EnableReferenceRenumbering { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// public bool EnableReferenceCompaction { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// public bool EnableImplicitTransparencyGroup { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// public bool EnableImplicitMetadata { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// public bool EnableWriterCommentInTrailer { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// public bool EnableLfLineEndings { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// + public bool EnableOwnBinaryHeader { get; set; } = true; + + /// + /// Set to to enable greater compatibility + /// + public bool EnableLineBreakInArrayObjects { get; set; } = true; + + public bool DisablePagesAndCatalogAtEnd { get; set; } = true; + // Testing only //public bool UseOldCode { get; set; } = false; 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 014032a3..f25deaa6 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs @@ -357,7 +357,8 @@ public void WriteBeginObject(PdfObject obj) if (obj is PdfArray) { WriteRaw("["); - //WriteRaw(_document.Options.LineEnding); + if(_document.Options.EnableLineBreakInArrayObjects) + WriteRaw(_document.Options.LineEnding); } else if (obj is PdfDictionary) { @@ -408,7 +409,8 @@ public void WriteEndObject() { if (indirect) { - //WriteRaw(_document.Options.LineEnding); + if (_document.Options.EnableLineBreakInArrayObjects) + WriteRaw(_document.Options.LineEnding); WriteRaw("]"); WriteRaw(_document.Options.LineEnding); _lastCat = CharCat.NewLine; @@ -559,8 +561,10 @@ public void WriteFileHeader(PdfDocument document) int version = document._version; WriteRaw(Invariant($"%PDF-{version / 10}.{version % 10}")); WriteRaw(document.Options.LineEnding); - //WriteRaw("%\xD3\xF4\xCC\xE1"); - WriteRaw("%\xE2\xE3\xCF\xD3"); + if(document.Options.EnableOwnBinaryHeader) + WriteRaw("%\xD3\xF4\xCC\xE1"); + else + WriteRaw("%\xE2\xE3\xCF\xD3"); WriteRaw(document.Options.LineEnding); if (Layout == PdfWriterLayout.Verbose) diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs index 185021f9..4b503539 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocument.cs @@ -360,23 +360,26 @@ async Task DoSaveAsync(PdfWriter writer) writer.WriteFileHeader(this); var irefs = IrefTable.AllReferences.ToList(); - irefs.Sort((a, b) => + if (!Options.DisablePagesAndCatalogAtEnd) { - int getPrio(PdfReference r) => r.Value switch + irefs.Sort((a, b) => { - PdfPages => 1, - PdfCatalog => 2, - _ => 0, - }; - var cmp = getPrio(a).CompareTo(getPrio(b)); - if (cmp != 0) + int getPrio(PdfReference r) => r.Value switch + { + PdfPages => 1, + PdfCatalog => 2, + _ => 0, + }; + var cmp = getPrio(a).CompareTo(getPrio(b)); + if (cmp != 0) + return cmp; + cmp = a.GenerationNumber.CompareTo(b.GenerationNumber); + if (cmp != 0) + return cmp; + cmp = a.ObjectNumber.CompareTo(b.ObjectNumber); return cmp; - cmp = a.GenerationNumber.CompareTo(b.GenerationNumber); - if (cmp != 0) - return cmp; - cmp = a.ObjectNumber.CompareTo(b.ObjectNumber); - return cmp; - }); + }); + } int count = irefs.Count; foreach(var iref in irefs) { diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs index 48bbc26f..1c3d4c0f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs @@ -84,18 +84,48 @@ public PdfUseFlateDecoderForJpegImages UseFlateDecoderForJpegImages } PdfUseFlateDecoderForJpegImages _useFlateDecoderForJpegImages = PdfUseFlateDecoderForJpegImages.Never; - public bool EnableReferenceCompaction { get; set; } = true; - + /// + /// Set to to enable greater compatibility + /// public bool EnableReferenceRenumbering { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// + public bool EnableReferenceCompaction { get; set; } = true; + + /// + /// Set to to enable greater compatibility + /// public bool EnableImplicitTransparencyGroup { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// public bool EnableImplicitMetadata { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// public bool EnableWriterCommentInTrailer { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// public bool EnableLfLineEndings { get; set; } = true; + /// + /// Set to to enable greater compatibility + /// + public bool EnableOwnBinaryHeader { get; set; } = true; + + /// + /// Set to to enable greater compatibility + /// + public bool EnableLineBreakInArrayObjects { get; set; } = true; + + public bool DisablePagesAndCatalogAtEnd { get; set; } = true; + public string LineEnding => EnableLfLineEndings ? "\n" : "\r\n"; public byte[] LineEndingBytes => Encoding.ASCII.GetBytes(LineEnding); From 8d0661187a590627c5b8bb2603d27b4fbb570fa4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?P=C4=99czkowski=20Daniel?= Date: Thu, 20 Nov 2025 10:43:40 +0100 Subject: [PATCH 11/11] optional owner password check --- .../src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs | 7 ++++++- .../src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs | 2 ++ .../src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs | 2 ++ 3 files changed, 10 insertions(+), 1 deletion(-) 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 c3042211..8fed9f59 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReader.cs @@ -288,6 +288,7 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode _document.Options.EnableOwnBinaryHeader = _options.EnableOwnBinaryHeader; _document.Options.EnableLineBreakInArrayObjects = _options.EnableLineBreakInArrayObjects; _document.Options.DisablePagesAndCatalogAtEnd = _options.DisablePagesAndCatalogAtEnd; + _document.Options.EnableOwnerPasswordSecurityChecks = _options.EnableOwnerPasswordSecurityChecks; _document._state |= DocumentState.Imported; _document._openMode = openMode; @@ -380,7 +381,11 @@ PdfDocument OpenFromStream(Stream stream, string? password, PdfDocumentOpenMode goto TryAgain; } else - throw new PdfReaderException(PsMsgs.OwnerPasswordRequired); + { + if(_document.Options.EnableOwnerPasswordSecurityChecks) + throw new PdfReaderException(PsMsgs.OwnerPasswordRequired); + _document.SecuritySettings.HasOwnerPermissions = true; + } } // ReSharper restore RedundantIfElseBlock } 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 4a9824b7..0a19e853 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfReaderOptions.cs @@ -141,6 +141,8 @@ public class PdfReaderOptions public bool DisablePagesAndCatalogAtEnd { get; set; } = true; + public bool EnableOwnerPasswordSecurityChecks { get; set; } = true; + // Testing only //public bool UseOldCode { get; set; } = false; diff --git a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs index 1c3d4c0f..adf4df6f 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf/PdfDocumentOptions.cs @@ -126,6 +126,8 @@ public PdfUseFlateDecoderForJpegImages UseFlateDecoderForJpegImages public bool DisablePagesAndCatalogAtEnd { get; set; } = true; + public bool EnableOwnerPasswordSecurityChecks { get; set; } = true; + public string LineEnding => EnableLfLineEndings ? "\n" : "\r\n"; public byte[] LineEndingBytes => Encoding.ASCII.GetBytes(LineEnding);