Skip to content

Commit 50a9cef

Browse files
authored
Merge pull request #348 from codebude/feature/347-svg-logo-rendering-improvements
Feature/347 svg logo rendering improvements
2 parents 8395725 + 4bacf39 commit 50a9cef

File tree

2 files changed

+81
-22
lines changed

2 files changed

+81
-22
lines changed

QRCoder/SvgQRCode.cs

Lines changed: 29 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -179,33 +179,31 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex
179179
// Output SVG rectangles
180180
double x = xi * pixelsPerModule;
181181
if (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo(x, y, logoAttr, pixelsPerModule))
182-
svgFile.AppendLine($@"<rect x=""{CleanSvgVal(x)}"" y=""{CleanSvgVal(y)}"" width=""{CleanSvgVal(xL * pixelsPerModule)}"" height=""{CleanSvgVal(yL * pixelsPerModule)}"" fill=""{darkColorHex}"" />");
183-
182+
svgFile.AppendLine($@"<rect x=""{CleanSvgVal(x)}"" y=""{CleanSvgVal(y)}"" width=""{CleanSvgVal(xL * pixelsPerModule)}"" height=""{CleanSvgVal(yL * pixelsPerModule)}"" fill=""{darkColorHex}"" />");
184183
}
185184
}
186185
}
187186

188187
//Render logo, if set
189188
if (logo != null)
190-
{
191-
192-
if (logo.GetMediaType() == SvgLogo.MediaType.PNG)
189+
{
190+
if (!logo.IsEmbedded())
193191
{
194192
svgFile.AppendLine($@"<svg width=""100%"" height=""100%"" version=""1.1"" xmlns = ""http://www.w3.org/2000/svg"">");
195193
svgFile.AppendLine($@"<image x=""{CleanSvgVal(logoAttr.Value.X)}"" y=""{CleanSvgVal(logoAttr.Value.Y)}"" width=""{CleanSvgVal(logoAttr.Value.Width)}"" height=""{CleanSvgVal(logoAttr.Value.Height)}"" xlink:href=""{logo.GetDataUri()}"" />");
194+
svgFile.AppendLine(@"</svg>");
196195
}
197-
else if (logo.GetMediaType() == SvgLogo.MediaType.SVG)
196+
else
198197
{
199-
svgFile.AppendLine($@"<svg x=""{CleanSvgVal(logoAttr.Value.X)}"" y=""{CleanSvgVal(logoAttr.Value.Y)}"" width=""{CleanSvgVal(logoAttr.Value.Width)}"" height=""{CleanSvgVal(logoAttr.Value.Height)}"" version=""1.1"" xmlns = ""http://www.w3.org/2000/svg"">");
200-
var rawLogo = (string)logo.GetRawLogo();
201-
//Remove some attributes from logo, because it would lead to wrong sizing inside our svg wrapper
202-
new List<string>() { "width", "height", "x", "y" }.ForEach(attr =>
203-
{
204-
rawLogo = Regex.Replace(rawLogo, $@"(?!=<svg[^>]*?) +{attr}=(""[^""]+""|'[^']+')(?=[^>]*>)", "");
205-
});
206-
svgFile.Append(rawLogo);
198+
var rawLogo = (string)logo.GetRawLogo();
199+
var svg = System.Xml.Linq.XDocument.Parse(rawLogo);
200+
svg.Root.SetAttributeValue("x", CleanSvgVal(logoAttr.Value.X));
201+
svg.Root.SetAttributeValue("y", CleanSvgVal(logoAttr.Value.Y));
202+
svg.Root.SetAttributeValue("width", CleanSvgVal(logoAttr.Value.Width));
203+
svg.Root.SetAttributeValue("height", CleanSvgVal(logoAttr.Value.Height));
204+
svg.Root.SetAttributeValue("shape-rendering", "geometricPrecision");
205+
svgFile.AppendLine(svg.ToString(System.Xml.Linq.SaveOptions.DisableFormatting).Replace("svg:", ""));
207206
}
208-
svgFile.AppendLine(@"</svg>");
209207
}
210208

211209
svgFile.Append(@"</svg>");
@@ -267,8 +265,9 @@ public class SvgLogo
267265
private int _iconSizePercent;
268266
private bool _fillLogoBackground;
269267
private object _logoRaw;
268+
private bool _isEmbedded;
269+
270270

271-
272271
/// <summary>
273272
/// Create a logo object to be used in SvgQRCode renderer
274273
/// </summary>
@@ -289,6 +288,7 @@ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBac
289288
_mediaType = MediaType.PNG;
290289
_fillLogoBackground = fillLogoBackground;
291290
_logoRaw = iconRasterized;
291+
_isEmbedded = false;
292292
}
293293

294294
/// <summary>
@@ -297,13 +297,15 @@ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBac
297297
/// <param name="iconVectorized">Logo to be rendered as SVG/vectorized graphic/string</param>
298298
/// <param name="iconSizePercent">Degree of percentage coverage of the QR code by the logo</param>
299299
/// <param name="fillLogoBackground">If true, the background behind the logo will be cleaned</param>
300-
public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBackground = true)
300+
/// <param name="iconEmbedded">If true, the logo will embedded as native svg instead of embedding it as image-tag</param>
301+
public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBackground = true, bool iconEmbedded = true)
301302
{
302303
_iconSizePercent = iconSizePercent;
303304
_logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None);
304305
_mediaType = MediaType.SVG;
305306
_fillLogoBackground = fillLogoBackground;
306307
_logoRaw = iconVectorized;
308+
_isEmbedded = iconEmbedded;
307309
}
308310

309311
/// <summary>
@@ -315,6 +317,16 @@ public object GetRawLogo()
315317
return _logoRaw;
316318
}
317319

320+
/// <summary>
321+
/// Defines, if the logo shall be natively embedded.
322+
/// true=native svg embedding, false=embedding via image-tag
323+
/// </summary>
324+
/// <returns></returns>
325+
public bool IsEmbedded()
326+
{
327+
return _isEmbedded;
328+
}
329+
318330
/// <summary>
319331
/// Returns the media type of the logo
320332
/// </summary>

QRCoderTests/SvgQRCodeRendererTests.cs

Lines changed: 52 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.IO;
77
using System.Security.Cryptography;
88
using QRCoderTests.Helpers;
9+
910
#if !NETCOREAPP1_1
1011
using System.Drawing;
1112
#endif
@@ -67,6 +68,19 @@ public void can_render_svg_qrcode_viewbox_mode()
6768
result.ShouldBe("56719c7db39937c74377855a5dc4af0a");
6869
}
6970

71+
[Fact]
72+
[Category("QRRenderer/SvgQRCode")]
73+
public void can_render_svg_qrcode_viewbox_mode_viewboxattr()
74+
{
75+
//Create QR code
76+
var gen = new QRCodeGenerator();
77+
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
78+
var svg = new SvgQRCode(data).GetGraphic(new Size(128, 128), sizingMode: SvgQRCode.SizingMode.ViewBoxAttribute);
79+
80+
var result = HelperFunctions.StringToHash(svg);
81+
result.ShouldBe("788afdb693b0b71eed344e495c180b60");
82+
}
83+
7084
[Fact]
7185
[Category("QRRenderer/SvgQRCode")]
7286
public void can_render_svg_qrcode_without_quietzones()
@@ -80,6 +94,19 @@ public void can_render_svg_qrcode_without_quietzones()
8094
result.ShouldBe("2a582427d86b51504c08ebcbcf0472bd");
8195
}
8296

97+
[Fact]
98+
[Category("QRRenderer/SvgQRCode")]
99+
public void can_render_svg_qrcode_without_quietzones_hex()
100+
{
101+
//Create QR code
102+
var gen = new QRCodeGenerator();
103+
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
104+
var svg = new SvgQRCode(data).GetGraphic(10, "#000000", "#ffffff", false);
105+
106+
var result = HelperFunctions.StringToHash(svg);
107+
result.ShouldBe("4ab0417cc6127e347ca1b2322c49ed7d");
108+
}
109+
83110
[Fact]
84111
[Category("QRRenderer/SvgQRCode")]
85112
public void can_render_svg_qrcode_with_png_logo()
@@ -89,8 +116,9 @@ public void can_render_svg_qrcode_with_png_logo()
89116
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
90117

91118
//Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909346
92-
var logoBitmap = (Bitmap)Bitmap.FromFile(GetAssemblyPath() + "\\assets\\noun_software engineer_2909346.png");
93-
var logoObj = new SvgQRCode.SvgLogo(logoBitmap, 15);
119+
var logoBitmap = (Bitmap)Image.FromFile(GetAssemblyPath() + "\\assets\\noun_software engineer_2909346.png");
120+
var logoObj = new SvgQRCode.SvgLogo(iconRasterized: logoBitmap, 15);
121+
logoObj.GetMediaType().ShouldBe<SvgQRCode.SvgLogo.MediaType>(SvgQRCode.SvgLogo.MediaType.PNG);
94122

95123
var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj);
96124

@@ -100,20 +128,39 @@ public void can_render_svg_qrcode_with_png_logo()
100128

101129
[Fact]
102130
[Category("QRRenderer/SvgQRCode")]
103-
public void can_render_svg_qrcode_with_svg_logo()
131+
public void can_render_svg_qrcode_with_svg_logo_embedded()
132+
{
133+
//Create QR code
134+
var gen = new QRCodeGenerator();
135+
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
136+
137+
//Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909361
138+
var logoSvg = File.ReadAllText(GetAssemblyPath() + "\\assets\\noun_Scientist_2909361.svg");
139+
var logoObj = new SvgQRCode.SvgLogo(logoSvg, 20);
140+
logoObj.GetMediaType().ShouldBe<SvgQRCode.SvgLogo.MediaType>(SvgQRCode.SvgLogo.MediaType.SVG);
141+
142+
var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj);
143+
144+
var result = HelperFunctions.StringToHash(svg);
145+
result.ShouldBe("855eb988d3af035abd273ed1629aa952");
146+
}
147+
148+
[Fact]
149+
[Category("QRRenderer/SvgQRCode")]
150+
public void can_render_svg_qrcode_with_svg_logo_image_tag()
104151
{
105152
//Create QR code
106153
var gen = new QRCodeGenerator();
107154
var data = gen.CreateQrCode("This is a quick test! 123#?", QRCodeGenerator.ECCLevel.H);
108155

109156
//Used logo is licensed under public domain. Ref.: https://thenounproject.com/Iconathon1/collection/redefining-women/?i=2909361
110157
var logoSvg = File.ReadAllText(GetAssemblyPath() + "\\assets\\noun_Scientist_2909361.svg");
111-
var logoObj = new SvgQRCode.SvgLogo(logoSvg, 30);
158+
var logoObj = new SvgQRCode.SvgLogo(logoSvg, 20, iconEmbedded: false);
112159

113160
var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj);
114161

115162
var result = HelperFunctions.StringToHash(svg);
116-
result.ShouldBe("71f461136fdbe2ab85902d23ad2d7eb8");
163+
result.ShouldBe("bd442ea77d45a41a4f490b8d41591e04");
117164
}
118165

119166
[Fact]

0 commit comments

Comments
 (0)