From 378a5fa6accaad1ad0be797c6392609037c7cf57 Mon Sep 17 00:00:00 2001 From: Raffael Herrmann Date: Fri, 12 Nov 2021 15:42:21 +0100 Subject: [PATCH 1/5] Improved logo and background handling --- QRCoder/SvgQRCode.cs | 56 ++++++++++++++++++++++++++++++++++++++------ 1 file changed, 49 insertions(+), 7 deletions(-) diff --git a/QRCoder/SvgQRCode.cs b/QRCoder/SvgQRCode.cs index f0d01909..770f59f7 100644 --- a/QRCoder/SvgQRCode.cs +++ b/QRCoder/SvgQRCode.cs @@ -54,6 +54,9 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex double pixelsPerModule = Math.Min(viewBox.Width, viewBox.Height) / (double)drawableModulesCount; double qrSize = drawableModulesCount * pixelsPerModule; string svgSizeAttributes = (sizingMode == SizingMode.WidthHeightAttribute) ? $@"width=""{viewBox.Width}"" height=""{viewBox.Height}""" : $@"viewBox=""0 0 {viewBox.Width} {viewBox.Height}"""; + ImageAttributes? logoAttr = null; + if (logo != null) + logoAttr = GetLogoAttributes(logo, viewBox); // Merge horizontal rectangles int[,] matrix = new int[drawableModulesCount, drawableModulesCount]; @@ -66,7 +69,7 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex for (int xi = 0; xi < drawableModulesCount; xi += 1) { matrix[yi, xi] = 0; - if (bitArray[xi+offset]) + if (bitArray[xi+offset] && !IsBlockedByLogo((xi+offset)*pixelsPerModule, (yi+offset) * pixelsPerModule, logoAttr, pixelsPerModule)) { if(x0 == -1) { @@ -91,7 +94,7 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex } } - StringBuilder svgFile = new StringBuilder($@""); + StringBuilder svgFile = new StringBuilder($@""); svgFile.AppendLine($@""); for (int yi = 0; yi < drawableModulesCount; yi += 1) { @@ -118,16 +121,18 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex // Output SVG rectangles double x = xi * pixelsPerModule; - svgFile.AppendLine($@""); + if (!IsBlockedByLogo(x, y, logoAttr, pixelsPerModule)) + svgFile.AppendLine($@""); + } } } //Render logo, if set if (logo != null) - { + { svgFile.AppendLine($@""); - svgFile.AppendLine($@""); + svgFile.AppendLine($@""); svgFile.AppendLine(@""); } @@ -135,6 +140,36 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex return svgFile.ToString(); } + private bool IsBlockedByLogo(double x, double y, ImageAttributes? attr, double pixelPerModule) + { + if (attr == null) + return false; + return x + pixelPerModule >= attr.Value.X && x <= attr.Value.X + attr.Value.Width && y + pixelPerModule >= attr.Value.Y && y <= attr.Value.Y + attr.Value.Height; + } + + private ImageAttributes GetLogoAttributes(SvgLogo logo, Size viewBox) + { + var imgWidth = logo.GetIconSizePercent() / 100d * viewBox.Width; + var imgHeight = logo.GetIconSizePercent() / 100d * viewBox.Height; + var imgPosX = viewBox.Width / 2d - imgWidth / 2d; + var imgPosY = viewBox.Height / 2d - imgHeight / 2d; + return new ImageAttributes() + { + Width = imgWidth, + Height = imgHeight, + X = imgPosX, + Y = imgPosY + }; + } + + private struct ImageAttributes + { + public double Width; + public double Height; + public double X; + public double Y; + } + private string CleanSvgVal(double input) { //Clean double values for international use/formats @@ -152,13 +187,14 @@ public class SvgLogo private string _logoData; private string _mediaType; private int _iconSizePercent; + private bool _fillLogoBackground; /// /// Create a logo object to be used in SvgQRCode renderer /// /// Logo to be rendered as Bitmap/rasterized graphic /// Degree of percentage coverage of the QR code by the logo - public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15) + public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true) { _iconSizePercent = iconSizePercent; using (var ms = new System.IO.MemoryStream()) @@ -170,6 +206,7 @@ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15) } } _mediaType = "image/png"; + _fillLogoBackground = fillLogoBackground; } /// @@ -177,11 +214,12 @@ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15) /// /// Logo to be rendered as SVG/vectorized graphic/string /// Degree of percentage coverage of the QR code by the logo - public SvgLogo(string iconVectorized, int iconSizePercent = 15) + public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBackground = true) { _iconSizePercent = iconSizePercent; _logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None); _mediaType = "image/svg+xml"; + _fillLogoBackground = fillLogoBackground; } public string GetDataUri() @@ -193,6 +231,10 @@ public int GetIconSizePercent() { return _iconSizePercent; } + public bool FillLogoBackground() + { + return _fillLogoBackground; + } } } From 4ad5ea76bcc74d01b6a671d8d38806f5971482f8 Mon Sep 17 00:00:00 2001 From: Raffael Herrmann Date: Mon, 15 Nov 2021 18:55:12 +0100 Subject: [PATCH 2/5] Added SVG-native logo embedding --- QRCoder/Extensions/StringValueAttribute.cs | 52 ++++++++++++++++++++ QRCoder/SvgQRCode.cs | 56 ++++++++++++++++++---- QRCoderTests/SvgQRCodeRendererTests.cs | 6 +-- 3 files changed, 103 insertions(+), 11 deletions(-) create mode 100644 QRCoder/Extensions/StringValueAttribute.cs diff --git a/QRCoder/Extensions/StringValueAttribute.cs b/QRCoder/Extensions/StringValueAttribute.cs new file mode 100644 index 00000000..cc6e1bf4 --- /dev/null +++ b/QRCoder/Extensions/StringValueAttribute.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace QRCoder.Extensions +{ + /// + /// Used to represent a string value for a value in an enum + /// + public class StringValueAttribute : Attribute + { + + #region Properties + + /// + /// Holds the alue in an enum + /// + public string StringValue { get; protected set; } + + #endregion + + /// + /// Init a StringValue Attribute + /// + /// + public StringValueAttribute(string value) + { + this.StringValue = value; + } + } + + public static class CustomExtensions + { + /// + /// Will get the string value for a given enum's value + /// + /// + /// + public static string GetStringValue(this Enum value) + { +#if NETSTANDARD1_3 + var fieldInfo = value.GetType().GetRuntimeField(value.ToString()); +#else + var fieldInfo = value.GetType().GetField(value.ToString()); +#endif + var attr = fieldInfo.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[]; + return attr.Length > 0 ? attr[0].StringValue : null; + } + } +} diff --git a/QRCoder/SvgQRCode.cs b/QRCoder/SvgQRCode.cs index 770f59f7..2b139d28 100644 --- a/QRCoder/SvgQRCode.cs +++ b/QRCoder/SvgQRCode.cs @@ -1,8 +1,11 @@ #if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 +using QRCoder.Extensions; using System; using System.Collections; +using System.Collections.Generic; using System.Drawing; using System.Text; +using System.Text.RegularExpressions; using static QRCoder.QRCodeGenerator; using static QRCoder.SvgQRCode; @@ -69,7 +72,7 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex for (int xi = 0; xi < drawableModulesCount; xi += 1) { matrix[yi, xi] = 0; - if (bitArray[xi+offset] && !IsBlockedByLogo((xi+offset)*pixelsPerModule, (yi+offset) * pixelsPerModule, logoAttr, pixelsPerModule)) + if (bitArray[xi+offset] && (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo((xi+offset)*pixelsPerModule, (yi+offset) * pixelsPerModule, logoAttr, pixelsPerModule))) { if(x0 == -1) { @@ -121,7 +124,7 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex // Output SVG rectangles double x = xi * pixelsPerModule; - if (!IsBlockedByLogo(x, y, logoAttr, pixelsPerModule)) + if (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo(x, y, logoAttr, pixelsPerModule)) svgFile.AppendLine($@""); } @@ -131,8 +134,23 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex //Render logo, if set if (logo != null) { - svgFile.AppendLine($@""); - svgFile.AppendLine($@""); + + if (logo.GetMediaType() == SvgLogo.MediaType.PNG) + { + svgFile.AppendLine($@""); + svgFile.AppendLine($@""); + } + else if (logo.GetMediaType() == SvgLogo.MediaType.SVG) + { + svgFile.AppendLine($@""); + var rawLogo = (string)logo.GetRawLogo(); + //Remove some attributes from logo, because it would lead to wrong sizing inside our svg wrapper + new List() { "width", "height", "x", "y" }.ForEach(attr => + { + rawLogo = Regex.Replace(rawLogo, $@"(?!=]*?) +{attr}=(""[^""]+""|'[^']+')(?=[^>]*>)", ""); + }); + svgFile.Append(rawLogo); + } svgFile.AppendLine(@""); } @@ -185,9 +203,11 @@ public enum SizingMode public class SvgLogo { private string _logoData; - private string _mediaType; + private MediaType _mediaType; private int _iconSizePercent; private bool _fillLogoBackground; + private object _logoRaw; + /// /// Create a logo object to be used in SvgQRCode renderer @@ -205,8 +225,9 @@ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBac _logoData = Convert.ToBase64String(ms.GetBuffer(), Base64FormattingOptions.None); } } - _mediaType = "image/png"; + _mediaType = MediaType.PNG; _fillLogoBackground = fillLogoBackground; + _logoRaw = iconRasterized; } /// @@ -218,13 +239,24 @@ public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBac { _iconSizePercent = iconSizePercent; _logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None); - _mediaType = "image/svg+xml"; + _mediaType = MediaType.SVG; _fillLogoBackground = fillLogoBackground; + _logoRaw = iconVectorized; + } + + public object GetRawLogo() + { + return _logoRaw; + } + + public MediaType GetMediaType() + { + return _mediaType; } public string GetDataUri() { - return $"data:{_mediaType};base64,{_logoData}"; + return $"data:{_mediaType.GetStringValue()};base64,{_logoData}"; } public int GetIconSizePercent() @@ -235,6 +267,14 @@ public bool FillLogoBackground() { return _fillLogoBackground; } + + public enum MediaType : int + { + [StringValue("image/png")] + PNG = 0, + [StringValue("image/svg+xml")] + SVG = 1 + } } } diff --git a/QRCoderTests/SvgQRCodeRendererTests.cs b/QRCoderTests/SvgQRCodeRendererTests.cs index 65ef6cac..6cd36378 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.cs +++ b/QRCoderTests/SvgQRCodeRendererTests.cs @@ -72,12 +72,12 @@ public void can_render_svg_qrcode_with_png_logo() var logoObj = new SvgQRCode.SvgLogo(logoBitmap, 15); var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj); - + File.WriteAllText(@"C:\Temp\qr_png.svg", svg); var md5 = new MD5CryptoServiceProvider(); var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(svg)); var result = BitConverter.ToString(hash).Replace("-", "").ToLower(); - result.ShouldBe("4ff45872787f321524cc4d071239c25e"); + result.ShouldBe("4ff45872787f321524cc4d071239c25e"); } [Fact] @@ -93,7 +93,7 @@ public void can_render_svg_qrcode_with_svg_logo() var logoObj = new SvgQRCode.SvgLogo(logoSvg, 30); var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj); - + File.WriteAllText(@"C:\Temp\qr_svg.svg", svg); var md5 = new MD5CryptoServiceProvider(); var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(svg)); var result = BitConverter.ToString(hash).Replace("-", "").ToLower(); From 3fc14b77feae8460e5f3021edbb2003c2628fb0e Mon Sep 17 00:00:00 2001 From: Raffael Herrmann Date: Mon, 15 Nov 2021 19:12:54 +0100 Subject: [PATCH 3/5] Added code documentation/summary --- QRCoder/SvgQRCode.cs | 88 +++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 87 insertions(+), 1 deletion(-) diff --git a/QRCoder/SvgQRCode.cs b/QRCoder/SvgQRCode.cs index 2b139d28..8dfe61f0 100644 --- a/QRCoder/SvgQRCode.cs +++ b/QRCoder/SvgQRCode.cs @@ -19,11 +19,27 @@ public class SvgQRCode : AbstractQRCode, IDisposable public SvgQRCode() { } public SvgQRCode(QRCodeData data) : base(data) { } + /// + /// Returns a QR code as SVG string + /// + /// The pixel size each b/w module is drawn + /// SVG as string public string GetGraphic(int pixelsPerModule) { var viewBox = new Size(pixelsPerModule*this.QrCodeData.ModuleMatrix.Count, pixelsPerModule * this.QrCodeData.ModuleMatrix.Count); return this.GetGraphic(viewBox, Color.Black, Color.White); } + + /// + /// Returns a QR code as SVG string with custom colors, optional quietzone and logo + /// + /// The pixel size each b/w module is drawn + /// Color of the dark modules + /// Color of the light modules + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) { var offset = drawQuietZones ? 0 : 4; @@ -32,6 +48,16 @@ public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, return this.GetGraphic(viewBox, darkColor, lightColor, drawQuietZones, sizingMode, logo); } + /// + /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo + /// + /// The pixel size each b/w module is drawn + /// The color of the dark/black modules in hex (e.g. #000000) representation + /// The color of the light/white modules in hex (e.g. #ffffff) representation + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) { var offset = drawQuietZones ? 0 : 4; @@ -40,16 +66,44 @@ public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightC return this.GetGraphic(viewBox, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo); } + /// + /// Returns a QR code as SVG string with optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string public string GetGraphic(Size viewBox, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) { return this.GetGraphic(viewBox, Color.Black, Color.White, drawQuietZones, sizingMode, logo); } + /// + /// Returns a QR code as SVG string with custom colors and optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// Color of the dark modules + /// Color of the light modules + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) { return this.GetGraphic(viewBox, ColorTranslator.ToHtml(Color.FromArgb(darkColor.ToArgb())), ColorTranslator.ToHtml(Color.FromArgb(lightColor.ToArgb())), drawQuietZones, sizingMode, logo); } + /// + /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// The color of the dark/black modules in hex (e.g. #000000) representation + /// The color of the light/white modules in hex (e.g. #ffffff) representation + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) { int offset = drawQuietZones ? 0 : 4; @@ -194,12 +248,18 @@ private string CleanSvgVal(double input) return input.ToString(System.Globalization.CultureInfo.InvariantCulture); } + /// + /// Mode of sizing attribution on svg root node + /// public enum SizingMode { WidthHeightAttribute, ViewBoxAttribute } + /// + /// Represents a logo graphic that can be rendered on a SvgQRCode + /// public class SvgLogo { private string _logoData; @@ -207,13 +267,14 @@ public class SvgLogo private int _iconSizePercent; private bool _fillLogoBackground; private object _logoRaw; - + /// /// Create a logo object to be used in SvgQRCode renderer /// /// Logo to be rendered as Bitmap/rasterized graphic /// Degree of percentage coverage of the QR code by the logo + /// If true, the background behind the logo will be cleaned public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true) { _iconSizePercent = iconSizePercent; @@ -235,6 +296,7 @@ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBac /// /// Logo to be rendered as SVG/vectorized graphic/string /// Degree of percentage coverage of the QR code by the logo + /// If true, the background behind the logo will be cleaned public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBackground = true) { _iconSizePercent = iconSizePercent; @@ -244,30 +306,54 @@ public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBac _logoRaw = iconVectorized; } + /// + /// Returns the raw logo's data + /// + /// public object GetRawLogo() { return _logoRaw; } + /// + /// Returns the media type of the logo + /// + /// public MediaType GetMediaType() { return _mediaType; } + /// + /// Returns the logo as data-uri + /// + /// public string GetDataUri() { return $"data:{_mediaType.GetStringValue()};base64,{_logoData}"; } + /// + /// Returns how much of the QR code should be covered by the logo (in percent) + /// + /// public int GetIconSizePercent() { return _iconSizePercent; } + + /// + /// Returns if the background of the logo should be cleaned (no QR modules will be rendered behind the logo) + /// + /// public bool FillLogoBackground() { return _fillLogoBackground; } + /// + /// Media types for SvgLogos + /// public enum MediaType : int { [StringValue("image/png")] From 9e9957b624257495e87fe18aff2264489c75634f Mon Sep 17 00:00:00 2001 From: Raffael Herrmann Date: Mon, 15 Nov 2021 19:29:39 +0100 Subject: [PATCH 4/5] Updated test cases --- QRCoder/SvgQRCode.cs | 2 - QRCoderTests/Helpers/HelperFunctions.cs | 12 +++- QRCoderTests/SvgQRCodeRendererTests.cs | 73 ++++++++++++++++++------- 3 files changed, 65 insertions(+), 22 deletions(-) diff --git a/QRCoder/SvgQRCode.cs b/QRCoder/SvgQRCode.cs index 8dfe61f0..6cc53d34 100644 --- a/QRCoder/SvgQRCode.cs +++ b/QRCoder/SvgQRCode.cs @@ -214,8 +214,6 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex private bool IsBlockedByLogo(double x, double y, ImageAttributes? attr, double pixelPerModule) { - if (attr == null) - return false; return x + pixelPerModule >= attr.Value.X && x <= attr.Value.X + attr.Value.Width && y + pixelPerModule >= attr.Value.Y && y <= attr.Value.Y + attr.Value.Height; } diff --git a/QRCoderTests/Helpers/HelperFunctions.cs b/QRCoderTests/Helpers/HelperFunctions.cs index d8f7e78c..c67fce99 100644 --- a/QRCoderTests/Helpers/HelperFunctions.cs +++ b/QRCoderTests/Helpers/HelperFunctions.cs @@ -36,10 +36,20 @@ public static string BitmapToHash(Bitmap bmp) imgBytes = ms.ToArray(); ms.Dispose(); } + return ByteArrayToHash(imgBytes); + } + + public static string ByteArrayToHash(byte[] data) + { var md5 = new MD5CryptoServiceProvider(); - var hash = md5.ComputeHash(imgBytes); + var hash = md5.ComputeHash(data); return BitConverter.ToString(hash).Replace("-", "").ToLower(); } + + public static string StringToHash(string data) + { + return ByteArrayToHash(Encoding.UTF8.GetBytes(data)); + } #endif } diff --git a/QRCoderTests/SvgQRCodeRendererTests.cs b/QRCoderTests/SvgQRCodeRendererTests.cs index 6cd36378..b2b9b557 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.cs +++ b/QRCoderTests/SvgQRCodeRendererTests.cs @@ -5,6 +5,7 @@ using QRCoderTests.Helpers.XUnitExtenstions; using System.IO; using System.Security.Cryptography; +using QRCoderTests.Helpers; #if !NETCOREAPP1_1 using System.Drawing; #endif @@ -27,6 +28,19 @@ private string GetAssemblyPath() #endif } + [Fact] + [Category("QRRenderer/SvgQRCode")] + public void can_render_svg_qrcode_simple() + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var svg = new SvgQRCode(data).GetGraphic(5); + + var result = HelperFunctions.StringToHash(svg); + result.ShouldBe("5c251275a435a9aed7e591eb9c2e9949"); + } + [Fact] [Category("QRRenderer/SvgQRCode")] public void can_render_svg_qrcode() @@ -36,11 +50,21 @@ public void can_render_svg_qrcode() var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); var svg = new SvgQRCode(data).GetGraphic(10, Color.Red, Color.White); - var md5 = new MD5CryptoServiceProvider(); - var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(svg)); - var result = BitConverter.ToString(hash).Replace("-", "").ToLower(); + var result = HelperFunctions.StringToHash(svg); + result.ShouldBe("1baa8c6ac3bd8c1eabcd2c5422dd9f78"); + } + + [Fact] + [Category("QRRenderer/SvgQRCode")] + public void can_render_svg_qrcode_viewbox_mode() + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + var svg = new SvgQRCode(data).GetGraphic(new Size(128,128)); - result.ShouldBe("0ad8bc75675d04ba0caff51c7a89992c"); + var result = HelperFunctions.StringToHash(svg); + result.ShouldBe("ec9a13c4484d246e3e2e0574958845c8"); } [Fact] @@ -52,11 +76,8 @@ public void can_render_svg_qrcode_without_quietzones() var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); var svg = new SvgQRCode(data).GetGraphic(10, Color.Red, Color.White, false); - var md5 = new MD5CryptoServiceProvider(); - var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(svg)); - var result = BitConverter.ToString(hash).Replace("-", "").ToLower(); - - result.ShouldBe("24392f47d4c1c2c5097bd6b3f8eefccc"); + var result = HelperFunctions.StringToHash(svg); + result.ShouldBe("2a582427d86b51504c08ebcbcf0472bd"); } [Fact] @@ -72,12 +93,9 @@ public void can_render_svg_qrcode_with_png_logo() var logoObj = new SvgQRCode.SvgLogo(logoBitmap, 15); var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj); - File.WriteAllText(@"C:\Temp\qr_png.svg", svg); - var md5 = new MD5CryptoServiceProvider(); - var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(svg)); - var result = BitConverter.ToString(hash).Replace("-", "").ToLower(); - result.ShouldBe("4ff45872787f321524cc4d071239c25e"); + var result = HelperFunctions.StringToHash(svg); + result.ShouldBe("78e02e8ba415f15817d5ed88c4afca31"); } [Fact] @@ -93,12 +111,29 @@ public void can_render_svg_qrcode_with_svg_logo() var logoObj = new SvgQRCode.SvgLogo(logoSvg, 30); var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj); - File.WriteAllText(@"C:\Temp\qr_svg.svg", svg); - var md5 = new MD5CryptoServiceProvider(); - var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(svg)); - var result = BitConverter.ToString(hash).Replace("-", "").ToLower(); - result.ShouldBe("b4ded3964e2e640b6b6c74d1c89d71fa"); + var result = HelperFunctions.StringToHash(svg); + result.ShouldBe("71f461136fdbe2ab85902d23ad2d7eb8"); + } + + [Fact] + [Category("QRRenderer/SvgQRCode")] + public void can_instantate_parameterless() + { + var svgCode = new SvgQRCode(); + svgCode.ShouldNotBeNull(); + svgCode.ShouldBeOfType(); + } + + [Fact] + [Category("QRRenderer/SvgQRCode")] + public void can_render_svg_qrcode_from_helper() + { + //Create QR code + var svg = SvgQRCodeHelper.GetQRCode("A", 2, "#000000", "#ffffff", QRCodeGenerator.ECCLevel.Q); + + var result = HelperFunctions.StringToHash(svg); + result.ShouldBe("f5ec37aa9fb207e3701cc0d86c4a357d"); } #endif } From 093c3a6a8a4830984252d8e59ff13fff047bbd83 Mon Sep 17 00:00:00 2001 From: Raffael Herrmann Date: Mon, 15 Nov 2021 19:55:07 +0100 Subject: [PATCH 5/5] Updated decimal formatting and test cases Harmonized decimal formatting between different target platforms --- QRCoder/SvgQRCode.cs | 4 +++- QRCoderTests/SvgQRCodeRendererTests.cs | 2 +- 2 files changed, 4 insertions(+), 2 deletions(-) diff --git a/QRCoder/SvgQRCode.cs b/QRCoder/SvgQRCode.cs index 6cc53d34..3dc0c097 100644 --- a/QRCoder/SvgQRCode.cs +++ b/QRCoder/SvgQRCode.cs @@ -243,7 +243,9 @@ private struct ImageAttributes private string CleanSvgVal(double input) { //Clean double values for international use/formats - return input.ToString(System.Globalization.CultureInfo.InvariantCulture); + //We use explicitly "G15" to avoid differences between .NET full and Core platforms + //https://stackoverflow.com/questions/64898117/tostring-has-a-different-behavior-between-net-462-and-net-core-3-1 + return input.ToString("G15", System.Globalization.CultureInfo.InvariantCulture); } /// diff --git a/QRCoderTests/SvgQRCodeRendererTests.cs b/QRCoderTests/SvgQRCodeRendererTests.cs index b2b9b557..b4124d21 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.cs +++ b/QRCoderTests/SvgQRCodeRendererTests.cs @@ -64,7 +64,7 @@ public void can_render_svg_qrcode_viewbox_mode() var svg = new SvgQRCode(data).GetGraphic(new Size(128,128)); var result = HelperFunctions.StringToHash(svg); - result.ShouldBe("ec9a13c4484d246e3e2e0574958845c8"); + result.ShouldBe("56719c7db39937c74377855a5dc4af0a"); } [Fact]