Skip to content

Commit 4ad5ea7

Browse files
committed
Added SVG-native logo embedding
1 parent 378a5fa commit 4ad5ea7

File tree

3 files changed

+103
-11
lines changed

3 files changed

+103
-11
lines changed
Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
using System;
2+
using System.Collections.Generic;
3+
using System.Linq;
4+
using System.Reflection;
5+
using System.Text;
6+
7+
namespace QRCoder.Extensions
8+
{
9+
/// <summary>
10+
/// Used to represent a string value for a value in an enum
11+
/// </summary>
12+
public class StringValueAttribute : Attribute
13+
{
14+
15+
#region Properties
16+
17+
/// <summary>
18+
/// Holds the alue in an enum
19+
/// </summary>
20+
public string StringValue { get; protected set; }
21+
22+
#endregion
23+
24+
/// <summary>
25+
/// Init a StringValue Attribute
26+
/// </summary>
27+
/// <param name="value"></param>
28+
public StringValueAttribute(string value)
29+
{
30+
this.StringValue = value;
31+
}
32+
}
33+
34+
public static class CustomExtensions
35+
{
36+
/// <summary>
37+
/// Will get the string value for a given enum's value
38+
/// </summary>
39+
/// <param name="value"></param>
40+
/// <returns></returns>
41+
public static string GetStringValue(this Enum value)
42+
{
43+
#if NETSTANDARD1_3
44+
var fieldInfo = value.GetType().GetRuntimeField(value.ToString());
45+
#else
46+
var fieldInfo = value.GetType().GetField(value.ToString());
47+
#endif
48+
var attr = fieldInfo.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[];
49+
return attr.Length > 0 ? attr[0].StringValue : null;
50+
}
51+
}
52+
}

QRCoder/SvgQRCode.cs

Lines changed: 48 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -1,8 +1,11 @@
11
#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0
2+
using QRCoder.Extensions;
23
using System;
34
using System.Collections;
5+
using System.Collections.Generic;
46
using System.Drawing;
57
using System.Text;
8+
using System.Text.RegularExpressions;
69
using static QRCoder.QRCodeGenerator;
710
using static QRCoder.SvgQRCode;
811

@@ -69,7 +72,7 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex
6972
for (int xi = 0; xi < drawableModulesCount; xi += 1)
7073
{
7174
matrix[yi, xi] = 0;
72-
if (bitArray[xi+offset] && !IsBlockedByLogo((xi+offset)*pixelsPerModule, (yi+offset) * pixelsPerModule, logoAttr, pixelsPerModule))
75+
if (bitArray[xi+offset] && (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo((xi+offset)*pixelsPerModule, (yi+offset) * pixelsPerModule, logoAttr, pixelsPerModule)))
7376
{
7477
if(x0 == -1)
7578
{
@@ -121,7 +124,7 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex
121124

122125
// Output SVG rectangles
123126
double x = xi * pixelsPerModule;
124-
if (!IsBlockedByLogo(x, y, logoAttr, pixelsPerModule))
127+
if (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo(x, y, logoAttr, pixelsPerModule))
125128
svgFile.AppendLine($@"<rect x=""{CleanSvgVal(x)}"" y=""{CleanSvgVal(y)}"" width=""{CleanSvgVal(xL * pixelsPerModule)}"" height=""{CleanSvgVal(yL * pixelsPerModule)}"" fill=""{darkColorHex}"" />");
126129

127130
}
@@ -131,8 +134,23 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex
131134
//Render logo, if set
132135
if (logo != null)
133136
{
134-
svgFile.AppendLine($@"<svg width=""100%"" height=""100%"" version=""1.1"" xmlns = ""http://www.w3.org/2000/svg"">");
135-
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()}"" />");
137+
138+
if (logo.GetMediaType() == SvgLogo.MediaType.PNG)
139+
{
140+
svgFile.AppendLine($@"<svg width=""100%"" height=""100%"" version=""1.1"" xmlns = ""http://www.w3.org/2000/svg"">");
141+
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()}"" />");
142+
}
143+
else if (logo.GetMediaType() == SvgLogo.MediaType.SVG)
144+
{
145+
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"">");
146+
var rawLogo = (string)logo.GetRawLogo();
147+
//Remove some attributes from logo, because it would lead to wrong sizing inside our svg wrapper
148+
new List<string>() { "width", "height", "x", "y" }.ForEach(attr =>
149+
{
150+
rawLogo = Regex.Replace(rawLogo, $@"(?!=<svg[^>]*?) +{attr}=(""[^""]+""|'[^']+')(?=[^>]*>)", "");
151+
});
152+
svgFile.Append(rawLogo);
153+
}
136154
svgFile.AppendLine(@"</svg>");
137155
}
138156

@@ -185,9 +203,11 @@ public enum SizingMode
185203
public class SvgLogo
186204
{
187205
private string _logoData;
188-
private string _mediaType;
206+
private MediaType _mediaType;
189207
private int _iconSizePercent;
190208
private bool _fillLogoBackground;
209+
private object _logoRaw;
210+
191211

192212
/// <summary>
193213
/// Create a logo object to be used in SvgQRCode renderer
@@ -205,8 +225,9 @@ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBac
205225
_logoData = Convert.ToBase64String(ms.GetBuffer(), Base64FormattingOptions.None);
206226
}
207227
}
208-
_mediaType = "image/png";
228+
_mediaType = MediaType.PNG;
209229
_fillLogoBackground = fillLogoBackground;
230+
_logoRaw = iconRasterized;
210231
}
211232

212233
/// <summary>
@@ -218,13 +239,24 @@ public SvgLogo(string iconVectorized, int iconSizePercent = 15, bool fillLogoBac
218239
{
219240
_iconSizePercent = iconSizePercent;
220241
_logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None);
221-
_mediaType = "image/svg+xml";
242+
_mediaType = MediaType.SVG;
222243
_fillLogoBackground = fillLogoBackground;
244+
_logoRaw = iconVectorized;
245+
}
246+
247+
public object GetRawLogo()
248+
{
249+
return _logoRaw;
250+
}
251+
252+
public MediaType GetMediaType()
253+
{
254+
return _mediaType;
223255
}
224256

225257
public string GetDataUri()
226258
{
227-
return $"data:{_mediaType};base64,{_logoData}";
259+
return $"data:{_mediaType.GetStringValue()};base64,{_logoData}";
228260
}
229261

230262
public int GetIconSizePercent()
@@ -235,6 +267,14 @@ public bool FillLogoBackground()
235267
{
236268
return _fillLogoBackground;
237269
}
270+
271+
public enum MediaType : int
272+
{
273+
[StringValue("image/png")]
274+
PNG = 0,
275+
[StringValue("image/svg+xml")]
276+
SVG = 1
277+
}
238278
}
239279
}
240280

QRCoderTests/SvgQRCodeRendererTests.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -72,12 +72,12 @@ public void can_render_svg_qrcode_with_png_logo()
7272
var logoObj = new SvgQRCode.SvgLogo(logoBitmap, 15);
7373

7474
var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj);
75-
75+
File.WriteAllText(@"C:\Temp\qr_png.svg", svg);
7676
var md5 = new MD5CryptoServiceProvider();
7777
var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(svg));
7878
var result = BitConverter.ToString(hash).Replace("-", "").ToLower();
7979

80-
result.ShouldBe("4ff45872787f321524cc4d071239c25e");
80+
result.ShouldBe("4ff45872787f321524cc4d071239c25e");
8181
}
8282

8383
[Fact]
@@ -93,7 +93,7 @@ public void can_render_svg_qrcode_with_svg_logo()
9393
var logoObj = new SvgQRCode.SvgLogo(logoSvg, 30);
9494

9595
var svg = new SvgQRCode(data).GetGraphic(10, Color.DarkGray, Color.White, logo: logoObj);
96-
96+
File.WriteAllText(@"C:\Temp\qr_svg.svg", svg);
9797
var md5 = new MD5CryptoServiceProvider();
9898
var hash = md5.ComputeHash(System.Text.Encoding.UTF8.GetBytes(svg));
9999
var result = BitConverter.ToString(hash).Replace("-", "").ToLower();

0 commit comments

Comments
 (0)