diff --git a/QRCoder/Extensions/StringExtensions.cs b/QRCoder/Extensions/StringExtensions.cs index 6dcbb292..0442ecfa 100644 --- a/QRCoder/Extensions/StringExtensions.cs +++ b/QRCoder/Extensions/StringExtensions.cs @@ -55,4 +55,48 @@ internal static byte[] HexColorToByteArray(this string colorString) internal static string ToString(this char c, CultureInfo _) => c.ToString(); #endif + + /// + /// Appends an integer value to the StringBuilder using invariant culture formatting. + /// + /// The StringBuilder to append to. + /// The integer value to append. + internal static void AppendInvariant(this StringBuilder sb, int num) + { +#if NET6_0_OR_GREATER + sb.Append(CultureInfo.InvariantCulture, $"{num}"); +#else +#if HAS_SPAN + Span buffer = stackalloc char[16]; + if (num.TryFormat(buffer, out int charsWritten, default, CultureInfo.InvariantCulture)) + { + sb.Append(buffer.Slice(0, charsWritten)); + return; + } +#endif + sb.Append(num.ToString(CultureInfo.InvariantCulture)); +#endif + } + + /// + /// Appends a float value to the StringBuilder using invariant culture formatting with G7 precision. + /// + /// The StringBuilder to append to. + /// The float value to append. + internal static void AppendInvariant(this StringBuilder sb, float num) + { +#if NET6_0_OR_GREATER + sb.Append(CultureInfo.InvariantCulture, $"{num:G7}"); +#else +#if HAS_SPAN + Span buffer = stackalloc char[16]; + if (num.TryFormat(buffer, out int charsWritten, "G7", CultureInfo.InvariantCulture)) + { + sb.Append(buffer.Slice(0, charsWritten)); + return; + } +#endif + sb.Append(num.ToString("G7", CultureInfo.InvariantCulture)); +#endif + } } diff --git a/QRCoder/PdfByteQRCode.cs b/QRCoder/PdfByteQRCode.cs index 84b7833f..dd80fc84 100644 --- a/QRCoder/PdfByteQRCode.cs +++ b/QRCoder/PdfByteQRCode.cs @@ -204,11 +204,11 @@ private string CreatePathFromModules() // Create a single rectangle for the entire run of dark modules // Format: x y width height re - pathCommands.Append(ToStr(startX)); + pathCommands.AppendInvariant(startX); pathCommands.Append(' '); - pathCommands.Append(ToStr(y)); + pathCommands.AppendInvariant(y); pathCommands.Append(' '); - pathCommands.Append(ToStr(x - startX)); + pathCommands.AppendInvariant(x - startX); pathCommands.Append(" 1 re\r\n"); } } @@ -242,11 +242,11 @@ private static string ColorToPdfRgb(byte[] color) private static string ToStr(int value) => value.ToString(CultureInfo.InvariantCulture); /// - /// Converts an integer to a string using invariant culture for consistent PDF formatting. + /// Converts a float to a string using invariant culture for consistent PDF formatting. /// - /// The integer value to convert. - /// String representation of the integer. - private static string ToStr(float value) => value.ToString("0.######", CultureInfo.InvariantCulture); + /// The float value to convert. + /// String representation of the float. + private static string ToStr(float value) => value.ToString("G7", CultureInfo.InvariantCulture); } /// diff --git a/QRCoder/SvgQRCode.cs b/QRCoder/SvgQRCode.cs index 1079baae..a859e5b0 100644 --- a/QRCoder/SvgQRCode.cs +++ b/QRCoder/SvgQRCode.cs @@ -23,6 +23,15 @@ public SvgQRCode() { } /// generated by the QRCodeGenerator. public SvgQRCode(QRCodeData data) : base(data) { } + /// + /// Returns a scalable QR code as an SVG string. + /// + /// Returns the QR code graphic as an SVG string. + public string GetGraphic() + { + return GetGraphic(Size.Empty, Color.Black, Color.White, true, SizingMode.ViewBoxAttribute, null); + } + /// /// Returns a QR code as an SVG string. /// @@ -37,13 +46,13 @@ public string GetGraphic(int pixelsPerModule) /// /// Returns a QR code as an SVG string with custom colors, optional quiet zones, and an optional logo. /// - /// The pixel size each dark/light module is drawn. + /// The pixel size each dark/light module is drawn; applicable only when is . /// The color of the dark modules. /// The color of the light modules. /// If true, a white border is drawn around the entire QR code. /// Defines whether width/height or viewBox should be used for size definition. /// An optional logo to be rendered on the code (either Bitmap or SVG). - /// Returns the QR code graphic as an SVG string. + /// Returns the QR code graphic as an SVG 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; @@ -55,7 +64,7 @@ public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, /// /// Returns a QR code as an SVG string with custom colors (in HEX syntax), optional quiet zones, and an optional logo. /// - /// The pixel size each dark/light module is drawn. + /// The pixel size each dark/light module is drawn; applicable only when is . /// The color of the dark/black modules in HEX format (e.g., #000000). /// The color of the light/white modules in HEX format (e.g., #ffffff). /// If true, a white border is drawn around the entire QR code. @@ -73,7 +82,7 @@ public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightC /// /// Returns a QR code as an SVG string with optional quiet zones and an optional logo. /// - /// The viewBox of the QR code graphic. + /// The width and height for the SVG when is ; unused otherwise. /// If true, a white border is drawn around the entire QR code. /// Defines whether width/height or viewBox should be used for size definition. /// An optional logo to be rendered on the code (either Bitmap or SVG). @@ -84,7 +93,7 @@ public string GetGraphic(Size viewBox, bool drawQuietZones = true, SizingMode si /// /// Returns a QR code as an SVG string with custom colors and optional quiet zones and an optional logo. /// - /// The viewBox of the QR code graphic. + /// The width and height for the SVG when is ; unused otherwise. /// The color of the dark modules. /// The color of the light modules. /// If true, a white border is drawn around the entire QR code. @@ -92,12 +101,12 @@ public string GetGraphic(Size viewBox, bool drawQuietZones = true, SizingMode si /// An optional logo to be rendered on the code (either Bitmap or SVG). /// Returns the QR code graphic as an SVG string. public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo? logo = null) - => GetGraphic(viewBox, ColorTranslator.ToHtml(Color.FromArgb(darkColor.ToArgb())), ColorTranslator.ToHtml(Color.FromArgb(lightColor.ToArgb())), drawQuietZones, sizingMode, logo); + => GetGraphic(viewBox, ColorToHex(darkColor), ColorToHex(lightColor), drawQuietZones, sizingMode, logo); /// /// Returns a QR code as an SVG string with custom colors (in HEX syntax), optional quiet zones, and an optional logo. /// - /// The viewBox of the QR code graphic. + /// The width and height for the SVG when is ; unused otherwise. /// The color of the dark/black modules in HEX format (e.g., #000000). /// The color of the light/white modules in HEX format (e.g., #ffffff). /// If true, a white border is drawn around the entire QR code. @@ -108,96 +117,85 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex { int offset = drawQuietZones ? 0 : 4; int drawableModulesCount = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : offset * 2); - 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]; - for (int yi = 0; yi < drawableModulesCount; yi += 1) + // Build SVG opening tag with size attributes + var svgFile = new StringBuilder(); + + svgFile.Append(" 0) - { - matrix[yi, x0] = xL; - x0 = -1; - xL = 0; - } - } - } + // Add width/height attributes if specified by sizing mode + if (sizingMode == SizingMode.WidthHeightAttribute) + { + svgFile.Append(" width=\""); + svgFile.AppendInvariant(viewBox.Width); + svgFile.Append("\" height=\""); + svgFile.AppendInvariant(viewBox.Height); + svgFile.Append('"'); + } + svgFile.Append('>'); + svgFile.AppendLine(); - if (xL > 0) - { - matrix[yi, x0] = xL; - } + // Determine if we need to draw light modules as a path or as a background + bool drawLightModulesAsPath = IsPartiallyTransparent(darkColorHex); + + // Draw light color background if not transparent and dark is fully opaque + if (!IsFullyTransparent(lightColorHex) && !drawLightModulesAsPath) + { + svgFile.Append(""); } - var svgFile = new StringBuilder($@""); -#pragma warning disable CA1305 // Specify IFormatProvider - svgFile.AppendLine($@""); -#pragma warning restore CA1305 // Specify IFormatProvider - for (int yi = 0; yi < drawableModulesCount; yi += 1) + // Calculate logo attributes if needed (in module coordinates) + RectangleF? logoAttr = null; + if (logo != null) { - double y = yi * pixelsPerModule; - for (int xi = 0; xi < drawableModulesCount; xi += 1) - { - int xL = matrix[yi, xi]; - if (xL > 0) - { - // Merge vertical rectangles - int yL = 1; - for (int y2 = yi + 1; y2 < drawableModulesCount; y2 += 1) - { - if (matrix[y2, xi] == xL) - { - matrix[y2, xi] = 0; - yL += 1; - } - else - { - break; - } - } + logoAttr = GetLogoAttributes(logo, new Size(drawableModulesCount, drawableModulesCount)); + } - // Output SVG rectangles - double x = xi * pixelsPerModule; - if (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo(x, y, logoAttr!.Value, pixelsPerModule)) -#pragma warning disable CA1305 // Specify IFormatProvider - svgFile.AppendLine($@""); -#pragma warning restore CA1305 // Specify IFormatProvider - } - } + // Draw light modules as path if dark is not fully opaque + if (!IsFullyTransparent(lightColorHex) && drawLightModulesAsPath) + { + DrawModulesPath(false, lightColorHex); } - //Render logo, if set + // Draw dark modules if not fully transparent + if (!IsFullyTransparent(darkColorHex)) + { + DrawModulesPath(true, darkColorHex); + } + + // Render logo, if set if (logo != null) { + if (!logo.IsEmbedded()) { - svgFile.AppendLine($@""); -#pragma warning disable CA1305 // Specify IFormatProvider - svgFile.AppendLine($@""); -#pragma warning restore CA1305 // Specify IFormatProvider - svgFile.AppendLine(@""); + svgFile.Append(""); } else { @@ -214,54 +212,215 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex svgFile.Append(@""); return svgFile.ToString(); - } - private static bool IsBlockedByLogo(double x, double y, ImageAttributes attr, double pixelPerModule) - => x + pixelPerModule >= attr.X && x <= attr.X + attr.Width && y + pixelPerModule >= attr.Y && y <= attr.Y + attr.Height; + // Local function to draw modules as a path + void DrawModulesPath(bool drawDarkModules, string colorHex) + { + svgFile.Append(" 0) + { + // Absolute move to start of rectangle + svgFile.Append('M'); + svgFile.AppendInvariant(startX); + svgFile.Append(' '); + svgFile.AppendInvariant(y); + + // Draw rectangle using relative movements (width, height of 1) + svgFile.Append('h'); + svgFile.AppendInvariant(width); + svgFile.Append("v1h-"); + svgFile.AppendInvariant(width); + svgFile.Append('z'); + } + } + } + + svgFile.AppendLine("\"/>"); + + // Local function to determine if a module should be drawn + bool ShouldDrawModule(int x, int y) + { + bool isDarkModule = QrCodeData.ModuleMatrix[y + offset][x + offset]; + bool isBlockedByLogo = logo != null && logo.FillLogoBackground() && + IsBlockedByLogo(x, y, logoAttr!.Value); + + // For dark modules: draw only dark modules not blocked by logo + // For light modules: draw light modules not blocked by logo, or anywhere blocked by logo (regardless of module color) + return drawDarkModules + ? (isDarkModule && !isBlockedByLogo) + : (!isDarkModule || isBlockedByLogo); + } + } + } - private static ImageAttributes GetLogoAttributes(SvgLogo logo, Size viewBox) + /// + /// Determines if a module at (x,y) is blocked by the logo area defined by attr. + /// + private static bool IsBlockedByLogo(int x, int y, RectangleF attr) { - 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 - }; + return + x + 1 > attr.X && // Right edge of module > left edge of logo + x < attr.X + attr.Width && // Left edge of module < right edge of logo + y + 1 > attr.Y && // Bottom edge of module > top edge of logo + y < attr.Y + attr.Height; // Top edge of module < bottom edge of logo } - private struct ImageAttributes + /// + /// Calculates the logo's position and size within the QR code based on the specified percentage size. + /// + private static RectangleF GetLogoAttributes(SvgLogo logo, Size viewBox) { - public double Width; - public double Height; - public double X; - public double Y; + var imgWidth = logo.GetIconSizePercent() * viewBox.Width / 100f; + var imgHeight = logo.GetIconSizePercent() * viewBox.Height / 100f; + var imgPosX = viewBox.Width / 2f - imgWidth / 2f; + var imgPosY = viewBox.Height / 2f - imgHeight / 2f; + return new RectangleF(imgPosX, imgPosY, imgWidth, imgHeight); } //Clean double values for international use/formats - //We use explicitly "G15" to avoid differences between .NET full and Core platforms + //We use explicitly "G7" 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 - private static string CleanSvgVal(double input) - => input.ToString("G15", System.Globalization.CultureInfo.InvariantCulture); + private static string CleanSvgVal(float input) + => input.ToString("G7", CultureInfo.InvariantCulture); + + /// + /// Gets the transparency value (0-255) from a color string. + /// + /// The color string in hex format (e.g., #RRGGBB, #RRGGBBAA) or the literal "transparent". + /// The alpha/transparency value from 0 (fully transparent) to 255 (fully opaque). Returns 255 for colors without alpha channel. + private static int GetTransparency(string colorHex) + { + if (string.IsNullOrEmpty(colorHex)) + throw new ArgumentNullException(nameof(colorHex), "Please specify a color using a hex format such as #RRGGBB or the string 'transparent'."); + + // Check for literal "transparent" keyword + if (colorHex.Equals("transparent", StringComparison.OrdinalIgnoreCase)) + return 0; // Fully transparent + + // Check for hex color with alpha channel + if (colorHex.StartsWith("#", StringComparison.Ordinal)) + { + // #RRGGBBAA format (9 characters) + if (colorHex.Length == 9) + { + // Extract alpha channel (last 2 characters) +#if HAS_SPAN + if (int.TryParse(colorHex.AsSpan(7, 2), NumberStyles.HexNumber, null, out int alpha)) +#else + if (int.TryParse(colorHex.Substring(7, 2), NumberStyles.HexNumber, null, out int alpha)) +#endif + { + return alpha; + } + } + // #RGBA format (5 characters) + else if (colorHex.Length == 5) + { + // Extract alpha channel (last character) and multiply by 17 to convert 4-bit to 8-bit +#if HAS_SPAN + if (int.TryParse(colorHex.AsSpan(4, 1), NumberStyles.HexNumber, null, out int alpha)) +#else + if (int.TryParse(colorHex.Substring(4, 1), NumberStyles.HexNumber, null, out int alpha)) +#endif + { + return alpha * 17; + } + } + } + + return 255; // Fully opaque by default + } + + /// + /// Determines if a color string represents a partially transparent color. + /// + /// The color string in hex format (e.g., #RRGGBB, #RRGGBBAA) or the literal "transparent". + /// True if the color is transparent; otherwise false. + private static bool IsPartiallyTransparent(string colorHex) + => GetTransparency(colorHex) < 255; + + /// + /// Determines if a color string represents a fully transparent color. + /// + /// The color string in hex format (e.g., #RRGGBB, #RRGGBBAA) or the literal "transparent". + /// True if the color is fully transparent; otherwise false. + private static bool IsFullyTransparent(string colorHex) + => GetTransparency(colorHex) == 0; + + /// + /// Converts a Color to a hex string in #RGB, #RRGGBB or #RRGGBBAA format; or 'transparent' for fully transparent colors. + /// + /// The color to convert. + /// A hex string representation of the color. + private static string ColorToHex(Color color) + { + if (color == Color.Black) + { + // Use shorthand #000 for black + return "#000"; + } + else if (color == Color.White) + { + // Use shorthand #FFF for white + return "#FFF"; + } + else if (color.A == 255) + { + // Fully opaque - use #RRGGBB format + return $"#{color.R:X2}{color.G:X2}{color.B:X2}"; + } + else if (color.A == 0) + { + // Fully transparent - use "transparent" keyword + return "transparent"; + } + else + { + // Has transparency - use #RRGGBBAA format + return $"#{color.R:X2}{color.G:X2}{color.B:X2}{color.A:X2}"; + } + } /// - /// Mode of sizing attribution on svg root node + /// Mode of sizing attribute on SVG root node /// public enum SizingMode { /// - /// Use width and height attributes for SVG sizing + /// Specifies width and height attributes for SVG sizing /// - WidthHeightAttribute, + WidthHeightAttribute = 0, /// - /// Use viewBox attribute for SVG sizing + /// Width and height are not included within the SVG tag; viewBox is scaled to fit the container /// - ViewBoxAttribute + ViewBoxAttribute = 1, } /// diff --git a/QRCoderApiTests/net35+net40+net50+netstandard20+netstandard21/QRCoder.approved.txt b/QRCoderApiTests/net35+net40+net50+netstandard20+netstandard21/QRCoder.approved.txt index 03d31fc1..fa119ea8 100644 --- a/QRCoderApiTests/net35+net40+net50+netstandard20+netstandard21/QRCoder.approved.txt +++ b/QRCoderApiTests/net35+net40+net50+netstandard20+netstandard21/QRCoder.approved.txt @@ -952,6 +952,7 @@ namespace QRCoder { public SvgQRCode() { } public SvgQRCode(QRCoder.QRCodeData data) { } + public string GetGraphic() { } public string GetGraphic(int pixelsPerModule) { } public string GetGraphic(System.Drawing.Size viewBox, bool drawQuietZones = true, QRCoder.SvgQRCode.SizingMode sizingMode = 0, QRCoder.SvgQRCode.SvgLogo? logo = null) { } public string GetGraphic(System.Drawing.Size viewBox, System.Drawing.Color darkColor, System.Drawing.Color lightColor, bool drawQuietZones = true, QRCoder.SvgQRCode.SizingMode sizingMode = 0, QRCoder.SvgQRCode.SvgLogo? logo = null) { } diff --git a/QRCoderApiTests/net60/QRCoder.approved.txt b/QRCoderApiTests/net60/QRCoder.approved.txt index 37fde645..c0599c3a 100644 --- a/QRCoderApiTests/net60/QRCoder.approved.txt +++ b/QRCoderApiTests/net60/QRCoder.approved.txt @@ -959,6 +959,7 @@ namespace QRCoder { public SvgQRCode() { } public SvgQRCode(QRCoder.QRCodeData data) { } + public string GetGraphic() { } public string GetGraphic(int pixelsPerModule) { } public string GetGraphic(System.Drawing.Size viewBox, bool drawQuietZones = true, QRCoder.SvgQRCode.SizingMode sizingMode = 0, QRCoder.SvgQRCode.SvgLogo? logo = null) { } public string GetGraphic(System.Drawing.Size viewBox, System.Drawing.Color darkColor, System.Drawing.Color lightColor, bool drawQuietZones = true, QRCoder.SvgQRCode.SizingMode sizingMode = 0, QRCoder.SvgQRCode.SvgLogo? logo = null) { } diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode.approved.svg index 0325ba28..5f9978b5 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode.approved.svg @@ -1,227 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_blue_light_with_half_red_dark.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_blue_light_with_half_red_dark.approved.svg new file mode 100644 index 00000000..4e3c979e --- /dev/null +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_blue_light_with_half_red_dark.approved.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_from_helper.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_from_helper.approved.svg index 12f0c5df..7f099058 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_from_helper.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_from_helper.approved.svg @@ -1,78 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_simple.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_simple.approved.svg index f96419f8..934d8ed1 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_simple.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_simple.approved.svg @@ -1,122 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_simple_unscaled.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_simple_unscaled.approved.svg new file mode 100644 index 00000000..5d15c32e --- /dev/null +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_simple_unscaled.approved.svg @@ -0,0 +1,4 @@ + + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_dark_with_black_light.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_dark_with_black_light.approved.svg new file mode 100644 index 00000000..39b4bcf0 --- /dev/null +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_dark_with_black_light.approved.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_light_with_black_dark.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_light_with_black_dark.approved.svg new file mode 100644 index 00000000..2afb3b4d --- /dev/null +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_light_with_black_dark.approved.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_light_with_half_red_dark.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_light_with_half_red_dark.approved.svg new file mode 100644 index 00000000..dab0e347 --- /dev/null +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_transparent_light_with_half_red_dark.approved.svg @@ -0,0 +1,3 @@ + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_viewbox_mode.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_viewbox_mode.approved.svg index cffad3c3..9a9fe1c3 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_viewbox_mode.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_viewbox_mode.approved.svg @@ -1,227 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_viewbox_mode_viewboxattr.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_viewbox_mode_viewboxattr.approved.svg index 9c396ee8..82fe816c 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_viewbox_mode_viewboxattr.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_viewbox_mode_viewboxattr.approved.svg @@ -1,227 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap.approved.svg index fdf9f61f..c0aa04cb 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap.approved.svg @@ -1,218 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap_without_background.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap_without_background.approved.svg index b4366c61..f87e9449 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap_without_background.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap_without_background.approved.svg @@ -1,230 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap_without_quietzones.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap_without_quietzones.approved.svg index a1d8cb88..0c5196f7 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap_without_quietzones.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bitmap_without_quietzones.approved.svg @@ -1,223 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bytearray.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bytearray.approved.svg index 7f72f4c2..03e7e428 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bytearray.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_png_logo_bytearray.approved.svg @@ -1,218 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_svg_logo_embedded.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_svg_logo_embedded.approved.svg index e749b0be..c274870c 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_svg_logo_embedded.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_svg_logo_embedded.approved.svg @@ -1,210 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_svg_logo_image_tag.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_svg_logo_image_tag.approved.svg index 9b150b37..8388e758 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_svg_logo_image_tag.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_with_svg_logo_image_tag.approved.svg @@ -1,212 +1,5 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_without_quietzones.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_without_quietzones.approved.svg index 4d3184a3..7fb9cdd2 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_without_quietzones.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_without_quietzones.approved.svg @@ -1,227 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_without_quietzones_hex.approved.svg b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_without_quietzones_hex.approved.svg index 7ea9ecf6..ca78bb48 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_without_quietzones_hex.approved.svg +++ b/QRCoderTests/SvgQRCodeRendererTests.can_render_svg_qrcode_without_quietzones_hex.approved.svg @@ -1,227 +1,4 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - + + + \ No newline at end of file diff --git a/QRCoderTests/SvgQRCodeRendererTests.cs b/QRCoderTests/SvgQRCodeRendererTests.cs index 174ed6ba..0aab3d83 100644 --- a/QRCoderTests/SvgQRCodeRendererTests.cs +++ b/QRCoderTests/SvgQRCodeRendererTests.cs @@ -2,6 +2,16 @@ namespace QRCoderTests; public class SvgQRCodeRendererTests { + [Fact] + public void can_render_svg_qrcode_simple_unscaled() + { + //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(); + svg.ShouldMatchApproved(x => x.NoDiff().WithFileExtension("svg")); + } + [Fact] public void can_render_svg_qrcode_simple() { @@ -210,4 +220,59 @@ public void can_render_svg_qrcode_from_helper() var svg = SvgQRCodeHelper.GetQRCode("A", 2, "#000000", "#ffffff", QRCodeGenerator.ECCLevel.Q); svg.ShouldMatchApproved(x => x.NoDiff().WithFileExtension("svg")); } + [Theory] + [InlineData(true)] + [InlineData(false)] + public void can_render_svg_qrcode_blue_light_with_half_red_dark(bool useColor) + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var svg = useColor + ? new SvgQRCode(data).GetGraphic(10, Color.FromArgb(128, 255, 0, 0), Color.Blue) + : new SvgQRCode(data).GetGraphic(10, "#FF000080", "#0000FF"); + svg.ShouldMatchApproved(x => x.NoDiff().WithFileExtension("svg")); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void can_render_svg_qrcode_transparent_light_with_black_dark(bool useColor) + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var svg = useColor + ? new SvgQRCode(data).GetGraphic(10, Color.Black, Color.Transparent) + : new SvgQRCode(data).GetGraphic(10, "#000", "transparent"); + svg.ShouldMatchApproved(x => x.NoDiff().WithFileExtension("svg")); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void can_render_svg_qrcode_transparent_light_with_half_red_dark(bool useColor) + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.L); + var svg = useColor + ? new SvgQRCode(data).GetGraphic(10, Color.FromArgb(128, 255, 0, 0), Color.Transparent) + : new SvgQRCode(data).GetGraphic(10, "#FF000080", "transparent"); + svg.ShouldMatchApproved(x => x.NoDiff().WithFileExtension("svg")); + } + + [Theory] + [InlineData(true)] + [InlineData(false)] + public void can_render_svg_qrcode_transparent_dark_with_black_light(bool useColor) + { + //Create QR code + var gen = new QRCodeGenerator(); + var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H); + var svg = useColor + ? new SvgQRCode(data).GetGraphic(10, Color.Transparent, Color.Black) + : new SvgQRCode(data).GetGraphic(10, "transparent", "#000"); + svg.ShouldMatchApproved(x => x.NoDiff().WithFileExtension("svg")); + } }