From 585d059e8d39f6808d906c3436463709df70003a Mon Sep 17 00:00:00 2001 From: Maxence Date: Sat, 6 Jun 2026 14:43:31 +0200 Subject: [PATCH] Fix decimal separator using invariant culture Coordinates, MediaBox dimensions, line weights and RGB colors were formatted with the current culture. Under a culture that uses the comma as decimal separator (e.g. fr-FR), this produced numbers like "283,4646" instead of "283.4646", resulting in an invalid pdf content stream that viewers render as blank. Force CultureInfo.InvariantCulture wherever doubles are written: - PdfPen.toPdfDouble (entity coordinates) - PdfPen.applyStyle (line width) - PdfReference.GetPdfForm (MediaBox dimensions) - ColorExtensions.ToPdfString (RGB color components) Add a regression test that exports under the fr-FR culture and asserts that no number uses the comma as decimal separator. Co-Authored-By: Claude Opus 4.8 (1M context) --- src/ACadSharp.Pdf.Tests/PdfExporterTests.cs | 40 +++++++++++++++++++ src/ACadSharp.Pdf/Core/IO/PdfPen.cs | 5 ++- src/ACadSharp.Pdf/Core/PdfReference.cs | 10 ++++- src/ACadSharp.Pdf/Extensions/XYZExtensions.cs | 3 +- 4 files changed, 54 insertions(+), 4 deletions(-) diff --git a/src/ACadSharp.Pdf.Tests/PdfExporterTests.cs b/src/ACadSharp.Pdf.Tests/PdfExporterTests.cs index 1e20661..f97102a 100644 --- a/src/ACadSharp.Pdf.Tests/PdfExporterTests.cs +++ b/src/ACadSharp.Pdf.Tests/PdfExporterTests.cs @@ -1,5 +1,9 @@ +using ACadSharp.Entities; using ACadSharp.IO; +using CSMath; +using System.Globalization; using System.IO; +using System.Text; using Xunit; using Xunit.Abstractions; @@ -55,6 +59,42 @@ public void AddModelSpaceTest() exporter.Close(); } + [Fact] + public void InvariantDecimalSeparatorTest() + { + CultureInfo previousCulture = CultureInfo.CurrentCulture; + + try + { + //A culture such as fr-FR uses the comma as decimal separator, + //which would produce an invalid pdf stream if numbers were not + //formatted using the invariant culture. + CultureInfo.CurrentCulture = new CultureInfo("fr-FR"); + + CadDocument doc = new CadDocument(); + doc.Entities.Add(new Line(new XYZ(0, 0, 0), new XYZ(12.5, 7.25, 0))); + + string content; + using (MemoryStream stream = new MemoryStream()) + { + PdfExporter exporter = new PdfExporter(stream); + exporter.AddModelSpace(doc); + exporter.Close(); + + content = Encoding.ASCII.GetString(stream.ToArray()); + } + + //No number must use the comma as decimal separator. + Assert.DoesNotMatch(@"\d,\d", content); + //The decimal point must be used (e.g. MediaBox, coordinates). + Assert.Contains(".", content); + } + finally + { + CultureInfo.CurrentCulture = previousCulture; + } + } + [Theory] [MemberData(nameof(LayoutNames))] public void WriteLayouts(string name) diff --git a/src/ACadSharp.Pdf/Core/IO/PdfPen.cs b/src/ACadSharp.Pdf/Core/IO/PdfPen.cs index 43a67d6..6db4b8c 100644 --- a/src/ACadSharp.Pdf/Core/IO/PdfPen.cs +++ b/src/ACadSharp.Pdf/Core/IO/PdfPen.cs @@ -7,6 +7,7 @@ using CSMath; using System; using System.Collections.Generic; +using System.Globalization; using System.Linq; using System.Text; @@ -123,7 +124,7 @@ private void applyStyle(Entity entity) { LineWeightType lw = entity.GetActiveLineWeightType(); double lwValue = lw.GetLineWeightValue(); - this._sb.AppendLine($"{lwValue.ToPdfUnit(PdfUnitType.Millimeter)} {PdfKey.LineWidth}"); + this._sb.AppendLine($"{lwValue.ToPdfUnit(PdfUnitType.Millimeter).ToString(this._configuration.DecimalFormat, CultureInfo.InvariantCulture)} {PdfKey.LineWidth}"); Color color = entity.GetActiveColor(); @@ -322,7 +323,7 @@ private void drawViewport(Viewport viewport) private string toPdfDouble(double value) { - return (value / this.DenominatorScale).ToPdfUnit(this.PaperUnits).ToString(this._configuration.DecimalFormat); + return (value / this.DenominatorScale).ToPdfUnit(this.PaperUnits).ToString(this._configuration.DecimalFormat, CultureInfo.InvariantCulture); } private void writeEntityEnd(Entity entity) diff --git a/src/ACadSharp.Pdf/Core/PdfReference.cs b/src/ACadSharp.Pdf/Core/PdfReference.cs index 5d816cd..31adc4c 100644 --- a/src/ACadSharp.Pdf/Core/PdfReference.cs +++ b/src/ACadSharp.Pdf/Core/PdfReference.cs @@ -1,4 +1,5 @@ using System; +using System.Globalization; namespace ACadSharp.Pdf.Core { @@ -20,7 +21,14 @@ public void Print() public override string GetPdfForm(PdfConfiguration configuration) { - return this._f.Invoke().ToString(); + T value = this._f.Invoke(); + + if (value is IFormattable formattable) + { + return formattable.ToString(null, CultureInfo.InvariantCulture); + } + + return value.ToString(); } } } diff --git a/src/ACadSharp.Pdf/Extensions/XYZExtensions.cs b/src/ACadSharp.Pdf/Extensions/XYZExtensions.cs index 2c38aa6..6ea2844 100644 --- a/src/ACadSharp.Pdf/Extensions/XYZExtensions.cs +++ b/src/ACadSharp.Pdf/Extensions/XYZExtensions.cs @@ -2,6 +2,7 @@ using ACadSharp.Pdf.Core; using CSMath; using System; +using System.Globalization; namespace ACadSharp.Pdf.Extensions { @@ -39,7 +40,7 @@ public static class ColorExtensions { public static string ToPdfString(this Color color) { - return $"{color.R / 255d} {color.G / 255d} {color.B / 255d} RG"; + return string.Format(CultureInfo.InvariantCulture, "{0} {1} {2} RG", color.R / 255d, color.G / 255d, color.B / 255d); } } } \ No newline at end of file