From 91f18e3ce0c7cbce3064aaea06afbe547f76d23c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=D0=9E=D0=BB=D0=B5=D0=BA=D1=81=D0=B0=D0=BD=D0=B4=D1=80=20?= =?UTF-8?q?=D0=9F=D0=B0=D0=BD=D0=B0=D1=81=D0=B5=D0=BD=D0=BA=D0=BE?= Date: Tue, 11 Aug 2026 14:53:13 +0300 Subject: [PATCH] Write rectangles with the precision of real numbers Write(PdfRectangle) rounded the coordinates to three decimal places, while Write(double) and Write(PdfReal) use seven. Writing a document therefore changed its geometry, e.g. the /MediaBox of an ISO A4 page created by another producer became 595.276 instead of 595.2756. That is a change of the page for every digital signature the document already contains: adding a second signature to a signed document made validators report the pages of the first revision as changed. /MediaBox, /CropBox, /BleedBox, /TrimBox, /ArtBox, /Rect and /BBox now round-trip unchanged. Note that the coordinates are written as they are, while Write(double) converts a real number to a single first. Doing that for rectangles as well would defeat the purpose, because the single nearest to 595.2756 is 595.27557, which does not round-trip either. An alternative would be to write the value that was parsed, as PRESERVE_PARSED_VALUES does for numbers. That preserves every number of a file and not only rectangles, but it needs the parsed value to survive until the document is written, which is not the case for rectangles today. --- .../PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs | 9 ++++++- .../tests/PdfSharp.Tests/IO/WriterTests.cs | 27 +++++++++++++++++++ 2 files changed, 35 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 18346cd5..915bbc5b 100644 --- a/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs +++ b/src/foundation/src/PDFsharp/src/PdfSharp/Pdf.IO/PdfWriter.cs @@ -236,7 +236,14 @@ public void Write(PdfLiteral value) /// public void Write(PdfRectangle rect) { - const string format = Config.SignificantDecimalPlaces3; + // With three decimal places, writing an existing page rounds its /MediaBox + // (e.g. 595.2756 -> 595.276). That is a change of the page geometry, which a validator + // reports as a changed page for every digital signature the document already contains. + // + // Note that the coordinates are written as they are, while Write(double) converts a real + // number to a single first. Doing that here would defeat the purpose: the single nearest to + // 595.2756 is 595.27557, which does not round-trip either. + const string format = Config.SignificantDecimalPlaces7; WriteSeparator(CharCat.Delimiter); WriteRaw(PdfEncoders.Format("[{0:" + format + "} {1:" + format + "} {2:" + format + "} {3:" + format + "}]", rect.X1, rect.Y1, rect.X2, rect.Y2)); } 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 3af04c71..2b1b37b1 100644 --- a/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs +++ b/src/foundation/src/PDFsharp/tests/PdfSharp.Tests/IO/WriterTests.cs @@ -29,5 +29,32 @@ public void Write_import_file() Action save = () => doc.Save(filename); save.Should().Throw(); } + + [Fact] + public void Write_rectangle_with_the_precision_of_a_real_number() + { + // ISO A4 in points. The values have four decimal places, which must survive a round-trip, + // because a changed /MediaBox is a changed page for every digital signature of the document. + const double width = 595.2756, height = 841.8898; + + using var stream = new MemoryStream(); + using (var document = new PdfDocument()) + { + var page = document.AddPage(); + page.MediaBox = new PdfRectangle(new XPoint(0, 0), new XPoint(width, height)); + document.Save(stream, false); + } + + var pdf = stream.ToArray(); + var chars = new char[pdf.Length]; + for (int idx = 0; idx < pdf.Length; idx++) + chars[idx] = (char)pdf[idx]; + new String(chars).Should().Contain("[0 0 595.2756 841.8898]"); + + using var writtenDocument = PdfReader.Open(new MemoryStream(pdf), PdfDocumentOpenMode.Import); + var mediaBox = writtenDocument.Pages[0].MediaBox; + mediaBox.X2.Should().Be(width); + mediaBox.Y2.Should().Be(height); + } } }