1
1
#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0
2
+ using QRCoder . Extensions ;
2
3
using System ;
3
4
using System . Collections ;
5
+ using System . Collections . Generic ;
4
6
using System . Drawing ;
5
7
using System . Text ;
8
+ using System . Text . RegularExpressions ;
6
9
using static QRCoder . QRCodeGenerator ;
7
10
using static QRCoder . SvgQRCode ;
8
11
@@ -16,11 +19,27 @@ public class SvgQRCode : AbstractQRCode, IDisposable
16
19
public SvgQRCode ( ) { }
17
20
public SvgQRCode ( QRCodeData data ) : base ( data ) { }
18
21
22
+ /// <summary>
23
+ /// Returns a QR code as SVG string
24
+ /// </summary>
25
+ /// <param name="pixelsPerModule">The pixel size each b/w module is drawn</param>
26
+ /// <returns>SVG as string</returns>
19
27
public string GetGraphic ( int pixelsPerModule )
20
28
{
21
29
var viewBox = new Size ( pixelsPerModule * this . QrCodeData . ModuleMatrix . Count , pixelsPerModule * this . QrCodeData . ModuleMatrix . Count ) ;
22
30
return this . GetGraphic ( viewBox , Color . Black , Color . White ) ;
23
31
}
32
+
33
+ /// <summary>
34
+ /// Returns a QR code as SVG string with custom colors, optional quietzone and logo
35
+ /// </summary>
36
+ /// <param name="pixelsPerModule">The pixel size each b/w module is drawn</param>
37
+ /// <param name="darkColor">Color of the dark modules</param>
38
+ /// <param name="lightColor">Color of the light modules</param>
39
+ /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
40
+ /// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
41
+ /// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
42
+ /// <returns>SVG as string</returns>
24
43
public string GetGraphic ( int pixelsPerModule , Color darkColor , Color lightColor , bool drawQuietZones = true , SizingMode sizingMode = SizingMode . WidthHeightAttribute , SvgLogo logo = null )
25
44
{
26
45
var offset = drawQuietZones ? 0 : 4 ;
@@ -29,6 +48,16 @@ public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor,
29
48
return this . GetGraphic ( viewBox , darkColor , lightColor , drawQuietZones , sizingMode , logo ) ;
30
49
}
31
50
51
+ /// <summary>
52
+ /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo
53
+ /// </summary>
54
+ /// <param name="pixelsPerModule">The pixel size each b/w module is drawn</param>
55
+ /// <param name="darkColorHex">The color of the dark/black modules in hex (e.g. #000000) representation</param>
56
+ /// <param name="lightColorHex">The color of the light/white modules in hex (e.g. #ffffff) representation</param>
57
+ /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
58
+ /// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
59
+ /// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
60
+ /// <returns>SVG as string</returns>
32
61
public string GetGraphic ( int pixelsPerModule , string darkColorHex , string lightColorHex , bool drawQuietZones = true , SizingMode sizingMode = SizingMode . WidthHeightAttribute , SvgLogo logo = null )
33
62
{
34
63
var offset = drawQuietZones ? 0 : 4 ;
@@ -37,23 +66,54 @@ public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightC
37
66
return this . GetGraphic ( viewBox , darkColorHex , lightColorHex , drawQuietZones , sizingMode , logo ) ;
38
67
}
39
68
69
+ /// <summary>
70
+ /// Returns a QR code as SVG string with optional quietzone and logo
71
+ /// </summary>
72
+ /// <param name="viewBox">The viewbox of the QR code graphic</param>
73
+ /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
74
+ /// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
75
+ /// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
76
+ /// <returns>SVG as string</returns>
40
77
public string GetGraphic ( Size viewBox , bool drawQuietZones = true , SizingMode sizingMode = SizingMode . WidthHeightAttribute , SvgLogo logo = null )
41
78
{
42
79
return this . GetGraphic ( viewBox , Color . Black , Color . White , drawQuietZones , sizingMode , logo ) ;
43
80
}
44
81
82
+ /// <summary>
83
+ /// Returns a QR code as SVG string with custom colors and optional quietzone and logo
84
+ /// </summary>
85
+ /// <param name="viewBox">The viewbox of the QR code graphic</param>
86
+ /// <param name="darkColor">Color of the dark modules</param>
87
+ /// <param name="lightColor">Color of the light modules</param>
88
+ /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
89
+ /// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
90
+ /// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
91
+ /// <returns>SVG as string</returns>
45
92
public string GetGraphic ( Size viewBox , Color darkColor , Color lightColor , bool drawQuietZones = true , SizingMode sizingMode = SizingMode . WidthHeightAttribute , SvgLogo logo = null )
46
93
{
47
94
return this . GetGraphic ( viewBox , ColorTranslator . ToHtml ( Color . FromArgb ( darkColor . ToArgb ( ) ) ) , ColorTranslator . ToHtml ( Color . FromArgb ( lightColor . ToArgb ( ) ) ) , drawQuietZones , sizingMode , logo ) ;
48
95
}
49
96
97
+ /// <summary>
98
+ /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo
99
+ /// </summary>
100
+ /// <param name="viewBox">The viewbox of the QR code graphic</param>
101
+ /// <param name="darkColorHex">The color of the dark/black modules in hex (e.g. #000000) representation</param>
102
+ /// <param name="lightColorHex">The color of the light/white modules in hex (e.g. #ffffff) representation</param>
103
+ /// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
104
+ /// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
105
+ /// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
106
+ /// <returns>SVG as string</returns>
50
107
public string GetGraphic ( Size viewBox , string darkColorHex , string lightColorHex , bool drawQuietZones = true , SizingMode sizingMode = SizingMode . WidthHeightAttribute , SvgLogo logo = null )
51
108
{
52
109
int offset = drawQuietZones ? 0 : 4 ;
53
110
int drawableModulesCount = this . QrCodeData . ModuleMatrix . Count - ( drawQuietZones ? 0 : offset * 2 ) ;
54
111
double pixelsPerModule = Math . Min ( viewBox . Width , viewBox . Height ) / ( double ) drawableModulesCount ;
55
112
double qrSize = drawableModulesCount * pixelsPerModule ;
56
113
string svgSizeAttributes = ( sizingMode == SizingMode . WidthHeightAttribute ) ? $@ "width=""{ viewBox . Width } "" height=""{ viewBox . Height } """ : $@"viewBox = "" 0 0 { viewBox . Width } { viewBox . Height } """;
114
+ ImageAttributes? logoAttr = null;
115
+ if (logo != null)
116
+ logoAttr = GetLogoAttributes(logo, viewBox);
57
117
58
118
// Merge horizontal rectangles
59
119
int[,] matrix = new int[drawableModulesCount, drawableModulesCount];
@@ -66,7 +126,7 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex
66
126
for (int xi = 0; xi < drawableModulesCount; xi += 1)
67
127
{
68
128
matrix[yi, xi] = 0;
69
- if (bitArray[xi+offset])
129
+ if (bitArray[xi+offset] && (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo((xi+offset)*pixelsPerModule, (yi+offset) * pixelsPerModule, logoAttr, pixelsPerModule)) )
70
130
{
71
131
if(x0 == -1)
72
132
{
@@ -91,7 +151,7 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex
91
151
}
92
152
}
93
153
94
- StringBuilder svgFile = new StringBuilder($@"<svg version=""1.1"" baseProfile=""full"" shape-rendering=""crispEdges"" {svgSizeAttributes} xmlns=""http://www.w3.org/2000/svg"">");
154
+ StringBuilder svgFile = new StringBuilder($@"<svg version=""1.1"" baseProfile=""full"" shape-rendering=""crispEdges"" {svgSizeAttributes} xmlns=""http://www.w3.org/2000/svg"" xmlns:xlink=""http://www.w3.org/1999/xlink"" >");
95
155
svgFile.AppendLine($@"<rect x=""0"" y=""0"" width=""{CleanSvgVal(qrSize)}"" height=""{CleanSvgVal(qrSize)}"" fill=""{lightColorHex}"" />");
96
156
for (int yi = 0; yi < drawableModulesCount; yi += 1)
97
157
{
@@ -118,47 +178,104 @@ public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex
118
178
119
179
// Output SVG rectangles
120
180
double x = xi * pixelsPerModule;
121
- svgFile.AppendLine($@"<rect x=""{CleanSvgVal(x)}"" y=""{CleanSvgVal(y)}"" width=""{CleanSvgVal(xL * pixelsPerModule)}"" height=""{CleanSvgVal(yL * pixelsPerModule)}"" fill=""{darkColorHex}"" />");
181
+ 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
+
122
184
}
123
185
}
124
186
}
125
187
126
188
//Render logo, if set
127
189
if (logo != null)
128
- {
129
- svgFile.AppendLine($@"<svg width=""100%"" height=""100%"" version=""1.1"" xmlns = ""http://www.w3.org/2000/svg"">");
130
- svgFile.AppendLine($@"<image x=""{50 - (logo.GetIconSizePercent() / 2)}%"" y=""{50 - (logo.GetIconSizePercent() / 2)}%"" width=""{logo.GetIconSizePercent()}%"" height=""{logo.GetIconSizePercent()}%"" href=""{logo.GetDataUri()}"" />");
190
+ {
191
+
192
+ if (logo.GetMediaType() == SvgLogo.MediaType.PNG)
193
+ {
194
+ svgFile.AppendLine($@"<svg width=""100%"" height=""100%"" version=""1.1"" xmlns = ""http://www.w3.org/2000/svg"">");
195
+ 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()}"" />");
196
+ }
197
+ else if (logo.GetMediaType() == SvgLogo.MediaType.SVG)
198
+ {
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);
207
+ }
131
208
svgFile.AppendLine(@"</svg>");
132
209
}
133
210
134
211
svgFile.Append(@"</svg>");
135
212
return svgFile.ToString();
136
213
}
137
214
215
+ private bool IsBlockedByLogo(double x, double y, ImageAttributes? attr, double pixelPerModule)
216
+ {
217
+ 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;
218
+ }
219
+
220
+ private ImageAttributes GetLogoAttributes(SvgLogo logo, Size viewBox)
221
+ {
222
+ var imgWidth = logo.GetIconSizePercent() / 100d * viewBox.Width;
223
+ var imgHeight = logo.GetIconSizePercent() / 100d * viewBox.Height;
224
+ var imgPosX = viewBox.Width / 2d - imgWidth / 2d;
225
+ var imgPosY = viewBox.Height / 2d - imgHeight / 2d;
226
+ return new ImageAttributes()
227
+ {
228
+ Width = imgWidth,
229
+ Height = imgHeight,
230
+ X = imgPosX,
231
+ Y = imgPosY
232
+ };
233
+ }
234
+
235
+ private struct ImageAttributes
236
+ {
237
+ public double Width;
238
+ public double Height;
239
+ public double X;
240
+ public double Y;
241
+ }
242
+
138
243
private string CleanSvgVal(double input)
139
244
{
140
245
//Clean double values for international use/formats
141
- return input.ToString(System.Globalization.CultureInfo.InvariantCulture);
246
+ //We use explicitly "G15" to avoid differences between .NET full and Core platforms
247
+ //https://stackoverflow.com/questions/64898117/tostring-has-a-different-behavior-between-net-462-and-net-core-3-1
248
+ return input.ToString("G15", System.Globalization.CultureInfo.InvariantCulture);
142
249
}
143
250
251
+ /// <summary>
252
+ /// Mode of sizing attribution on svg root node
253
+ /// </summary>
144
254
public enum SizingMode
145
255
{
146
256
WidthHeightAttribute,
147
257
ViewBoxAttribute
148
258
}
149
259
260
+ /// <summary>
261
+ /// Represents a logo graphic that can be rendered on a SvgQRCode
262
+ /// </summary>
150
263
public class SvgLogo
151
264
{
152
265
private string _logoData;
153
- private string _mediaType;
266
+ private MediaType _mediaType;
154
267
private int _iconSizePercent;
268
+ private bool _fillLogoBackground;
269
+ private object _logoRaw;
155
270
271
+
156
272
/// <summary>
157
273
/// Create a logo object to be used in SvgQRCode renderer
158
274
/// </summary>
159
275
/// <param name="iconRasterized">Logo to be rendered as Bitmap/rasterized graphic</param>
160
276
/// <param name="iconSizePercent">Degree of percentage coverage of the QR code by the logo</param>
161
- public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15)
277
+ /// <param name="fillLogoBackground">If true, the background behind the logo will be cleaned</param>
278
+ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true)
162
279
{
163
280
_iconSizePercent = iconSizePercent;
164
281
using (var ms = new System.IO.MemoryStream())
@@ -169,30 +286,81 @@ public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15)
169
286
_logoData = Convert.ToBase64String(ms.GetBuffer(), Base64FormattingOptions.None);
170
287
}
171
288
}
172
- _mediaType = "image/png";
289
+ _mediaType = MediaType.PNG;
290
+ _fillLogoBackground = fillLogoBackground;
291
+ _logoRaw = iconRasterized;
173
292
}
174
293
175
294
/// <summary>
176
295
/// Create a logo object to be used in SvgQRCode renderer
177
296
/// </summary>
178
297
/// <param name="iconVectorized">Logo to be rendered as SVG/vectorized graphic/string</param>
179
298
/// <param name="iconSizePercent">Degree of percentage coverage of the QR code by the logo</param>
180
- public SvgLogo(string iconVectorized, int iconSizePercent = 15)
299
+ /// <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)
181
301
{
182
302
_iconSizePercent = iconSizePercent;
183
303
_logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None);
184
- _mediaType = "image/svg+xml";
304
+ _mediaType = MediaType.SVG;
305
+ _fillLogoBackground = fillLogoBackground;
306
+ _logoRaw = iconVectorized;
307
+ }
308
+
309
+ /// <summary>
310
+ /// Returns the raw logo's data
311
+ /// </summary>
312
+ /// <returns></returns>
313
+ public object GetRawLogo()
314
+ {
315
+ return _logoRaw;
316
+ }
317
+
318
+ /// <summary>
319
+ /// Returns the media type of the logo
320
+ /// </summary>
321
+ /// <returns></returns>
322
+ public MediaType GetMediaType()
323
+ {
324
+ return _mediaType;
185
325
}
186
326
327
+ /// <summary>
328
+ /// Returns the logo as data-uri
329
+ /// </summary>
330
+ /// <returns></returns>
187
331
public string GetDataUri()
188
332
{
189
- return $"data:{_mediaType};base64,{_logoData}";
333
+ return $"data:{_mediaType.GetStringValue() };base64,{_logoData}";
190
334
}
191
335
336
+ /// <summary>
337
+ /// Returns how much of the QR code should be covered by the logo (in percent)
338
+ /// </summary>
339
+ /// <returns></returns>
192
340
public int GetIconSizePercent()
193
341
{
194
342
return _iconSizePercent;
195
343
}
344
+
345
+ /// <summary>
346
+ /// Returns if the background of the logo should be cleaned (no QR modules will be rendered behind the logo)
347
+ /// </summary>
348
+ /// <returns></returns>
349
+ public bool FillLogoBackground()
350
+ {
351
+ return _fillLogoBackground;
352
+ }
353
+
354
+ /// <summary>
355
+ /// Media types for SvgLogos
356
+ /// </summary>
357
+ public enum MediaType : int
358
+ {
359
+ [StringValue("image/png")]
360
+ PNG = 0,
361
+ [StringValue("image/svg+xml")]
362
+ SVG = 1
363
+ }
196
364
}
197
365
}
198
366
0 commit comments