Skip to content

Use SkiaSharp to support SvgQRCode in .NET6 #462

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
121 changes: 66 additions & 55 deletions QRCoder/QRCoder.csproj
Original file line number Diff line number Diff line change
@@ -1,63 +1,74 @@
<Project Sdk="Microsoft.NET.Sdk">

<PropertyGroup>
<TargetFrameworks>net35;net40;netstandard1.3;netstandard2.0;net5.0;net5.0-windows;net6.0;net6.0-windows</TargetFrameworks>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<DefineConstants Condition="'$(TargetFramework)' == 'net5.0-windows'">$(DefineConstants);NET5_0_WINDOWS</DefineConstants>
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0-windows'">$(DefineConstants);NET6_0_WINDOWS</DefineConstants>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn />
</PropertyGroup>

<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageId>QRCoder</PackageId>
<Version>1.4.3</Version>
<Authors>Raffael Herrmann</Authors>
<PackageOwners>Raffael Herrmann</PackageOwners>
<AssemblyName>QRCoder</AssemblyName>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/codebude/QRCoder/</PackageProjectUrl>
<PackageIcon>nuget-icon.png</PackageIcon>
<PackageReadmeFile>nuget-readme.md</PackageReadmeFile>
<PackageTags>c# csharp qr qrcoder qrcode qr-generator qr-code-generator</PackageTags>
<RepositoryUrl>https://github.com/codebude/QRCoder.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Description>QRCoder is a simple library, written in C#.NET, which enables you to create QR codes.</Description>
</PropertyGroup>

<ItemGroup>
<None Include="Assets\nuget-icon.png" Pack="true" PackagePath="\" />
<None Include="Assets\nuget-readme.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net35' or '$(TargetFramework)' == 'net40' ">
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="WindowsBase" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' or '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net5.0-windows' ">
<PackageReference Include="System.Drawing.Common" Version="5.0.3" />
</ItemGroup>
<PropertyGroup>
<TargetFrameworks>net35;net40;netstandard1.3;netstandard2.0;net5.0;net5.0-windows;net6.0;net6.0-windows</TargetFrameworks>
<PackageRequireLicenseAcceptance>false</PackageRequireLicenseAcceptance>
<DefineConstants Condition="'$(TargetFramework)' == 'net5.0-windows'">$(DefineConstants);NET5_0_WINDOWS</DefineConstants>
<DefineConstants Condition="'$(TargetFramework)' == 'net6.0-windows'">$(DefineConstants);NET6_0_WINDOWS</DefineConstants>
<CheckEolTargetFramework>false</CheckEolTargetFramework>
<DisableImplicitNuGetFallbackFolder>true</DisableImplicitNuGetFallbackFolder>
</PropertyGroup>

<PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|AnyCPU'">
<NoWarn />
</PropertyGroup>

<PropertyGroup>
<GenerateAssemblyInfo>false</GenerateAssemblyInfo>
<PackageId>QRCoder</PackageId>
<Version>1.4.3</Version>
<Authors>Raffael Herrmann</Authors>
<PackageOwners>Raffael Herrmann</PackageOwners>
<AssemblyName>QRCoder</AssemblyName>
<PackageLicenseExpression>MIT</PackageLicenseExpression>
<PackageProjectUrl>https://github.com/codebude/QRCoder/</PackageProjectUrl>
<PackageIcon>nuget-icon.png</PackageIcon>
<PackageReadmeFile>nuget-readme.md</PackageReadmeFile>
<PackageTags>c# csharp qr qrcoder qrcode qr-generator qr-code-generator</PackageTags>
<RepositoryUrl>https://github.com/codebude/QRCoder.git</RepositoryUrl>
<RepositoryType>git</RepositoryType>
<Description>QRCoder is a simple library, written in C#.NET, which enables you to create QR codes.</Description>
</PropertyGroup>

<ItemGroup>
<None Include="Assets\nuget-icon.png" Pack="true" PackagePath="\" />
<None Include="Assets\nuget-readme.md" Pack="true" PackagePath="\" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'net35' or '$(TargetFramework)' == 'net40' ">
<Reference Include="PresentationCore" />
<Reference Include="PresentationFramework" />
<Reference Include="WindowsBase" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard1.3' or '$(TargetFramework)' == 'netstandard2.0' ">
<PackageReference Include="System.Text.Encoding.CodePages" Version="5.0.0" />
</ItemGroup>

<ItemGroup Condition=" '$(TargetFramework)' == 'netstandard2.0' or '$(TargetFramework)' == 'net5.0' or '$(TargetFramework)' == 'net5.0-windows' ">
<PackageReference Include="System.Drawing.Common" Version="5.0.3" />
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0-windows' ">
<PackageReference Include="System.Drawing.Common" Version="6.0.0" />
<PackageReference Include="SkiaSharp">
<Version>2.88.3</Version>
</PackageReference>
</ItemGroup>
<ItemGroup Condition=" '$(TargetFramework)' == 'net6.0' ">
<PackageReference Include="SkiaSharp">
<Version>2.88.3</Version>
</PackageReference>
</ItemGroup>

<PropertyGroup>
<FrameworkPathOverride Condition="'$(TargetFramework)' == 'net35'">$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client</FrameworkPathOverride>
<AutomaticallyUseReferenceAssemblyPackages Condition=" '$(TargetFramework)' == 'net35' ">false</AutomaticallyUseReferenceAssemblyPackages>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>QRCoderStrongName.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
</PropertyGroup>



<PropertyGroup>
<FrameworkPathOverride Condition="'$(TargetFramework)' == 'net35'">$(MSBuildProgramFiles32)\Reference Assemblies\Microsoft\Framework\.NETFramework\v3.5\Profile\Client</FrameworkPathOverride>
<AutomaticallyUseReferenceAssemblyPackages Condition=" '$(TargetFramework)' == 'net35' ">false</AutomaticallyUseReferenceAssemblyPackages>
<SignAssembly>true</SignAssembly>
<AssemblyOriginatorKeyFile>QRCoderStrongName.snk</AssemblyOriginatorKeyFile>
<DelaySign>false</DelaySign>
</PropertyGroup>

</Project>
384 changes: 384 additions & 0 deletions QRCoder/SvgQRCode_NET6.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,384 @@
#if NET6_0
using QRCoder.Extensions;
using SkiaSharp;
using System;
using System.Collections;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using static QRCoder.QRCodeGenerator;
using static QRCoder.SvgQRCode_NET6;

namespace QRCoder
{
/// <summary>
/// There class is basically the same as SvgQrCode but removed all System.Drawing
/// </summary>
public class SvgQRCode_NET6 : AbstractQRCode, IDisposable
{
/// <summary>
/// Constructor without params to be used in COM Objects connections
/// </summary>
public SvgQRCode_NET6() { }
public SvgQRCode_NET6(QRCodeData data) : base(data) { }
public string GetGraphic(int pixelsPerModule)
{
var viewBox = new SKSize(pixelsPerModule * this.QrCodeData.ModuleMatrix.Count, pixelsPerModule * this.QrCodeData.ModuleMatrix.Count);
return this.GetGraphic(viewBox, SKColors.Black, SKColors.White);
}

/// <summary>
/// Returns a QR code as SVG string with custom colors, optional quietzone and logo
/// </summary>
/// <param name="pixelsPerModule">The pixel size each b/w module is drawn</param>
/// <param name="darkColor">Color of the dark modules</param>
/// <param name="lightColor">Color of the light modules</param>
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
/// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
/// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
/// <returns>SVG as string</returns>
public string GetGraphic(int pixelsPerModule, SKColor darkColor, SKColor lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null)
{
var offset = drawQuietZones ? 0 : 4;
var edgeSize = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule);
var viewBox = new SKSize(edgeSize, edgeSize);
return this.GetGraphic(viewBox, darkColor.ToString(), lightColor.ToString(), drawQuietZones, sizingMode, logo);
}

/// <summary>
/// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo
/// </summary>
/// <param name="pixelsPerModule">The pixel size each b/w module is drawn</param>
/// <param name="darkColorHex">The color of the dark/black modules in hex (e.g. #000000) representation</param>
/// <param name="lightColorHex">The color of the light/white modules in hex (e.g. #ffffff) representation</param>
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
/// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
/// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
/// <returns>SVG as string</returns>
public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null)
{
var offset = drawQuietZones ? 0 : 4;
var edgeSize = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule);
var viewBox = new SKSize(edgeSize, edgeSize);
return this.GetGraphic(viewBox, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo);
}

/// <summary>
/// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo
/// </summary>
/// <param name="viewBox">The viewbox of the QR code graphic</param>
/// <param name="darkColorHex">The color of the dark/black modules in hex (e.g. #000000) representation</param>
/// <param name="lightColorHex">The color of the light/white modules in hex (e.g. #ffffff) representation</param>
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
/// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
/// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
/// <returns>SVG as string</returns>
public string GetGraphic(SKSize viewBox, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null)
{
int offset = drawQuietZones ? 0 : 4;
int drawableModulesCount = this.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)
{
BitArray bitArray = this.QrCodeData.ModuleMatrix[yi + offset];

int x0 = -1;
int xL = 0;
for (int xi = 0; xi < drawableModulesCount; xi += 1)
{
matrix[yi, xi] = 0;
if (bitArray[xi + offset] && (logo == null || !logo.FillLogoBackground() || !IsBlockedByLogo((xi + offset) * pixelsPerModule, (yi + offset) * pixelsPerModule, logoAttr, pixelsPerModule)))
{
if (x0 == -1)
{
x0 = xi;
}
xL += 1;
}
else
{
if (xL > 0)
{
matrix[yi, x0] = xL;
x0 = -1;
xL = 0;
}
}
}

if (xL > 0)
{
matrix[yi, x0] = xL;
}
}

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"">");
svgFile.AppendLine($@"<rect x=""0"" y=""0"" width=""{CleanSvgVal(qrSize)}"" height=""{CleanSvgVal(qrSize)}"" fill=""{lightColorHex}"" />");
for (int yi = 0; yi < drawableModulesCount; yi += 1)
{
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;
}
}

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

//Render logo, if set
if (logo != null)
{
if (!logo.IsEmbedded())
{
svgFile.AppendLine($@"<svg width=""100%"" height=""100%"" version=""1.1"" xmlns = ""http://www.w3.org/2000/svg"">");
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()}"" />");
svgFile.AppendLine(@"</svg>");
}
else
{
var rawLogo = (string)logo.GetRawLogo();
var svg = System.Xml.Linq.XDocument.Parse(rawLogo);
svg.Root.SetAttributeValue("x", CleanSvgVal(logoAttr.Value.X));
svg.Root.SetAttributeValue("y", CleanSvgVal(logoAttr.Value.Y));
svg.Root.SetAttributeValue("width", CleanSvgVal(logoAttr.Value.Width));
svg.Root.SetAttributeValue("height", CleanSvgVal(logoAttr.Value.Height));
svg.Root.SetAttributeValue("shape-rendering", "geometricPrecision");
svgFile.AppendLine(svg.ToString(System.Xml.Linq.SaveOptions.DisableFormatting).Replace("svg:", ""));
}
}

svgFile.Append(@"</svg>");
return svgFile.ToString();
}

/// <summary>
/// Returns a QR code as SVG string with optional quietzone and logo
/// </summary>
/// <param name="viewBox">The viewbox of the QR code graphic</param>
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
/// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
/// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
/// <returns>SVG as string</returns>
public string GetGraphic(SKSize viewBox, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null)
{
return this.GetGraphic(viewBox, SKColors.Black, SKColors.White, drawQuietZones, sizingMode, logo);
}

/// <summary>
/// Returns a QR code as SVG string with custom colors and optional quietzone and logo
/// </summary>
/// <param name="viewBox">The viewbox of the QR code graphic</param>
/// <param name="darkColor">Color of the dark modules</param>
/// <param name="lightColor">Color of the light modules</param>
/// <param name="drawQuietZones">If true a white border is drawn around the whole QR Code</param>
/// <param name="sizingMode">Defines if width/height or viewbox should be used for size definition</param>
/// <param name="logo">A (optional) logo to be rendered on the code (either Bitmap or SVG)</param>
/// <returns>SVG as string</returns>
public string GetGraphic(SKSize viewBox, SKColor darkColor, SKColor lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null)
{
return this.GetGraphic(viewBox, darkColor.ToString(), lightColor.ToString(), drawQuietZones, sizingMode, logo);
}

private bool IsBlockedByLogo(double x, double y, ImageAttributes? attr, double pixelPerModule)
{
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_NET6 logo, SKSize 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
//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);
}

/// <summary>
/// Mode of sizing attribution on svg root node
/// </summary>
public enum SizingMode
{
WidthHeightAttribute,
ViewBoxAttribute
}

/// <summary>
/// Represents a logo graphic that can be rendered on a SvgQRCode_NET6
/// </summary>
public class SvgLogo_NET6
{
private string _logoData;
private MediaType _mediaType;
private int _iconSizePercent;
private bool _fillLogoBackground;
private object _logoRaw;
private bool _isEmbedded;


/// <summary>
/// Create a logo object to be used in SvgQRCode_NET6 renderer
/// </summary>
/// <param name="iconRasterized">Logo to be rendered as Bitmap/rasterized graphic</param>
/// <param name="iconSizePercent">Degree of percentage coverage of the QR code by the logo</param>
/// <param name="fillLogoBackground">If true, the background behind the logo will be cleaned</param>
public SvgLogo_NET6(SKImage iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true)
{
_iconSizePercent = iconSizePercent;
using (var ms = new System.IO.MemoryStream())
{
iconRasterized.Encode(SKEncodedImageFormat.Png, 100).SaveTo(ms);
_logoData = Convert.ToBase64String(ms.GetBuffer(), Base64FormattingOptions.None);
}

_mediaType = MediaType.PNG;
_fillLogoBackground = fillLogoBackground;
_logoRaw = iconRasterized;
_isEmbedded = false;
}

/// <summary>
/// Create a logo object to be used in SvgQRCode_NET6 renderer
/// </summary>
/// <param name="iconVectorized">Logo to be rendered as SVG/vectorized graphic/string</param>
/// <param name="iconSizePercent">Degree of percentage coverage of the QR code by the logo</param>
/// <param name="fillLogoBackground">If true, the background behind the logo will be cleaned</param>
/// <param name="iconEmbedded">If true, the logo will embedded as native svg instead of embedding it as image-tag</param>
public SvgLogo_NET6(string iconVectorized, int iconSizePercent = 15, bool fillLogoBackground = true, bool iconEmbedded = true)
{
_iconSizePercent = iconSizePercent;
_logoData = Convert.ToBase64String(Encoding.UTF8.GetBytes(iconVectorized), Base64FormattingOptions.None);
_mediaType = MediaType.SVG;
_fillLogoBackground = fillLogoBackground;
_logoRaw = iconVectorized;
_isEmbedded = iconEmbedded;
}

/// <summary>
/// Returns the raw logo's data
/// </summary>
/// <returns></returns>
public object GetRawLogo()
{
return _logoRaw;
}

/// <summary>
/// Defines, if the logo shall be natively embedded.
/// true=native svg embedding, false=embedding via image-tag
/// </summary>
/// <returns></returns>
public bool IsEmbedded()
{
return _isEmbedded;
}

/// <summary>
/// Returns the media type of the logo
/// </summary>
/// <returns></returns>
public MediaType GetMediaType()
{
return _mediaType;
}

/// <summary>
/// Returns the logo as data-uri
/// </summary>
/// <returns></returns>
public string GetDataUri()
{
return $"data:{_mediaType.GetStringValue()};base64,{_logoData}";
}

/// <summary>
/// Returns how much of the QR code should be covered by the logo (in percent)
/// </summary>
/// <returns></returns>
public int GetIconSizePercent()
{
return _iconSizePercent;
}

/// <summary>
/// Returns if the background of the logo should be cleaned (no QR modules will be rendered behind the logo)
/// </summary>
/// <returns></returns>
public bool FillLogoBackground()
{
return _fillLogoBackground;
}

/// <summary>
/// Media types for SvgLogo_NET6s
/// </summary>
public enum MediaType : int
{
[StringValue("image/png")]
PNG = 0,
[StringValue("image/svg+xml")]
SVG = 1
}
}
}
public static class SvgQRCode_NET6Helper
{
public static string GetQRCode(string plainText, int pixelsPerModule, string darkColorHex, string lightColorHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo_NET6 logo = null)
{
using (var qrGenerator = new QRCodeGenerator())
using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion))
using (var qrCode = new SvgQRCode_NET6(qrCodeData))
return qrCode.GetGraphic(pixelsPerModule, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo);
}
}
}
#endif