From 5dfb6c3e582c639a3239d6f633cda3aa8893ed1f Mon Sep 17 00:00:00 2001 From: Shane Krueger Date: Mon, 6 Oct 2025 00:29:09 -0400 Subject: [PATCH] Make Postscript renderer deterministic --- QRCoder/PostscriptQRCode.cs | 133 +++++++++--------- QRCoderTests/PostscriptQRCodeRendererTests.cs | 19 +-- 2 files changed, 75 insertions(+), 77 deletions(-) diff --git a/QRCoder/PostscriptQRCode.cs b/QRCoder/PostscriptQRCode.cs index 2653bb02..e57a4204 100644 --- a/QRCoder/PostscriptQRCode.cs +++ b/QRCoder/PostscriptQRCode.cs @@ -101,7 +101,7 @@ public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool d var pointsPerModule = (double)Math.Min(viewBox.Width, viewBox.Height) / (double)drawableModulesCount; string psFile = string.Format(PS_HEADER, new object[] { - DateTime.Now.ToString("s"), CleanSvgVal(viewBox.Width), CleanSvgVal(pointsPerModule), + CleanSvgVal(viewBox.Width), CleanSvgVal(pointsPerModule), epsFormat ? "EPSF-3.0" : string.Empty }); psFile += string.Format(PS_FUNCTIONS, new object[] { @@ -130,68 +130,75 @@ public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool d /// Returns the cleaned string representation of the double value. private string CleanSvgVal(double input) => input.ToString(System.Globalization.CultureInfo.InvariantCulture); - private const string PS_HEADER = @"%!PS-Adobe-3.0 {3} -%%Creator: QRCoder.NET -%%Title: QRCode -%%CreationDate: {0} -%%DocumentData: Clean7Bit -%%Origin: 0 -%%DocumentMedia: Default {1} {1} 0 () () -%%BoundingBox: 0 0 {1} {1} -%%LanguageLevel: 2 -%%Pages: 1 -%%Page: 1 1 -%%EndComments -%%BeginConstants -/sz {1} def -/sc {2} def -%%EndConstants -%%BeginFeature: *PageSize Default -<< /PageSize [ sz sz ] /ImagingBBox null >> setpagedevice -%%EndFeature -"; - - private const string PS_FUNCTIONS = @"%%BeginFunctions -/csquare {{ - newpath - 0 0 moveto - 0 1 rlineto - 1 0 rlineto - 0 -1 rlineto - closepath - setrgbcolor - fill -}} def -/f {{ - {0} {1} {2} csquare - 1 0 translate -}} def -/b {{ - 1 0 translate -}} def -/background {{ - {3} {4} {5} csquare -}} def -/nl {{ - -{6} -1 translate -}} def -%%EndFunctions -%%BeginBody -0 0 moveto -gsave -sz sz scale -background -grestore -gsave -sc sc scale -0 {6} 1 sub translate -"; - - private const string PS_FOOTER = @"%%EndBody -grestore -showpage -%%EOF -"; + // Note: line terminations here will encode differently based on which platform QRCoder was compiled on (CRLF vs LF); + // however, PostScript interpreters should handle both equally well. + private const string PS_HEADER = """ + %!PS-Adobe-3.0 {2} + %%Creator: QRCoder.NET + %%Title: QRCode + %%DocumentData: Clean7Bit + %%Origin: 0 + %%DocumentMedia: Default {0} {0} 0 () () + %%BoundingBox: 0 0 {0} {0} + %%LanguageLevel: 2 + %%Pages: 1 + %%Page: 1 1 + %%EndComments + %%BeginConstants + /sz {0} def + /sc {1} def + %%EndConstants + %%BeginFeature: *PageSize Default + << /PageSize [ sz sz ] /ImagingBBox null >> setpagedevice + %%EndFeature + + """; + + private const string PS_FUNCTIONS = """ + %%BeginFunctions + /csquare {{ + newpath + 0 0 moveto + 0 1 rlineto + 1 0 rlineto + 0 -1 rlineto + closepath + setrgbcolor + fill + }} def + /f {{ + {0} {1} {2} csquare + 1 0 translate + }} def + /b {{ + 1 0 translate + }} def + /background {{ + {3} {4} {5} csquare + }} def + /nl {{ + -{6} -1 translate + }} def + %%EndFunctions + %%BeginBody + 0 0 moveto + gsave + sz sz scale + background + grestore + gsave + sc sc scale + 0 {6} 1 sub translate + + """; + + private const string PS_FOOTER = """ + %%EndBody + grestore + showpage + %%EOF + + """; } /// diff --git a/QRCoderTests/PostscriptQRCodeRendererTests.cs b/QRCoderTests/PostscriptQRCodeRendererTests.cs index 03c2a759..fb03f68f 100644 --- a/QRCoderTests/PostscriptQRCodeRendererTests.cs +++ b/QRCoderTests/PostscriptQRCodeRendererTests.cs @@ -9,7 +9,7 @@ public void can_render_postscript_qrcode_simple() var gen = new QRCodeGenerator(); var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); var ps = new PostscriptQRCode(data).GetGraphic(5); - RemoveCreationDate(ps).ShouldMatchApproved(x => x.NoDiff()); + ps.ShouldMatchApproved(x => x.NoDiff()); } [Fact] @@ -19,7 +19,7 @@ public void can_render_postscript_qrcode_eps() var gen = new QRCodeGenerator(); var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); var ps = new PostscriptQRCode(data).GetGraphic(5, true); - RemoveCreationDate(ps).ShouldMatchApproved(x => x.NoDiff()); + ps.ShouldMatchApproved(x => x.NoDiff()); } [Fact] @@ -29,7 +29,7 @@ public void can_render_postscript_qrcode_size() var gen = new QRCodeGenerator(); var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); var ps = new PostscriptQRCode(data).GetGraphic(new Size(33, 33)); - RemoveCreationDate(ps).ShouldMatchApproved(x => x.NoDiff()); + ps.ShouldMatchApproved(x => x.NoDiff()); } [Fact] @@ -39,7 +39,7 @@ public void can_render_postscript_qrcode_size_no_quiet_zones() var gen = new QRCodeGenerator(); var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); var ps = new PostscriptQRCode(data).GetGraphic(new Size(50, 50), false); - RemoveCreationDate(ps).ShouldMatchApproved(x => x.NoDiff()); + ps.ShouldMatchApproved(x => x.NoDiff()); } [Fact] @@ -49,15 +49,6 @@ public void can_render_postscript_qrcode_colors() var gen = new QRCodeGenerator(); var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); var ps = new PostscriptQRCode(data).GetGraphic(5, Color.Red, Color.Blue); - RemoveCreationDate(ps).ShouldMatchApproved(x => x.NoDiff()); - } - - private static string RemoveCreationDate(string text) - { - // Regex pattern to match lines that start with %%CreationDate: followed by any characters until the end of the line - string pattern = @"%%CreationDate:.*\r?\n?"; - - // Use Regex.Replace to remove matching lines - return Regex.Replace(text, pattern, string.Empty, RegexOptions.Multiline); + ps.ShouldMatchApproved(x => x.NoDiff()); } }