diff --git a/vCardEditor/Libs/QRCoder/ASCIIQRCode.cs b/vCardEditor/Libs/QRCoder/ASCIIQRCode.cs new file mode 100644 index 0000000..bffe7cd --- /dev/null +++ b/vCardEditor/Libs/QRCoder/ASCIIQRCode.cs @@ -0,0 +1,78 @@ +using System; +using System.Collections.Generic; +using System.Text; +using static QRCoder.QRCodeGenerator; + +namespace QRCoder +{ + public class AsciiQRCode : AbstractQRCode, IDisposable + { + /// + /// Constructor without params to be used in COM Objects connections + /// + public AsciiQRCode() { } + + public AsciiQRCode(QRCodeData data) : base(data) { } + + + /// + /// Returns a strings that contains the resulting QR code as ASCII chars. + /// + /// Number of repeated darkColorString/whiteSpaceString per module. + /// String for use as dark color modules. In case of string make sure whiteSpaceString has the same length. + /// String for use as white modules (whitespace). In case of string make sure darkColorString has the same length. + /// End of line separator. (Default: \n) + /// + public string GetGraphic(int repeatPerModule, string darkColorString = "██", string whiteSpaceString = " ", bool drawQuietZones = true, string endOfLine = "\n") + { + return string.Join(endOfLine, GetLineByLineGraphic(repeatPerModule, darkColorString, whiteSpaceString, drawQuietZones)); + } + + + /// + /// Returns an array of strings that contains each line of the resulting QR code as ASCII chars. + /// + /// Number of repeated darkColorString/whiteSpaceString per module. + /// String for use as dark color modules. In case of string make sure whiteSpaceString has the same length. + /// String for use as white modules (whitespace). In case of string make sure darkColorString has the same length. + /// + public string[] GetLineByLineGraphic(int repeatPerModule, string darkColorString = "██", string whiteSpaceString = " ", bool drawQuietZones = true) + { + var qrCode = new List(); + //We need to adjust the repeatPerModule based on number of characters in darkColorString + //(we assume whiteSpaceString has the same number of characters) + //to keep the QR code as square as possible. + var quietZonesModifier = (drawQuietZones ? 0 : 8); + var quietZonesOffset = (int)(quietZonesModifier * 0.5); + var adjustmentValueForNumberOfCharacters = darkColorString.Length / 2 != 1 ? darkColorString.Length / 2 : 0; + var verticalNumberOfRepeats = repeatPerModule + adjustmentValueForNumberOfCharacters; + var sideLength = (QrCodeData.ModuleMatrix.Count - quietZonesModifier) * verticalNumberOfRepeats; + for (var y = 0; y < sideLength; y++) + { + var lineBuilder = new StringBuilder(); + for (var x = 0; x < QrCodeData.ModuleMatrix.Count - quietZonesModifier; x++) + { + var module = QrCodeData.ModuleMatrix[x + quietZonesOffset][((y + verticalNumberOfRepeats) / verticalNumberOfRepeats - 1)+quietZonesOffset]; + for (var i = 0; i < repeatPerModule; i++) + { + lineBuilder.Append(module ? darkColorString : whiteSpaceString); + } + } + qrCode.Add(lineBuilder.ToString()); + } + return qrCode.ToArray(); + } + } + + + public static class AsciiQRCodeHelper + { + public static string GetQRCode(string plainText, int pixelsPerModule, string darkColorString, string whiteSpaceString, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, string endOfLine = "\n", bool drawQuietZones = true) + { + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) + using (var qrCode = new AsciiQRCode(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColorString, whiteSpaceString, drawQuietZones, endOfLine); + } + } +} \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/AbstractQRCode.cs b/vCardEditor/Libs/QRCoder/AbstractQRCode.cs new file mode 100644 index 0000000..4c4b486 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/AbstractQRCode.cs @@ -0,0 +1,28 @@ +namespace QRCoder +{ + public abstract class AbstractQRCode + { + protected QRCodeData QrCodeData { get; set; } + + protected AbstractQRCode() { + } + + protected AbstractQRCode(QRCodeData data) { + this.QrCodeData = data; + } + + /// + /// Set a QRCodeData object that will be used to generate QR code. Used in COM Objects connections + /// + /// Need a QRCodeData object generated by QRCodeGenerator.CreateQrCode() + virtual public void SetQRCodeData(QRCodeData data) { + this.QrCodeData = data; + } + + public void Dispose() + { + this.QrCodeData?.Dispose(); + this.QrCodeData = null; + } + } +} \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/ArtQRCode.cs b/vCardEditor/Libs/QRCoder/ArtQRCode.cs new file mode 100644 index 0000000..c1964c9 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/ArtQRCode.cs @@ -0,0 +1,298 @@ +#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 || NET6_0_WINDOWS + +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using static QRCoder.ArtQRCode; +using static QRCoder.QRCodeGenerator; + +// pull request raised to extend library used. +namespace QRCoder +{ +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public class ArtQRCode : AbstractQRCode, IDisposable + { + /// + /// Constructor without params to be used in COM Objects connections + /// + public ArtQRCode() { } + + /// + /// Creates new ArtQrCode object + /// + /// QRCodeData generated by the QRCodeGenerator + public ArtQRCode(QRCodeData data) : base(data) { } + + /// + /// Renders an art-style QR code with dots as modules. (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true) + /// + /// Amount of px each dark/light module of the QR code shall take place in the final QR code image + /// QRCode graphic as bitmap + public Bitmap GetGraphic(int pixelsPerModule) + { + return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, Color.Transparent); + } + + /// + /// Renders an art-style QR code with dots as modules and a background image (With default settings: DarkColor=Black, LightColor=White, Background=Transparent, QuietZone=true) + /// + /// A bitmap object that will be used as background picture + /// QRCode graphic as bitmap + public Bitmap GetGraphic(Bitmap backgroundImage = null) + { + return this.GetGraphic(10, Color.Black, Color.White, Color.Transparent, backgroundImage: backgroundImage); + } + + /// + /// Renders an art-style QR code with dots as modules and various user settings + /// + /// Amount of px each dark/light module of the QR code shall take place in the final QR code image + /// Color of the dark modules + /// Color of the light modules + /// Color of the background + /// A bitmap object that will be used as background picture + /// Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be. + /// If true a white border is drawn around the whole QR Code + /// Style of the quiet zones + /// Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone + /// Optional image that should be used instead of the default finder patterns + /// QRCode graphic as bitmap + public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, Bitmap backgroundImage = null, double pixelSizeFactor = 0.8, + bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Dotted, + BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap finderPatternImage = null) + { + if (pixelSizeFactor > 1) + throw new Exception("The parameter pixelSize must be between 0 and 1. (0-100%)"); + int pixelSize = (int)Math.Min(pixelsPerModule, Math.Floor(pixelsPerModule / pixelSizeFactor)); + + var numModules = QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8); + var offset = (drawQuietZones ? 0 : 4); + var size = numModules * pixelsPerModule; + + var bitmap = new Bitmap(size, size); + + using (var graphics = Graphics.FromImage(bitmap)) + { + using (var lightBrush = new SolidBrush(lightColor)) + { + using (var darkBrush = new SolidBrush(darkColor)) + { + // make background transparent + using (var brush = new SolidBrush(backgroundColor)) + graphics.FillRectangle(brush, new Rectangle(0, 0, size, size)); + //Render background if set + if (backgroundImage != null) + { + if (backgroundImageStyle == BackgroundImageStyle.Fill) + graphics.DrawImage(Resize(backgroundImage, size), 0, 0); + else if (backgroundImageStyle == BackgroundImageStyle.DataAreaOnly) + { + var bgOffset = 4 - offset; + graphics.DrawImage(Resize(backgroundImage, size - (2 * bgOffset * pixelsPerModule)), 0 + (bgOffset * pixelsPerModule), (bgOffset * pixelsPerModule)); + } + } + + + var darkModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, darkBrush); + var lightModulePixel = MakeDotPixel(pixelsPerModule, pixelSize, lightBrush); + + for (var x = 0; x < numModules; x += 1) + { + for (var y = 0; y < numModules; y += 1) + { + var rectangleF = new Rectangle(x * pixelsPerModule, y * pixelsPerModule, pixelsPerModule, pixelsPerModule); + + var pixelIsDark = this.QrCodeData.ModuleMatrix[offset + y][offset + x]; + var solidBrush = pixelIsDark ? darkBrush : lightBrush; + var pixelImage = pixelIsDark ? darkModulePixel : lightModulePixel; + + if (!IsPartOfFinderPattern(x, y, numModules, offset)) + if (drawQuietZones && quietZoneRenderingStyle == QuietZoneStyle.Flat && IsPartOfQuietZone(x, y, numModules)) + graphics.FillRectangle(solidBrush, rectangleF); + else + graphics.DrawImage(pixelImage, rectangleF); + else if (finderPatternImage == null) + graphics.FillRectangle(solidBrush, rectangleF); + } + } + if (finderPatternImage != null) + { + var finderPatternSize = 7 * pixelsPerModule; + graphics.DrawImage(finderPatternImage, new Rectangle(0, 0, finderPatternSize, finderPatternSize)); + graphics.DrawImage(finderPatternImage, new Rectangle(size - finderPatternSize, 0, finderPatternSize, finderPatternSize)); + graphics.DrawImage(finderPatternImage, new Rectangle(0, size - finderPatternSize, finderPatternSize, finderPatternSize)); + } + graphics.Save(); + } + } + } + return bitmap; + } + + /// + /// If the pixelSize is bigger than the pixelsPerModule or may end up filling the Module making a traditional QR code. + /// + /// Pixels used per module rendered + /// Size of the dots + /// Color of the pixels + /// + private Bitmap MakeDotPixel(int pixelsPerModule, int pixelSize, SolidBrush brush) + { + // draw a dot + var bitmap = new Bitmap(pixelSize, pixelSize); + using (var graphics = Graphics.FromImage(bitmap)) + { + graphics.FillEllipse(brush, new Rectangle(0, 0, pixelSize, pixelSize)); + graphics.Save(); + } + + var pixelWidth = Math.Min(pixelsPerModule, pixelSize); + var margin = Math.Max((pixelsPerModule - pixelWidth) / 2, 0); + + // center the dot in the module and crop to stay the right size. + var cropped = new Bitmap(pixelsPerModule, pixelsPerModule); + using (var graphics = Graphics.FromImage(cropped)) + { + graphics.DrawImage(bitmap, new Rectangle(margin, margin, pixelWidth, pixelWidth), + new RectangleF(((float)pixelSize - pixelWidth) / 2, ((float)pixelSize - pixelWidth) / 2, pixelWidth, pixelWidth), + GraphicsUnit.Pixel); + graphics.Save(); + } + + return cropped; + } + + + /// + /// Checks if a given module(-position) is part of the quietzone of a QR code + /// + /// X position + /// Y position + /// Total number of modules per row + /// true, if position is part of quiet zone + private bool IsPartOfQuietZone(int x, int y, int numModules) + { + return + x < 4 || //left + y < 4 || //top + x > numModules - 5 || //right + y > numModules - 5; //bottom + } + + + /// + /// Checks if a given module(-position) is part of one of the three finder patterns of a QR code + /// + /// X position + /// Y position + /// Total number of modules per row + /// Offset in modules (usually depending on drawQuietZones parameter) + /// true, if position is part of any finder pattern + private bool IsPartOfFinderPattern(int x, int y, int numModules, int offset) + { + var cornerSize = 11 - offset; + var outerLimitLow = (numModules - cornerSize - 1); + var outerLimitHigh = outerLimitLow + 8; + var invertedOffset = 4 - offset; + return + (x >= invertedOffset && x < cornerSize && y >= invertedOffset && y < cornerSize) || //Top-left finder pattern + (x > outerLimitLow && x < outerLimitHigh && y >= invertedOffset && y < cornerSize) || //Top-right finder pattern + (x >= invertedOffset && x < cornerSize && y > outerLimitLow && y < outerLimitHigh); //Bottom-left finder pattern + } + + /// + /// Resize to a square bitmap, but maintain the aspect ratio by padding transparently. + /// + /// + /// + /// Resized image as bitmap + private Bitmap Resize(Bitmap image, int newSize) + { + if (image == null) return null; + + float scale = Math.Min((float)newSize / image.Width, (float)newSize / image.Height); + var scaledWidth = (int)(image.Width * scale); + var scaledHeight = (int)(image.Height * scale); + var offsetX = (newSize - scaledWidth) / 2; + var offsetY = (newSize - scaledHeight) / 2; + + var scaledImage = new Bitmap(image, new Size(scaledWidth, scaledHeight)); + + var bm = new Bitmap(newSize, newSize); + + using (Graphics graphics = Graphics.FromImage(bm)) + { + using (var brush = new SolidBrush(Color.Transparent)) + { + graphics.FillRectangle(brush, new Rectangle(0, 0, newSize, newSize)); + + graphics.InterpolationMode = InterpolationMode.High; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.SmoothingMode = SmoothingMode.AntiAlias; + + graphics.DrawImage(scaledImage, new Rectangle(offsetX, offsetY, scaledWidth, scaledHeight)); + } + } + return bm; + } + + /// + /// Defines how the quiet zones shall be rendered. + /// + public enum QuietZoneStyle + { + Dotted, + Flat + } + + /// + /// Defines how the background image (if set) shall be rendered. + /// + public enum BackgroundImageStyle + { + Fill, + DataAreaOnly + } + } + +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public static class ArtQRCodeHelper + { + /// + /// Helper function to create an ArtQRCode graphic with a single function call + /// + /// Text/payload to be encoded inside the QR code + /// Amount of px each dark/light module of the QR code shall take place in the final QR code image + /// Color of the dark modules + /// Color of the light modules + /// Color of the background + /// The level of error correction data + /// Shall the generator be forced to work in UTF-8 mode? + /// Should the byte-order-mark be used? + /// Which ECI mode shall be used? + /// Set fixed QR code target version. + /// A bitmap object that will be used as background picture + /// Value between 0.0 to 1.0 that defines how big the module dots are. The bigger the value, the less round the dots will be. + /// If true a white border is drawn around the whole QR Code + /// Style of the quiet zones + /// Style of the background image (if set). Fill=spanning complete graphic; DataAreaOnly=Don't paint background into quietzone + /// Optional image that should be used instead of the default finder patterns + /// QRCode graphic as bitmap + public static Bitmap GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, Color backgroundColor, ECCLevel eccLevel, bool forceUtf8 = false, + bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Bitmap backgroundImage = null, double pixelSizeFactor = 0.8, + bool drawQuietZones = true, QuietZoneStyle quietZoneRenderingStyle = QuietZoneStyle.Flat, + BackgroundImageStyle backgroundImageStyle = BackgroundImageStyle.DataAreaOnly, Bitmap finderPatternImage = null) + { + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) + using (var qrCode = new ArtQRCode(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColor, lightColor, backgroundColor, backgroundImage, pixelSizeFactor, drawQuietZones, quietZoneRenderingStyle, backgroundImageStyle, finderPatternImage); + } + } +} + +#endif \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/Assets/nuget-icon.png b/vCardEditor/Libs/QRCoder/Assets/nuget-icon.png new file mode 100644 index 0000000..118fe3a Binary files /dev/null and b/vCardEditor/Libs/QRCoder/Assets/nuget-icon.png differ diff --git a/vCardEditor/Libs/QRCoder/Assets/nuget-readme.md b/vCardEditor/Libs/QRCoder/Assets/nuget-readme.md new file mode 100644 index 0000000..d8fe3c5 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/Assets/nuget-readme.md @@ -0,0 +1,59 @@ +## About + +QRCoder is a simple library, written in C#.NET, which enables you to create QR codes. It hasn't any dependencies to other libraries and is available as .NET Framework and .NET Core PCL version on NuGet. + +*** + +## Documentation + +👉 *Your first place to go should be our wiki. Here you can find a detailed documentation of the QRCoder and its functions.* +* [**QRCode Wiki**](https://github.com/codebude/QRCoder/wiki) +* [Creator's blog (english)](http://en.code-bude.net/2013/10/17/qrcoder-an-open-source-qr-code-generator-implementation-in-csharp/) +* [Creator's blog (german)](http://code-bude.net/2013/10/17/qrcoder-eine-open-source-qr-code-implementierung-in-csharp/) + +### Release Notes +The release notes for the current and all past releases can be read here: [📄 Release Notes](https://github.com/codebude/QRCoder/wiki/Release-notes) + +## Usage / Quick start + +You only need four lines of code, to generate and view your first QR code. + +```csharp +using (QRCodeGenerator qrGenerator = new QRCodeGenerator()) +using (QRCodeData qrCodeData = qrGenerator.CreateQrCode("The text which should be encoded.", QRCodeGenerator.ECCLevel.Q)) +using (QRCode qrCode = new QRCode(qrCodeData)) +{ + Bitmap qrCodeImage = qrCode.GetGraphic(20); +} +``` + +### Optional parameters and overloads + +The GetGraphics-method has some more overloads. The first two enable you to set the color of the QR code graphic. One uses Color-class-types, the other HTML hex color notation. + +```csharp +//Set color by using Color-class types +Bitmap qrCodeImage = qrCode.GetGraphic(20, Color.DarkRed, Color.PaleGreen, true); + +//Set color by using HTML hex color notation +Bitmap qrCodeImage = qrCode.GetGraphic(20, "#000ff0", "#0ff000"); +``` + +The other overload enables you to render a logo/image in the center of the QR code. + +```csharp +Bitmap qrCodeImage = qrCode.GetGraphic(20, Color.Black, Color.White, (Bitmap)Bitmap.FromFile("C:\\myimage.png")); +``` + +There are a plenty of other options. So feel free to read more on that in our wiki: [Wiki: How to use QRCoder](https://github.com/codebude/QRCoder/wiki/How-to-use-QRCoder) + +## Help & Issues + +If you think you have found a bug or have new ideas or feature requests, then feel free to open a new issue: https://github.com/codebude/QRCoder/issues + +In case you have a question about using the library (and couldn't find an answer in our wiki), feel free to open a new question/discussion: https://github.com/codebude/QRCoder/discussions + + +## Legal information and credits + +QRCoder is a project by [Raffael Herrmann](https://raffaelherrmann.de) and was first released in 10/2013. It's licensed under the [MIT license](https://github.com/codebude/QRCoder/blob/master/LICENSE.txt). \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/Base64QRCode.cs b/vCardEditor/Libs/QRCoder/Base64QRCode.cs new file mode 100644 index 0000000..539af07 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/Base64QRCode.cs @@ -0,0 +1,115 @@ +#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 || NET6_0_WINDOWS +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using static QRCoder.Base64QRCode; +using static QRCoder.QRCodeGenerator; + +namespace QRCoder +{ +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public class Base64QRCode : AbstractQRCode, IDisposable + { + private QRCode qr; + + /// + /// Constructor without params to be used in COM Objects connections + /// + public Base64QRCode() { + qr = new QRCode(); + } + + public Base64QRCode(QRCodeData data) : base(data) { + qr = new QRCode(data); + } + + public override void SetQRCodeData(QRCodeData data) { + this.qr.SetQRCodeData(data); + } + + public string GetGraphic(int pixelsPerModule) + { + return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, true); + } + + + public string GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex, bool drawQuietZones = true, ImageType imgType = ImageType.Png) + { + return this.GetGraphic(pixelsPerModule, ColorTranslator.FromHtml(darkColorHtmlHex), ColorTranslator.FromHtml(lightColorHtmlHex), drawQuietZones, imgType); + } + + public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true, ImageType imgType = ImageType.Png) + { + var base64 = string.Empty; + using (Bitmap bmp = qr.GetGraphic(pixelsPerModule, darkColor, lightColor, drawQuietZones)) + { + base64 = BitmapToBase64(bmp, imgType); + } + return base64; + } + + public string GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Bitmap icon, int iconSizePercent = 15, int iconBorderWidth = 6, bool drawQuietZones = true, ImageType imgType = ImageType.Png) + { + var base64 = string.Empty; + using (Bitmap bmp = qr.GetGraphic(pixelsPerModule, darkColor, lightColor, icon, iconSizePercent, iconBorderWidth, drawQuietZones)) + { + base64 = BitmapToBase64(bmp, imgType); + } + return base64; + } + + + private string BitmapToBase64(Bitmap bmp, ImageType imgType) + { + var base64 = string.Empty; + ImageFormat iFormat; + switch (imgType) { + case ImageType.Png: + iFormat = ImageFormat.Png; + break; + case ImageType.Jpeg: + iFormat = ImageFormat.Jpeg; + break; + case ImageType.Gif: + iFormat = ImageFormat.Gif; + break; + default: + iFormat = ImageFormat.Png; + break; + } + using (MemoryStream memoryStream = new MemoryStream()) + { + bmp.Save(memoryStream, iFormat); + base64 = Convert.ToBase64String(memoryStream.ToArray(), Base64FormattingOptions.None); + } + return base64; + } + + public enum ImageType + { + Gif, + Jpeg, + Png + } + + } + +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public static class Base64QRCodeHelper + { + public static string GetQRCode(string plainText, int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true, ImageType imgType = ImageType.Png) + { + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) + using (var qrCode = new Base64QRCode(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColorHtmlHex, lightColorHtmlHex, drawQuietZones, imgType); + } + } +} + +#endif \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/BitmapByteQRCode.cs b/vCardEditor/Libs/QRCoder/BitmapByteQRCode.cs new file mode 100644 index 0000000..9a1ab67 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/BitmapByteQRCode.cs @@ -0,0 +1,125 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using static QRCoder.QRCodeGenerator; + +namespace QRCoder +{ + + // ReSharper disable once InconsistentNaming + public class BitmapByteQRCode : AbstractQRCode, IDisposable + { + /// + /// Constructor without params to be used in COM Objects connections + /// + public BitmapByteQRCode() { } + + public BitmapByteQRCode(QRCodeData data) : base(data) { } + + public byte[] GetGraphic(int pixelsPerModule) + { + return GetGraphic(pixelsPerModule, new byte[] { 0x00, 0x00, 0x00 }, new byte[] { 0xFF, 0xFF, 0xFF }); + } + + public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex) + { + return GetGraphic(pixelsPerModule, HexColorToByteArray(darkColorHtmlHex), HexColorToByteArray(lightColorHtmlHex)); + } + + public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgb, byte[] lightColorRgb) + { + var sideLength = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule; + + var moduleDark = darkColorRgb.Reverse(); + var moduleLight = lightColorRgb.Reverse(); + + List bmp = new List(); + + //header + bmp.AddRange(new byte[] { 0x42, 0x4D, 0x4C, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x1A, 0x00, 0x00, 0x00, 0x0C, 0x00, 0x00, 0x00 }); + + //width + bmp.AddRange(IntTo4Byte(sideLength)); + //height + bmp.AddRange(IntTo4Byte(sideLength)); + + //header end + bmp.AddRange(new byte[] { 0x01, 0x00, 0x18, 0x00 }); + + //draw qr code + for (var x = sideLength-1; x >= 0; x = x - pixelsPerModule) + { + for (int pm = 0; pm < pixelsPerModule; pm++) + { + for (var y = 0; y < sideLength; y = y + pixelsPerModule) + { + var module = + this.QrCodeData.ModuleMatrix[(x + pixelsPerModule)/pixelsPerModule - 1][(y + pixelsPerModule)/pixelsPerModule - 1]; + for (int i = 0; i < pixelsPerModule; i++) + { + bmp.AddRange(module ? moduleDark : moduleLight); + } + } + if (sideLength%4 != 0) + { + for (int i = 0; i < sideLength%4; i++) + { + bmp.Add(0x00); + } + } + } + } + + //finalize with terminator + bmp.AddRange(new byte[] { 0x00, 0x00 }); + + return bmp.ToArray(); + } + + private byte[] HexColorToByteArray(string colorString) + { + if (colorString.StartsWith("#")) + colorString = colorString.Substring(1); + byte[] byteColor = new byte[colorString.Length / 2]; + for (int i = 0; i < byteColor.Length; i++) + byteColor[i] = byte.Parse(colorString.Substring(i * 2, 2), System.Globalization.NumberStyles.HexNumber, System.Globalization.CultureInfo.InvariantCulture); + return byteColor; + } + + private byte[] IntTo4Byte(int inp) + { + byte[] bytes = new byte[2]; + unchecked + { + bytes[1] = (byte)(inp >> 8); + bytes[0] = (byte)(inp); + } + return bytes; + } + } + + + public static class BitmapByteQRCodeHelper + { + public static byte[] GetQRCode(string plainText, int pixelsPerModule, string darkColorHtmlHex, + string lightColorHtmlHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, + EciMode eciMode = EciMode.Default, int requestedVersion = -1) + { + using (var qrGenerator = new QRCodeGenerator()) + using ( + var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, + requestedVersion)) + using (var qrCode = new BitmapByteQRCode(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColorHtmlHex, lightColorHtmlHex); + } + + public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size) + { + using (var qrGen = new QRCodeGenerator()) + using (var qrCode = qrGen.CreateQrCode(txt, eccLevel)) + using (var qrBmp = new BitmapByteQRCode(qrCode)) + return qrBmp.GetGraphic(size); + + } + } +} diff --git a/vCardEditor/Libs/QRCoder/Exceptions/DataTooLongException.cs b/vCardEditor/Libs/QRCoder/Exceptions/DataTooLongException.cs new file mode 100644 index 0000000..bb7fbe2 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/Exceptions/DataTooLongException.cs @@ -0,0 +1,16 @@ +using System; + +namespace QRCoder.Exceptions +{ + public class DataTooLongException : Exception + { + public DataTooLongException(string eccLevel, string encodingMode, int maxSizeByte) : base( + $"The given payload exceeds the maximum size of the QR code standard. The maximum size allowed for the choosen paramters (ECC level={eccLevel}, EncodingMode={encodingMode}) is {maxSizeByte} byte." + ){} + + public DataTooLongException(string eccLevel, string encodingMode, int version, int maxSizeByte) : base( + $"The given payload exceeds the maximum size of the QR code standard. The maximum size allowed for the choosen paramters (ECC level={eccLevel}, EncodingMode={encodingMode}, FixedVersion={version}) is {maxSizeByte} byte." + ) + { } + } +} diff --git a/vCardEditor/Libs/QRCoder/Extensions/StringValueAttribute.cs b/vCardEditor/Libs/QRCoder/Extensions/StringValueAttribute.cs new file mode 100644 index 0000000..cc6e1bf --- /dev/null +++ b/vCardEditor/Libs/QRCoder/Extensions/StringValueAttribute.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Reflection; +using System.Text; + +namespace QRCoder.Extensions +{ + /// + /// Used to represent a string value for a value in an enum + /// + public class StringValueAttribute : Attribute + { + + #region Properties + + /// + /// Holds the alue in an enum + /// + public string StringValue { get; protected set; } + + #endregion + + /// + /// Init a StringValue Attribute + /// + /// + public StringValueAttribute(string value) + { + this.StringValue = value; + } + } + + public static class CustomExtensions + { + /// + /// Will get the string value for a given enum's value + /// + /// + /// + public static string GetStringValue(this Enum value) + { +#if NETSTANDARD1_3 + var fieldInfo = value.GetType().GetRuntimeField(value.ToString()); +#else + var fieldInfo = value.GetType().GetField(value.ToString()); +#endif + var attr = fieldInfo.GetCustomAttributes(typeof(StringValueAttribute), false) as StringValueAttribute[]; + return attr.Length > 0 ? attr[0].StringValue : null; + } + } +} diff --git a/vCardEditor/Libs/QRCoder/Framework4.0Methods/Stream4Methods.cs b/vCardEditor/Libs/QRCoder/Framework4.0Methods/Stream4Methods.cs new file mode 100644 index 0000000..6c58eb7 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/Framework4.0Methods/Stream4Methods.cs @@ -0,0 +1,20 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; + +namespace QRCoder.Framework4._0Methods +{ + class Stream4Methods + { + public static void CopyTo(System.IO.Stream input, System.IO.Stream output) + { + byte[] buffer = new byte[16 * 1024]; + int bytesRead; + while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0) + { + output.Write(buffer, 0, bytesRead); + } + } + } +} diff --git a/vCardEditor/Libs/QRCoder/Framework4.0Methods/String4Methods.cs b/vCardEditor/Libs/QRCoder/Framework4.0Methods/String4Methods.cs new file mode 100644 index 0000000..748c0d6 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/Framework4.0Methods/String4Methods.cs @@ -0,0 +1,48 @@ +using System; + +namespace QRCoder +{ + internal static class String40Methods + { + /// + /// The IsNullOrWhiteSpace method from Framework4.0 + /// + /// + /// true if the is null or white space; otherwise, false. + /// + public static bool IsNullOrWhiteSpace(String value) + { + if (value == null) return true; + + for (int i = 0; i < value.Length; i++) + { + if (!Char.IsWhiteSpace(value[i])) return false; + } + + return true; + } + + public static string ReverseString(string str) + { + char[] chars = str.ToCharArray(); + char[] result = new char[chars.Length]; + for (int i = 0, j = str.Length - 1; i < str.Length; i++, j--) + { + result[i] = chars[j]; + } + return new string(result); + } + + public static bool IsAllDigit(string str) + { + foreach (var c in str) + { + if (!char.IsDigit(c)) + { + return false; + } + } + return true; + } + } +} \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/PayloadGenerator.cs b/vCardEditor/Libs/QRCoder/PayloadGenerator.cs new file mode 100644 index 0000000..a315ff3 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/PayloadGenerator.cs @@ -0,0 +1,3124 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Globalization; +using System.Text; +using System.Text.RegularExpressions; +#if NETSTANDARD1_3 +using System.Reflection; +#endif + +namespace QRCoder +{ + public static class PayloadGenerator + { + public abstract class Payload + { + public virtual int Version { get { return -1; } } + public virtual QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } } + public virtual QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Default; } } + public abstract override string ToString(); + } + + public class WiFi : Payload + { + private readonly string ssid, password, authenticationMode; + private readonly bool isHiddenSsid; + + /// + /// Generates a WiFi payload. Scanned by a QR Code scanner app, the device will connect to the WiFi. + /// + /// SSID of the WiFi network + /// Password of the WiFi network + /// Authentification mode (WEP, WPA, WPA2) + /// Set flag, if the WiFi network hides its SSID + /// Set flag, if ssid/password is delivered as HEX string. Note: May not be supported on iOS devices. + public WiFi(string ssid, string password, Authentication authenticationMode, bool isHiddenSSID = false, bool escapeHexStrings = true) + { + this.ssid = EscapeInput(ssid); + this.ssid = escapeHexStrings && isHexStyle(this.ssid) ? "\"" + this.ssid + "\"" : this.ssid; + this.password = EscapeInput(password); + this.password = escapeHexStrings && isHexStyle(this.password) ? "\"" + this.password + "\"" : this.password; + this.authenticationMode = authenticationMode.ToString(); + this.isHiddenSsid = isHiddenSSID; + } + + public override string ToString() + { + return + $"WIFI:T:{this.authenticationMode};S:{this.ssid};P:{this.password};{(this.isHiddenSsid ? "H:true" : string.Empty)};"; + } + + public enum Authentication + { + WEP, + WPA, + nopass + } + } + + public class Mail : Payload + { + private readonly string mailReceiver, subject, message; + private readonly MailEncoding encoding; + + + /// + /// Creates an email payload with subject and message/text + /// + /// Receiver's email address + /// Subject line of the email + /// Message content of the email + /// Payload encoding type. Choose dependent on your QR Code scanner app. + public Mail(string mailReceiver = null, string subject = null, string message = null, MailEncoding encoding = MailEncoding.MAILTO) + { + this.mailReceiver = mailReceiver; + this.subject = subject; + this.message = message; + this.encoding = encoding; + } + + public override string ToString() + { + var returnVal = string.Empty; + switch (this.encoding) + { + case MailEncoding.MAILTO: + var parts = new List(); + if (!string.IsNullOrEmpty(this.subject)) + parts.Add("subject=" + Uri.EscapeDataString(this.subject)); + if (!string.IsNullOrEmpty(this.message)) + parts.Add("body=" + Uri.EscapeDataString(this.message)); + var queryString = parts.Any() ? $"?{string.Join("&", parts.ToArray())}" : ""; + returnVal = $"mailto:{this.mailReceiver}{queryString}"; + break; + case MailEncoding.MATMSG: + returnVal = $"MATMSG:TO:{this.mailReceiver};SUB:{EscapeInput(this.subject)};BODY:{EscapeInput(this.message)};;"; + break; + case MailEncoding.SMTP: + returnVal = $"SMTP:{this.mailReceiver}:{EscapeInput(this.subject, true)}:{EscapeInput(this.message, true)}"; + break; + } + return returnVal; + } + + public enum MailEncoding + { + MAILTO, + MATMSG, + SMTP + } + } + + public class SMS : Payload + { + private readonly string number, subject; + private readonly SMSEncoding encoding; + + /// + /// Creates a SMS payload without text + /// + /// Receiver phone number + /// Encoding type + public SMS(string number, SMSEncoding encoding = SMSEncoding.SMS) + { + this.number = number; + this.subject = string.Empty; + this.encoding = encoding; + } + + /// + /// Creates a SMS payload with text (subject) + /// + /// Receiver phone number + /// Text of the SMS + /// Encoding type + public SMS(string number, string subject, SMSEncoding encoding = SMSEncoding.SMS) + { + this.number = number; + this.subject = subject; + this.encoding = encoding; + } + + public override string ToString() + { + var returnVal = string.Empty; + switch (this.encoding) + { + case SMSEncoding.SMS: + var queryString = string.Empty; + if (!string.IsNullOrEmpty(this.subject)) + queryString = $"?body={Uri.EscapeDataString(this.subject)}"; + returnVal = $"sms:{this.number}{queryString}"; + break; + case SMSEncoding.SMS_iOS: + var queryStringiOS = string.Empty; + if (!string.IsNullOrEmpty(this.subject)) + queryStringiOS = $";body={Uri.EscapeDataString(this.subject)}"; + returnVal = $"sms:{this.number}{queryStringiOS}"; + break; + case SMSEncoding.SMSTO: + returnVal = $"SMSTO:{this.number}:{this.subject}"; + break; + } + return returnVal; + } + + public enum SMSEncoding + { + SMS, + SMSTO, + SMS_iOS + } + } + + public class MMS : Payload + { + private readonly string number, subject; + private readonly MMSEncoding encoding; + + /// + /// Creates a MMS payload without text + /// + /// Receiver phone number + /// Encoding type + public MMS(string number, MMSEncoding encoding = MMSEncoding.MMS) + { + this.number = number; + this.subject = string.Empty; + this.encoding = encoding; + } + + /// + /// Creates a MMS payload with text (subject) + /// + /// Receiver phone number + /// Text of the MMS + /// Encoding type + public MMS(string number, string subject, MMSEncoding encoding = MMSEncoding.MMS) + { + this.number = number; + this.subject = subject; + this.encoding = encoding; + } + + public override string ToString() + { + var returnVal = string.Empty; + switch (this.encoding) + { + case MMSEncoding.MMSTO: + var queryStringMmsTo = string.Empty; + if (!string.IsNullOrEmpty(this.subject)) + queryStringMmsTo = $"?subject={Uri.EscapeDataString(this.subject)}"; + returnVal = $"mmsto:{this.number}{queryStringMmsTo}"; + break; + case MMSEncoding.MMS: + var queryStringMms = string.Empty; + if (!string.IsNullOrEmpty(this.subject)) + queryStringMms = $"?body={Uri.EscapeDataString(this.subject)}"; + returnVal = $"mms:{this.number}{queryStringMms}"; + break; + } + return returnVal; + } + + public enum MMSEncoding + { + MMS, + MMSTO + } + } + + public class Geolocation : Payload + { + private readonly string latitude, longitude; + private readonly GeolocationEncoding encoding; + + /// + /// Generates a geo location payload. Supports raw location (GEO encoding) or Google Maps link (GoogleMaps encoding) + /// + /// Latitude with . as splitter + /// Longitude with . as splitter + /// Encoding type - GEO or GoogleMaps + public Geolocation(string latitude, string longitude, GeolocationEncoding encoding = GeolocationEncoding.GEO) + { + this.latitude = latitude.Replace(",","."); + this.longitude = longitude.Replace(",", "."); + this.encoding = encoding; + } + + public override string ToString() + { + switch (this.encoding) + { + case GeolocationEncoding.GEO: + return $"geo:{this.latitude},{this.longitude}"; + case GeolocationEncoding.GoogleMaps: + return $"http://maps.google.com/maps?q={this.latitude},{this.longitude}"; + default: + return "geo:"; + } + } + + public enum GeolocationEncoding + { + GEO, + GoogleMaps + } + } + + public class PhoneNumber : Payload + { + private readonly string number; + + /// + /// Generates a phone call payload + /// + /// Phonenumber of the receiver + public PhoneNumber(string number) + { + this.number = number; + } + + public override string ToString() + { + return $"tel:{this.number}"; + } + } + + public class SkypeCall : Payload + { + private readonly string skypeUsername; + + /// + /// Generates a Skype call payload + /// + /// Skype username which will be called + public SkypeCall(string skypeUsername) + { + this.skypeUsername = skypeUsername; + } + + public override string ToString() + { + return $"skype:{this.skypeUsername}?call"; + } + } + + public class Url : Payload + { + private readonly string url; + + /// + /// Generates a link. If not given, http/https protocol will be added. + /// + /// Link url target + public Url(string url) + { + this.url = url; + } + + public override string ToString() + { + return (!this.url.StartsWith("http") ? "http://" + this.url : this.url); + } + } + + + public class WhatsAppMessage : Payload + { + private readonly string number, message; + + /// + /// Let's you compose a WhatApp message and send it the receiver number. + /// + /// Receiver phone number where the is a full phone number in international format. + /// Omit any zeroes, brackets, or dashes when adding the phone number in international format. + /// Use: 1XXXXXXXXXX | Don't use: +001-(XXX)XXXXXXX + /// + /// The message + public WhatsAppMessage(string number, string message) + { + this.number = number; + this.message = message; + } + + /// + /// Let's you compose a WhatApp message. When scanned the user is asked to choose a contact who will receive the message. + /// + /// The message + public WhatsAppMessage(string message) + { + this.number = string.Empty; + this.message = message; + } + + public override string ToString() + { + var cleanedPhone = Regex.Replace(this.number, @"^[0+]+|[ ()-]", string.Empty); + return ($"https://wa.me/{cleanedPhone}?text={Uri.EscapeDataString(message)}"); + } + } + + + public class Bookmark : Payload + { + private readonly string url, title; + + /// + /// Generates a bookmark payload. Scanned by an QR Code reader, this one creates a browser bookmark. + /// + /// Url of the bookmark + /// Title of the bookmark + public Bookmark(string url, string title) + { + this.url = EscapeInput(url); + this.title = EscapeInput(title); + } + + public override string ToString() + { + return $"MEBKM:TITLE:{this.title};URL:{this.url};;"; + } + } + + public class ContactData : Payload + { + private readonly string firstname; + private readonly string lastname; + private readonly string nickname; + private readonly string org; + private readonly string orgTitle; + private readonly string phone; + private readonly string mobilePhone; + private readonly string workPhone; + private readonly string email; + private readonly DateTime? birthday; + private readonly string website; + private readonly string street; + private readonly string houseNumber; + private readonly string city; + private readonly string zipCode; + private readonly string stateRegion; + private readonly string country; + private readonly string note; + private readonly ContactOutputType outputType; + private readonly AddressOrder addressOrder; + + + /// + /// Generates a vCard or meCard contact dataset + /// + /// Payload output type + /// The firstname + /// The lastname + /// The displayname + /// Normal phone number + /// Mobile phone + /// Office phone number + /// E-Mail address + /// Birthday + /// Website / Homepage + /// Street + /// Housenumber + /// City + /// State or Region + /// Zip code + /// Country + /// The address order format to use + /// Memo text / notes + /// Organisation/Company + /// Organisation/Company Title + public ContactData(ContactOutputType outputType, string firstname, string lastname, string nickname = null, string phone = null, string mobilePhone = null, string workPhone = null, string email = null, DateTime? birthday = null, string website = null, string street = null, string houseNumber = null, string city = null, string zipCode = null, string country = null, string note = null, string stateRegion = null, AddressOrder addressOrder = AddressOrder.Default, string org = null, string orgTitle = null) + { + this.firstname = firstname; + this.lastname = lastname; + this.nickname = nickname; + this.org = org; + this.orgTitle = orgTitle; + this.phone = phone; + this.mobilePhone = mobilePhone; + this.workPhone = workPhone; + this.email = email; + this.birthday = birthday; + this.website = website; + this.street = street; + this.houseNumber = houseNumber; + this.city = city; + this.stateRegion = stateRegion; + this.zipCode = zipCode; + this.country = country; + this.addressOrder = addressOrder; + this.note = note; + this.outputType = outputType; + } + + public override string ToString() + { + string payload = string.Empty; + if (outputType == ContactOutputType.MeCard) + { + payload += "MECARD+\r\n"; + if (!string.IsNullOrEmpty(firstname) && !string.IsNullOrEmpty(lastname)) + payload += $"N:{lastname}, {firstname}\r\n"; + else if (!string.IsNullOrEmpty(firstname) || !string.IsNullOrEmpty(lastname)) + payload += $"N:{firstname}{lastname}\r\n"; + if (!string.IsNullOrEmpty(org)) + payload += $"ORG:{org}\r\n"; + if (!string.IsNullOrEmpty(orgTitle)) + payload += $"TITLE:{orgTitle}\r\n"; + if (!string.IsNullOrEmpty(phone)) + payload += $"TEL:{phone}\r\n"; + if (!string.IsNullOrEmpty(mobilePhone)) + payload += $"TEL:{mobilePhone}\r\n"; + if (!string.IsNullOrEmpty(workPhone)) + payload += $"TEL:{workPhone}\r\n"; + if (!string.IsNullOrEmpty(email)) + payload += $"EMAIL:{email}\r\n"; + if (!string.IsNullOrEmpty(note)) + payload += $"NOTE:{note}\r\n"; + if (birthday != null) + payload += $"BDAY:{((DateTime)birthday).ToString("yyyyMMdd")}\r\n"; + string addressString = string.Empty; + if(addressOrder == AddressOrder.Default) + { + addressString = $"ADR:,,{(!string.IsNullOrEmpty(street) ? street + " " : "")}{(!string.IsNullOrEmpty(houseNumber) ? houseNumber : "")},{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")},{(!string.IsNullOrEmpty(city) ? city : "")},{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")},{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; + } + else + { + addressString = $"ADR:,,{(!string.IsNullOrEmpty(houseNumber) ? houseNumber + " " : "")}{(!string.IsNullOrEmpty(street) ? street : "")},{(!string.IsNullOrEmpty(city) ? city : "")},{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")},{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")},{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; + } + payload += addressString; + if (!string.IsNullOrEmpty(website)) + payload += $"URL:{website}\r\n"; + if (!string.IsNullOrEmpty(nickname)) + payload += $"NICKNAME:{nickname}\r\n"; + payload = payload.Trim(new char[] { '\r', '\n' }); + } + else + { + var version = outputType.ToString().Substring(5); + if (version.Length > 1) + version = version.Insert(1, "."); + else + version += ".0"; + + payload += "BEGIN:VCARD\r\n"; + payload += $"VERSION:{version}\r\n"; + + payload += $"N:{(!string.IsNullOrEmpty(lastname) ? lastname : "")};{(!string.IsNullOrEmpty(firstname) ? firstname : "")};;;\r\n"; + payload += $"FN:{(!string.IsNullOrEmpty(firstname) ? firstname + " " : "")}{(!string.IsNullOrEmpty(lastname) ? lastname : "")}\r\n"; + if (!string.IsNullOrEmpty(org)) + { + payload += $"ORG:" + org + "\r\n"; + } + if (!string.IsNullOrEmpty(orgTitle)) + { + payload += $"TITLE:" + orgTitle + "\r\n"; + } + if (!string.IsNullOrEmpty(phone)) + { + payload += $"TEL;"; + if (outputType == ContactOutputType.VCard21) + payload += $"HOME;VOICE:{phone}"; + else if (outputType == ContactOutputType.VCard3) + payload += $"TYPE=HOME,VOICE:{phone}"; + else + payload += $"TYPE=home,voice;VALUE=uri:tel:{phone}"; + payload += "\r\n"; + } + + if (!string.IsNullOrEmpty(mobilePhone)) + { + payload += $"TEL;"; + if (outputType == ContactOutputType.VCard21) + payload += $"HOME;CELL:{mobilePhone}"; + else if (outputType == ContactOutputType.VCard3) + payload += $"TYPE=HOME,CELL:{mobilePhone}"; + else + payload += $"TYPE=home,cell;VALUE=uri:tel:{mobilePhone}"; + payload += "\r\n"; + } + + if (!string.IsNullOrEmpty(workPhone)) + { + payload += $"TEL;"; + if (outputType == ContactOutputType.VCard21) + payload += $"WORK;VOICE:{workPhone}"; + else if (outputType == ContactOutputType.VCard3) + payload += $"TYPE=WORK,VOICE:{workPhone}"; + else + payload += $"TYPE=work,voice;VALUE=uri:tel:{workPhone}"; + payload += "\r\n"; + } + + + payload += "ADR;"; + if (outputType == ContactOutputType.VCard21) + payload += "HOME;PREF:"; + else if (outputType == ContactOutputType.VCard3) + payload += "TYPE=HOME,PREF:"; + else + payload += "TYPE=home,pref:"; + string addressString = string.Empty; + if(addressOrder == AddressOrder.Default) + { + addressString = $";;{(!string.IsNullOrEmpty(street) ? street + " " : "")}{(!string.IsNullOrEmpty(houseNumber) ? houseNumber : "")};{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")};{(!string.IsNullOrEmpty(city) ? city : "")};{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")};{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; + } + else + { + addressString = $";;{(!string.IsNullOrEmpty(houseNumber) ? houseNumber + " " : "")}{(!string.IsNullOrEmpty(street) ? street : "")};{(!string.IsNullOrEmpty(city) ? city : "")};{(!string.IsNullOrEmpty(stateRegion) ? stateRegion : "")};{(!string.IsNullOrEmpty(zipCode) ? zipCode : "")};{(!string.IsNullOrEmpty(country) ? country : "")}\r\n"; + } + payload += addressString; + + if (birthday != null) + payload += $"BDAY:{((DateTime)birthday).ToString("yyyyMMdd")}\r\n"; + if (!string.IsNullOrEmpty(website)) + payload += $"URL:{website}\r\n"; + if (!string.IsNullOrEmpty(email)) + payload += $"EMAIL:{email}\r\n"; + if (!string.IsNullOrEmpty(note)) + payload += $"NOTE:{note}\r\n"; + if (outputType != ContactOutputType.VCard21 && !string.IsNullOrEmpty(nickname)) + payload += $"NICKNAME:{nickname}\r\n"; + + payload += "END:VCARD"; + } + + return payload; + } + + /// + /// Possible output types. Either vCard 2.1, vCard 3.0, vCard 4.0 or MeCard. + /// + public enum ContactOutputType + { + MeCard, + VCard21, + VCard3, + VCard4 + } + + + /// + /// define the address format + /// Default: European format, ([Street] [House Number] and [Postal Code] [City] + /// Reversed: North American and others format ([House Number] [Street] and [City] [Postal Code]) + /// + public enum AddressOrder + { + Default, + Reversed + } + } + + public class BitcoinLikeCryptoCurrencyAddress : Payload + { + private readonly BitcoinLikeCryptoCurrencyType currencyType; + private readonly string address, label, message; + private readonly double? amount; + + /// + /// Generates a Bitcoin like cryptocurrency payment payload. QR Codes with this payload can open a payment app. + /// + /// Bitcoin like cryptocurrency address of the payment receiver + /// Bitcoin like cryptocurrency address of the payment receiver + /// Amount of coins to transfer + /// Reference label + /// Referece text aka message + public BitcoinLikeCryptoCurrencyAddress(BitcoinLikeCryptoCurrencyType currencyType, string address, double? amount, string label = null, string message = null) + { + this.currencyType = currencyType; + this.address = address; + + if (!string.IsNullOrEmpty(label)) + { + this.label = Uri.EscapeDataString(label); + } + + if (!string.IsNullOrEmpty(message)) + { + this.message = Uri.EscapeDataString(message); + } + + this.amount = amount; + } + + public override string ToString() + { + string query = null; + + var queryValues = new KeyValuePair[]{ + new KeyValuePair(nameof(label), label), + new KeyValuePair(nameof(message), message), + new KeyValuePair(nameof(amount), amount.HasValue ? amount.Value.ToString("#.########", CultureInfo.InvariantCulture) : null) + }; + + if (queryValues.Any(keyPair => !string.IsNullOrEmpty(keyPair.Value))) + { + query = "?" + string.Join("&", queryValues + .Where(keyPair => !string.IsNullOrEmpty(keyPair.Value)) + .Select(keyPair => $"{keyPair.Key}={keyPair.Value}") + .ToArray()); + } + + return $"{Enum.GetName(typeof(BitcoinLikeCryptoCurrencyType), currencyType).ToLower()}:{address}{query}"; + } + + public enum BitcoinLikeCryptoCurrencyType + { + Bitcoin, + BitcoinCash, + Litecoin + } + } + + public class BitcoinAddress : BitcoinLikeCryptoCurrencyAddress + { + public BitcoinAddress(string address, double? amount, string label = null, string message = null) + : base(BitcoinLikeCryptoCurrencyType.Bitcoin, address, amount, label, message) { } + } + + public class BitcoinCashAddress : BitcoinLikeCryptoCurrencyAddress + { + public BitcoinCashAddress(string address, double? amount, string label = null, string message = null) + : base(BitcoinLikeCryptoCurrencyType.BitcoinCash, address, amount, label, message) { } + } + + public class LitecoinAddress : BitcoinLikeCryptoCurrencyAddress + { + public LitecoinAddress(string address, double? amount, string label = null, string message = null) + : base(BitcoinLikeCryptoCurrencyType.Litecoin, address, amount, label, message) { } + } + + public class SwissQrCode : Payload + { + //Keep in mind, that the ECC level has to be set to "M" when generating a SwissQrCode! + //SwissQrCode specification: + // - (de) https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-de.pdf + // - (en) https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf + //Changes between version 1.0 and 2.0: https://www.paymentstandards.ch/dam/downloads/change-documentation-qrr-de.pdf + + private readonly string br = "\r\n"; + private readonly string alternativeProcedure1, alternativeProcedure2; + private readonly Iban iban; + private readonly decimal? amount; + private readonly Contact creditor, ultimateCreditor, debitor; + private readonly Currency currency; + private readonly DateTime? requestedDateOfPayment; + private readonly Reference reference; + private readonly AdditionalInformation additionalInformation; + + /// + /// Generates the payload for a SwissQrCode v2.0. (Don't forget to use ECC-Level=M, EncodingMode=UTF-8 and to set the Swiss flag icon to the final QR code.) + /// + /// IBAN object + /// Currency (either EUR or CHF) + /// Creditor (payee) information + /// Reference information + /// Debitor (payer) information + /// Amount + /// Requested date of debitor's payment + /// Ultimate creditor information (use only in consultation with your bank - for future use only!) + /// Optional command for alternative processing mode - line 1 + /// Optional command for alternative processing mode - line 2 + public SwissQrCode(Iban iban, Currency currency, Contact creditor, Reference reference, AdditionalInformation additionalInformation = null, Contact debitor = null, decimal? amount = null, DateTime? requestedDateOfPayment = null, Contact ultimateCreditor = null, string alternativeProcedure1 = null, string alternativeProcedure2 = null) + { + this.iban = iban; + + this.creditor = creditor; + this.ultimateCreditor = ultimateCreditor; + + this.additionalInformation = additionalInformation != null ? additionalInformation : new AdditionalInformation(); + + if (amount != null && amount.ToString().Length > 12) + throw new SwissQrCodeException("Amount (including decimals) must be shorter than 13 places."); + this.amount = amount; + + this.currency = currency; + this.requestedDateOfPayment = requestedDateOfPayment; + this.debitor = debitor; + + if (iban.IsQrIban && reference.RefType != Reference.ReferenceType.QRR) + throw new SwissQrCodeException("If QR-IBAN is used, you have to choose \"QRR\" as reference type!"); + if (!iban.IsQrIban && reference.RefType == Reference.ReferenceType.QRR) + throw new SwissQrCodeException("If non QR-IBAN is used, you have to choose either \"SCOR\" or \"NON\" as reference type!"); + this.reference = reference; + + if (alternativeProcedure1 != null && alternativeProcedure1.Length > 100) + throw new SwissQrCodeException("Alternative procedure information block 1 must be shorter than 101 chars."); + this.alternativeProcedure1 = alternativeProcedure1; + if (alternativeProcedure2 != null && alternativeProcedure2.Length > 100) + throw new SwissQrCodeException("Alternative procedure information block 2 must be shorter than 101 chars."); + this.alternativeProcedure2 = alternativeProcedure2; + } + + public class AdditionalInformation + { + private readonly string unstructuredMessage, billInformation, trailer; + + /// + /// Creates an additional information object. Both parameters are optional and must be shorter than 141 chars in combination. + /// + /// Unstructured text message + /// Bill information + public AdditionalInformation(string unstructuredMessage = null, string billInformation = null) + { + if (((unstructuredMessage != null ? unstructuredMessage.Length : 0) + (billInformation != null ? billInformation.Length : 0)) > 140) + throw new SwissQrCodeAdditionalInformationException("Unstructured message and bill information must be shorter than 141 chars in total/combined."); + this.unstructuredMessage = unstructuredMessage; + this.billInformation = billInformation; + this.trailer = "EPD"; + } + + public string UnstructureMessage + { + get { return !string.IsNullOrEmpty(unstructuredMessage) ? unstructuredMessage.Replace("\n", "") : null; } + } + + public string BillInformation + { + get { return !string.IsNullOrEmpty(billInformation) ? billInformation.Replace("\n", "") : null; } + } + + public string Trailer + { + get { return trailer; } + } + + + public class SwissQrCodeAdditionalInformationException : Exception + { + public SwissQrCodeAdditionalInformationException() + { + } + + public SwissQrCodeAdditionalInformationException(string message) + : base(message) + { + } + + public SwissQrCodeAdditionalInformationException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class Reference + { + private readonly ReferenceType referenceType; + private readonly string reference; + private readonly ReferenceTextType? referenceTextType; + + /// + /// Creates a reference object which must be passed to the SwissQrCode instance + /// + /// Type of the reference (QRR, SCOR or NON) + /// Reference text + /// Type of the reference text (QR-reference or Creditor Reference) + public Reference(ReferenceType referenceType, string reference = null, ReferenceTextType? referenceTextType = null) + { + this.referenceType = referenceType; + this.referenceTextType = referenceTextType; + + if (referenceType == ReferenceType.NON && reference != null) + throw new SwissQrCodeReferenceException("Reference is only allowed when referenceType not equals \"NON\""); + if (referenceType != ReferenceType.NON && reference != null && referenceTextType == null) + throw new SwissQrCodeReferenceException("You have to set an ReferenceTextType when using the reference text."); + if (referenceTextType == ReferenceTextType.QrReference && reference != null && (reference.Length > 27)) + throw new SwissQrCodeReferenceException("QR-references have to be shorter than 28 chars."); + if (referenceTextType == ReferenceTextType.QrReference && reference != null && !Regex.IsMatch(reference, @"^[0-9]+$")) + throw new SwissQrCodeReferenceException("QR-reference must exist out of digits only."); + if (referenceTextType == ReferenceTextType.QrReference && reference != null && !ChecksumMod10(reference)) + throw new SwissQrCodeReferenceException("QR-references is invalid. Checksum error."); + if (referenceTextType == ReferenceTextType.CreditorReferenceIso11649 && reference != null && (reference.Length > 25)) + throw new SwissQrCodeReferenceException("Creditor references (ISO 11649) have to be shorter than 26 chars."); + + this.reference = reference; + } + + public ReferenceType RefType { + get { return referenceType; } + } + + public string ReferenceText + { + get { return !string.IsNullOrEmpty(reference) ? reference.Replace("\n", "") : null; } + } + + /// + /// Reference type. When using a QR-IBAN you have to use either "QRR" or "SCOR" + /// + public enum ReferenceType + { + QRR, + SCOR, + NON + } + + public enum ReferenceTextType + { + QrReference, + CreditorReferenceIso11649 + } + + public class SwissQrCodeReferenceException : Exception + { + public SwissQrCodeReferenceException() + { + } + + public SwissQrCodeReferenceException(string message) + : base(message) + { + } + + public SwissQrCodeReferenceException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class Iban + { + private string iban; + private IbanType ibanType; + + /// + /// IBAN object with type information + /// + /// IBAN + /// Type of IBAN (normal or QR-IBAN) + public Iban(string iban, IbanType ibanType) + { + if (ibanType == IbanType.Iban && !IsValidIban(iban)) + throw new SwissQrCodeIbanException("The IBAN entered isn't valid."); + if (ibanType == IbanType.QrIban && !IsValidQRIban(iban)) + throw new SwissQrCodeIbanException("The QR-IBAN entered isn't valid."); + if (!iban.StartsWith("CH") && !iban.StartsWith("LI")) + throw new SwissQrCodeIbanException("The IBAN must start with \"CH\" or \"LI\"."); + this.iban = iban; + this.ibanType = ibanType; + } + + public bool IsQrIban + { + get { return ibanType == IbanType.QrIban; } + } + + public override string ToString() + { + return iban.Replace("-", "").Replace("\n", "").Replace(" ",""); + } + + public enum IbanType + { + Iban, + QrIban + } + + public class SwissQrCodeIbanException : Exception + { + public SwissQrCodeIbanException() + { + } + + public SwissQrCodeIbanException(string message) + : base(message) + { + } + + public SwissQrCodeIbanException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class Contact + { + private static readonly HashSet twoLetterCodes = ValidTwoLetterCodes(); + private string br = "\r\n"; + private string name, streetOrAddressline1, houseNumberOrAddressline2, zipCode, city, country; + private AddressType adrType; + + /// + /// Contact type. Can be used for payee, ultimate payee, etc. with address in structured mode (S). + /// + /// Last name or company (optional first name) + /// Zip-/Postcode + /// City name + /// Two-letter country code as defined in ISO 3166-1 + /// Streetname without house number + /// House number + [Obsolete("This constructor is deprecated. Use WithStructuredAddress instead.")] + public Contact(string name, string zipCode, string city, string country, string street = null, string houseNumber = null) : this (name, zipCode, city, country, street, houseNumber, AddressType.StructuredAddress) + { + } + + + /// + /// Contact type. Can be used for payee, ultimate payee, etc. with address in combined mode (K). + /// + /// Last name or company (optional first name) + /// Two-letter country code as defined in ISO 3166-1 + /// Adress line 1 + /// Adress line 2 + [Obsolete("This constructor is deprecated. Use WithCombinedAddress instead.")] + public Contact(string name, string country, string addressLine1, string addressLine2) : this(name, null, null, country, addressLine1, addressLine2, AddressType.CombinedAddress) + { + } + + public static Contact WithStructuredAddress(string name, string zipCode, string city, string country, string street = null, string houseNumber = null) + { + return new Contact(name, zipCode, city, country, street, houseNumber, AddressType.StructuredAddress); + } + + public static Contact WithCombinedAddress(string name, string country, string addressLine1, string addressLine2) + { + return new Contact(name, null, null, country, addressLine1, addressLine2, AddressType.CombinedAddress); + } + + + private Contact(string name, string zipCode, string city, string country, string streetOrAddressline1, string houseNumberOrAddressline2, AddressType addressType) + { + //Pattern extracted from https://qr-validation.iso-payments.ch as explained in https://github.com/codebude/QRCoder/issues/97 + var charsetPattern = @"^([a-zA-Z0-9\.,;:'\ \+\-/\(\)?\*\[\]\{\}\\`´~ ]|[!""#%&<>÷=@_$£]|[àáâäçèéêëìíîïñòóôöùúûüýßÀÁÂÄÇÈÉÊËÌÍÎÏÒÓÔÖÙÚÛÜÑ])*$"; + + this.adrType = addressType; + + if (string.IsNullOrEmpty(name)) + throw new SwissQrCodeContactException("Name must not be empty."); + if (name.Length > 70) + throw new SwissQrCodeContactException("Name must be shorter than 71 chars."); + if (!Regex.IsMatch(name, charsetPattern)) + throw new SwissQrCodeContactException($"Name must match the following pattern as defined in pain.001: {charsetPattern}"); + this.name = name; + + if (AddressType.StructuredAddress == this.adrType) + { + if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70)) + throw new SwissQrCodeContactException("Street must be shorter than 71 chars."); + if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, charsetPattern)) + throw new SwissQrCodeContactException($"Street must match the following pattern as defined in pain.001: {charsetPattern}"); + this.streetOrAddressline1 = streetOrAddressline1; + + if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && houseNumberOrAddressline2.Length > 16) + throw new SwissQrCodeContactException("House number must be shorter than 17 chars."); + this.houseNumberOrAddressline2 = houseNumberOrAddressline2; + } + else + { + if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70)) + throw new SwissQrCodeContactException("Address line 1 must be shorter than 71 chars."); + if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, charsetPattern)) + throw new SwissQrCodeContactException($"Address line 1 must match the following pattern as defined in pain.001: {charsetPattern}"); + this.streetOrAddressline1 = streetOrAddressline1; + + if (string.IsNullOrEmpty(houseNumberOrAddressline2)) + throw new SwissQrCodeContactException("Address line 2 must be provided for combined addresses (address line-based addresses)."); + if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && (houseNumberOrAddressline2.Length > 70)) + throw new SwissQrCodeContactException("Address line 2 must be shorter than 71 chars."); + if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && !Regex.IsMatch(houseNumberOrAddressline2, charsetPattern)) + throw new SwissQrCodeContactException($"Address line 2 must match the following pattern as defined in pain.001: {charsetPattern}"); + this.houseNumberOrAddressline2 = houseNumberOrAddressline2; + } + + if (AddressType.StructuredAddress == this.adrType) { + if (string.IsNullOrEmpty(zipCode)) + throw new SwissQrCodeContactException("Zip code must not be empty."); + if (zipCode.Length > 16) + throw new SwissQrCodeContactException("Zip code must be shorter than 17 chars."); + if (!Regex.IsMatch(zipCode, charsetPattern)) + throw new SwissQrCodeContactException($"Zip code must match the following pattern as defined in pain.001: {charsetPattern}"); + this.zipCode = zipCode; + + if (string.IsNullOrEmpty(city)) + throw new SwissQrCodeContactException("City must not be empty."); + if (city.Length > 35) + throw new SwissQrCodeContactException("City name must be shorter than 36 chars."); + if (!Regex.IsMatch(city, charsetPattern)) + throw new SwissQrCodeContactException($"City name must match the following pattern as defined in pain.001: {charsetPattern}"); + this.city = city; + } + else + { + this.zipCode = this.city = string.Empty; + } + + if (!IsValidTwoLetterCode(country)) + throw new SwissQrCodeContactException("Country must be a valid \"two letter\" country code as defined by ISO 3166-1, but it isn't."); + + this.country = country; + } + + private static bool IsValidTwoLetterCode(string code) => twoLetterCodes.Contains(code); + + private static HashSet ValidTwoLetterCodes() + { + string[] codes = new string[]{ "AF", "AL", "DZ", "AS", "AD", "AO", "AI", "AQ", "AG", "AR", "AM", "AW", "AU", "AT", "AZ", "BS", "BH", "BD", "BB", "BY", "BE", "BZ", "BJ", "BM", "BT", "BO", "BQ", "BA", "BW", "BV", "BR", "IO", "BN", "BG", "BF", "BI", "CV", "KH", "CM", "CA", "KY", "CF", "TD", "CL", "CN", "CX", "CC", "CO", "KM", "CG", "CD", "CK", "CR", "CI", "HR", "CU", "CW", "CY", "CZ", "DK", "DJ", "DM", "DO", "EC", "EG", "SV", "GQ", "ER", "EE", "SZ", "ET", "FK", "FO", "FJ", "FI", "FR", "GF", "PF", "TF", "GA", "GM", "GE", "DE", "GH", "GI", "GR", "GL", "GD", "GP", "GU", "GT", "GG", "GN", "GW", "GY", "HT", "HM", "VA", "HN", "HK", "HU", "IS", "IN", "ID", "IR", "IQ", "IE", "IM", "IL", "IT", "JM", "JP", "JE", "JO", "KZ", "KE", "KI", "KP", "KR", "KW", "KG", "LA", "LV", "LB", "LS", "LR", "LY", "LI", "LT", "LU", "MO", "MG", "MW", "MY", "MV", "ML", "MT", "MH", "MQ", "MR", "MU", "YT", "MX", "FM", "MD", "MC", "MN", "ME", "MS", "MA", "MZ", "MM", "NA", "NR", "NP", "NL", "NC", "NZ", "NI", "NE", "NG", "NU", "NF", "MP", "MK", "NO", "OM", "PK", "PW", "PS", "PA", "PG", "PY", "PE", "PH", "PN", "PL", "PT", "PR", "QA", "RE", "RO", "RU", "RW", "BL", "SH", "KN", "LC", "MF", "PM", "VC", "WS", "SM", "ST", "SA", "SN", "RS", "SC", "SL", "SG", "SX", "SK", "SI", "SB", "SO", "ZA", "GS", "SS", "ES", "LK", "SD", "SR", "SJ", "SE", "CH", "SY", "TW", "TJ", "TZ", "TH", "TL", "TG", "TK", "TO", "TT", "TN", "TR", "TM", "TC", "TV", "UG", "UA", "AE", "GB", "US", "UM", "UY", "UZ", "VU", "VE", "VN", "VG", "VI", "WF", "EH", "YE", "ZM", "ZW", "AX" }; + return new HashSet(codes, StringComparer.OrdinalIgnoreCase); + } + + public override string ToString() + { + string contactData = $"{(AddressType.StructuredAddress == adrType ? "S" : "K")}{br}"; //AdrTp + contactData += name.Replace("\n", "") + br; //Name + contactData += (!string.IsNullOrEmpty(streetOrAddressline1) ? streetOrAddressline1.Replace("\n","") : string.Empty) + br; //StrtNmOrAdrLine1 + contactData += (!string.IsNullOrEmpty(houseNumberOrAddressline2) ? houseNumberOrAddressline2.Replace("\n", "") : string.Empty) + br; //BldgNbOrAdrLine2 + contactData += zipCode.Replace("\n", "") + br; //PstCd + contactData += city.Replace("\n", "") + br; //TwnNm + contactData += country + br; //Ctry + return contactData; + } + + public enum AddressType + { + StructuredAddress, + CombinedAddress + } + + public class SwissQrCodeContactException : Exception + { + public SwissQrCodeContactException() + { + } + + public SwissQrCodeContactException(string message) + : base(message) + { + } + + public SwissQrCodeContactException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public override string ToString() + { + //Header "logical" element + var SwissQrCodePayload = "SPC" + br; //QRType + SwissQrCodePayload += "0200" + br; //Version + SwissQrCodePayload += "1" + br; //Coding + + //CdtrInf "logical" element + SwissQrCodePayload += iban.ToString() + br; //IBAN + + + //Cdtr "logical" element + SwissQrCodePayload += creditor.ToString(); + + //UltmtCdtr "logical" element + //Since version 2.0 ultimate creditor was marked as "for future use" and has to be delivered empty in any case! + SwissQrCodePayload += string.Concat(Enumerable.Repeat(br, 7).ToArray()); + + //CcyAmtDate "logical" element + //Amoutn has to use . as decimal seperator in any case. See https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf page 27. + SwissQrCodePayload += (amount != null ? $"{amount:0.00}".Replace(",", ".") : string.Empty) + br; //Amt + SwissQrCodePayload += currency + br; //Ccy + //Removed in S-QR version 2.0 + //SwissQrCodePayload += (requestedDateOfPayment != null ? ((DateTime)requestedDateOfPayment).ToString("yyyy-MM-dd") : string.Empty) + br; //ReqdExctnDt + + //UltmtDbtr "logical" element + if (debitor != null) + SwissQrCodePayload += debitor.ToString(); + else + SwissQrCodePayload += string.Concat(Enumerable.Repeat(br, 7).ToArray()); + + + //RmtInf "logical" element + SwissQrCodePayload += reference.RefType.ToString() + br; //Tp + SwissQrCodePayload += (!string.IsNullOrEmpty(reference.ReferenceText) ? reference.ReferenceText : string.Empty) + br; //Ref + + + //AddInf "logical" element + SwissQrCodePayload += (!string.IsNullOrEmpty(additionalInformation.UnstructureMessage) ? additionalInformation.UnstructureMessage : string.Empty) + br; //Ustrd + SwissQrCodePayload += additionalInformation.Trailer + br; //Trailer + SwissQrCodePayload += (!string.IsNullOrEmpty(additionalInformation.BillInformation) ? additionalInformation.BillInformation : string.Empty) + br; //StrdBkgInf + + //AltPmtInf "logical" element + if (!string.IsNullOrEmpty(alternativeProcedure1)) + SwissQrCodePayload += alternativeProcedure1.Replace("\n", "") + br; //AltPmt + if (!string.IsNullOrEmpty(alternativeProcedure2)) + SwissQrCodePayload += alternativeProcedure2.Replace("\n", "") + br; //AltPmt + + //S-QR specification 2.0, chapter 4.2.3 + if (SwissQrCodePayload.EndsWith(br)) + SwissQrCodePayload = SwissQrCodePayload.Remove(SwissQrCodePayload.Length - br.Length); + + return SwissQrCodePayload; + } + + + + + /// + /// ISO 4217 currency codes + /// + public enum Currency + { + CHF = 756, + EUR = 978 + } + + public class SwissQrCodeException : Exception + { + public SwissQrCodeException() + { + } + + public SwissQrCodeException(string message) + : base(message) + { + } + + public SwissQrCodeException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class Girocode : Payload + { + //Keep in mind, that the ECC level has to be set to "M" when generating a Girocode! + //Girocode specification: http://www.europeanpaymentscouncil.eu/index.cfm/knowledge-bank/epc-documents/quick-response-code-guidelines-to-enable-data-capture-for-the-initiation-of-a-sepa-credit-transfer/epc069-12-quick-response-code-guidelines-to-enable-data-capture-for-the-initiation-of-a-sepa-credit-transfer1/ + + private string br = "\n"; + private readonly string iban, bic, name, purposeOfCreditTransfer, remittanceInformation, messageToGirocodeUser; + private readonly decimal amount; + private readonly GirocodeVersion version; + private readonly GirocodeEncoding encoding; + private readonly TypeOfRemittance typeOfRemittance; + + + /// + /// Generates the payload for a Girocode (QR-Code with credit transfer information). + /// Attention: When using Girocode payload, QR code must be generated with ECC level M! + /// + /// Account number of the Beneficiary. Only IBAN is allowed. + /// BIC of the Beneficiary Bank. + /// Name of the Beneficiary. + /// Amount of the Credit Transfer in Euro. + /// (Amount must be more than 0.01 and less than 999999999.99) + /// Remittance Information (Purpose-/reference text). (optional) + /// Type of remittance information. Either structured (e.g. ISO 11649 RF Creditor Reference) and max. 35 chars or unstructured and max. 140 chars. + /// Purpose of the Credit Transfer (optional) + /// Beneficiary to originator information. (optional) + /// Girocode version. Either 001 or 002. Default: 001. + /// Encoding of the Girocode payload. Default: ISO-8859-1 + public Girocode(string iban, string bic, string name, decimal amount, string remittanceInformation = "", TypeOfRemittance typeOfRemittance = TypeOfRemittance.Unstructured, string purposeOfCreditTransfer = "", string messageToGirocodeUser = "", GirocodeVersion version = GirocodeVersion.Version1, GirocodeEncoding encoding = GirocodeEncoding.ISO_8859_1) + { + this.version = version; + this.encoding = encoding; + if (!IsValidIban(iban)) + throw new GirocodeException("The IBAN entered isn't valid."); + this.iban = iban.Replace(" ","").ToUpper(); + if (!IsValidBic(bic)) + throw new GirocodeException("The BIC entered isn't valid."); + this.bic = bic.Replace(" ", "").ToUpper(); + if (name.Length > 70) + throw new GirocodeException("(Payee-)Name must be shorter than 71 chars."); + this.name = name; + if (amount.ToString().Replace(",", ".").Contains(".") && amount.ToString().Replace(",",".").Split('.')[1].TrimEnd('0').Length > 2) + throw new GirocodeException("Amount must have less than 3 digits after decimal point."); + if (amount < 0.01m || amount > 999999999.99m) + throw new GirocodeException("Amount has to at least 0.01 and must be smaller or equal to 999999999.99."); + this.amount = amount; + if (purposeOfCreditTransfer.Length > 4) + throw new GirocodeException("Purpose of credit transfer can only have 4 chars at maximum."); + this.purposeOfCreditTransfer = purposeOfCreditTransfer; + if (typeOfRemittance == TypeOfRemittance.Unstructured && remittanceInformation.Length > 140) + throw new GirocodeException("Unstructured reference texts have to shorter than 141 chars."); + if (typeOfRemittance == TypeOfRemittance.Structured && remittanceInformation.Length > 35) + throw new GirocodeException("Structured reference texts have to shorter than 36 chars."); + this.typeOfRemittance = typeOfRemittance; + this.remittanceInformation = remittanceInformation; + if (messageToGirocodeUser.Length > 70) + throw new GirocodeException("Message to the Girocode-User reader texts have to shorter than 71 chars."); + this.messageToGirocodeUser = messageToGirocodeUser; + } + + public override string ToString() + { + var girocodePayload = "BCD" + br; + girocodePayload += ((version == GirocodeVersion.Version1) ? "001" : "002") + br; + girocodePayload += (int)encoding + 1 + br; + girocodePayload += "SCT" + br; + girocodePayload += bic + br; + girocodePayload += name + br; + girocodePayload += iban + br; + girocodePayload += $"EUR{amount:0.00}".Replace(",",".") + br; + girocodePayload += purposeOfCreditTransfer + br; + girocodePayload += ((typeOfRemittance == TypeOfRemittance.Structured) + ? remittanceInformation + : string.Empty) + br; + girocodePayload += ((typeOfRemittance == TypeOfRemittance.Unstructured) + ? remittanceInformation + : string.Empty) + br; + girocodePayload += messageToGirocodeUser; + + return ConvertStringToEncoding(girocodePayload, encoding.ToString().Replace("_","-")); + } + + public enum GirocodeVersion + { + Version1, + Version2 + } + + public enum TypeOfRemittance + { + Structured, + Unstructured + } + + public enum GirocodeEncoding + { + UTF_8, + ISO_8859_1, + ISO_8859_2, + ISO_8859_4, + ISO_8859_5, + ISO_8859_7, + ISO_8859_10, + ISO_8859_15 + } + + public class GirocodeException : Exception + { + public GirocodeException() + { + } + + public GirocodeException(string message) + : base(message) + { + } + + public GirocodeException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class BezahlCode : Payload + { + //BezahlCode specification: http://www.bezahlcode.de/wp-content/uploads/BezahlCode_TechDok.pdf + + private readonly string name, iban, bic, account, bnc, sepaReference, reason, creditorId, mandateId, periodicTimeunit; + private readonly decimal amount; + private readonly int postingKey, periodicTimeunitRotation; + private readonly Currency currency; + private readonly AuthorityType authority; + private readonly DateTime executionDate, dateOfSignature, periodicFirstExecutionDate, periodicLastExecutionDate; + + + /// + /// Constructor for contact data + /// + /// Type of the bank transfer + /// Name of the receiver (Empfänger) + /// Bank account (Kontonummer) + /// Bank institute (Bankleitzahl) + /// IBAN + /// BIC + /// Reason (Verwendungszweck) + public BezahlCode(AuthorityType authority, string name, string account = "", string bnc = "", string iban = "", string bic = "", string reason = "") : this(authority, name, account, bnc, iban, bic, 0, string.Empty, 0, null, null, string.Empty, string.Empty, null, reason, 0, string.Empty, Currency.EUR, null, 1) + { + } + + + /// + /// Constructor for non-SEPA payments + /// + /// Type of the bank transfer + /// Name of the receiver (Empfänger) + /// Bank account (Kontonummer) + /// Bank institute (Bankleitzahl) + /// Amount (Betrag) + /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) + /// Intervall for payment. This value is combined with 'periodicTimeunit' + /// Date of first periodic execution + /// Date of last periodic execution + /// Reason (Verwendungszweck) + /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) + /// Currency (Währung) + /// Execution date (Ausführungsdatum) + public BezahlCode(AuthorityType authority, string name, string account, string bnc, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string reason = "", int postingKey = 0, Currency currency = Currency.EUR, DateTime? executionDate = null) : this(authority, name, account, bnc, string.Empty, string.Empty, amount, periodicTimeunit, periodicTimeunitRotation, periodicFirstExecutionDate, periodicLastExecutionDate, string.Empty, string.Empty, null, reason, postingKey, string.Empty, currency, executionDate, 2) + { + } + + /// + /// Constructor for SEPA payments + /// + /// Type of the bank transfer + /// Name of the receiver (Empfänger) + /// IBAN + /// BIC + /// Amount (Betrag) + /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) + /// Intervall for payment. This value is combined with 'periodicTimeunit' + /// Date of first periodic execution + /// Date of last periodic execution + /// Creditor id (Gläubiger ID) + /// Manadate id (Mandatsreferenz) + /// Signature date (Erteilungsdatum des Mandats) + /// Reason (Verwendungszweck) + /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) + /// SEPA reference (SEPA-Referenz) + /// Currency (Währung) + /// Execution date (Ausführungsdatum) + public BezahlCode(AuthorityType authority, string name, string iban, string bic, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string creditorId = "", string mandateId = "", DateTime? dateOfSignature = null, string reason = "", string sepaReference = "", Currency currency = Currency.EUR, DateTime? executionDate = null) : this(authority, name, string.Empty, string.Empty, iban, bic, amount, periodicTimeunit, periodicTimeunitRotation, periodicFirstExecutionDate, periodicLastExecutionDate, creditorId, mandateId, dateOfSignature, reason, 0, sepaReference, currency, executionDate, 3) + { + } + + + + + /// + /// Generic constructor. Please use specific (non-SEPA or SEPA) constructor + /// + /// Type of the bank transfer + /// Name of the receiver (Empfänger) + /// Bank account (Kontonummer) + /// Bank institute (Bankleitzahl) + /// IBAN + /// BIC + /// Amount (Betrag) + /// Unit of intervall for payment ('M' = monthly, 'W' = weekly) + /// Intervall for payment. This value is combined with 'periodicTimeunit' + /// Date of first periodic execution + /// Date of last periodic execution + /// Creditor id (Gläubiger ID) + /// Manadate id (Mandatsreferenz) + /// Signature date (Erteilungsdatum des Mandats) + /// Reason (Verwendungszweck) + /// Transfer Key (Textschlüssel, z.B. Spendenzahlung = 69) + /// SEPA reference (SEPA-Referenz) + /// Currency (Währung) + /// Execution date (Ausführungsdatum) + /// Only used for internal state handdling + public BezahlCode(AuthorityType authority, string name, string account, string bnc, string iban, string bic, decimal amount, string periodicTimeunit = "", int periodicTimeunitRotation = 0, DateTime? periodicFirstExecutionDate = null, DateTime? periodicLastExecutionDate = null, string creditorId = "", string mandateId = "", DateTime? dateOfSignature = null, string reason = "", int postingKey = 0, string sepaReference = "", Currency currency = Currency.EUR, DateTime? executionDate = null, int internalMode = 0) + { + //Loaded via "contact-constructor" + if (internalMode == 1) + { + if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) + throw new BezahlCodeException("The constructor without an amount may only ne used with authority types 'contact' and 'contact_v2'."); + if (authority == AuthorityType.contact && (string.IsNullOrEmpty(account) || string.IsNullOrEmpty(bnc))) + throw new BezahlCodeException("When using authority type 'contact' the parameters 'account' and 'bnc' must be set."); + + if (authority != AuthorityType.contact_v2) + { + var oldFilled = (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)); + var newFilled = (!string.IsNullOrEmpty(iban) && !string.IsNullOrEmpty(bic)); + if ((!oldFilled && !newFilled) || (oldFilled && newFilled)) + throw new BezahlCodeException("When using authority type 'contact_v2' either the parameters 'account' and 'bnc' or the parameters 'iban' and 'bic' must be set. Leave the other parameter pair empty."); + } + } + else if (internalMode == 2) + { +#pragma warning disable CS0612 + if (authority != AuthorityType.periodicsinglepayment && authority != AuthorityType.singledirectdebit && authority != AuthorityType.singlepayment) + throw new BezahlCodeException("The constructor with 'account' and 'bnc' may only be used with 'non SEPA' authority types. Either choose another authority type or switch constructor."); + if (authority == AuthorityType.periodicsinglepayment && (string.IsNullOrEmpty(periodicTimeunit) || periodicTimeunitRotation == 0)) + throw new BezahlCodeException("When using 'periodicsinglepayment' as authority type, the parameters 'periodicTimeunit' and 'periodicTimeunitRotation' must be set."); +#pragma warning restore CS0612 + } + else if (internalMode == 3) + { + if (authority != AuthorityType.periodicsinglepaymentsepa && authority != AuthorityType.singledirectdebitsepa && authority != AuthorityType.singlepaymentsepa) + throw new BezahlCodeException("The constructor with 'iban' and 'bic' may only be used with 'SEPA' authority types. Either choose another authority type or switch constructor."); + if (authority == AuthorityType.periodicsinglepaymentsepa && (string.IsNullOrEmpty(periodicTimeunit) || periodicTimeunitRotation == 0)) + throw new BezahlCodeException("When using 'periodicsinglepaymentsepa' as authority type, the parameters 'periodicTimeunit' and 'periodicTimeunitRotation' must be set."); + } + + this.authority = authority; + + if (name.Length > 70) + throw new BezahlCodeException("(Payee-)Name must be shorter than 71 chars."); + this.name = name; + + if (reason.Length > 27) + throw new BezahlCodeException("Reasons texts have to be shorter than 28 chars."); + this.reason = reason; + + var oldWayFilled = (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)); + var newWayFilled = (!string.IsNullOrEmpty(iban) && !string.IsNullOrEmpty(bic)); + + //Non-SEPA payment types +#pragma warning disable CS0612 + if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.singledirectdebit || authority == AuthorityType.singlepayment || authority == AuthorityType.contact || (authority == AuthorityType.contact_v2 && oldWayFilled)) + { +#pragma warning restore CS0612 + if (!Regex.IsMatch(account.Replace(" ", ""), @"^[0-9]{1,9}$")) + throw new BezahlCodeException("The account entered isn't valid."); + this.account = account.Replace(" ", "").ToUpper(); + if(!Regex.IsMatch(bnc.Replace(" ", ""), @"^[0-9]{1,9}$")) + throw new BezahlCodeException("The bnc entered isn't valid."); + this.bnc = bnc.Replace(" ", "").ToUpper(); + + if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) + { + if (postingKey < 0 || postingKey >= 100) + throw new BezahlCodeException("PostingKey must be within 0 and 99."); + this.postingKey = postingKey; + } + } + + //SEPA payment types + if (authority == AuthorityType.periodicsinglepaymentsepa || authority == AuthorityType.singledirectdebitsepa || authority == AuthorityType.singlepaymentsepa || (authority == AuthorityType.contact_v2 && newWayFilled)) + { + if (!IsValidIban(iban)) + throw new BezahlCodeException("The IBAN entered isn't valid."); + this.iban = iban.Replace(" ", "").ToUpper(); + if (!IsValidBic(bic)) + throw new BezahlCodeException("The BIC entered isn't valid."); + this.bic = bic.Replace(" ", "").ToUpper(); + + if (authority != AuthorityType.contact_v2) + { + if (sepaReference.Length > 35) + throw new BezahlCodeException("SEPA reference texts have to be shorter than 36 chars."); + this.sepaReference = sepaReference; + + if (!string.IsNullOrEmpty(creditorId) && !Regex.IsMatch(creditorId.Replace(" ", ""), @"^[a-zA-Z]{2,2}[0-9]{2,2}([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){3,3}([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,28}$")) + throw new BezahlCodeException("The creditorId entered isn't valid."); + this.creditorId = creditorId; + if (!string.IsNullOrEmpty(mandateId) && !Regex.IsMatch(mandateId.Replace(" ", ""), @"^([A-Za-z0-9]|[\+|\?|/|\-|:|\(|\)|\.|,|']){1,35}$")) + throw new BezahlCodeException("The mandateId entered isn't valid."); + this.mandateId = mandateId; + if (dateOfSignature != null) + this.dateOfSignature = (DateTime)dateOfSignature; + } + } + + //Checks for all payment types + if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) + { + if (amount.ToString().Replace(",", ".").Contains(".") && amount.ToString().Replace(",", ".").Split('.')[1].TrimEnd('0').Length > 2) + throw new BezahlCodeException("Amount must have less than 3 digits after decimal point."); + if (amount < 0.01m || amount > 999999999.99m) + throw new BezahlCodeException("Amount has to at least 0.01 and must be smaller or equal to 999999999.99."); + this.amount = amount; + + this.currency = currency; + + if (executionDate == null) + this.executionDate = DateTime.Now; + else + { + if (DateTime.Today.Ticks > executionDate.Value.Ticks) + throw new BezahlCodeException("Execution date must be today or in future."); + this.executionDate = (DateTime)executionDate; + } +#pragma warning disable CS0612 + if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.periodicsinglepaymentsepa) +#pragma warning restore CS0612 + { + if (periodicTimeunit.ToUpper() != "M" && periodicTimeunit.ToUpper() != "W") + throw new BezahlCodeException("The periodicTimeunit must be either 'M' (monthly) or 'W' (weekly)."); + this.periodicTimeunit = periodicTimeunit; + if (periodicTimeunitRotation < 1 || periodicTimeunitRotation > 52) + throw new BezahlCodeException("The periodicTimeunitRotation must be 1 or greater. (It means repeat the payment every 'periodicTimeunitRotation' weeks/months."); + this.periodicTimeunitRotation = periodicTimeunitRotation; + if (periodicFirstExecutionDate != null) + this.periodicFirstExecutionDate = (DateTime)periodicFirstExecutionDate; + if (periodicLastExecutionDate != null) + this.periodicLastExecutionDate = (DateTime)periodicLastExecutionDate; + } + + } + + + + } + + public override string ToString() + { + var bezahlCodePayload = $"bank://{authority}?"; + + bezahlCodePayload += $"name={Uri.EscapeDataString(name)}&"; + + if (authority != AuthorityType.contact && authority != AuthorityType.contact_v2) + { + //Handle what is same for all payments +#pragma warning disable CS0612 + if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.singledirectdebit || authority == AuthorityType.singlepayment) +#pragma warning restore CS0612 + { + bezahlCodePayload += $"account={account}&"; + bezahlCodePayload += $"bnc={bnc}&"; + if (postingKey > 0) + bezahlCodePayload += $"postingkey={postingKey}&"; + } + else + { + bezahlCodePayload += $"iban={iban}&"; + bezahlCodePayload += $"bic={bic}&"; + + if (!string.IsNullOrEmpty(sepaReference)) + bezahlCodePayload += $"separeference={ Uri.EscapeDataString(sepaReference)}&"; + + if (authority == AuthorityType.singledirectdebitsepa) + { + if (!string.IsNullOrEmpty(creditorId)) + bezahlCodePayload += $"creditorid={ Uri.EscapeDataString(creditorId)}&"; + if (!string.IsNullOrEmpty(mandateId)) + bezahlCodePayload += $"mandateid={ Uri.EscapeDataString(mandateId)}&"; + if (dateOfSignature != DateTime.MinValue) + bezahlCodePayload += $"dateofsignature={dateOfSignature.ToString("ddMMyyyy")}&"; + } + } + bezahlCodePayload += $"amount={amount:0.00}&".Replace(".", ","); + + if (!string.IsNullOrEmpty(reason)) + bezahlCodePayload += $"reason={ Uri.EscapeDataString(reason)}&"; + bezahlCodePayload += $"currency={currency}&"; + bezahlCodePayload += $"executiondate={executionDate.ToString("ddMMyyyy")}&"; +#pragma warning disable CS0612 + if (authority == AuthorityType.periodicsinglepayment || authority == AuthorityType.periodicsinglepaymentsepa) + { + bezahlCodePayload += $"periodictimeunit={periodicTimeunit}&"; + bezahlCodePayload += $"periodictimeunitrotation={periodicTimeunitRotation}&"; + if (periodicFirstExecutionDate != DateTime.MinValue) + bezahlCodePayload += $"periodicfirstexecutiondate={periodicFirstExecutionDate.ToString("ddMMyyyy")}&"; + if (periodicLastExecutionDate != DateTime.MinValue) + bezahlCodePayload += $"periodiclastexecutiondate={periodicLastExecutionDate.ToString("ddMMyyyy")}&"; + } +#pragma warning restore CS0612 + } + else + { + //Handle what is same for all contacts + if (authority == AuthorityType.contact) + { + bezahlCodePayload += $"account={account}&"; + bezahlCodePayload += $"bnc={bnc}&"; + } + else if (authority == AuthorityType.contact_v2) + { + if (!string.IsNullOrEmpty(account) && !string.IsNullOrEmpty(bnc)) + { + bezahlCodePayload += $"account={account}&"; + bezahlCodePayload += $"bnc={bnc}&"; + } + else + { + bezahlCodePayload += $"iban={iban}&"; + bezahlCodePayload += $"bic={bic}&"; + } + } + + if (!string.IsNullOrEmpty(reason)) + bezahlCodePayload += $"reason={ Uri.EscapeDataString(reason)}&"; + } + + return bezahlCodePayload.Trim('&'); + } + + /// + /// ISO 4217 currency codes + /// + public enum Currency + { + AED = 784, + AFN = 971, + ALL = 008, + AMD = 051, + ANG = 532, + AOA = 973, + ARS = 032, + AUD = 036, + AWG = 533, + AZN = 944, + BAM = 977, + BBD = 052, + BDT = 050, + BGN = 975, + BHD = 048, + BIF = 108, + BMD = 060, + BND = 096, + BOB = 068, + BOV = 984, + BRL = 986, + BSD = 044, + BTN = 064, + BWP = 072, + BYR = 974, + BZD = 084, + CAD = 124, + CDF = 976, + CHE = 947, + CHF = 756, + CHW = 948, + CLF = 990, + CLP = 152, + CNY = 156, + COP = 170, + COU = 970, + CRC = 188, + CUC = 931, + CUP = 192, + CVE = 132, + CZK = 203, + DJF = 262, + DKK = 208, + DOP = 214, + DZD = 012, + EGP = 818, + ERN = 232, + ETB = 230, + EUR = 978, + FJD = 242, + FKP = 238, + GBP = 826, + GEL = 981, + GHS = 936, + GIP = 292, + GMD = 270, + GNF = 324, + GTQ = 320, + GYD = 328, + HKD = 344, + HNL = 340, + HRK = 191, + HTG = 332, + HUF = 348, + IDR = 360, + ILS = 376, + INR = 356, + IQD = 368, + IRR = 364, + ISK = 352, + JMD = 388, + JOD = 400, + JPY = 392, + KES = 404, + KGS = 417, + KHR = 116, + KMF = 174, + KPW = 408, + KRW = 410, + KWD = 414, + KYD = 136, + KZT = 398, + LAK = 418, + LBP = 422, + LKR = 144, + LRD = 430, + LSL = 426, + LYD = 434, + MAD = 504, + MDL = 498, + MGA = 969, + MKD = 807, + MMK = 104, + MNT = 496, + MOP = 446, + MRO = 478, + MUR = 480, + MVR = 462, + MWK = 454, + MXN = 484, + MXV = 979, + MYR = 458, + MZN = 943, + NAD = 516, + NGN = 566, + NIO = 558, + NOK = 578, + NPR = 524, + NZD = 554, + OMR = 512, + PAB = 590, + PEN = 604, + PGK = 598, + PHP = 608, + PKR = 586, + PLN = 985, + PYG = 600, + QAR = 634, + RON = 946, + RSD = 941, + RUB = 643, + RWF = 646, + SAR = 682, + SBD = 090, + SCR = 690, + SDG = 938, + SEK = 752, + SGD = 702, + SHP = 654, + SLL = 694, + SOS = 706, + SRD = 968, + SSP = 728, + STD = 678, + SVC = 222, + SYP = 760, + SZL = 748, + THB = 764, + TJS = 972, + TMT = 934, + TND = 788, + TOP = 776, + TRY = 949, + TTD = 780, + TWD = 901, + TZS = 834, + UAH = 980, + UGX = 800, + USD = 840, + USN = 997, + UYI = 940, + UYU = 858, + UZS = 860, + VEF = 937, + VND = 704, + VUV = 548, + WST = 882, + XAF = 950, + XAG = 961, + XAU = 959, + XBA = 955, + XBB = 956, + XBC = 957, + XBD = 958, + XCD = 951, + XDR = 960, + XOF = 952, + XPD = 964, + XPF = 953, + XPT = 962, + XSU = 994, + XTS = 963, + XUA = 965, + XXX = 999, + YER = 886, + ZAR = 710, + ZMW = 967, + ZWL = 932 + } + + + /// + /// Operation modes of the BezahlCode + /// + public enum AuthorityType + { + /// + /// Single payment (Überweisung) + /// + [Obsolete] + singlepayment, + /// + /// Single SEPA payment (SEPA-Überweisung) + /// + singlepaymentsepa, + /// + /// Single debit (Lastschrift) + /// + [Obsolete] + singledirectdebit, + /// + /// Single SEPA debit (SEPA-Lastschrift) + /// + singledirectdebitsepa, + /// + /// Periodic payment (Dauerauftrag) + /// + [Obsolete] + periodicsinglepayment, + /// + /// Periodic SEPA payment (SEPA-Dauerauftrag) + /// + periodicsinglepaymentsepa, + /// + /// Contact data + /// + contact, + /// + /// Contact data V2 + /// + contact_v2 + } + + public class BezahlCodeException : Exception + { + public BezahlCodeException() + { + } + + public BezahlCodeException(string message) + : base(message) + { + } + + public BezahlCodeException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class CalendarEvent : Payload + { + private readonly string subject, description, location, start, end; + private readonly EventEncoding encoding; + + /// + /// Generates a calender entry/event payload. + /// + /// Subject/title of the calender event + /// Description of the event + /// Location (lat:long or address) of the event + /// Start time of the event + /// End time of the event + /// Is it a full day event? + /// Type of encoding (universal or iCal) + public CalendarEvent(string subject, string description, string location, DateTime start, DateTime end, bool allDayEvent, EventEncoding encoding = EventEncoding.Universal) + { + this.subject = subject; + this.description = description; + this.location = location; + this.encoding = encoding; + string dtFormat = allDayEvent ? "yyyyMMdd" : "yyyyMMddTHHmmss"; + this.start = start.ToString(dtFormat); + this.end = end.ToString(dtFormat); + } + + public override string ToString() + { + var vEvent = $"BEGIN:VEVENT{Environment.NewLine}"; + vEvent += $"SUMMARY:{this.subject}{Environment.NewLine}"; + vEvent += !string.IsNullOrEmpty(this.description) ? $"DESCRIPTION:{this.description}{Environment.NewLine}" : ""; + vEvent += !string.IsNullOrEmpty(this.location) ? $"LOCATION:{this.location}{Environment.NewLine}" : ""; + vEvent += $"DTSTART:{this.start}{Environment.NewLine}"; + vEvent += $"DTEND:{this.end}{Environment.NewLine}"; + vEvent += "END:VEVENT"; + + if (this.encoding == EventEncoding.iCalComplete) + vEvent = $@"BEGIN:VCALENDAR{Environment.NewLine}VERSION:2.0{Environment.NewLine}{vEvent}{Environment.NewLine}END:VCALENDAR"; + + return vEvent; + } + + public enum EventEncoding + { + iCalComplete, + Universal + } + } + + public class OneTimePassword : Payload + { + //https://github.com/google/google-authenticator/wiki/Key-Uri-Format + public OneTimePasswordAuthType Type { get; set; } = OneTimePasswordAuthType.TOTP; + public string Secret { get; set; } + + public OneTimePasswordAuthAlgorithm AuthAlgorithm { get; set; } = OneTimePasswordAuthAlgorithm.SHA1; + + [Obsolete("This property is obsolete, use " + nameof(AuthAlgorithm) + " instead", false)] + public OoneTimePasswordAuthAlgorithm Algorithm + { + get { return (OoneTimePasswordAuthAlgorithm)Enum.Parse(typeof(OoneTimePasswordAuthAlgorithm), AuthAlgorithm.ToString()); } + set { AuthAlgorithm = (OneTimePasswordAuthAlgorithm)Enum.Parse(typeof(OneTimePasswordAuthAlgorithm), value.ToString()); } + } + + public string Issuer { get; set; } + public string Label { get; set; } + public int Digits { get; set; } = 6; + public int? Counter { get; set; } = null; + public int? Period { get; set; } = 30; + + public enum OneTimePasswordAuthType + { + TOTP, + HOTP, + } + + public enum OneTimePasswordAuthAlgorithm + { + SHA1, + SHA256, + SHA512, + } + + [Obsolete("This enum is obsolete, use " + nameof(OneTimePasswordAuthAlgorithm) + " instead", false)] + public enum OoneTimePasswordAuthAlgorithm + { + SHA1, + SHA256, + SHA512, + } + + public override string ToString() + { + switch (Type) + { + case OneTimePasswordAuthType.TOTP: + return TimeToString(); + case OneTimePasswordAuthType.HOTP: + return HMACToString(); + default: + throw new ArgumentOutOfRangeException(); + } + } + + // Note: Issuer:Label must only contain 1 : if either of the Issuer or the Label has a : then it is invalid. + // Defaults are 6 digits and 30 for Period + private string HMACToString() + { + var sb = new StringBuilder("otpauth://hotp/"); + ProcessCommonFields(sb); + var actualCounter = Counter ?? 1; + sb.Append("&counter=" + actualCounter); + return sb.ToString(); + } + + private string TimeToString() + { + if (Period == null) + { + throw new Exception("Period must be set when using OneTimePasswordAuthType.TOTP"); + } + + var sb = new StringBuilder("otpauth://totp/"); + + ProcessCommonFields(sb); + + if (Period != 30) + { + sb.Append("&period=" + Period); + } + + return sb.ToString(); + } + + private void ProcessCommonFields(StringBuilder sb) + { + if (String40Methods.IsNullOrWhiteSpace(Secret)) + { + throw new Exception("Secret must be a filled out base32 encoded string"); + } + string strippedSecret = Secret.Replace(" ", ""); + string escapedIssuer = null; + string label = null; + + if (!String40Methods.IsNullOrWhiteSpace(Issuer)) + { + if (Issuer.Contains(":")) + { + throw new Exception("Issuer must not have a ':'"); + } + escapedIssuer = Uri.EscapeDataString(Issuer); + } + + if (!String40Methods.IsNullOrWhiteSpace(Label) && Label.Contains(":")) + { + throw new Exception("Label must not have a ':'"); + } + + if (Label != null && Issuer != null) + { + label = Issuer + ":" + Label; + } + else if (Issuer != null) + { + label = Issuer; + } + + if (label != null) + { + sb.Append(label); + } + + sb.Append("?secret=" + strippedSecret); + + if (escapedIssuer != null) + { + sb.Append("&issuer=" + escapedIssuer); + } + + if (Digits != 6) + { + sb.Append("&digits=" + Digits); + } + } + } + + public class ShadowSocksConfig : Payload + { + private readonly string hostname, password, tag, methodStr, parameter; + private readonly Method method; + private readonly int port; + private Dictionary encryptionTexts = new Dictionary() { + { "Chacha20IetfPoly1305", "chacha20-ietf-poly1305" }, + { "Aes128Gcm", "aes-128-gcm" }, + { "Aes192Gcm", "aes-192-gcm" }, + { "Aes256Gcm", "aes-256-gcm" }, + + { "XChacha20IetfPoly1305", "xchacha20-ietf-poly1305" }, + + { "Aes128Cfb", "aes-128-cfb" }, + { "Aes192Cfb", "aes-192-cfb" }, + { "Aes256Cfb", "aes-256-cfb" }, + { "Aes128Ctr", "aes-128-ctr" }, + { "Aes192Ctr", "aes-192-ctr" }, + { "Aes256Ctr", "aes-256-ctr" }, + { "Camellia128Cfb", "camellia-128-cfb" }, + { "Camellia192Cfb", "camellia-192-cfb" }, + { "Camellia256Cfb", "camellia-256-cfb" }, + { "Chacha20Ietf", "chacha20-ietf" }, + + { "Aes256Cb", "aes-256-cfb" }, + + { "Aes128Ofb", "aes-128-ofb" }, + { "Aes192Ofb", "aes-192-ofb" }, + { "Aes256Ofb", "aes-256-ofb" }, + { "Aes128Cfb1", "aes-128-cfb1" }, + { "Aes192Cfb1", "aes-192-cfb1" }, + { "Aes256Cfb1", "aes-256-cfb1" }, + { "Aes128Cfb8", "aes-128-cfb8" }, + { "Aes192Cfb8", "aes-192-cfb8" }, + { "Aes256Cfb8", "aes-256-cfb8" }, + + { "Chacha20", "chacha20" }, + { "BfCfb", "bf-cfb" }, + { "Rc4Md5", "rc4-md5" }, + { "Salsa20", "salsa20" }, + + { "DesCfb", "des-cfb" }, + { "IdeaCfb", "idea-cfb" }, + { "Rc2Cfb", "rc2-cfb" }, + { "Cast5Cfb", "cast5-cfb" }, + { "Salsa20Ctr", "salsa20-ctr" }, + { "Rc4", "rc4" }, + { "SeedCfb", "seed-cfb" }, + { "Table", "table" } + }; + + /// + /// Generates a ShadowSocks proxy config payload. + /// + /// Hostname of the ShadowSocks proxy + /// Port of the ShadowSocks proxy + /// Password of the SS proxy + /// Encryption type + /// Optional tag line + public ShadowSocksConfig(string hostname, int port, string password, Method method, string tag = null) : + this(hostname, port, password, method, null, tag) + { } + + public ShadowSocksConfig(string hostname, int port, string password, Method method, string plugin, string pluginOption, string tag = null) : + this(hostname, port, password, method, new Dictionary + { + ["plugin"] = plugin + ( + string.IsNullOrEmpty(pluginOption) + ? "" + : $";{pluginOption}" + ) + }, tag) + { } + private Dictionary UrlEncodeTable = new Dictionary + { + [" "] = "+", + ["\0"] = "%00", + ["\t"] = "%09", + ["\n"] = "%0a", + ["\r"] = "%0d", + ["\""] = "%22", + ["#"] = "%23", + ["$"] = "%24", + ["%"] = "%25", + ["&"] = "%26", + ["'"] = "%27", + ["+"] = "%2b", + [","] = "%2c", + ["/"] = "%2f", + [":"] = "%3a", + [";"] = "%3b", + ["<"] = "%3c", + ["="] = "%3d", + [">"] = "%3e", + ["?"] = "%3f", + ["@"] = "%40", + ["["] = "%5b", + ["\\"] = "%5c", + ["]"] = "%5d", + ["^"] = "%5e", + ["`"] = "%60", + ["{"] = "%7b", + ["|"] = "%7c", + ["}"] = "%7d", + ["~"] = "%7e", + }; + + private string UrlEncode(string i) + { + string j = i; + foreach (var kv in UrlEncodeTable) + { + j = j.Replace(kv.Key, kv.Value); + } + return j; + } + + public ShadowSocksConfig(string hostname, int port, string password, Method method, Dictionary parameters, string tag = null) + { + this.hostname = Uri.CheckHostName(hostname) == UriHostNameType.IPv6 + ? $"[{hostname}]" + : hostname; + if (port < 1 || port > 65535) + throw new ShadowSocksConfigException("Value of 'port' must be within 0 and 65535."); + this.port = port; + this.password = password; + this.method = method; + this.methodStr = encryptionTexts[method.ToString()]; + this.tag = tag; + + if (parameters != null) + this.parameter = + string.Join("&", + parameters.Select( + kv => $"{UrlEncode(kv.Key)}={UrlEncode(kv.Value)}" + ).ToArray()); + } + + public override string ToString() + { + if (string.IsNullOrEmpty(parameter)) + { + var connectionString = $"{methodStr}:{password}@{hostname}:{port}"; + var connectionStringEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(connectionString)); + return $"ss://{connectionStringEncoded}{(!string.IsNullOrEmpty(tag) ? $"#{tag}" : string.Empty)}"; + } + var authString = $"{methodStr}:{password}"; + var authStringEncoded = Convert.ToBase64String(Encoding.UTF8.GetBytes(authString)) + .Replace('+', '-') + .Replace('/', '_') + .TrimEnd('='); + return $"ss://{authStringEncoded}@{hostname}:{port}/?{parameter}{(!string.IsNullOrEmpty(tag) ? $"#{tag}" : string.Empty)}"; + } + + public enum Method + { + // AEAD + Chacha20IetfPoly1305, + Aes128Gcm, + Aes192Gcm, + Aes256Gcm, + // AEAD, not standard + XChacha20IetfPoly1305, + // Stream cipher + Aes128Cfb, + Aes192Cfb, + Aes256Cfb, + Aes128Ctr, + Aes192Ctr, + Aes256Ctr, + Camellia128Cfb, + Camellia192Cfb, + Camellia256Cfb, + Chacha20Ietf, + // alias of Aes256Cfb + Aes256Cb, + // Stream cipher, not standard + Aes128Ofb, + Aes192Ofb, + Aes256Ofb, + Aes128Cfb1, + Aes192Cfb1, + Aes256Cfb1, + Aes128Cfb8, + Aes192Cfb8, + Aes256Cfb8, + // Stream cipher, deprecated + Chacha20, + BfCfb, + Rc4Md5, + Salsa20, + // Not standard and not in acitve use + DesCfb, + IdeaCfb, + Rc2Cfb, + Cast5Cfb, + Salsa20Ctr, + Rc4, + SeedCfb, + Table + } + + public class ShadowSocksConfigException : Exception + { + public ShadowSocksConfigException() + { + } + + public ShadowSocksConfigException(string message) + : base(message) + { + } + + public ShadowSocksConfigException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class MoneroTransaction : Payload + { + private readonly string address, txPaymentId, recipientName, txDescription; + private readonly float? txAmount; + + /// + /// Creates a monero transaction payload + /// + /// Receiver's monero address + /// Amount to transfer + /// Payment id + /// Receipient's name + /// Reference text / payment description + public MoneroTransaction(string address, float? txAmount = null, string txPaymentId = null, string recipientName = null, string txDescription = null) + { + if (string.IsNullOrEmpty(address)) + throw new MoneroTransactionException("The address is mandatory and has to be set."); + this.address = address; + if (txAmount != null && txAmount <= 0) + throw new MoneroTransactionException("Value of 'txAmount' must be greater than 0."); + this.txAmount = txAmount; + this.txPaymentId = txPaymentId; + this.recipientName = recipientName; + this.txDescription = txDescription; + } + + public override string ToString() + { + var moneroUri = $"monero://{address}{(!string.IsNullOrEmpty(txPaymentId) || !string.IsNullOrEmpty(recipientName) || !string.IsNullOrEmpty(txDescription) || txAmount != null ? "?" : string.Empty)}"; + moneroUri += (!string.IsNullOrEmpty(txPaymentId) ? $"tx_payment_id={Uri.EscapeDataString(txPaymentId)}&" : string.Empty); + moneroUri += (!string.IsNullOrEmpty(recipientName) ? $"recipient_name={Uri.EscapeDataString(recipientName)}&" : string.Empty); + moneroUri += (txAmount != null ? $"tx_amount={txAmount.ToString().Replace(",",".")}&" : string.Empty); + moneroUri += (!string.IsNullOrEmpty(txDescription) ? $"tx_description={Uri.EscapeDataString(txDescription)}" : string.Empty); + return moneroUri.TrimEnd('&'); + } + + + public class MoneroTransactionException : Exception + { + public MoneroTransactionException() + { + } + + public MoneroTransactionException(string message) + : base(message) + { + } + + public MoneroTransactionException(string message, Exception inner) + : base(message, inner) + { + } + } + } + + public class SlovenianUpnQr : Payload + { + //Keep in mind, that the ECC level has to be set to "M", version to 15 and ECI to EciMode.Iso8859_2 when generating a SlovenianUpnQr! + //SlovenianUpnQr specification: https://www.upn-qr.si/uploads/files/NavodilaZaProgramerjeUPNQR.pdf + + private string _payerName = ""; + private string _payerAddress = ""; + private string _payerPlace = ""; + private string _amount = ""; + private string _code = ""; + private string _purpose = ""; + private string _deadLine = ""; + private string _recipientIban = ""; + private string _recipientName = ""; + private string _recipientAddress = ""; + private string _recipientPlace = ""; + private string _recipientSiModel = ""; + private string _recipientSiReference = ""; + + public override int Version { get { return 15; } } + public override QRCodeGenerator.ECCLevel EccLevel { get { return QRCodeGenerator.ECCLevel.M; } } + public override QRCodeGenerator.EciMode EciMode { get { return QRCodeGenerator.EciMode.Iso8859_2; } } + + private string LimitLength(string value, int maxLength) + { + return (value.Length <= maxLength) ? value : value.Substring(0, maxLength); + } + + public SlovenianUpnQr(string payerName, string payerAddress, string payerPlace, string recipientName, string recipientAddress, string recipientPlace, string recipientIban, string description, double amount, string recipientSiModel = "SI00", string recipientSiReference = "", string code = "OTHR") : + this(payerName, payerAddress, payerPlace, recipientName, recipientAddress, recipientPlace, recipientIban, description, amount, null, recipientSiModel, recipientSiReference, code) + { } + + public SlovenianUpnQr(string payerName, string payerAddress, string payerPlace, string recipientName, string recipientAddress, string recipientPlace, string recipientIban, string description, double amount, DateTime? deadline, string recipientSiModel = "SI99", string recipientSiReference = "", string code = "OTHR") + { + _payerName = LimitLength(payerName.Trim(), 33); + _payerAddress = LimitLength(payerAddress.Trim(), 33); + _payerPlace = LimitLength(payerPlace.Trim(), 33); + _amount = FormatAmount(amount); + _code = LimitLength(code.Trim().ToUpper(), 4); + _purpose = LimitLength(description.Trim(), 42); + _deadLine = (deadline == null) ? "" : deadline?.ToString("dd.MM.yyyy"); + _recipientIban = LimitLength(recipientIban.Trim(), 34); + _recipientName = LimitLength(recipientName.Trim(), 33); + _recipientAddress = LimitLength(recipientAddress.Trim(), 33); + _recipientPlace = LimitLength(recipientPlace.Trim(), 33); + _recipientSiModel = LimitLength(recipientSiModel.Trim().ToUpper(), 4); + _recipientSiReference = LimitLength(recipientSiReference.Trim(), 22); + } + + + private string FormatAmount(double amount) + { + int _amt = (int)Math.Round(amount * 100.0); + return String.Format("{0:00000000000}", _amt); + } + + private int CalculateChecksum() + { + int _cs = 5 + _payerName.Length; //5 = UPNQR constant Length + _cs += _payerAddress.Length; + _cs += _payerPlace.Length; + _cs += _amount.Length; + _cs += _code.Length; + _cs += _purpose.Length; + _cs += _deadLine.Length; + _cs += _recipientIban.Length; + _cs += _recipientName.Length; + _cs += _recipientAddress.Length; + _cs += _recipientPlace.Length; + _cs += _recipientSiModel.Length; + _cs += _recipientSiReference.Length; + _cs += 19; + return _cs; + } + + public override string ToString() + { + var _sb = new StringBuilder(); + _sb.Append("UPNQR"); + _sb.Append('\n').Append('\n').Append('\n').Append('\n').Append('\n'); + _sb.Append(_payerName).Append('\n'); + _sb.Append(_payerAddress).Append('\n'); + _sb.Append(_payerPlace).Append('\n'); + _sb.Append(_amount).Append('\n').Append('\n').Append('\n'); + _sb.Append(_code.ToUpper()).Append('\n'); + _sb.Append(_purpose).Append('\n'); + _sb.Append(_deadLine).Append('\n'); + _sb.Append(_recipientIban.ToUpper()).Append('\n'); + _sb.Append(_recipientSiModel).Append(_recipientSiReference).Append('\n'); + _sb.Append(_recipientName).Append('\n'); + _sb.Append(_recipientAddress).Append('\n'); + _sb.Append(_recipientPlace).Append('\n'); + _sb.AppendFormat("{0:000}", CalculateChecksum()).Append('\n'); + return _sb.ToString(); + } + } + + + public class RussiaPaymentOrder : Payload + { + // Specification of RussianPaymentOrder + //https://docs.cntd.ru/document/1200110981 + //https://roskazna.gov.ru/upload/iblock/5fa/gost_r_56042_2014.pdf + //https://sbqr.ru/standard/files/standart.pdf + + // Specification of data types described in the above standard + // https://gitea.sergeybochkov.com/bochkov/emuik/src/commit/d18f3b550f6415ea4a4a5e6097eaab4661355c72/template/ed + + // Tool for QR validation + // https://www.sbqr.ru/validator/index.html + + //base + private CharacterSets characterSet; + private MandatoryFields mFields; + private OptionalFields oFields; + private string separator = "|"; + + private RussiaPaymentOrder() + { + mFields = new MandatoryFields(); + oFields = new OptionalFields(); + } + + /// + /// Generates a RussiaPaymentOrder payload + /// + /// Name of the payee (Наименование получателя платежа) + /// Beneficiary account number (Номер счета получателя платежа) + /// Name of the beneficiary's bank (Наименование банка получателя платежа) + /// BIC (БИК) + /// Box number / account payee's bank (Номер кор./сч. банка получателя платежа) + /// An (optional) object of additional fields + /// Type of encoding (default UTF-8) + public RussiaPaymentOrder(string name, string personalAcc, string bankName, string BIC, string correspAcc, OptionalFields optionalFields = null, CharacterSets characterSet = CharacterSets.utf_8) : this() + { + this.characterSet = characterSet; + mFields.Name = ValidateInput(name, "Name", @"^.{1,160}$"); + mFields.PersonalAcc = ValidateInput(personalAcc, "PersonalAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$"); + mFields.BankName = ValidateInput(bankName, "BankName", @"^.{1,45}$"); + mFields.BIC = ValidateInput(BIC, "BIC", @"^\d{9}$"); + mFields.CorrespAcc = ValidateInput(correspAcc, "CorrespAcc", @"^[1-9]\d{4}[0-9ABCEHKMPTX]\d{14}$"); + + if (optionalFields != null) + oFields = optionalFields; + } + + /// + /// Returns payload as string. + /// + /// ⚠ Attention: If CharacterSets was set to windows-1251 or koi8-r you should use ToBytes() instead of ToString() and pass the bytes to CreateQrCode()! + /// + public override string ToString() + { + var cp = characterSet.ToString().Replace("_", "-"); + var bytes = ToBytes(); + +//#if !NET35_OR_GREATER && !NETSTANDARD1_3_OR_GREATER +// System.Text.Encoding.RegisterProvider(System.Text.CodePagesEncodingProvider.Instance); +//#endif +//#if NETSTANDARD1_3 +// return Encoding.GetEncoding(cp).GetString(bytes,0,bytes.Length); +//#else + return Encoding.GetEncoding(cp).GetString(bytes); +//#endif + } + + /// + /// Returns payload as byte[]. + /// + /// Should be used if CharacterSets equals windows-1251 or koi8-r + /// + + public byte[] ToBytes() + { + //Calculate the seperator + separator = DetermineSeparator(); + + //Create the payload string + string ret = $"ST0001" + ((int)characterSet).ToString() + //(separator != "|" ? separator : "") + + $"{separator}Name={mFields.Name}" + + $"{separator}PersonalAcc={mFields.PersonalAcc}" + + $"{separator}BankName={mFields.BankName}" + + $"{separator}BIC={mFields.BIC}" + + $"{separator}CorrespAcc={mFields.CorrespAcc}"; + + //Add optional fields, if filled + var optionalFieldsList = GetOptionalFieldsAsList(); + if (optionalFieldsList.Count > 0) + ret += $"|{string.Join("|", optionalFieldsList.ToArray())}"; + ret += separator; + + //Encode return string as byte[] with correct CharacterSet +//#if !NET35_OR_GREATER +// Encoding.RegisterProvider(CodePagesEncodingProvider.Instance); +//#endif + var cp = this.characterSet.ToString().Replace("_", "-"); + byte[] bytesOut = Encoding.Convert(Encoding.UTF8, Encoding.GetEncoding(cp), Encoding.UTF8.GetBytes(ret)); + if (bytesOut.Length > 300) + throw new RussiaPaymentOrderException($"Data too long. Payload must not exceed 300 bytes, but actually is {bytesOut.Length} bytes long. Remove additional data fields or shorten strings/values."); + return bytesOut; + } + + + /// + /// Determines a valid separator + /// + /// + private string DetermineSeparator() + { + // See chapter 5.2.1 of Standard (https://sbqr.ru/standard/files/standart.pdf) + + var mandatoryValues = GetMandatoryFieldsAsList(); + var optionalValues = GetOptionalFieldsAsList(); + + // Possible candidates for field separation + var separatorCandidates = new string[]{ "|", "#", ";", ":", "^", "_", "~", "{", "}", "!", "#", "$", "%", "&", "(", ")", "*", "+", ",", "/", "@" }; + foreach (var sepCandidate in separatorCandidates) + { + if (!mandatoryValues.Any(x => x.Contains(sepCandidate)) && !optionalValues.Any(x => x.Contains(sepCandidate))) + return sepCandidate; + } + throw new RussiaPaymentOrderException("No valid separator found."); + } + + /// + /// Takes all optional fields that are not null and returns their string represantion + /// + /// A List of strings + private List GetOptionalFieldsAsList() + { +#if NETSTANDARD1_3 + return oFields.GetType().GetRuntimeProperties() + .Where(field => field.GetValue(oFields) != null) + .Select(field => { + var objValue = field.GetValue(oFields, null); + var value = field.PropertyType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); + return $"{field.Name}={value}"; + }) + .ToList(); +#else + return oFields.GetType().GetProperties() + .Where(field => field.GetValue(oFields, null) != null) + .Select(field => { + var objValue = field.GetValue(oFields, null); + var value = field.PropertyType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); + return $"{field.Name}={value}"; + }) + .ToList(); +#endif + } + + + /// + /// Takes all mandatory fields that are not null and returns their string represantion + /// + /// A List of strings + private List GetMandatoryFieldsAsList() + { +#if NETSTANDARD1_3 + return mFields.GetType().GetRuntimeFields() + .Where(field => field.GetValue(mFields) != null) + .Select(field => { + var objValue = field.GetValue(mFields); + var value = field.FieldType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); + return $"{field.Name}={value}"; + }) + .ToList(); +#else + return mFields.GetType().GetFields() + .Where(field => field.GetValue(mFields) != null) + .Select(field => { + var objValue = field.GetValue(mFields); + var value = field.FieldType.Equals(typeof(DateTime?)) ? ((DateTime)objValue).ToString("dd.MM.yyyy") : objValue.ToString(); + return $"{field.Name}={value}"; + }) + .ToList(); +#endif + } + + /// + /// Validates a string against a given Regex pattern. Returns input if it matches the Regex expression (=valid) or throws Exception in case there's a mismatch + /// + /// String to be validated + /// Name/descriptor of the string to be validated + /// A regex pattern to be used for validation + /// An optional error text. If null, a standard error text is generated + /// Input value (in case it is valid) + private static string ValidateInput(string input, string fieldname, string pattern, string errorText = null) + { + return ValidateInput(input, fieldname, new string[] { pattern }, errorText); + } + + /// + /// Validates a string against one or more given Regex patterns. Returns input if it matches all regex expressions (=valid) or throws Exception in case there's a mismatch + /// + /// String to be validated + /// Name/descriptor of the string to be validated + /// An array of regex patterns to be used for validation + /// An optional error text. If null, a standard error text is generated + /// Input value (in case it is valid) + private static string ValidateInput(string input, string fieldname, string[] patterns, string errorText = null) + { + if (input == null) + throw new RussiaPaymentOrderException($"The input for '{fieldname}' must not be null."); + foreach (var pattern in patterns) + { + if (!Regex.IsMatch(input, pattern)) + throw new RussiaPaymentOrderException(errorText ?? $"The input for '{fieldname}' ({input}) doesn't match the pattern {pattern}"); + } + return input; + } + + private class MandatoryFields + { + public string Name; + public string PersonalAcc; + public string BankName; + public string BIC; + public string CorrespAcc; + } + + public class OptionalFields + { + private string _sum; + /// + /// Payment amount, in kopecks (FTI’s Amount.) + /// Сумма платежа, в копейках + /// + public string Sum + { + get { return _sum; } + set { _sum = ValidateInput(value, "Sum", @"^\d{1,18}$"); } + } + + private string _purpose; + /// + /// Payment name (purpose) + /// Наименование платежа (назначение) + /// + public string Purpose + { + get { return _purpose; } + set { _purpose = ValidateInput(value, "Purpose", @"^.{1,160}$"); } + } + + private string _payeeInn; + /// + /// Payee's INN (Resident Tax Identification Number; Text, up to 12 characters.) + /// ИНН получателя платежа + /// + public string PayeeINN + { + get { return _payeeInn; } + set { _payeeInn = ValidateInput(value, "PayeeINN", @"^.{1,12}$"); } + } + + private string _payerInn; + /// + /// Payer's INN (Resident Tax Identification Number; Text, up to 12 characters.) + /// ИНН плательщика + /// + public string PayerINN + { + get { return _payerInn; } + set { _payerInn = ValidateInput(value, "PayerINN", @"^.{1,12}$"); } + } + + private string _drawerStatus; + /// + /// Status compiler payment document + /// Статус составителя платежного документа + /// + public string DrawerStatus + { + get { return _drawerStatus; } + set { _drawerStatus = ValidateInput(value, "DrawerStatus", @"^.{1,2}$"); } + } + + private string _kpp; + /// + /// KPP of the payee (Tax Registration Code; Text, up to 9 characters.) + /// КПП получателя платежа + /// + public string KPP + { + get { return _kpp; } + set { _kpp = ValidateInput(value, "KPP", @"^.{1,9}$"); } + } + + private string _cbc; + /// + /// CBC + /// КБК + /// + public string CBC + { + get { return _cbc; } + set { _cbc = ValidateInput(value, "CBC", @"^.{1,20}$"); } + } + + private string _oktmo; + /// + /// All-Russian classifier territories of municipal formations + /// Общероссийский классификатор территорий муниципальных образований + /// + public string OKTMO + { + get { return _oktmo; } + set { _oktmo = ValidateInput(value, "OKTMO", @"^.{1,11}$"); } + } + + private string _paytReason; + /// + /// Basis of tax payment + /// Основание налогового платежа + /// + public string PaytReason + { + get { return _paytReason; } + set { _paytReason = ValidateInput(value, "PaytReason", @"^.{1,2}$"); } + } + + private string _taxPeriod; + /// + /// Taxable period + /// Налоговый период + /// + public string TaxPeriod + { + get { return _taxPeriod; } + set { _taxPeriod = ValidateInput(value, "ТaxPeriod", @"^.{1,10}$"); } + } + + private string _docNo; + /// + /// Document number + /// Номер документа + /// + public string DocNo + { + get { return _docNo; } + set { _docNo = ValidateInput(value, "DocNo", @"^.{1,15}$"); } + } + + /// + /// Document date + /// Дата документа + /// + public DateTime? DocDate { get; set; } + + private string _taxPaytKind; + /// + /// Payment type + /// Тип платежа + /// + public string TaxPaytKind + { + get { return _taxPaytKind; } + set { _taxPaytKind = ValidateInput(value, "TaxPaytKind", @"^.{1,2}$"); } + } + + /************************************************************************** + * The following fiels are no further specified in the standard + * document (https://sbqr.ru/standard/files/standart.pdf) thus there + * is no addition input validation implemented. + * **************************************************************************/ + + /// + /// Payer's surname + /// Фамилия плательщика + /// + public string LastName { get; set; } + + /// + /// Payer's name + /// Имя плательщика + /// + public string FirstName { get; set; } + + /// + /// Payer's patronymic + /// Отчество плательщика + /// + public string MiddleName { get; set; } + + /// + /// Payer's address + /// Адрес плательщика + /// + public string PayerAddress { get; set; } + + /// + /// Personal account of a budget recipient + /// Лицевой счет бюджетного получателя + /// + public string PersonalAccount { get; set; } + + /// + /// Payment document index + /// Индекс платежного документа + /// + public string DocIdx { get; set; } + + /// + /// Personal account number in the personalized accounting system in the Pension Fund of the Russian Federation - SNILS + /// № лицевого счета в системе персонифицированного учета в ПФР - СНИЛС + /// + public string PensAcc { get; set; } + + /// + /// Number of contract + /// Номер договора + /// + public string Contract { get; set; } + + /// + /// Personal account number of the payer in the organization (in the accounting system of the PU) + /// Номер лицевого счета плательщика в организации (в системе учета ПУ) + /// + public string PersAcc { get; set; } + + /// + /// Apartment number + /// Номер квартиры + /// + public string Flat { get; set; } + + /// + /// Phone number + /// Номер телефона + /// + public string Phone { get; set; } + + /// + /// DUL payer type + /// Вид ДУЛ плательщика + /// + public string PayerIdType { get; set; } + + /// + /// DUL number of the payer + /// Номер ДУЛ плательщика + /// + public string PayerIdNum { get; set; } + + /// + /// FULL NAME. child / student + /// Ф.И.О. ребенка/учащегося + /// + public string ChildFio { get; set; } + + /// + /// Date of birth + /// Дата рождения + /// + public DateTime? BirthDate { get; set; } + + /// + /// Due date / Invoice date + /// Срок платежа/дата выставления счета + /// + public string PaymTerm { get; set; } + + /// + /// Payment period + /// Период оплаты + /// + public string PaymPeriod { get; set; } + + /// + /// Payment type + /// Вид платежа + /// + public string Category { get; set; } + + /// + /// Service code / meter name + /// Код услуги/название прибора учета + /// + public string ServiceName { get; set; } + + /// + /// Metering device number + /// Номер прибора учета + /// + public string CounterId { get; set; } + + /// + /// Meter reading + /// Показание прибора учета + /// + public string CounterVal { get; set; } + + /// + /// Notification, accrual, account number + /// Номер извещения, начисления, счета + /// + public string QuittId { get; set; } + + /// + /// Date of notification / accrual / invoice / resolution (for traffic police) + /// Дата извещения/начисления/счета/постановления (для ГИБДД) + /// + public DateTime? QuittDate { get; set; } + + /// + /// Institution number (educational, medical) + /// Номер учреждения (образовательного, медицинского) + /// + public string InstNum { get; set; } + + /// + /// Kindergarten / school class number + /// Номер группы детсада/класса школы + /// + public string ClassNum { get; set; } + + /// + /// Full name of the teacher, specialist providing the service + /// ФИО преподавателя, специалиста, оказывающего услугу + /// + public string SpecFio { get; set; } + + /// + /// Insurance / additional service amount / Penalty amount (in kopecks) + /// Сумма страховки/дополнительной услуги/Сумма пени (в копейках) + /// + public string AddAmount { get; set; } + + /// + /// Resolution number (for traffic police) + /// Номер постановления (для ГИБДД) + /// + public string RuleId { get; set; } + + /// + /// Enforcement Proceedings Number + /// Номер исполнительного производства + /// + public string ExecId { get; set; } + + /// + /// Type of payment code (for example, for payments to Rosreestr) + /// Код вида платежа (например, для платежей в адрес Росреестра) + /// + public string RegType { get; set; } + + /// + /// Unique accrual identifier + /// Уникальный идентификатор начисления + /// + public string UIN { get; set; } + + /// + /// The technical code recommended by the service provider. Maybe used by the receiving organization to call the appropriate processing IT system. + /// Технический код, рекомендуемый для заполнения поставщиком услуг. Может использоваться принимающей организацией для вызова соответствующей обрабатывающей ИТ-системы. + /// + public TechCode? TechCode { get; set; } + } + + /// + /// (List of values of the technical code of the payment) + /// Перечень значений технического кода платежа + /// + public enum TechCode + { + Мобильная_связь_стационарный_телефон = 01, + Коммунальные_услуги_ЖКХAFN = 02, + ГИБДД_налоги_пошлины_бюджетные_платежи = 03, + Охранные_услуги = 04, + Услуги_оказываемые_УФМС = 05, + ПФР = 06, + Погашение_кредитов = 07, + Образовательные_учреждения = 08, + Интернет_и_ТВ = 09, + Электронные_деньги = 10, + Отдых_и_путешествия = 11, + Инвестиции_и_страхование = 12, + Спорт_и_здоровье = 13, + Благотворительные_и_общественные_организации = 14, + Прочие_услуги = 15 + } + + public enum CharacterSets + { + windows_1251 = 1, // Encoding.GetEncoding("windows-1251") + utf_8 = 2, // Encoding.UTF8 + koi8_r = 3 // Encoding.GetEncoding("koi8-r") + + } + + public class RussiaPaymentOrderException : Exception + { + public RussiaPaymentOrderException(string message) + : base(message) + { + } + } + + } + + + private static bool IsValidIban(string iban) + { + //Clean IBAN + var ibanCleared = iban.ToUpper().Replace(" ", "").Replace("-", ""); + + //Check for general structure + var structurallyValid = Regex.IsMatch(ibanCleared, @"^[a-zA-Z]{2}[0-9]{2}([a-zA-Z0-9]?){16,30}$"); + + //Check IBAN checksum + var checksumValid = false; + var sum = $"{ibanCleared.Substring(4)}{ibanCleared.Substring(0, 4)}".ToCharArray().Aggregate("", (current, c) => current + (char.IsLetter(c) ? (c - 55).ToString() : c.ToString())); + int m = 0; + for (int i = 0; i < (int)Math.Ceiling((sum.Length - 2) / 7d); i++){ + var offset = (i == 0 ? 0 : 2); + var start = i * 7 + offset; + var n = (i == 0 ? "" : m.ToString()) + sum.Substring(start, Math.Min(9 - offset, sum.Length - start)); + if (!int.TryParse(n, NumberStyles.Any, CultureInfo.InvariantCulture, out m)) + break; + m = m % 97; + } + checksumValid = m == 1; + return structurallyValid && checksumValid; + } + + private static bool IsValidQRIban(string iban) + { + var foundQrIid = false; + try + { + var ibanCleared = iban.ToUpper().Replace(" ", "").Replace("-", ""); + var possibleQrIid = Convert.ToInt32(ibanCleared.Substring(4, 5)); + foundQrIid = possibleQrIid >= 30000 && possibleQrIid <= 31999; + } catch { } + return IsValidIban(iban) && foundQrIid; + } + + private static bool IsValidBic(string bic) + { + return Regex.IsMatch(bic.Replace(" ", ""), @"^([a-zA-Z]{4}[a-zA-Z]{2}[a-zA-Z0-9]{2}([a-zA-Z0-9]{3})?)$"); + } + + + private static string ConvertStringToEncoding(string message, string encoding) + { + Encoding iso = Encoding.GetEncoding(encoding); + Encoding utf8 = Encoding.UTF8; + byte[] utfBytes = utf8.GetBytes(message); + byte[] isoBytes = Encoding.Convert(utf8, iso, utfBytes); + return iso.GetString(isoBytes, 0, isoBytes.Length); + } + + private static string EscapeInput(string inp, bool simple = false) + { + char[] forbiddenChars = {'\\', ';', ',', ':'}; + if (simple) + { + forbiddenChars = new char[1] {':'}; + } + foreach (var c in forbiddenChars) + { + inp = inp.Replace(c.ToString(), "\\" + c); + } + return inp; + } + + + + public static bool ChecksumMod10(string digits) + { + if (string.IsNullOrEmpty(digits) || digits.Length < 2) + return false; + int[] mods = new int[] { 0, 9, 4, 6, 8, 2, 7, 1, 3, 5 }; + + int remainder = 0; + for (int i = 0; i < digits.Length - 1; i++) + { + var num = Convert.ToInt32(digits[i]) - 48; + remainder = mods[(num + remainder) % 10]; + } + var checksum = (10 - remainder) % 10; + return checksum == Convert.ToInt32(digits[digits.Length - 1]) - 48; + } + + private static bool isHexStyle(string inp) + { + return (System.Text.RegularExpressions.Regex.IsMatch(inp, @"\A\b[0-9a-fA-F]+\b\Z") || System.Text.RegularExpressions.Regex.IsMatch(inp, @"\A\b(0[xX])?[0-9a-fA-F]+\b\Z")); + } + } +} diff --git a/vCardEditor/Libs/QRCoder/PdfByteQRCode.cs b/vCardEditor/Libs/QRCoder/PdfByteQRCode.cs new file mode 100644 index 0000000..2254979 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/PdfByteQRCode.cs @@ -0,0 +1,243 @@ +#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 || NET6_0_WINDOWS +using System; +using System.Collections.Generic; +using System.Drawing.Imaging; +using System.Globalization; +using System.IO; +using System.Linq; +using static QRCoder.QRCodeGenerator; + +/* This renderer is inspired by RemusVasii: https://github.com/codebude/QRCoder/issues/223 */ +namespace QRCoder +{ + +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + // ReSharper disable once InconsistentNaming + public class PdfByteQRCode : AbstractQRCode, IDisposable + { + private readonly byte[] pdfBinaryComment = new byte[] { 0x25, 0xe2, 0xe3, 0xcf, 0xd3 }; + + /// + /// Constructor without params to be used in COM Objects connections + /// + public PdfByteQRCode() { } + + public PdfByteQRCode(QRCodeData data) : base(data) { } + + /// + /// Creates a PDF document with a black & white QR code + /// + /// + /// + public byte[] GetGraphic(int pixelsPerModule) + { + return GetGraphic(pixelsPerModule, "#000000", "#ffffff"); + } + + /// + /// Takes hexadecimal color string #000000 and returns byte[]{ 0, 0, 0 } + /// + /// Color in HEX format like #ffffff + /// + private byte[] HexColorToByteArray(string colorString) + { + if (colorString.StartsWith("#")) + colorString = colorString.Substring(1); + byte[] byteColor = new byte[colorString.Length / 2]; + for (int i = 0; i < byteColor.Length; i++) + byteColor[i] = byte.Parse(colorString.Substring(i * 2, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture); + return byteColor; + } + + /// + /// Creates a PDF document with given colors DPI and quality + /// + /// + /// + /// + /// + /// + /// + public byte[] GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex, int dpi = 150, long jpgQuality = 85) + { + byte[] jpgArray = null, pngArray = null; + var imgSize = QrCodeData.ModuleMatrix.Count * pixelsPerModule; + var pdfMediaSize = (imgSize * 72 / dpi).ToString(CultureInfo.InvariantCulture); + + //Get QR code image + using (var qrCode = new PngByteQRCode(QrCodeData)) + { + pngArray = qrCode.GetGraphic(pixelsPerModule, HexColorToByteArray(darkColorHtmlHex), HexColorToByteArray(lightColorHtmlHex)); + } + + //Create image and transofrm to JPG + using (var msPng = new MemoryStream()) + { + msPng.Write(pngArray, 0, pngArray.Length); + var img = System.Drawing.Image.FromStream(msPng); + using (var msJpeg = new MemoryStream()) + { + // Create JPEG with specified quality + var jpgImageCodecInfo = ImageCodecInfo.GetImageEncoders().First(x => x.MimeType == "image/jpeg"); + var jpgEncoderParameters = new EncoderParameters(1) { + Param = new EncoderParameter[]{ new EncoderParameter(Encoder.Quality, jpgQuality) } + }; + img.Save(msJpeg, jpgImageCodecInfo, jpgEncoderParameters); + jpgArray = msJpeg.ToArray(); + } + } + + //Create PDF document + using (var stream = new MemoryStream()) + { + var writer = new StreamWriter(stream, System.Text.Encoding.GetEncoding("ASCII")); + + var xrefs = new List(); + + writer.Write("%PDF-1.5\r\n"); + writer.Flush(); + + stream.Write(pdfBinaryComment, 0, pdfBinaryComment.Length); + writer.WriteLine(); + + writer.Flush(); + xrefs.Add(stream.Position); + + writer.Write( + xrefs.Count.ToString() + " 0 obj\r\n" + + "<<\r\n" + + "/Type /Catalog\r\n" + + "/Pages 2 0 R\r\n" + + ">>\r\n" + + "endobj\r\n" + ); + + writer.Flush(); + xrefs.Add(stream.Position); + + writer.Write( + xrefs.Count.ToString() + " 0 obj\r\n" + + "<<\r\n" + + "/Count 1\r\n" + + "/Kids [ <<\r\n" + + "/Type /Page\r\n" + + "/Parent 2 0 R\r\n" + + "/MediaBox [0 0 " + pdfMediaSize + " " + pdfMediaSize + "]\r\n" + + "/Resources << /ProcSet [ /PDF /ImageC ]\r\n" + + "/XObject << /Im1 4 0 R >> >>\r\n" + + "/Contents 3 0 R\r\n" + + ">> ]\r\n" + + ">>\r\n" + + "endobj\r\n" + ); + + var X = "q\r\n" + + pdfMediaSize + " 0 0 " + pdfMediaSize + " 0 0 cm\r\n" + + "/Im1 Do\r\n" + + "Q"; + + writer.Flush(); + xrefs.Add(stream.Position); + + writer.Write( + xrefs.Count.ToString() + " 0 obj\r\n" + + "<< /Length " + X.Length.ToString() + " >>\r\n" + + "stream\r\n" + + X + "endstream\r\n" + + "endobj\r\n" + ); + + writer.Flush(); + xrefs.Add(stream.Position); + + writer.Write( + xrefs.Count.ToString() + " 0 obj\r\n" + + "<<\r\n" + + "/Name /Im1\r\n" + + "/Type /XObject\r\n" + + "/Subtype /Image\r\n" + + "/Width " + imgSize.ToString() + "/Height " + imgSize.ToString() + "/Length 5 0 R\r\n" + + "/Filter /DCTDecode\r\n" + + "/ColorSpace /DeviceRGB\r\n" + + "/BitsPerComponent 8\r\n" + + ">>\r\n" + + "stream\r\n" + ); + writer.Flush(); + stream.Write(jpgArray, 0, jpgArray.Length); + writer.Write( + "\r\n" + + "endstream\r\n" + + "endobj\r\n" + ); + + writer.Flush(); + xrefs.Add(stream.Position); + + writer.Write( + xrefs.Count.ToString() + " 0 obj\r\n" + + jpgArray.Length.ToString() + " endobj\r\n" + ); + + writer.Flush(); + var startxref = stream.Position; + + writer.Write( + "xref\r\n" + + "0 " + (xrefs.Count + 1).ToString() + "\r\n" + + "0000000000 65535 f\r\n" + ); + + foreach (var refValue in xrefs) + writer.Write(refValue.ToString("0000000000") + " 00000 n\r\n"); + + writer.Write( + "trailer\r\n" + + "<<\r\n" + + "/Size " + (xrefs.Count + 1).ToString() + "\r\n" + + "/Root 1 0 R\r\n" + + ">>\r\n" + + "startxref\r\n" + + startxref.ToString() + "\r\n" + + "%%EOF" + ); + + writer.Flush(); + + stream.Position = 0; + + return stream.ToArray(); + } + } + } + +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public static class PdfByteQRCodeHelper + { + public static byte[] GetQRCode(string plainText, int pixelsPerModule, string darkColorHtmlHex, + string lightColorHtmlHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, + EciMode eciMode = EciMode.Default, int requestedVersion = -1) + { + using (var qrGenerator = new QRCodeGenerator()) + using ( + var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, + requestedVersion)) + using (var qrCode = new PdfByteQRCode(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColorHtmlHex, lightColorHtmlHex); + } + + public static byte[] GetQRCode(string txt, ECCLevel eccLevel, int size) + { + using (var qrGen = new QRCodeGenerator()) + using (var qrCode = qrGen.CreateQrCode(txt, eccLevel)) + using (var qrBmp = new PdfByteQRCode(qrCode)) + return qrBmp.GetGraphic(size); + + } + } +} +#endif \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/PngByteQRCode.cs b/vCardEditor/Libs/QRCoder/PngByteQRCode.cs new file mode 100644 index 0000000..239287d --- /dev/null +++ b/vCardEditor/Libs/QRCoder/PngByteQRCode.cs @@ -0,0 +1,341 @@ +using System; +using System.IO; +using System.IO.Compression; +using static QRCoder.QRCodeGenerator; + +namespace QRCoder +{ + public sealed class PngByteQRCode : AbstractQRCode, IDisposable + { + /// + /// Constructor without params to be used in COM Objects connections + /// + public PngByteQRCode() { } + + public PngByteQRCode(QRCodeData data) : base(data) + { + } + + + /// + /// Creates a black & white PNG of the QR code, using 1-bit grayscale. + /// + public byte[] GetGraphic(int pixelsPerModule, bool drawQuietZones = true) + { + using (var png = new PngBuilder()) + { + var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; + png.WriteHeader(size, size, 1, PngBuilder.ColorType.Greyscale); + png.WriteScanlines(this.DrawScanlines(pixelsPerModule, drawQuietZones)); + png.WriteEnd(); + return png.GetBytes(); + } + } + + /// + /// Creates 2-color PNG of the QR code, using 1-bit indexed color. Accepts 3-byte RGB colors for normal images and 4-byte RGBA-colors for transparent images. + /// + public byte[] GetGraphic(int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, bool drawQuietZones = true) + { + using (var png = new PngBuilder()) + { + var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; + png.WriteHeader(size, size, 1, PngBuilder.ColorType.Indexed); + png.WritePalette(darkColorRgba, lightColorRgba); + png.WriteScanlines(this.DrawScanlines(pixelsPerModule, drawQuietZones)); + png.WriteEnd(); + return png.GetBytes(); + } + } + + /// + /// Creates a bitmap where each pixel is represented by a single bit, dark = 0 and light = 1. + /// + private byte[] DrawScanlines(int pixelsPerModule, bool drawQuietZones) + { + var moduleMatrix = this.QrCodeData.ModuleMatrix; + var matrixSize = moduleMatrix.Count - (drawQuietZones ? 0 : 8); + var quietZoneOffset = (drawQuietZones ? 0 : 4); + var bytesPerScanline = (matrixSize * pixelsPerModule + 7) / 8 + 1; // A monochrome scanline is one byte for filter type then one bit per pixel. + var scanlines = new byte[bytesPerScanline * matrixSize * pixelsPerModule]; + + for (var y = 0; y < matrixSize; y++) + { + var modules = moduleMatrix[y+quietZoneOffset]; + var scanlineOffset = y * pixelsPerModule * bytesPerScanline; + + // Draw a scanline with the modules from the QR code. + for (var x = 0; x < matrixSize; x++) + { + if (modules[x + quietZoneOffset]) + { + continue; + } + + var pixelIndex = x * pixelsPerModule; + var endIndex = pixelIndex + pixelsPerModule; + for (; pixelIndex < endIndex; pixelIndex++) + { + scanlines[scanlineOffset + 1 + pixelIndex / 8] |= (byte)(0x80 >> (pixelIndex % 8)); + } + } + + // Copy the scanline required number of times. + for (var copyCount = 1; copyCount < pixelsPerModule; copyCount++) + { + Array.Copy(scanlines, scanlineOffset, scanlines, scanlineOffset + copyCount * bytesPerScanline, bytesPerScanline); + } + } + + return scanlines; + } + + /// + /// Writes the chunks that make up a PNG file. + /// + /// + /// See https://www.w3.org/TR/2003/REC-PNG-20031110 and https://www.ietf.org/rfc/rfc1950.txt. + /// + private sealed class PngBuilder : IDisposable + { + private static readonly byte[] PngSignature = { 0x89, 0x50, 0x4E, 0x47, 0x0D, 0x0A, 0x1A, 0x0A }; + + private static readonly uint[] CrcTable = { + 0x00000000, 0x77073096, 0xEE0E612C, 0x990951BA, 0x076DC419, 0x706AF48F, 0xE963A535, 0x9E6495A3, 0x0EDB8832, 0x79DCB8A4, 0xE0D5E91E, 0x97D2D988, 0x09B64C2B, 0x7EB17CBD, 0xE7B82D07, 0x90BF1D91, 0x1DB71064, 0x6AB020F2, 0xF3B97148, 0x84BE41DE, 0x1ADAD47D, 0x6DDDE4EB, 0xF4D4B551, 0x83D385C7, 0x136C9856, 0x646BA8C0, 0xFD62F97A, 0x8A65C9EC, 0x14015C4F, 0x63066CD9, 0xFA0F3D63, 0x8D080DF5, 0x3B6E20C8, 0x4C69105E, 0xD56041E4, 0xA2677172, 0x3C03E4D1, 0x4B04D447, 0xD20D85FD, 0xA50AB56B, 0x35B5A8FA, 0x42B2986C, 0xDBBBC9D6, 0xACBCF940, 0x32D86CE3, 0x45DF5C75, 0xDCD60DCF, 0xABD13D59, 0x26D930AC, 0x51DE003A, 0xC8D75180, 0xBFD06116, 0x21B4F4B5, 0x56B3C423, 0xCFBA9599, 0xB8BDA50F, 0x2802B89E, 0x5F058808, 0xC60CD9B2, 0xB10BE924, 0x2F6F7C87, 0x58684C11, 0xC1611DAB, 0xB6662D3D, + 0x76DC4190, 0x01DB7106, 0x98D220BC, 0xEFD5102A, 0x71B18589, 0x06B6B51F, 0x9FBFE4A5, 0xE8B8D433, 0x7807C9A2, 0x0F00F934, 0x9609A88E, 0xE10E9818, 0x7F6A0DBB, 0x086D3D2D, 0x91646C97, 0xE6635C01, 0x6B6B51F4, 0x1C6C6162, 0x856530D8, 0xF262004E, 0x6C0695ED, 0x1B01A57B, 0x8208F4C1, 0xF50FC457, 0x65B0D9C6, 0x12B7E950, 0x8BBEB8EA, 0xFCB9887C, 0x62DD1DDF, 0x15DA2D49, 0x8CD37CF3, 0xFBD44C65, 0x4DB26158, 0x3AB551CE, 0xA3BC0074, 0xD4BB30E2, 0x4ADFA541, 0x3DD895D7, 0xA4D1C46D, 0xD3D6F4FB, 0x4369E96A, 0x346ED9FC, 0xAD678846, 0xDA60B8D0, 0x44042D73, 0x33031DE5, 0xAA0A4C5F, 0xDD0D7CC9, 0x5005713C, 0x270241AA, 0xBE0B1010, 0xC90C2086, 0x5768B525, 0x206F85B3, 0xB966D409, 0xCE61E49F, 0x5EDEF90E, 0x29D9C998, 0xB0D09822, 0xC7D7A8B4, 0x59B33D17, 0x2EB40D81, 0xB7BD5C3B, 0xC0BA6CAD, + 0xEDB88320, 0x9ABFB3B6, 0x03B6E20C, 0x74B1D29A, 0xEAD54739, 0x9DD277AF, 0x04DB2615, 0x73DC1683, 0xE3630B12, 0x94643B84, 0x0D6D6A3E, 0x7A6A5AA8, 0xE40ECF0B, 0x9309FF9D, 0x0A00AE27, 0x7D079EB1, 0xF00F9344, 0x8708A3D2, 0x1E01F268, 0x6906C2FE, 0xF762575D, 0x806567CB, 0x196C3671, 0x6E6B06E7, 0xFED41B76, 0x89D32BE0, 0x10DA7A5A, 0x67DD4ACC, 0xF9B9DF6F, 0x8EBEEFF9, 0x17B7BE43, 0x60B08ED5, 0xD6D6A3E8, 0xA1D1937E, 0x38D8C2C4, 0x4FDFF252, 0xD1BB67F1, 0xA6BC5767, 0x3FB506DD, 0x48B2364B, 0xD80D2BDA, 0xAF0A1B4C, 0x36034AF6, 0x41047A60, 0xDF60EFC3, 0xA867DF55, 0x316E8EEF, 0x4669BE79, 0xCB61B38C, 0xBC66831A, 0x256FD2A0, 0x5268E236, 0xCC0C7795, 0xBB0B4703, 0x220216B9, 0x5505262F, 0xC5BA3BBE, 0xB2BD0B28, 0x2BB45A92, 0x5CB36A04, 0xC2D7FFA7, 0xB5D0CF31, 0x2CD99E8B, 0x5BDEAE1D, + 0x9B64C2B0, 0xEC63F226, 0x756AA39C, 0x026D930A, 0x9C0906A9, 0xEB0E363F, 0x72076785, 0x05005713, 0x95BF4A82, 0xE2B87A14, 0x7BB12BAE, 0x0CB61B38, 0x92D28E9B, 0xE5D5BE0D, 0x7CDCEFB7, 0x0BDBDF21, 0x86D3D2D4, 0xF1D4E242, 0x68DDB3F8, 0x1FDA836E, 0x81BE16CD, 0xF6B9265B, 0x6FB077E1, 0x18B74777, 0x88085AE6, 0xFF0F6A70, 0x66063BCA, 0x11010B5C, 0x8F659EFF, 0xF862AE69, 0x616BFFD3, 0x166CCF45, 0xA00AE278, 0xD70DD2EE, 0x4E048354, 0x3903B3C2, 0xA7672661, 0xD06016F7, 0x4969474D, 0x3E6E77DB, 0xAED16A4A, 0xD9D65ADC, 0x40DF0B66, 0x37D83BF0, 0xA9BCAE53, 0xDEBB9EC5, 0x47B2CF7F, 0x30B5FFE9, 0xBDBDF21C, 0xCABAC28A, 0x53B39330, 0x24B4A3A6, 0xBAD03605, 0xCDD70693, 0x54DE5729, 0x23D967BF, 0xB3667A2E, 0xC4614AB8, 0x5D681B02, 0x2A6F2B94, 0xB40BBE37, 0xC30C8EA1, 0x5A05DF1B, 0x2D02EF8D + }; + + // ReSharper disable InconsistentNaming + // Chunk types + private static readonly byte[] IHDR = { 73, 72, 68, 82 }; + + private static readonly byte[] IDAT = { 73, 68, 65, 84 }; + + private static readonly byte[] IEND = { 73, 69, 78, 68 }; + + private static readonly byte[] PLTE = { 80, 76, 84, 69 }; + + private static readonly byte[] tRNS = { 116, 82, 78, 83 }; + // ReSharper enable InconsistentNaming + + public enum ColorType : byte + { + Greyscale = 0, + Indexed = 3 + } + + private MemoryStream stream = new MemoryStream(); + + public void Dispose() + { + this.stream?.Dispose(); + this.stream = null; + } + + public byte[] GetBytes() + { + var bytes = this.stream.ToArray(); + + // Enumerate chunks in file and insert their CRC32 checksums. + var chunkOffset = PngSignature.Length; + while (chunkOffset < bytes.Length) + { + // Read length field. + var dataLength = (bytes[chunkOffset] << 24) | (bytes[chunkOffset + 1] << 16) | (bytes[chunkOffset + 2] << 8) | bytes[chunkOffset + 3]; + + // CRC is computed from type and data fields. + var crc = Crc32(bytes, chunkOffset + 4, dataLength + 4); + + // Write CRC to end of chunk. + var crcOffset = chunkOffset + 8 + dataLength; + bytes[crcOffset + 0] = (byte)(crc >> 24); + bytes[crcOffset + 1] = (byte)(crc >> 16); + bytes[crcOffset + 2] = (byte)(crc >> 8); + bytes[crcOffset + 3] = (byte)crc; + + // Seek to next chunk. + chunkOffset = crcOffset + 4; + } + + return bytes; + } + + /// + /// Writes the IHDR chunk. This must be the first chunk in the file. + /// + public void WriteHeader(int width, int height, byte bitDepth, ColorType colorType) + { + this.stream.Write(PngSignature, 0, PngSignature.Length); + this.WriteChunkStart(IHDR, 13); + + // Size. + this.WriteIntBigEndian((uint)width); + this.WriteIntBigEndian((uint)height); + + // Color. + this.stream.WriteByte(bitDepth); + this.stream.WriteByte((byte)colorType); + + // Constants. + this.stream.WriteByte(0); + this.stream.WriteByte(0); + this.stream.WriteByte(0); + + this.WriteChunkEnd(); + } + + /// + /// Writes the PLTE chunk, and also the tRNS chunk if necessary. Must come before the IDAT chunk. + /// + public void WritePalette(params byte[][] rgbaColors) + { + const int Red = 0, Green = 1, Blue = 2, Alpha = 3; + const byte Opaque = 255; + var hasAlpha = false; + + this.WriteChunkStart(PLTE, 3 * rgbaColors.Length); + foreach (var color in rgbaColors) + { + hasAlpha |= color.Length > Alpha && color[Alpha] < Opaque; + this.stream.WriteByte(color[Red]); + this.stream.WriteByte(color[Green]); + this.stream.WriteByte(color[Blue]); + } + this.WriteChunkEnd(); + + if (!hasAlpha) + { + return; + } + + this.WriteChunkStart(tRNS, rgbaColors.Length); + foreach (var color in rgbaColors) + { + this.stream.WriteByte(color.Length > Alpha ? color[Alpha] : Opaque); + } + this.WriteChunkEnd(); + } + + /// + /// Writes the IDAT chunk with the actual picture. + /// + public void WriteScanlines(byte[] scanlines) + { + using (var idatStream = new MemoryStream()) + { + Deflate(idatStream, scanlines); + + this.WriteChunkStart(IDAT, (int)(idatStream.Length + 6)); + + // Deflate header. + this.stream.WriteByte(0x78); // 8 Deflate algorithm, 7 max window size + this.stream.WriteByte(0x9C); // Check bits. + + // Compressed data. + idatStream.Position = 0; +#if NET35 + idatStream.WriteTo(this.stream); +#else + idatStream.CopyTo(this.stream); +#endif + // Deflate checksum. + var adler = Adler32(scanlines, 0, scanlines.Length); + this.WriteIntBigEndian(adler); + + this.WriteChunkEnd(); + } + } + + /// + /// Writes the IEND chunk. This must be the last chunk in the file. + /// + public void WriteEnd() + { + this.WriteChunkStart(IEND, 0); + this.WriteChunkEnd(); + } + + private void WriteChunkStart(byte[] type, int length) + { + this.WriteIntBigEndian((uint)length); + this.stream.Write(type, 0, 4); + } + + private void WriteChunkEnd() + { + // Reserves 4 bytes space for crc32 so GetBytes can add it later. + this.stream.SetLength(this.stream.Length + 4); + this.stream.Position += 4; + } + + private void WriteIntBigEndian(uint value) + { + this.stream.WriteByte((byte)(value >> 24)); + this.stream.WriteByte((byte)(value >> 16)); + this.stream.WriteByte((byte)(value >> 8)); + this.stream.WriteByte((byte)value); + } + + private static void Deflate(Stream output, byte[] bytes) + { + using (var deflateStream = new DeflateStream(output, CompressionMode.Compress, leaveOpen: true)) + { + deflateStream.Write(bytes, 0, bytes.Length); + } + } + + // Reference implementation from RFC 1950. Not optimized. + private static uint Adler32(byte[] data, int index, int length) + { + const uint Base = 65521; + uint s1 = 1, s2 = 0; + + var end = index + length; + for (var n = index; n < end; n++) + { + s1 = (s1 + data[n]) % Base; + s2 = (s2 + s1) % Base; + } + + return (s2 << 16) + s1; + } + + // Reference implementation from REC-PNG-20031110. Not optimized. + private static uint Crc32(byte[] data, int index, int length) + { + var c = 0xffffffff; + + var end = index + length; + for (var n = index; n < end; n++) + { + c = CrcTable[(c ^ data[n]) & 0xff] ^ (c >> 8); + } + + return c ^ 0xffffffff; + } + } + } + + public static class PngByteQRCodeHelper + { + public static byte[] GetQRCode(string plainText, int pixelsPerModule, byte[] darkColorRgba, byte[] lightColorRgba, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true) + { + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) + using (var qrCode = new PngByteQRCode(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColorRgba, lightColorRgba, drawQuietZones); + } + + + + public static byte[] GetQRCode(string txt, QRCodeGenerator.ECCLevel eccLevel, int size, bool drawQuietZones = true) + { + using (var qrGen = new QRCodeGenerator()) + using (var qrCode = qrGen.CreateQrCode(txt, eccLevel)) + using (var qrPng = new PngByteQRCode(qrCode)) + return qrPng.GetGraphic(size, drawQuietZones); + } + } +} diff --git a/vCardEditor/Libs/QRCoder/PostscriptQRCode.cs b/vCardEditor/Libs/QRCoder/PostscriptQRCode.cs new file mode 100644 index 0000000..710551d --- /dev/null +++ b/vCardEditor/Libs/QRCoder/PostscriptQRCode.cs @@ -0,0 +1,161 @@ +#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 || NET6_0_WINDOWS +using System; +using System.Drawing; +using static QRCoder.QRCodeGenerator; + +namespace QRCoder +{ + +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public class PostscriptQRCode : AbstractQRCode, IDisposable + { + /// + /// Constructor without params to be used in COM Objects connections + /// + public PostscriptQRCode() { } + public PostscriptQRCode(QRCodeData data) : base(data) { } + + public string GetGraphic(int pointsPerModule, bool epsFormat = false) + { + var viewBox = new Size(pointsPerModule * this.QrCodeData.ModuleMatrix.Count, pointsPerModule * this.QrCodeData.ModuleMatrix.Count); + return this.GetGraphic(viewBox, Color.Black, Color.White, true, epsFormat); + } + public string GetGraphic(int pointsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true, bool epsFormat = false) + { + var viewBox = new Size(pointsPerModule * this.QrCodeData.ModuleMatrix.Count, pointsPerModule * this.QrCodeData.ModuleMatrix.Count); + return this.GetGraphic(viewBox, darkColor, lightColor, drawQuietZones, epsFormat); + } + + public string GetGraphic(int pointsPerModule, string darkColorHex, string lightColorHex, bool drawQuietZones = true, bool epsFormat = false) + { + var viewBox = new Size(pointsPerModule * this.QrCodeData.ModuleMatrix.Count, pointsPerModule * this.QrCodeData.ModuleMatrix.Count); + return this.GetGraphic(viewBox, darkColorHex, lightColorHex, drawQuietZones, epsFormat); + } + + public string GetGraphic(Size viewBox, bool drawQuietZones = true, bool epsFormat = false) + { + return this.GetGraphic(viewBox, Color.Black, Color.White, drawQuietZones, epsFormat); + } + + public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex, bool drawQuietZones = true, bool epsFormat = false) + { + return this.GetGraphic(viewBox, ColorTranslator.FromHtml(darkColorHex), ColorTranslator.FromHtml(lightColorHex), drawQuietZones, epsFormat); + } + + public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool drawQuietZones = true, bool epsFormat = false) + { + var offset = drawQuietZones ? 0 : 4; + var drawableModulesCount = this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : offset * 2); + var pointsPerModule = (double)Math.Min(viewBox.Width, viewBox.Height) / (double)drawableModulesCount; + + string psFile = string.Format(psHeader, new object[] { + DateTime.Now.ToString("s"), CleanSvgVal(viewBox.Width), CleanSvgVal(pointsPerModule), + epsFormat ? "EPSF-3.0" : string.Empty + }); + psFile += string.Format(psFunctions, new object[] { + CleanSvgVal(darkColor.R /255.0), CleanSvgVal(darkColor.G /255.0), CleanSvgVal(darkColor.B /255.0), + CleanSvgVal(lightColor.R /255.0), CleanSvgVal(lightColor.G /255.0), CleanSvgVal(lightColor.B /255.0), + drawableModulesCount + }); + + for (int xi = offset; xi < offset + drawableModulesCount; xi++) + { + if (xi > offset) + psFile += "nl\n"; + for (int yi = offset; yi < offset + drawableModulesCount; yi++) + { + psFile += (this.QrCodeData.ModuleMatrix[xi][yi] ? "f " : "b "); + } + psFile += "\n"; + } + return psFile + psFooter; + } + + private string CleanSvgVal(double input) + { + //Clean double values for international use/formats + return input.ToString(System.Globalization.CultureInfo.InvariantCulture); + } + + private const string psHeader = @"%!PS-Adobe-3.0 {3} +%%Creator: QRCoder.NET +%%Title: QRCode +%%CreationDate: {0} +%%DocumentData: Clean7Bit +%%Origin: 0 +%%DocumentMedia: Default {1} {1} 0 () () +%%BoundingBox: 0 0 {1} {1} +%%LanguageLevel: 2 +%%Pages: 1 +%%Page: 1 1 +%%EndComments +%%BeginConstants +/sz {1} def +/sc {2} def +%%EndConstants +%%BeginFeature: *PageSize Default +<< /PageSize [ sz sz ] /ImagingBBox null >> setpagedevice +%%EndFeature +"; + + private const string psFunctions = @"%%BeginFunctions +/csquare {{ + newpath + 0 0 moveto + 0 1 rlineto + 1 0 rlineto + 0 -1 rlineto + closepath + setrgbcolor + fill +}} def +/f {{ + {0} {1} {2} csquare + 1 0 translate +}} def +/b {{ + 1 0 translate +}} def +/background {{ + {3} {4} {5} csquare +}} def +/nl {{ + -{6} -1 translate +}} def +%%EndFunctions +%%BeginBody +0 0 moveto +gsave +sz sz scale +background +grestore +gsave +sc sc scale +0 {6} 1 sub translate +"; + + private const string psFooter = @"%%EndBody +grestore +showpage +%%EOF +"; + } + +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public static class PostscriptQRCodeHelper + { + public static string GetQRCode(string plainText, int pointsPerModule, string darkColorHex, string lightColorHex, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, bool drawQuietZones = true, bool epsFormat = false) + { + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) + using (var qrCode = new PostscriptQRCode(qrCodeData)) + return qrCode.GetGraphic(pointsPerModule, darkColorHex, lightColorHex, drawQuietZones, epsFormat); + } + } +} + +#endif \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/QRCode.cs b/vCardEditor/Libs/QRCoder/QRCode.cs new file mode 100644 index 0000000..b23cd07 --- /dev/null +++ b/vCardEditor/Libs/QRCoder/QRCode.cs @@ -0,0 +1,138 @@ +using System; +using System.Drawing; +using System.Drawing.Drawing2D; +using static QRCoder.QRCodeGenerator; + +namespace QRCoder +{ + public class QRCode : AbstractQRCode, IDisposable + { + /// + /// Constructor without params to be used in COM Objects connections + /// + public QRCode() { } + + public QRCode(QRCodeData data) : base(data) {} + + public Bitmap GetGraphic(int pixelsPerModule) + { + return this.GetGraphic(pixelsPerModule, Color.Black, Color.White, true); + } + + public Bitmap GetGraphic(int pixelsPerModule, string darkColorHtmlHex, string lightColorHtmlHex, bool drawQuietZones = true) + { + return this.GetGraphic(pixelsPerModule, ColorTranslator.FromHtml(darkColorHtmlHex), ColorTranslator.FromHtml(lightColorHtmlHex), drawQuietZones); + } + + public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, bool drawQuietZones = true) + { + var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; + var offset = drawQuietZones ? 0 : 4 * pixelsPerModule; + + var bmp = new Bitmap(size, size); + using (var gfx = Graphics.FromImage(bmp)) + using (var lightBrush = new SolidBrush(lightColor)) + using (var darkBrush = new SolidBrush(darkColor)) + { + for (var x = 0; x < size + offset; x = x + pixelsPerModule) + { + for (var y = 0; y < size + offset; y = y + pixelsPerModule) + { + var module = this.QrCodeData.ModuleMatrix[(y + pixelsPerModule) / pixelsPerModule - 1][(x + pixelsPerModule) / pixelsPerModule - 1]; + + if (module) + { + gfx.FillRectangle(darkBrush, new Rectangle(x - offset, y - offset, pixelsPerModule, pixelsPerModule)); + } + else + { + gfx.FillRectangle(lightBrush, new Rectangle(x - offset, y - offset, pixelsPerModule, pixelsPerModule)); + } + } + } + + gfx.Save(); + } + + return bmp; + } + + public Bitmap GetGraphic(int pixelsPerModule, Color darkColor, Color lightColor, Bitmap icon=null, int iconSizePercent=15, int iconBorderWidth = 0, bool drawQuietZones = true, Color? iconBackgroundColor = null) + { + var size = (this.QrCodeData.ModuleMatrix.Count - (drawQuietZones ? 0 : 8)) * pixelsPerModule; + var offset = drawQuietZones ? 0 : 4 * pixelsPerModule; + + var bmp = new Bitmap(size, size, System.Drawing.Imaging.PixelFormat.Format32bppArgb); + + using (var gfx = Graphics.FromImage(bmp)) + using (var lightBrush = new SolidBrush(lightColor)) + using (var darkBrush = new SolidBrush(darkColor)) + { + gfx.InterpolationMode = InterpolationMode.HighQualityBicubic; + gfx.CompositingQuality = CompositingQuality.HighQuality; + gfx.Clear(lightColor); + var drawIconFlag = icon != null && iconSizePercent > 0 && iconSizePercent <= 100; + + for (var x = 0; x < size + offset; x = x + pixelsPerModule) + { + for (var y = 0; y < size + offset; y = y + pixelsPerModule) + { + var moduleBrush = this.QrCodeData.ModuleMatrix[(y + pixelsPerModule) / pixelsPerModule - 1][(x + pixelsPerModule) / pixelsPerModule - 1] ? darkBrush : lightBrush; + gfx.FillRectangle(moduleBrush , new Rectangle(x - offset, y - offset, pixelsPerModule, pixelsPerModule)); + } + } + + if (drawIconFlag) + { + float iconDestWidth = iconSizePercent * bmp.Width / 100f; + float iconDestHeight = drawIconFlag ? iconDestWidth * icon.Height / icon.Width : 0; + float iconX = (bmp.Width - iconDestWidth) / 2; + float iconY = (bmp.Height - iconDestHeight) / 2; + var centerDest = new RectangleF(iconX - iconBorderWidth, iconY - iconBorderWidth, iconDestWidth + iconBorderWidth * 2, iconDestHeight + iconBorderWidth * 2); + var iconDestRect = new RectangleF(iconX, iconY, iconDestWidth, iconDestHeight); + var iconBgBrush = iconBackgroundColor != null ? new SolidBrush((Color)iconBackgroundColor) : lightBrush; + //Only render icon/logo background, if iconBorderWith is set > 0 + if (iconBorderWidth > 0) + { + using (GraphicsPath iconPath = CreateRoundedRectanglePath(centerDest, iconBorderWidth * 2)) + { + gfx.FillPath(iconBgBrush, iconPath); + } + } + gfx.DrawImage(icon, iconDestRect, new RectangleF(0, 0, icon.Width, icon.Height), GraphicsUnit.Pixel); + } + + gfx.Save(); + } + + return bmp; + } + + internal GraphicsPath CreateRoundedRectanglePath(RectangleF rect, int cornerRadius) + { + var roundedRect = new GraphicsPath(); + roundedRect.AddArc(rect.X, rect.Y, cornerRadius * 2, cornerRadius * 2, 180, 90); + roundedRect.AddLine(rect.X + cornerRadius, rect.Y, rect.Right - cornerRadius * 2, rect.Y); + roundedRect.AddArc(rect.X + rect.Width - cornerRadius * 2, rect.Y, cornerRadius * 2, cornerRadius * 2, 270, 90); + roundedRect.AddLine(rect.Right, rect.Y + cornerRadius * 2, rect.Right, rect.Y + rect.Height - cornerRadius * 2); + roundedRect.AddArc(rect.X + rect.Width - cornerRadius * 2, rect.Y + rect.Height - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 0, 90); + roundedRect.AddLine(rect.Right - cornerRadius * 2, rect.Bottom, rect.X + cornerRadius * 2, rect.Bottom); + roundedRect.AddArc(rect.X, rect.Bottom - cornerRadius * 2, cornerRadius * 2, cornerRadius * 2, 90, 90); + roundedRect.AddLine(rect.X, rect.Bottom - cornerRadius * 2, rect.X, rect.Y + cornerRadius * 2); + roundedRect.CloseFigure(); + return roundedRect; + } + } + + + public static class QRCodeHelper + { + public static Bitmap GetQRCode(string plainText, int pixelsPerModule, Color darkColor, Color lightColor, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1, Bitmap icon = null, int iconSizePercent = 15, int iconBorderWidth = 0, bool drawQuietZones = true) + { + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) + using (var qrCode = new QRCode(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColor, lightColor, icon, iconSizePercent, iconBorderWidth, drawQuietZones); + } + } +} \ No newline at end of file diff --git a/vCardEditor/Libs/QRCoder/QRCodeData.cs b/vCardEditor/Libs/QRCoder/QRCodeData.cs new file mode 100644 index 0000000..8a19b5a --- /dev/null +++ b/vCardEditor/Libs/QRCoder/QRCodeData.cs @@ -0,0 +1,185 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; + +namespace QRCoder +{ + using QRCoder.Framework4._0Methods; + using System; + using System.IO; + using System.IO.Compression; + + public class QRCodeData : IDisposable + { + public List ModuleMatrix { get; set; } + + public QRCodeData(int version) + { + this.Version = version; + var size = ModulesPerSideFromVersion(version); + this.ModuleMatrix = new List(); + for (var i = 0; i < size; i++) + this.ModuleMatrix.Add(new BitArray(size)); + } +#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 + public QRCodeData(string pathToRawData, Compression compressMode) : this(File.ReadAllBytes(pathToRawData), compressMode) + { + } +#endif + public QRCodeData(byte[] rawData, Compression compressMode) + { + var bytes = new List(rawData); + + //Decompress + if (compressMode == Compression.Deflate) + { + using (var input = new MemoryStream(bytes.ToArray())) + { + using (var output = new MemoryStream()) + { + using (var dstream = new DeflateStream(input, CompressionMode.Decompress)) + { + Stream4Methods.CopyTo(dstream, output); + } + bytes = new List(output.ToArray()); + } + } + } + else if (compressMode == Compression.GZip) + { + using (var input = new MemoryStream(bytes.ToArray())) + { + using (var output = new MemoryStream()) + { + using (var dstream = new GZipStream(input, CompressionMode.Decompress)) + { + Stream4Methods.CopyTo(dstream, output); + } + bytes = new List(output.ToArray()); + } + } + } + + if (bytes[0] != 0x51 || bytes[1] != 0x52 || bytes[2] != 0x52) + throw new Exception("Invalid raw data file. Filetype doesn't match \"QRR\"."); + + //Set QR code version + var sideLen = (int)bytes[4]; + bytes.RemoveRange(0, 5); + this.Version = (sideLen - 21 - 8) / 4 + 1; + + //Unpack + var modules = new Queue(8 * bytes.Count); + foreach (var b in bytes) + { + var bArr = new BitArray(new byte[] { b }); + for (int i = 7; i >= 0; i--) + { + modules.Enqueue((b & (1 << i)) != 0); + } + } + + //Build module matrix + this.ModuleMatrix = new List(sideLen); + for (int y = 0; y < sideLen; y++) + { + this.ModuleMatrix.Add(new BitArray(sideLen)); + for (int x = 0; x < sideLen; x++) + { + this.ModuleMatrix[y][x] = modules.Dequeue(); + } + } + + } + + public byte[] GetRawData(Compression compressMode) + { + var bytes = new List(); + + //Add header - signature ("QRR") + bytes.AddRange(new byte[]{ 0x51, 0x52, 0x52, 0x00 }); + + //Add header - rowsize + bytes.Add((byte)ModuleMatrix.Count); + + //Build data queue + var dataQueue = new Queue(); + foreach (var row in ModuleMatrix) + { + foreach (var module in row) + { + dataQueue.Enqueue((bool)module ? 1 : 0); + } + } + for (int i = 0; i < 8 - (ModuleMatrix.Count * ModuleMatrix.Count) % 8; i++) + { + dataQueue.Enqueue(0); + } + + //Process queue + while (dataQueue.Count > 0) + { + byte b = 0; + for (int i = 7; i >= 0; i--) + { + b += (byte)(dataQueue.Dequeue() << i); + } + bytes.Add(b); + } + var rawData = bytes.ToArray(); + + //Compress stream (optional) + if (compressMode == Compression.Deflate) + { + using (var output = new MemoryStream()) + { + using (var dstream = new DeflateStream(output, CompressionMode.Compress)) + { + dstream.Write(rawData, 0, rawData.Length); + } + rawData = output.ToArray(); + } + } + else if (compressMode == Compression.GZip) + { + using (var output = new MemoryStream()) + { + using (GZipStream gzipStream = new GZipStream(output, CompressionMode.Compress, true)) + { + gzipStream.Write(rawData, 0, rawData.Length); + } + rawData = output.ToArray(); + } + } + return rawData; + } + +#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 + public void SaveRawData(string filePath, Compression compressMode) + { + File.WriteAllBytes(filePath, GetRawData(compressMode)); + } +#endif + + public int Version { get; private set; } + + private static int ModulesPerSideFromVersion(int version) + { + return 21 + (version - 1) * 4; + } + + public void Dispose() + { + this.ModuleMatrix = null; + this.Version = 0; + + } + + public enum Compression + { + Uncompressed, + Deflate, + GZip + } + } +} diff --git a/vCardEditor/Libs/QRCoder/QRCodeGenerator.cs b/vCardEditor/Libs/QRCoder/QRCodeGenerator.cs new file mode 100644 index 0000000..c7aa20e --- /dev/null +++ b/vCardEditor/Libs/QRCoder/QRCodeGenerator.cs @@ -0,0 +1,1596 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Collections; + +namespace QRCoder +{ + public class QRCodeGenerator : IDisposable + { + private static readonly char[] alphanumEncTable = { ' ', '$', '%', '*', '+', '-', '.', '/', ':' }; + private static readonly int[] capacityBaseValues = { 41, 25, 17, 10, 34, 20, 14, 8, 27, 16, 11, 7, 17, 10, 7, 4, 77, 47, 32, 20, 63, 38, 26, 16, 48, 29, 20, 12, 34, 20, 14, 8, 127, 77, 53, 32, 101, 61, 42, 26, 77, 47, 32, 20, 58, 35, 24, 15, 187, 114, 78, 48, 149, 90, 62, 38, 111, 67, 46, 28, 82, 50, 34, 21, 255, 154, 106, 65, 202, 122, 84, 52, 144, 87, 60, 37, 106, 64, 44, 27, 322, 195, 134, 82, 255, 154, 106, 65, 178, 108, 74, 45, 139, 84, 58, 36, 370, 224, 154, 95, 293, 178, 122, 75, 207, 125, 86, 53, 154, 93, 64, 39, 461, 279, 192, 118, 365, 221, 152, 93, 259, 157, 108, 66, 202, 122, 84, 52, 552, 335, 230, 141, 432, 262, 180, 111, 312, 189, 130, 80, 235, 143, 98, 60, 652, 395, 271, 167, 513, 311, 213, 131, 364, 221, 151, 93, 288, 174, 119, 74, 772, 468, 321, 198, 604, 366, 251, 155, 427, 259, 177, 109, 331, 200, 137, 85, 883, 535, 367, 226, 691, 419, 287, 177, 489, 296, 203, 125, 374, 227, 155, 96, 1022, 619, 425, 262, 796, 483, 331, 204, 580, 352, 241, 149, 427, 259, 177, 109, 1101, 667, 458, 282, 871, 528, 362, 223, 621, 376, 258, 159, 468, 283, 194, 120, 1250, 758, 520, 320, 991, 600, 412, 254, 703, 426, 292, 180, 530, 321, 220, 136, 1408, 854, 586, 361, 1082, 656, 450, 277, 775, 470, 322, 198, 602, 365, 250, 154, 1548, 938, 644, 397, 1212, 734, 504, 310, 876, 531, 364, 224, 674, 408, 280, 173, 1725, 1046, 718, 442, 1346, 816, 560, 345, 948, 574, 394, 243, 746, 452, 310, 191, 1903, 1153, 792, 488, 1500, 909, 624, 384, 1063, 644, 442, 272, 813, 493, 338, 208, 2061, 1249, 858, 528, 1600, 970, 666, 410, 1159, 702, 482, 297, 919, 557, 382, 235, 2232, 1352, 929, 572, 1708, 1035, 711, 438, 1224, 742, 509, 314, 969, 587, 403, 248, 2409, 1460, 1003, 618, 1872, 1134, 779, 480, 1358, 823, 565, 348, 1056, 640, 439, 270, 2620, 1588, 1091, 672, 2059, 1248, 857, 528, 1468, 890, 611, 376, 1108, 672, 461, 284, 2812, 1704, 1171, 721, 2188, 1326, 911, 561, 1588, 963, 661, 407, 1228, 744, 511, 315, 3057, 1853, 1273, 784, 2395, 1451, 997, 614, 1718, 1041, 715, 440, 1286, 779, 535, 330, 3283, 1990, 1367, 842, 2544, 1542, 1059, 652, 1804, 1094, 751, 462, 1425, 864, 593, 365, 3517, 2132, 1465, 902, 2701, 1637, 1125, 692, 1933, 1172, 805, 496, 1501, 910, 625, 385, 3669, 2223, 1528, 940, 2857, 1732, 1190, 732, 2085, 1263, 868, 534, 1581, 958, 658, 405, 3909, 2369, 1628, 1002, 3035, 1839, 1264, 778, 2181, 1322, 908, 559, 1677, 1016, 698, 430, 4158, 2520, 1732, 1066, 3289, 1994, 1370, 843, 2358, 1429, 982, 604, 1782, 1080, 742, 457, 4417, 2677, 1840, 1132, 3486, 2113, 1452, 894, 2473, 1499, 1030, 634, 1897, 1150, 790, 486, 4686, 2840, 1952, 1201, 3693, 2238, 1538, 947, 2670, 1618, 1112, 684, 2022, 1226, 842, 518, 4965, 3009, 2068, 1273, 3909, 2369, 1628, 1002, 2805, 1700, 1168, 719, 2157, 1307, 898, 553, 5253, 3183, 2188, 1347, 4134, 2506, 1722, 1060, 2949, 1787, 1228, 756, 2301, 1394, 958, 590, 5529, 3351, 2303, 1417, 4343, 2632, 1809, 1113, 3081, 1867, 1283, 790, 2361, 1431, 983, 605, 5836, 3537, 2431, 1496, 4588, 2780, 1911, 1176, 3244, 1966, 1351, 832, 2524, 1530, 1051, 647, 6153, 3729, 2563, 1577, 4775, 2894, 1989, 1224, 3417, 2071, 1423, 876, 2625, 1591, 1093, 673, 6479, 3927, 2699, 1661, 5039, 3054, 2099, 1292, 3599, 2181, 1499, 923, 2735, 1658, 1139, 701, 6743, 4087, 2809, 1729, 5313, 3220, 2213, 1362, 3791, 2298, 1579, 972, 2927, 1774, 1219, 750, 7089, 4296, 2953, 1817, 5596, 3391, 2331, 1435, 3993, 2420, 1663, 1024, 3057, 1852, 1273, 784 }; + private static readonly int[] capacityECCBaseValues = { 19, 7, 1, 19, 0, 0, 16, 10, 1, 16, 0, 0, 13, 13, 1, 13, 0, 0, 9, 17, 1, 9, 0, 0, 34, 10, 1, 34, 0, 0, 28, 16, 1, 28, 0, 0, 22, 22, 1, 22, 0, 0, 16, 28, 1, 16, 0, 0, 55, 15, 1, 55, 0, 0, 44, 26, 1, 44, 0, 0, 34, 18, 2, 17, 0, 0, 26, 22, 2, 13, 0, 0, 80, 20, 1, 80, 0, 0, 64, 18, 2, 32, 0, 0, 48, 26, 2, 24, 0, 0, 36, 16, 4, 9, 0, 0, 108, 26, 1, 108, 0, 0, 86, 24, 2, 43, 0, 0, 62, 18, 2, 15, 2, 16, 46, 22, 2, 11, 2, 12, 136, 18, 2, 68, 0, 0, 108, 16, 4, 27, 0, 0, 76, 24, 4, 19, 0, 0, 60, 28, 4, 15, 0, 0, 156, 20, 2, 78, 0, 0, 124, 18, 4, 31, 0, 0, 88, 18, 2, 14, 4, 15, 66, 26, 4, 13, 1, 14, 194, 24, 2, 97, 0, 0, 154, 22, 2, 38, 2, 39, 110, 22, 4, 18, 2, 19, 86, 26, 4, 14, 2, 15, 232, 30, 2, 116, 0, 0, 182, 22, 3, 36, 2, 37, 132, 20, 4, 16, 4, 17, 100, 24, 4, 12, 4, 13, 274, 18, 2, 68, 2, 69, 216, 26, 4, 43, 1, 44, 154, 24, 6, 19, 2, 20, 122, 28, 6, 15, 2, 16, 324, 20, 4, 81, 0, 0, 254, 30, 1, 50, 4, 51, 180, 28, 4, 22, 4, 23, 140, 24, 3, 12, 8, 13, 370, 24, 2, 92, 2, 93, 290, 22, 6, 36, 2, 37, 206, 26, 4, 20, 6, 21, 158, 28, 7, 14, 4, 15, 428, 26, 4, 107, 0, 0, 334, 22, 8, 37, 1, 38, 244, 24, 8, 20, 4, 21, 180, 22, 12, 11, 4, 12, 461, 30, 3, 115, 1, 116, 365, 24, 4, 40, 5, 41, 261, 20, 11, 16, 5, 17, 197, 24, 11, 12, 5, 13, 523, 22, 5, 87, 1, 88, 415, 24, 5, 41, 5, 42, 295, 30, 5, 24, 7, 25, 223, 24, 11, 12, 7, 13, 589, 24, 5, 98, 1, 99, 453, 28, 7, 45, 3, 46, 325, 24, 15, 19, 2, 20, 253, 30, 3, 15, 13, 16, 647, 28, 1, 107, 5, 108, 507, 28, 10, 46, 1, 47, 367, 28, 1, 22, 15, 23, 283, 28, 2, 14, 17, 15, 721, 30, 5, 120, 1, 121, 563, 26, 9, 43, 4, 44, 397, 28, 17, 22, 1, 23, 313, 28, 2, 14, 19, 15, 795, 28, 3, 113, 4, 114, 627, 26, 3, 44, 11, 45, 445, 26, 17, 21, 4, 22, 341, 26, 9, 13, 16, 14, 861, 28, 3, 107, 5, 108, 669, 26, 3, 41, 13, 42, 485, 30, 15, 24, 5, 25, 385, 28, 15, 15, 10, 16, 932, 28, 4, 116, 4, 117, 714, 26, 17, 42, 0, 0, 512, 28, 17, 22, 6, 23, 406, 30, 19, 16, 6, 17, 1006, 28, 2, 111, 7, 112, 782, 28, 17, 46, 0, 0, 568, 30, 7, 24, 16, 25, 442, 24, 34, 13, 0, 0, 1094, 30, 4, 121, 5, 122, 860, 28, 4, 47, 14, 48, 614, 30, 11, 24, 14, 25, 464, 30, 16, 15, 14, 16, 1174, 30, 6, 117, 4, 118, 914, 28, 6, 45, 14, 46, 664, 30, 11, 24, 16, 25, 514, 30, 30, 16, 2, 17, 1276, 26, 8, 106, 4, 107, 1000, 28, 8, 47, 13, 48, 718, 30, 7, 24, 22, 25, 538, 30, 22, 15, 13, 16, 1370, 28, 10, 114, 2, 115, 1062, 28, 19, 46, 4, 47, 754, 28, 28, 22, 6, 23, 596, 30, 33, 16, 4, 17, 1468, 30, 8, 122, 4, 123, 1128, 28, 22, 45, 3, 46, 808, 30, 8, 23, 26, 24, 628, 30, 12, 15, 28, 16, 1531, 30, 3, 117, 10, 118, 1193, 28, 3, 45, 23, 46, 871, 30, 4, 24, 31, 25, 661, 30, 11, 15, 31, 16, 1631, 30, 7, 116, 7, 117, 1267, 28, 21, 45, 7, 46, 911, 30, 1, 23, 37, 24, 701, 30, 19, 15, 26, 16, 1735, 30, 5, 115, 10, 116, 1373, 28, 19, 47, 10, 48, 985, 30, 15, 24, 25, 25, 745, 30, 23, 15, 25, 16, 1843, 30, 13, 115, 3, 116, 1455, 28, 2, 46, 29, 47, 1033, 30, 42, 24, 1, 25, 793, 30, 23, 15, 28, 16, 1955, 30, 17, 115, 0, 0, 1541, 28, 10, 46, 23, 47, 1115, 30, 10, 24, 35, 25, 845, 30, 19, 15, 35, 16, 2071, 30, 17, 115, 1, 116, 1631, 28, 14, 46, 21, 47, 1171, 30, 29, 24, 19, 25, 901, 30, 11, 15, 46, 16, 2191, 30, 13, 115, 6, 116, 1725, 28, 14, 46, 23, 47, 1231, 30, 44, 24, 7, 25, 961, 30, 59, 16, 1, 17, 2306, 30, 12, 121, 7, 122, 1812, 28, 12, 47, 26, 48, 1286, 30, 39, 24, 14, 25, 986, 30, 22, 15, 41, 16, 2434, 30, 6, 121, 14, 122, 1914, 28, 6, 47, 34, 48, 1354, 30, 46, 24, 10, 25, 1054, 30, 2, 15, 64, 16, 2566, 30, 17, 122, 4, 123, 1992, 28, 29, 46, 14, 47, 1426, 30, 49, 24, 10, 25, 1096, 30, 24, 15, 46, 16, 2702, 30, 4, 122, 18, 123, 2102, 28, 13, 46, 32, 47, 1502, 30, 48, 24, 14, 25, 1142, 30, 42, 15, 32, 16, 2812, 30, 20, 117, 4, 118, 2216, 28, 40, 47, 7, 48, 1582, 30, 43, 24, 22, 25, 1222, 30, 10, 15, 67, 16, 2956, 30, 19, 118, 6, 119, 2334, 28, 18, 47, 31, 48, 1666, 30, 34, 24, 34, 25, 1276, 30, 20, 15, 61, 16 }; + private static readonly int[] alignmentPatternBaseValues = { 0, 0, 0, 0, 0, 0, 0, 6, 18, 0, 0, 0, 0, 0, 6, 22, 0, 0, 0, 0, 0, 6, 26, 0, 0, 0, 0, 0, 6, 30, 0, 0, 0, 0, 0, 6, 34, 0, 0, 0, 0, 0, 6, 22, 38, 0, 0, 0, 0, 6, 24, 42, 0, 0, 0, 0, 6, 26, 46, 0, 0, 0, 0, 6, 28, 50, 0, 0, 0, 0, 6, 30, 54, 0, 0, 0, 0, 6, 32, 58, 0, 0, 0, 0, 6, 34, 62, 0, 0, 0, 0, 6, 26, 46, 66, 0, 0, 0, 6, 26, 48, 70, 0, 0, 0, 6, 26, 50, 74, 0, 0, 0, 6, 30, 54, 78, 0, 0, 0, 6, 30, 56, 82, 0, 0, 0, 6, 30, 58, 86, 0, 0, 0, 6, 34, 62, 90, 0, 0, 0, 6, 28, 50, 72, 94, 0, 0, 6, 26, 50, 74, 98, 0, 0, 6, 30, 54, 78, 102, 0, 0, 6, 28, 54, 80, 106, 0, 0, 6, 32, 58, 84, 110, 0, 0, 6, 30, 58, 86, 114, 0, 0, 6, 34, 62, 90, 118, 0, 0, 6, 26, 50, 74, 98, 122, 0, 6, 30, 54, 78, 102, 126, 0, 6, 26, 52, 78, 104, 130, 0, 6, 30, 56, 82, 108, 134, 0, 6, 34, 60, 86, 112, 138, 0, 6, 30, 58, 86, 114, 142, 0, 6, 34, 62, 90, 118, 146, 0, 6, 30, 54, 78, 102, 126, 150, 6, 24, 50, 76, 102, 128, 154, 6, 28, 54, 80, 106, 132, 158, 6, 32, 58, 84, 110, 136, 162, 6, 26, 54, 82, 110, 138, 166, 6, 30, 58, 86, 114, 142, 170 }; + private static readonly int[] remainderBits = { 0, 7, 7, 7, 7, 7, 0, 0, 0, 0, 0, 0, 0, 3, 3, 3, 3, 3, 3, 3, 4, 4, 4, 4, 4, 4, 4, 3, 3, 3, 3, 3, 3, 3, 0, 0, 0, 0, 0, 0 }; + + private static readonly List alignmentPatternTable = CreateAlignmentPatternTable(); + private static readonly List capacityECCTable = CreateCapacityECCTable(); + private static readonly List capacityTable = CreateCapacityTable(); + private static readonly List galoisField = CreateAntilogTable(); + private static readonly Dictionary alphanumEncDict = CreateAlphanumEncDict(); + + public enum EciMode + { + Default = 0, + Iso8859_1 = 3, + Iso8859_2 = 4, + Utf8 = 26 + } + + /// + /// Initializes the QR code generator + /// + public QRCodeGenerator() + { + } + + /// + /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// A payload object, generated by the PayloadGenerator-class + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public QRCodeData CreateQrCode(PayloadGenerator.Payload payload) + { + return GenerateQrCode(payload); + } + + /// + /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// A payload object, generated by the PayloadGenerator-class + /// The level of error correction data + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public QRCodeData CreateQrCode(PayloadGenerator.Payload payload, ECCLevel eccLevel) + { + return GenerateQrCode(payload, eccLevel); + } + + /// + /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// The payload which shall be encoded in the QR code + /// The level of error correction data + /// Shall the generator be forced to work in UTF-8 mode? + /// Should the byte-order-mark be used? + /// Which ECI mode shall be used? + /// Set fixed QR code target version. + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public QRCodeData CreateQrCode(string plainText, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1) + { + return GenerateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion); + } + + /// + /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// A byte array which shall be encoded/stored in the QR code + /// The level of error correction data + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public QRCodeData CreateQrCode(byte[] binaryData, ECCLevel eccLevel) + { + return GenerateQrCode(binaryData, eccLevel); + } + + + /// + /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// A payload object, generated by the PayloadGenerator-class + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload) + { + return GenerateQrCode(payload.ToString(), payload.EccLevel, false, false, payload.EciMode, payload.Version); + } + + /// + /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// A payload object, generated by the PayloadGenerator-class + /// The level of error correction data + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public static QRCodeData GenerateQrCode(PayloadGenerator.Payload payload, ECCLevel eccLevel) + { + return GenerateQrCode(payload.ToString(), eccLevel, false, false, payload.EciMode, payload.Version); + } + + /// + /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// The payload which shall be encoded in the QR code + /// The level of error correction data + /// Shall the generator be forced to work in UTF-8 mode? + /// Should the byte-order-mark be used? + /// Which ECI mode shall be used? + /// Set fixed QR code target version. + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public static QRCodeData GenerateQrCode(string plainText, ECCLevel eccLevel, bool forceUtf8 = false, bool utf8BOM = false, EciMode eciMode = EciMode.Default, int requestedVersion = -1) + { + EncodingMode encoding = GetEncodingFromPlaintext(plainText, forceUtf8); + var codedText = PlainTextToBinary(plainText, encoding, eciMode, utf8BOM, forceUtf8); + var dataInputLength = GetDataLength(encoding, plainText, codedText, forceUtf8); + int version = requestedVersion; + if (version == -1) + { + version = GetVersion(dataInputLength+(eciMode != EciMode.Default?2:0), encoding, eccLevel); + } + else + { + //Version was passed as fixed version via parameter. Thus let's check if chosen version is valid. + var minVersion = GetVersion(dataInputLength + (eciMode != EciMode.Default ? 2 : 0), encoding, eccLevel); + if (minVersion > version) + { + var maxSizeByte = capacityTable[version - 1].Details.First(x => x.ErrorCorrectionLevel == eccLevel).CapacityDict[encoding]; + throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encoding.ToString(), version, maxSizeByte); + } + } + + string modeIndicator = String.Empty; + if (eciMode != EciMode.Default) + { + modeIndicator = DecToBin((int)EncodingMode.ECI, 4); + modeIndicator += DecToBin((int)eciMode, 8); + } + modeIndicator += DecToBin((int)encoding, 4); + var countIndicator = DecToBin(dataInputLength, GetCountIndicatorLength(version, encoding)); + var bitString = modeIndicator + countIndicator; + + bitString += codedText; + + return GenerateQrCode(bitString, eccLevel, version); + } + + + /// + /// Calculates the QR code data which than can be used in one of the rendering classes to generate a graphical representation. + /// + /// A byte array which shall be encoded/stored in the QR code + /// The level of error correction data + /// Thrown when the payload is too big to be encoded in a QR code. + /// Returns the raw QR code data which can be used for rendering. + public static QRCodeData GenerateQrCode(byte[] binaryData, ECCLevel eccLevel) + { + int version = GetVersion(binaryData.Length, EncodingMode.Byte, eccLevel); + + string modeIndicator = DecToBin((int)EncodingMode.Byte, 4); + string countIndicator = DecToBin(binaryData.Length, GetCountIndicatorLength(version, EncodingMode.Byte)); + + string bitString = modeIndicator + countIndicator; + foreach (byte b in binaryData) + { + bitString += DecToBin(b, 8); + } + + return GenerateQrCode(bitString, eccLevel, version); + } + + private static QRCodeData GenerateQrCode(string bitString, ECCLevel eccLevel, int version) + { + //Fill up data code word + var eccInfo = capacityECCTable.Single(x => x.Version == version && x.ErrorCorrectionLevel == eccLevel); + var dataLength = eccInfo.TotalDataCodewords * 8; + var lengthDiff = dataLength - bitString.Length; + if (lengthDiff > 0) + bitString += new string('0', Math.Min(lengthDiff, 4)); + if ((bitString.Length % 8) != 0) + bitString += new string('0', 8 - (bitString.Length % 8)); + while (bitString.Length < dataLength) + bitString += "1110110000010001"; + if (bitString.Length > dataLength) + bitString = bitString.Substring(0, dataLength); + + //Calculate error correction words + var codeWordWithECC = new List(eccInfo.BlocksInGroup1 + eccInfo.BlocksInGroup2); + for (var i = 0; i < eccInfo.BlocksInGroup1; i++) + { + var bitStr = bitString.Substring(i * eccInfo.CodewordsInGroup1 * 8, eccInfo.CodewordsInGroup1 * 8); + var bitBlockList = BinaryStringToBitBlockList(bitStr); + var bitBlockListDec = BinaryStringListToDecList(bitBlockList); + var eccWordList = CalculateECCWords(bitStr, eccInfo); + var eccWordListDec = BinaryStringListToDecList(eccWordList); + codeWordWithECC.Add( + new CodewordBlock(1, + i + 1, + bitStr, + bitBlockList, + eccWordList, + bitBlockListDec, + eccWordListDec) + ); + } + bitString = bitString.Substring(eccInfo.BlocksInGroup1 * eccInfo.CodewordsInGroup1 * 8); + for (var i = 0; i < eccInfo.BlocksInGroup2; i++) + { + var bitStr = bitString.Substring(i * eccInfo.CodewordsInGroup2 * 8, eccInfo.CodewordsInGroup2 * 8); + var bitBlockList = BinaryStringToBitBlockList(bitStr); + var bitBlockListDec = BinaryStringListToDecList(bitBlockList); + var eccWordList = CalculateECCWords(bitStr, eccInfo); + var eccWordListDec = BinaryStringListToDecList(eccWordList); + codeWordWithECC.Add(new CodewordBlock(2, + i + 1, + bitStr, + bitBlockList, + eccWordList, + bitBlockListDec, + eccWordListDec) + ); + } + + + //Interleave code words + var interleavedWordsSb = new StringBuilder(); + for (var i = 0; i < Math.Max(eccInfo.CodewordsInGroup1, eccInfo.CodewordsInGroup2); i++) + { + foreach (var codeBlock in codeWordWithECC) + if (codeBlock.CodeWords.Count > i) + interleavedWordsSb.Append(codeBlock.CodeWords[i]); + } + + + for (var i = 0; i < eccInfo.ECCPerBlock; i++) + { + foreach (var codeBlock in codeWordWithECC) + if (codeBlock.ECCWords.Count > i) + interleavedWordsSb.Append(codeBlock.ECCWords[i]); + } + interleavedWordsSb.Append(new string('0', remainderBits[version - 1])); + var interleavedData = interleavedWordsSb.ToString(); + + + //Place interleaved data on module matrix + var qr = new QRCodeData(version); + var blockedModules = new List(); + ModulePlacer.PlaceFinderPatterns(ref qr, ref blockedModules); + ModulePlacer.ReserveSeperatorAreas(qr.ModuleMatrix.Count, ref blockedModules); + ModulePlacer.PlaceAlignmentPatterns(ref qr, alignmentPatternTable.Where(x => x.Version == version).Select(x => x.PatternPositions).First(), ref blockedModules); + ModulePlacer.PlaceTimingPatterns(ref qr, ref blockedModules); + ModulePlacer.PlaceDarkModule(ref qr, version, ref blockedModules); + ModulePlacer.ReserveVersionAreas(qr.ModuleMatrix.Count, version, ref blockedModules); + ModulePlacer.PlaceDataWords(ref qr, interleavedData, ref blockedModules); + var maskVersion = ModulePlacer.MaskCode(ref qr, version, ref blockedModules, eccLevel); + var formatStr = GetFormatString(eccLevel, maskVersion); + + ModulePlacer.PlaceFormat(ref qr, formatStr); + if (version >= 7) + { + var versionString = GetVersionString(version); + ModulePlacer.PlaceVersion(ref qr, versionString); + } + + + ModulePlacer.AddQuietZone(ref qr); + return qr; + } + + private static string GetFormatString(ECCLevel level, int maskVersion) + { + var generator = "10100110111"; + var fStrMask = "101010000010010"; + + var fStr = (level == ECCLevel.L) ? "01" : (level == ECCLevel.M) ? "00" : (level == ECCLevel.Q) ? "11" : "10"; + fStr += DecToBin(maskVersion, 3); + var fStrEcc = fStr.PadRight(15, '0').TrimStart('0'); + while (fStrEcc.Length > 10) + { + var sb = new StringBuilder(); + generator = generator.PadRight(fStrEcc.Length, '0'); + for (var i = 0; i < fStrEcc.Length; i++) + sb.Append((Convert.ToInt32(fStrEcc[i]) ^ Convert.ToInt32(generator[i])).ToString()); + fStrEcc = sb.ToString().TrimStart('0'); + } + fStrEcc = fStrEcc.PadLeft(10, '0'); + fStr += fStrEcc; + + var sbMask = new StringBuilder(); + for (var i = 0; i < fStr.Length; i++) + sbMask.Append((Convert.ToInt32(fStr[i]) ^ Convert.ToInt32(fStrMask[i])).ToString()); + return sbMask.ToString(); + } + + private static string GetVersionString(int version) + { + var generator = "1111100100101"; + + var vStr = DecToBin(version, 6); + var vStrEcc = vStr.PadRight(18, '0').TrimStart('0'); + while (vStrEcc.Length > 12) + { + var sb = new StringBuilder(); + generator = generator.PadRight(vStrEcc.Length, '0'); + for (var i = 0; i < vStrEcc.Length; i++) + sb.Append((Convert.ToInt32(vStrEcc[i]) ^ Convert.ToInt32(generator[i])).ToString()); + vStrEcc = sb.ToString().TrimStart('0'); + } + vStrEcc = vStrEcc.PadLeft(12, '0'); + vStr += vStrEcc; + + return vStr; + } + + private static class ModulePlacer + { + public static void AddQuietZone(ref QRCodeData qrCode) + { + var quietLine = new bool[qrCode.ModuleMatrix.Count + 8]; + for (var i = 0; i < quietLine.Length; i++) + quietLine[i] = false; + for (var i = 0; i < 4; i++) + qrCode.ModuleMatrix.Insert(0, new BitArray(quietLine)); + for (var i = 0; i < 4; i++) + qrCode.ModuleMatrix.Add(new BitArray(quietLine)); + for (var i = 4; i < qrCode.ModuleMatrix.Count - 4; i++) + { + bool[] quietPart = { false, false, false, false }; + var tmpLine = new List(quietPart); + tmpLine.AddRange(qrCode.ModuleMatrix[i].Cast()); + tmpLine.AddRange(quietPart); + qrCode.ModuleMatrix[i] = new BitArray(tmpLine.ToArray()); + } + } + + private static string ReverseString(string inp) + { + string newStr = string.Empty; + if (inp.Length > 0) + { + for (int i = inp.Length - 1; i >= 0; i--) + newStr += inp[i]; + } + return newStr; + } + + public static void PlaceVersion(ref QRCodeData qrCode, string versionStr) + { + var size = qrCode.ModuleMatrix.Count; + + var vStr = ReverseString(versionStr); + + for (var x = 0; x < 6; x++) + { + for (var y = 0; y < 3; y++) + { + qrCode.ModuleMatrix[y + size - 11][x] = vStr[x * 3 + y] == '1'; + qrCode.ModuleMatrix[x][y + size - 11] = vStr[x * 3 + y] == '1'; + } + } + } + + public static void PlaceFormat(ref QRCodeData qrCode, string formatStr) + { + var size = qrCode.ModuleMatrix.Count; + var fStr = ReverseString(formatStr); + var modules = new[,] { + { 8, 0, size - 1, 8 }, + { 8, 1, size - 2, 8 }, + { 8, 2, size - 3, 8 }, + { 8, 3, size - 4, 8 }, + { 8, 4, size - 5, 8 }, + { 8, 5, size - 6, 8 }, + { 8, 7, size - 7, 8 }, + { 8, 8, size - 8, 8 }, + { 7, 8, 8, size - 7 }, + { 5, 8, 8, size - 6 }, + { 4, 8, 8, size - 5 }, + { 3, 8, 8, size - 4 }, + { 2, 8, 8, size - 3 }, + { 1, 8, 8, size - 2 }, + { 0, 8, 8, size - 1 } }; + for (var i = 0; i < 15; i++) + { + var p1 = new Point(modules[i, 0], modules[i, 1]); + var p2 = new Point(modules[i, 2], modules[i, 3]); + qrCode.ModuleMatrix[p1.Y][p1.X] = fStr[i] == '1'; + qrCode.ModuleMatrix[p2.Y][p2.X] = fStr[i] == '1'; + } + } + + + public static int MaskCode(ref QRCodeData qrCode, int version, ref List blockedModules, ECCLevel eccLevel) + { + int? selectedPattern = null; + var patternScore = 0; + + var size = qrCode.ModuleMatrix.Count; + + var methods = new Dictionary>(8) { + { 1, MaskPattern.Pattern1 }, {2, MaskPattern.Pattern2 }, {3, MaskPattern.Pattern3 }, {4, MaskPattern.Pattern4 }, + {5, MaskPattern.Pattern5 }, {6, MaskPattern.Pattern6 }, {7, MaskPattern.Pattern7 }, {8, MaskPattern.Pattern8 } + }; + + foreach (var pattern in methods) + { + var qrTemp = new QRCodeData(version); + for (var y = 0; y < size; y++) + { + for (var x = 0; x < size; x++) + { + qrTemp.ModuleMatrix[y][x] = qrCode.ModuleMatrix[y][x]; + } + + } + + var formatStr = GetFormatString(eccLevel, pattern.Key - 1); + ModulePlacer.PlaceFormat(ref qrTemp, formatStr); + if (version >= 7) + { + var versionString = GetVersionString(version); + ModulePlacer.PlaceVersion(ref qrTemp, versionString); + } + + for (var x = 0; x < size; x++) + { + for (var y = 0; y < x; y++) + { + if (!IsBlocked(new Rectangle(x, y, 1, 1), blockedModules)) + { + qrTemp.ModuleMatrix[y][x] ^= pattern.Value(x, y); + qrTemp.ModuleMatrix[x][y] ^= pattern.Value(y, x); + } + } + + if (!IsBlocked(new Rectangle(x, x, 1, 1), blockedModules)) + { + qrTemp.ModuleMatrix[x][x] ^= pattern.Value(x, x); + } + } + + var score = MaskPattern.Score(ref qrTemp); + if (!selectedPattern.HasValue || patternScore > score) + { + selectedPattern = pattern.Key; + patternScore = score; + } + } + + for (var x = 0; x < size; x++) + { + for (var y = 0; y < x; y++) + { + if (!IsBlocked(new Rectangle(x, y, 1, 1), blockedModules)) + { + qrCode.ModuleMatrix[y][x] ^= methods[selectedPattern.Value](x, y); + qrCode.ModuleMatrix[x][y] ^= methods[selectedPattern.Value](y, x); + } + } + + if (!IsBlocked(new Rectangle(x, x, 1, 1), blockedModules)) + { + qrCode.ModuleMatrix[x][x] ^= methods[selectedPattern.Value](x, x); + } + } + return selectedPattern.Value - 1; + } + + + public static void PlaceDataWords(ref QRCodeData qrCode, string data, ref List blockedModules) + { + var size = qrCode.ModuleMatrix.Count; + var up = true; + var datawords = new Queue(); + for (int i = 0; i< data.Length; i++) + { + datawords.Enqueue(data[i] != '0'); + } + for (var x = size - 1; x >= 0; x = x - 2) + { + if (x == 6) + x = 5; + for (var yMod = 1; yMod <= size; yMod++) + { + int y; + if (up) + { + y = size - yMod; + if (datawords.Count > 0 && !IsBlocked(new Rectangle(x, y, 1, 1), blockedModules)) + qrCode.ModuleMatrix[y][x] = datawords.Dequeue(); + if (datawords.Count > 0 && x > 0 && !IsBlocked(new Rectangle(x - 1, y, 1, 1), blockedModules)) + qrCode.ModuleMatrix[y][x - 1] = datawords.Dequeue(); + } + else + { + y = yMod - 1; + if (datawords.Count > 0 && !IsBlocked(new Rectangle(x, y, 1, 1), blockedModules)) + qrCode.ModuleMatrix[y][x] = datawords.Dequeue(); + if (datawords.Count > 0 && x > 0 && !IsBlocked(new Rectangle(x - 1, y, 1, 1), blockedModules)) + qrCode.ModuleMatrix[y][x - 1] = datawords.Dequeue(); + } + } + up = !up; + } + } + + public static void ReserveSeperatorAreas(int size, ref List blockedModules) + { + blockedModules.AddRange(new[]{ + new Rectangle(7, 0, 1, 8), + new Rectangle(0, 7, 7, 1), + new Rectangle(0, size-8, 8, 1), + new Rectangle(7, size-7, 1, 7), + new Rectangle(size-8, 0, 1, 8), + new Rectangle(size-7, 7, 7, 1) + }); + } + + public static void ReserveVersionAreas(int size, int version, ref List blockedModules) + { + blockedModules.AddRange(new[]{ + new Rectangle(8, 0, 1, 6), + new Rectangle(8, 7, 1, 1), + new Rectangle(0, 8, 6, 1), + new Rectangle(7, 8, 2, 1), + new Rectangle(size-8, 8, 8, 1), + new Rectangle(8, size-7, 1, 7) + }); + + if (version >= 7) + { + blockedModules.AddRange(new[]{ + new Rectangle(size-11, 0, 3, 6), + new Rectangle(0, size-11, 6, 3) + }); + } + } + public static void PlaceDarkModule(ref QRCodeData qrCode, int version, ref List blockedModules) + { + qrCode.ModuleMatrix[4 * version + 9][8] = true; + blockedModules.Add(new Rectangle(8, 4 * version + 9, 1, 1)); + } + + public static void PlaceFinderPatterns(ref QRCodeData qrCode, ref List blockedModules) + { + var size = qrCode.ModuleMatrix.Count; + int[] locations = { 0, 0, size - 7, 0, 0, size - 7 }; + + for (var i = 0; i < 6; i = i + 2) + { + for (var x = 0; x < 7; x++) + { + for (var y = 0; y < 7; y++) + { + if (!(((x == 1 || x == 5) && y > 0 && y < 6) || (x > 0 && x < 6 && (y == 1 || y == 5)))) + { + qrCode.ModuleMatrix[y + locations[i + 1]][x + locations[i]] = true; + } + } + } + blockedModules.Add(new Rectangle(locations[i], locations[i + 1], 7, 7)); + } + } + + public static void PlaceAlignmentPatterns(ref QRCodeData qrCode, List alignmentPatternLocations, ref List blockedModules) + { + foreach (var loc in alignmentPatternLocations) + { + var alignmentPatternRect = new Rectangle(loc.X, loc.Y, 5, 5); + var blocked = false; + foreach (var blockedRect in blockedModules) + { + if (Intersects(alignmentPatternRect, blockedRect)) + { + blocked = true; + break; + } + } + if (blocked) + continue; + + for (var x = 0; x < 5; x++) + { + for (var y = 0; y < 5; y++) + { + if (y == 0 || y == 4 || x == 0 || x == 4 || (x == 2 && y == 2)) + { + qrCode.ModuleMatrix[loc.Y + y][loc.X + x] = true; + } + } + } + blockedModules.Add(new Rectangle(loc.X, loc.Y, 5, 5)); + } + } + + public static void PlaceTimingPatterns(ref QRCodeData qrCode, ref List blockedModules) + { + var size = qrCode.ModuleMatrix.Count; + for (var i = 8; i < size - 8; i++) + { + if (i % 2 == 0) + { + qrCode.ModuleMatrix[6][i] = true; + qrCode.ModuleMatrix[i][6] = true; + } + } + blockedModules.AddRange(new[]{ + new Rectangle(6, 8, 1, size-16), + new Rectangle(8, 6, size-16, 1) + }); + } + + private static bool Intersects(Rectangle r1, Rectangle r2) + { + return r2.X < r1.X + r1.Width && r1.X < r2.X + r2.Width && r2.Y < r1.Y + r1.Height && r1.Y < r2.Y + r2.Height; + } + + private static bool IsBlocked(Rectangle r1, List blockedModules) + { + foreach (var blockedMod in blockedModules) + { + if (Intersects(blockedMod, r1)) + return true; + } + return false; + } + + private static class MaskPattern + { + public static bool Pattern1(int x, int y) + { + return (x + y) % 2 == 0; + } + + public static bool Pattern2(int x, int y) + { + return y % 2 == 0; + } + + public static bool Pattern3(int x, int y) + { + return x % 3 == 0; + } + + public static bool Pattern4(int x, int y) + { + return (x + y) % 3 == 0; + } + + public static bool Pattern5(int x, int y) + { + return ((int)(Math.Floor(y / 2d) + Math.Floor(x / 3d)) % 2) == 0; + } + + public static bool Pattern6(int x, int y) + { + return ((x * y) % 2) + ((x * y) % 3) == 0; + } + + public static bool Pattern7(int x, int y) + { + return (((x * y) % 2) + ((x * y) % 3)) % 2 == 0; + } + + public static bool Pattern8(int x, int y) + { + return (((x + y) % 2) + ((x * y) % 3)) % 2 == 0; + } + + public static int Score(ref QRCodeData qrCode) + { + int score1 = 0, + score2 = 0, + score3 = 0, + score4 = 0; + var size = qrCode.ModuleMatrix.Count; + + //Penalty 1 + for (var y = 0; y < size; y++) + { + var modInRow = 0; + var modInColumn = 0; + var lastValRow = qrCode.ModuleMatrix[y][0]; + var lastValColumn = qrCode.ModuleMatrix[0][y]; + for (var x = 0; x < size; x++) + { + if (qrCode.ModuleMatrix[y][x] == lastValRow) + modInRow++; + else + modInRow = 1; + if (modInRow == 5) + score1 += 3; + else if (modInRow > 5) + score1++; + lastValRow = qrCode.ModuleMatrix[y][x]; + + + if (qrCode.ModuleMatrix[x][y] == lastValColumn) + modInColumn++; + else + modInColumn = 1; + if (modInColumn == 5) + score1 += 3; + else if (modInColumn > 5) + score1++; + lastValColumn = qrCode.ModuleMatrix[x][y]; + } + } + + + //Penalty 2 + for (var y = 0; y < size - 1; y++) + { + for (var x = 0; x < size - 1; x++) + { + if (qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y][x + 1] && + qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y + 1][x] && + qrCode.ModuleMatrix[y][x] == qrCode.ModuleMatrix[y + 1][x + 1]) + score2 += 3; + } + } + + //Penalty 3 + for (var y = 0; y < size; y++) + { + for (var x = 0; x < size - 10; x++) + { + if ((qrCode.ModuleMatrix[y][x] && + !qrCode.ModuleMatrix[y][x + 1] && + qrCode.ModuleMatrix[y][x + 2] && + qrCode.ModuleMatrix[y][x + 3] && + qrCode.ModuleMatrix[y][x + 4] && + !qrCode.ModuleMatrix[y][x + 5] && + qrCode.ModuleMatrix[y][x + 6] && + !qrCode.ModuleMatrix[y][x + 7] && + !qrCode.ModuleMatrix[y][x + 8] && + !qrCode.ModuleMatrix[y][x + 9] && + !qrCode.ModuleMatrix[y][x + 10]) || + (!qrCode.ModuleMatrix[y][x] && + !qrCode.ModuleMatrix[y][x + 1] && + !qrCode.ModuleMatrix[y][x + 2] && + !qrCode.ModuleMatrix[y][x + 3] && + qrCode.ModuleMatrix[y][x + 4] && + !qrCode.ModuleMatrix[y][x + 5] && + qrCode.ModuleMatrix[y][x + 6] && + qrCode.ModuleMatrix[y][x + 7] && + qrCode.ModuleMatrix[y][x + 8] && + !qrCode.ModuleMatrix[y][x + 9] && + qrCode.ModuleMatrix[y][x + 10])) + { + score3 += 40; + } + + if ((qrCode.ModuleMatrix[x][y] && + !qrCode.ModuleMatrix[x + 1][y] && + qrCode.ModuleMatrix[x + 2][y] && + qrCode.ModuleMatrix[x + 3][y] && + qrCode.ModuleMatrix[x + 4][y] && + !qrCode.ModuleMatrix[x + 5][y] && + qrCode.ModuleMatrix[x + 6][y] && + !qrCode.ModuleMatrix[x + 7][y] && + !qrCode.ModuleMatrix[x + 8][y] && + !qrCode.ModuleMatrix[x + 9][y] && + !qrCode.ModuleMatrix[x + 10][y]) || + (!qrCode.ModuleMatrix[x][y] && + !qrCode.ModuleMatrix[x + 1][y] && + !qrCode.ModuleMatrix[x + 2][y] && + !qrCode.ModuleMatrix[x + 3][y] && + qrCode.ModuleMatrix[x + 4][y] && + !qrCode.ModuleMatrix[x + 5][y] && + qrCode.ModuleMatrix[x + 6][y] && + qrCode.ModuleMatrix[x + 7][y] && + qrCode.ModuleMatrix[x + 8][y] && + !qrCode.ModuleMatrix[x + 9][y] && + qrCode.ModuleMatrix[x + 10][y])) + { + score3 += 40; + } + } + } + + //Penalty 4 + double blackModules = 0; + foreach (var row in qrCode.ModuleMatrix) + foreach (bool bit in row) + if (bit) + blackModules++; + + var percent = (blackModules / (qrCode.ModuleMatrix.Count * qrCode.ModuleMatrix.Count)) * 100; + var prevMultipleOf5 = Math.Abs((int) Math.Floor(percent/5)*5 - 50)/5; + var nextMultipleOf5 = Math.Abs((int)Math.Floor(percent / 5) * 5 -45)/5; + score4 = Math.Min(prevMultipleOf5, nextMultipleOf5)*10; + + return score1 + score2 + score3 + score4; + } + } + + } + + private static List CalculateECCWords(string bitString, ECCInfo eccInfo) + { + var eccWords = eccInfo.ECCPerBlock; + var messagePolynom = CalculateMessagePolynom(bitString); + var generatorPolynom = CalculateGeneratorPolynom(eccWords); + + for (var i = 0; i < messagePolynom.PolyItems.Count; i++) + messagePolynom.PolyItems[i] = new PolynomItem(messagePolynom.PolyItems[i].Coefficient, + messagePolynom.PolyItems[i].Exponent + eccWords); + + for (var i = 0; i < generatorPolynom.PolyItems.Count; i++) + generatorPolynom.PolyItems[i] = new PolynomItem(generatorPolynom.PolyItems[i].Coefficient, + generatorPolynom.PolyItems[i].Exponent + (messagePolynom.PolyItems.Count-1)); + + var leadTermSource = messagePolynom; + for (var i = 0; (leadTermSource.PolyItems.Count > 0 && leadTermSource.PolyItems[leadTermSource.PolyItems.Count - 1].Exponent > 0); i++) + { + if (leadTermSource.PolyItems[0].Coefficient == 0) + { + leadTermSource.PolyItems.RemoveAt(0); + leadTermSource.PolyItems.Add(new PolynomItem(0, leadTermSource.PolyItems[leadTermSource.PolyItems.Count - 1].Exponent - 1)); + } + else + { + var resPoly = MultiplyGeneratorPolynomByLeadterm(generatorPolynom, ConvertToAlphaNotation(leadTermSource).PolyItems[0], i); + resPoly = ConvertToDecNotation(resPoly); + resPoly = XORPolynoms(leadTermSource, resPoly); + leadTermSource = resPoly; + } + } + return leadTermSource.PolyItems.Select(x => DecToBin(x.Coefficient, 8)).ToList(); + } + + private static Polynom ConvertToAlphaNotation(Polynom poly) + { + var newPoly = new Polynom(); + for (var i = 0; i < poly.PolyItems.Count; i++) + newPoly.PolyItems.Add( + new PolynomItem( + (poly.PolyItems[i].Coefficient != 0 + ? GetAlphaExpFromIntVal(poly.PolyItems[i].Coefficient) + : 0), poly.PolyItems[i].Exponent)); + return newPoly; + } + + private static Polynom ConvertToDecNotation(Polynom poly) + { + var newPoly = new Polynom(); + for (var i = 0; i < poly.PolyItems.Count; i++) + newPoly.PolyItems.Add(new PolynomItem(GetIntValFromAlphaExp(poly.PolyItems[i].Coefficient), poly.PolyItems[i].Exponent)); + return newPoly; + } + + private static int GetVersion(int length, EncodingMode encMode, ECCLevel eccLevel) + { + + var fittingVersions = capacityTable.Where( + x => x.Details.Any( + y => (y.ErrorCorrectionLevel == eccLevel + && y.CapacityDict[encMode] >= Convert.ToInt32(length) + ) + ) + ).Select(x => new + { + version = x.Version, + capacity = x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel) + .CapacityDict[encMode] + }); + + if (fittingVersions.Any()) + return fittingVersions.Min(x => x.version); + + var maxSizeByte = capacityTable.Where( + x => x.Details.Any( + y => (y.ErrorCorrectionLevel == eccLevel)) + ).Max(x => x.Details.Single(y => y.ErrorCorrectionLevel == eccLevel).CapacityDict[encMode]); + throw new QRCoder.Exceptions.DataTooLongException(eccLevel.ToString(), encMode.ToString(), maxSizeByte); + } + + private static EncodingMode GetEncodingFromPlaintext(string plainText, bool forceUtf8) + { + if (forceUtf8) return EncodingMode.Byte; + EncodingMode result = EncodingMode.Numeric; // assume numeric + foreach (char c in plainText) + { + if (IsInRange(c, '0', '9')) continue; // numeric - char.IsDigit() for Latin1 + result = EncodingMode.Alphanumeric; // not numeric, assume alphanumeric + if (IsInRange(c, 'A', 'Z') || alphanumEncTable.Contains(c)) continue; // alphanumeric + return EncodingMode.Byte; // not numeric or alphanumeric, assume byte + } + return result; // either numeric or alphanumeric + } + + private static bool IsInRange(char c, char min, char max) + { + return (uint)(c - min) <= (uint)(max - min); + } + + private static Polynom CalculateMessagePolynom(string bitString) + { + var messagePol = new Polynom(); + for (var i = bitString.Length / 8 - 1; i >= 0; i--) + { + messagePol.PolyItems.Add(new PolynomItem(BinToDec(bitString.Substring(0, 8)), i)); + bitString = bitString.Remove(0, 8); + } + return messagePol; + } + + + private static Polynom CalculateGeneratorPolynom(int numEccWords) + { + var generatorPolynom = new Polynom(); + generatorPolynom.PolyItems.AddRange(new[]{ + new PolynomItem(0,1), + new PolynomItem(0,0) + }); + for (var i = 1; i <= numEccWords - 1; i++) + { + var multiplierPolynom = new Polynom(); + multiplierPolynom.PolyItems.AddRange(new[]{ + new PolynomItem(0,1), + new PolynomItem(i,0) + }); + + generatorPolynom = MultiplyAlphaPolynoms(generatorPolynom, multiplierPolynom); + } + + return generatorPolynom; + } + + private static List BinaryStringToBitBlockList(string bitString) + { + const int blockSize = 8; + var numberOfBlocks = (int)Math.Ceiling(bitString.Length / (double)blockSize); + var blocklist = new List(numberOfBlocks); + + for (int i = 0; i < bitString.Length; i += blockSize) + { + blocklist.Add(bitString.Substring(i, blockSize)); + } + + return blocklist; + } + + private static List BinaryStringListToDecList(List binaryStringList) + { + return binaryStringList.Select(binaryString => BinToDec(binaryString)).ToList(); + } + + private static int BinToDec(string binStr) + { + return Convert.ToInt32(binStr, 2); + } + + private static string DecToBin(int decNum) + { + return Convert.ToString(decNum, 2); + } + + private static string DecToBin(int decNum, int padLeftUpTo) + { + var binStr = DecToBin(decNum); + return binStr.PadLeft(padLeftUpTo, '0'); + } + + private static int GetCountIndicatorLength(int version, EncodingMode encMode) + { + if (version < 10) + { + if (encMode == EncodingMode.Numeric) + return 10; + else if (encMode == EncodingMode.Alphanumeric) + return 9; + else + return 8; + } + else if (version < 27) + { + if (encMode == EncodingMode.Numeric) + return 12; + else if (encMode == EncodingMode.Alphanumeric) + return 11; + else if (encMode == EncodingMode.Byte) + return 16; + else + return 10; + } + else + { + if (encMode == EncodingMode.Numeric) + return 14; + else if (encMode == EncodingMode.Alphanumeric) + return 13; + else if (encMode == EncodingMode.Byte) + return 16; + else + return 12; + } + } + + private static int GetDataLength(EncodingMode encoding, string plainText, string codedText, bool forceUtf8) + { + return forceUtf8 || IsUtf8(encoding, plainText, forceUtf8) ? (codedText.Length / 8) : plainText.Length; + } + + private static bool IsUtf8(EncodingMode encoding, string plainText, bool forceUtf8) + { + return (encoding == EncodingMode.Byte && (!IsValidISO(plainText) || forceUtf8)); + } + + private static bool IsValidISO(string input) + { + var bytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(input); + //var result = Encoding.GetEncoding("ISO-8859-1").GetString(bytes); + var result = Encoding.GetEncoding("ISO-8859-1").GetString(bytes,0,bytes.Length); + return String.Equals(input, result); + } + + private static string PlainTextToBinary(string plainText, EncodingMode encMode, EciMode eciMode, bool utf8BOM, bool forceUtf8) + { + switch(encMode) + { + case EncodingMode.Alphanumeric: + return PlainTextToBinaryAlphanumeric(plainText); + case EncodingMode.Numeric: + return PlainTextToBinaryNumeric(plainText); + case EncodingMode.Byte: + return PlainTextToBinaryByte(plainText, eciMode, utf8BOM, forceUtf8); + case EncodingMode.Kanji: + return string.Empty; + case EncodingMode.ECI: + default: + return string.Empty; + } + } + + private static string PlainTextToBinaryNumeric(string plainText) + { + var codeText = string.Empty; + while (plainText.Length >= 3) + { + var dec = Convert.ToInt32(plainText.Substring(0, 3)); + codeText += DecToBin(dec, 10); + plainText = plainText.Substring(3); + + } + if (plainText.Length == 2) + { + var dec = Convert.ToInt32(plainText); + codeText += DecToBin(dec, 7); + } + else if (plainText.Length == 1) + { + var dec = Convert.ToInt32(plainText); + codeText += DecToBin(dec, 4); + } + return codeText; + } + + private static string PlainTextToBinaryAlphanumeric(string plainText) + { + var codeText = string.Empty; + while (plainText.Length >= 2) + { + var token = plainText.Substring(0, 2); + var dec = alphanumEncDict[token[0]] * 45 + alphanumEncDict[token[1]]; + codeText += DecToBin(dec, 11); + plainText = plainText.Substring(2); + + } + if (plainText.Length > 0) + { + codeText += DecToBin(alphanumEncDict[plainText[0]], 6); + } + return codeText; + } + + private string PlainTextToBinaryECI(string plainText) + { + var codeText = string.Empty; + byte[] _bytes = Encoding.GetEncoding("ascii").GetBytes(plainText); + foreach(byte _byte in _bytes) + { + codeText += DecToBin(_byte, 8); + } + return codeText; + } + + private static string ConvertToIso8859(string value, string Iso = "ISO-8859-2") + { + Encoding iso = Encoding.GetEncoding(Iso); + Encoding utf8 = Encoding.UTF8; + byte[] utfBytes = utf8.GetBytes(value); + byte[] isoBytes = Encoding.Convert(utf8, iso, utfBytes); +#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 + return iso.GetString(isoBytes); +#else + return iso.GetString(isoBytes, 0, isoBytes.Length); +#endif + } + + private static string PlainTextToBinaryByte(string plainText, EciMode eciMode, bool utf8BOM, bool forceUtf8) + { + byte[] codeBytes; + var codeText = string.Empty; + + if (IsValidISO(plainText) && !forceUtf8) + codeBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(plainText); + else + { + switch(eciMode) + { + case EciMode.Iso8859_1: + codeBytes = Encoding.GetEncoding("ISO-8859-1").GetBytes(ConvertToIso8859(plainText, "ISO-8859-1")); + break; + case EciMode.Iso8859_2: + codeBytes = Encoding.GetEncoding("ISO-8859-2").GetBytes(ConvertToIso8859(plainText, "ISO-8859-2")); + break; + case EciMode.Default: + case EciMode.Utf8: + default: + codeBytes = utf8BOM ? Encoding.UTF8.GetPreamble().Concat(Encoding.UTF8.GetBytes(plainText)).ToArray() : Encoding.UTF8.GetBytes(plainText); + break; + } + } + + foreach (var b in codeBytes) + codeText += DecToBin(b, 8); + + return codeText; + } + + + private static Polynom XORPolynoms(Polynom messagePolynom, Polynom resPolynom) + { + var resultPolynom = new Polynom(); + Polynom longPoly, shortPoly; + if (messagePolynom.PolyItems.Count >= resPolynom.PolyItems.Count) + { + longPoly = messagePolynom; + shortPoly = resPolynom; + } + else + { + longPoly = resPolynom; + shortPoly = messagePolynom; + } + + for (var i = 0; i < longPoly.PolyItems.Count; i++) + { + var polItemRes = new PolynomItem + ( + + longPoly.PolyItems[i].Coefficient ^ + (shortPoly.PolyItems.Count > i ? shortPoly.PolyItems[i].Coefficient : 0), + messagePolynom.PolyItems[0].Exponent - i + ); + resultPolynom.PolyItems.Add(polItemRes); + } + resultPolynom.PolyItems.RemoveAt(0); + return resultPolynom; + } + + + private static Polynom MultiplyGeneratorPolynomByLeadterm(Polynom genPolynom, PolynomItem leadTerm, int lowerExponentBy) + { + var resultPolynom = new Polynom(); + foreach (var polItemBase in genPolynom.PolyItems) + { + var polItemRes = new PolynomItem( + + (polItemBase.Coefficient + leadTerm.Coefficient) % 255, + polItemBase.Exponent - lowerExponentBy + ); + resultPolynom.PolyItems.Add(polItemRes); + } + return resultPolynom; + } + + + private static Polynom MultiplyAlphaPolynoms(Polynom polynomBase, Polynom polynomMultiplier) + { + var resultPolynom = new Polynom(); + foreach (var polItemBase in polynomMultiplier.PolyItems) + { + foreach (var polItemMulti in polynomBase.PolyItems) + { + var polItemRes = new PolynomItem + ( + ShrinkAlphaExp(polItemBase.Coefficient + polItemMulti.Coefficient), + (polItemBase.Exponent + polItemMulti.Exponent) + ); + resultPolynom.PolyItems.Add(polItemRes); + } + } + var exponentsToGlue = resultPolynom.PolyItems.GroupBy(x => x.Exponent).Where(x => x.Count() > 1).Select(x => x.First().Exponent); + var toGlue = exponentsToGlue as IList ?? exponentsToGlue.ToList(); + var gluedPolynoms = new List(toGlue.Count); + foreach (var exponent in toGlue) + { + var coefficient = resultPolynom.PolyItems.Where(x => x.Exponent == exponent).Aggregate(0, (current, polynomOld) + => current ^ GetIntValFromAlphaExp(polynomOld.Coefficient)); + var polynomFixed = new PolynomItem(GetAlphaExpFromIntVal(coefficient), exponent); + gluedPolynoms.Add(polynomFixed); + } + resultPolynom.PolyItems.RemoveAll(x => toGlue.Contains(x.Exponent)); + resultPolynom.PolyItems.AddRange(gluedPolynoms); + resultPolynom.PolyItems.Sort((x, y) => -x.Exponent.CompareTo(y.Exponent)); + return resultPolynom; + } + + private static int GetIntValFromAlphaExp(int exp) + { + return galoisField.Find(alog => alog.ExponentAlpha == exp).IntegerValue; + } + + private static int GetAlphaExpFromIntVal(int intVal) + { + return galoisField.Find(alog => alog.IntegerValue == intVal).ExponentAlpha; + } + + private static int ShrinkAlphaExp(int alphaExp) + { + // ReSharper disable once PossibleLossOfFraction + return (int)((alphaExp % 256) + Math.Floor((double)(alphaExp / 256))); + } + + private static Dictionary CreateAlphanumEncDict() + { + var localAlphanumEncDict = new Dictionary(45); + //Add numbers + for (int i = 0; i < 10; i++) + localAlphanumEncDict.Add($"{i}"[0], i); + //Add chars + for (char c = 'A'; c <= 'Z'; c++) + localAlphanumEncDict.Add(c, localAlphanumEncDict.Count()); + //Add special chars + for (int i = 0; i < alphanumEncTable.Length; i++) + localAlphanumEncDict.Add(alphanumEncTable[i], localAlphanumEncDict.Count()); + return localAlphanumEncDict; + } + + private static List CreateAlignmentPatternTable() + { + var localAlignmentPatternTable = new List(40); + + for (var i = 0; i < (7 * 40); i = i + 7) + { + var points = new List(); + for (var x = 0; x < 7; x++) + { + if (alignmentPatternBaseValues[i + x] != 0) + { + for (var y = 0; y < 7; y++) + { + if (alignmentPatternBaseValues[i + y] != 0) + { + var p = new Point(alignmentPatternBaseValues[i + x] - 2, alignmentPatternBaseValues[i + y] - 2); + if (!points.Contains(p)) + points.Add(p); + } + } + } + } + + localAlignmentPatternTable.Add(new AlignmentPattern() + { + Version = (i + 7) / 7, + PatternPositions = points + } + ); + } + return localAlignmentPatternTable; + } + + + private static List CreateCapacityECCTable() + { + var localCapacityECCTable = new List(160); + for (var i = 0; i < (4 * 6 * 40); i = i + (4 * 6)) + { + localCapacityECCTable.AddRange( + new[] + { + new ECCInfo( + (i+24) / 24, + ECCLevel.L, + capacityECCBaseValues[i], + capacityECCBaseValues[i+1], + capacityECCBaseValues[i+2], + capacityECCBaseValues[i+3], + capacityECCBaseValues[i+4], + capacityECCBaseValues[i+5]), + new ECCInfo + ( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.M, + totalDataCodewords: capacityECCBaseValues[i+6], + eccPerBlock: capacityECCBaseValues[i+7], + blocksInGroup1: capacityECCBaseValues[i+8], + codewordsInGroup1: capacityECCBaseValues[i+9], + blocksInGroup2: capacityECCBaseValues[i+10], + codewordsInGroup2: capacityECCBaseValues[i+11] + ), + new ECCInfo + ( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.Q, + totalDataCodewords: capacityECCBaseValues[i+12], + eccPerBlock: capacityECCBaseValues[i+13], + blocksInGroup1: capacityECCBaseValues[i+14], + codewordsInGroup1: capacityECCBaseValues[i+15], + blocksInGroup2: capacityECCBaseValues[i+16], + codewordsInGroup2: capacityECCBaseValues[i+17] + ), + new ECCInfo + ( + version: (i + 24) / 24, + errorCorrectionLevel: ECCLevel.H, + totalDataCodewords: capacityECCBaseValues[i+18], + eccPerBlock: capacityECCBaseValues[i+19], + blocksInGroup1: capacityECCBaseValues[i+20], + codewordsInGroup1: capacityECCBaseValues[i+21], + blocksInGroup2: capacityECCBaseValues[i+22], + codewordsInGroup2: capacityECCBaseValues[i+23] + ) + }); + } + return localCapacityECCTable; + } + + private static List CreateCapacityTable() + { + var localCapacityTable = new List(40); + for (var i = 0; i < (16 * 40); i = i + 16) + { + localCapacityTable.Add(new VersionInfo( + + (i + 16) / 16, + new List(4) + { + new VersionInfoDetails( + ECCLevel.L, + new Dictionary(){ + { EncodingMode.Numeric, capacityBaseValues[i] }, + { EncodingMode.Alphanumeric, capacityBaseValues[i+1] }, + { EncodingMode.Byte, capacityBaseValues[i+2] }, + { EncodingMode.Kanji, capacityBaseValues[i+3] }, + } + ), + new VersionInfoDetails( + ECCLevel.M, + new Dictionary(){ + { EncodingMode.Numeric, capacityBaseValues[i+4] }, + { EncodingMode.Alphanumeric, capacityBaseValues[i+5] }, + { EncodingMode.Byte, capacityBaseValues[i+6] }, + { EncodingMode.Kanji, capacityBaseValues[i+7] }, + } + ), + new VersionInfoDetails( + ECCLevel.Q, + new Dictionary(){ + { EncodingMode.Numeric, capacityBaseValues[i+8] }, + { EncodingMode.Alphanumeric, capacityBaseValues[i+9] }, + { EncodingMode.Byte, capacityBaseValues[i+10] }, + { EncodingMode.Kanji, capacityBaseValues[i+11] }, + } + ), + new VersionInfoDetails( + ECCLevel.H, + new Dictionary(){ + { EncodingMode.Numeric, capacityBaseValues[i+12] }, + { EncodingMode.Alphanumeric, capacityBaseValues[i+13] }, + { EncodingMode.Byte, capacityBaseValues[i+14] }, + { EncodingMode.Kanji, capacityBaseValues[i+15] }, + } + ) + } + )); + } + return localCapacityTable; + } + + private static List CreateAntilogTable() + { + var localGaloisField = new List(256); + + int gfItem = 1; + for (var i = 0; i < 256; i++) + { + localGaloisField.Add(new Antilog(i, gfItem)); + gfItem *= 2; + if (gfItem > 255) + gfItem ^= 285; + } + return localGaloisField; + } + + /// + /// Error correction level. These define the tolerance levels for how much of the code can be lost before the code cannot be recovered. + /// + public enum ECCLevel + { + /// + /// 7% may be lost before recovery is not possible + /// + L, + /// + /// 15% may be lost before recovery is not possible + /// + M, + /// + /// 25% may be lost before recovery is not possible + /// + Q, + /// + /// 30% may be lost before recovery is not possible + /// + H + } + + private enum EncodingMode + { + Numeric = 1, + Alphanumeric = 2, + Byte = 4, + Kanji = 8, + ECI = 7 + } + + private struct AlignmentPattern + { + public int Version; + public List PatternPositions; + } + + private struct CodewordBlock + { + public CodewordBlock(int groupNumber, int blockNumber, string bitString, List codeWords, + List eccWords, List codeWordsInt, List eccWordsInt) + { + this.GroupNumber = groupNumber; + this.BlockNumber = blockNumber; + this.BitString = bitString; + this.CodeWords = codeWords; + this.ECCWords = eccWords; + this.CodeWordsInt = codeWordsInt; + this.ECCWordsInt = eccWordsInt; + } + + public int GroupNumber { get; } + public int BlockNumber { get; } + public string BitString { get; } + public List CodeWords { get; } + public List CodeWordsInt { get; } + public List ECCWords { get; } + public List ECCWordsInt { get; } + } + + private struct ECCInfo + { + public ECCInfo(int version, ECCLevel errorCorrectionLevel, int totalDataCodewords, int eccPerBlock, int blocksInGroup1, + int codewordsInGroup1, int blocksInGroup2, int codewordsInGroup2) + { + this.Version = version; + this.ErrorCorrectionLevel = errorCorrectionLevel; + this.TotalDataCodewords = totalDataCodewords; + this.ECCPerBlock = eccPerBlock; + this.BlocksInGroup1 = blocksInGroup1; + this.CodewordsInGroup1 = codewordsInGroup1; + this.BlocksInGroup2 = blocksInGroup2; + this.CodewordsInGroup2 = codewordsInGroup2; + } + public int Version { get; } + public ECCLevel ErrorCorrectionLevel { get; } + public int TotalDataCodewords { get; } + public int ECCPerBlock { get; } + public int BlocksInGroup1 { get; } + public int CodewordsInGroup1 { get; } + public int BlocksInGroup2 { get; } + public int CodewordsInGroup2 { get; } + } + + private struct VersionInfo + { + public VersionInfo(int version, List versionInfoDetails) + { + this.Version = version; + this.Details = versionInfoDetails; + } + public int Version { get; } + public List Details { get; } + } + + private struct VersionInfoDetails + { + public VersionInfoDetails(ECCLevel errorCorrectionLevel, Dictionary capacityDict) + { + this.ErrorCorrectionLevel = errorCorrectionLevel; + this.CapacityDict = capacityDict; + } + + public ECCLevel ErrorCorrectionLevel { get; } + public Dictionary CapacityDict { get; } + } + + private struct Antilog + { + public Antilog(int exponentAlpha, int integerValue) + { + this.ExponentAlpha = exponentAlpha; + this.IntegerValue = integerValue; + } + public int ExponentAlpha { get; } + public int IntegerValue { get; } + } + + private struct PolynomItem + { + public PolynomItem(int coefficient, int exponent) + { + this.Coefficient = coefficient; + this.Exponent = exponent; + } + + public int Coefficient { get; } + public int Exponent { get; } + } + + private class Polynom + { + public Polynom() + { + this.PolyItems = new List(); + } + + public List PolyItems { get; set; } + + public override string ToString() + { + var sb = new StringBuilder(); + //this.PolyItems.ForEach(x => sb.Append("a^" + x.Coefficient + "*x^" + x.Exponent + " + ")); + foreach (var polyItem in this.PolyItems) + { + sb.Append("a^" + polyItem.Coefficient + "*x^" + polyItem.Exponent + " + "); + } + + return sb.ToString().TrimEnd(new[] { ' ', '+' }); + } + } + + private class Point + { + public int X { get; } + public int Y { get; } + public Point(int x, int y) + { + this.X = x; + this.Y = y; + } + } + + private class Rectangle + { + public int X { get; } + public int Y { get; } + public int Width { get; } + public int Height { get; } + + public Rectangle(int x, int y, int w, int h) + { + this.X = x; + this.Y = y; + this.Width = w; + this.Height = h; + } + } + + public void Dispose() + { + // left for back-compat + } + } +} diff --git a/vCardEditor/Libs/QRCoder/SvgQRCode.cs b/vCardEditor/Libs/QRCoder/SvgQRCode.cs new file mode 100644 index 0000000..e03ce5d --- /dev/null +++ b/vCardEditor/Libs/QRCoder/SvgQRCode.cs @@ -0,0 +1,397 @@ +#if NETFRAMEWORK || NETSTANDARD2_0 || NET5_0 || NET6_0_WINDOWS +using QRCoder.Extensions; +using System; +using System.Collections; +using System.Collections.Generic; +using System.Drawing; +using System.Text; +using System.Text.RegularExpressions; +using static QRCoder.QRCodeGenerator; +using static QRCoder.SvgQRCode; + +namespace QRCoder +{ +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public class SvgQRCode : AbstractQRCode, IDisposable + { + /// + /// Constructor without params to be used in COM Objects connections + /// + public SvgQRCode() { } + public SvgQRCode(QRCodeData data) : base(data) { } + + /// + /// Returns a QR code as SVG string + /// + /// The pixel size each b/w module is drawn + /// SVG as string + public string GetGraphic(int pixelsPerModule) + { + var viewBox = new Size(pixelsPerModule*this.QrCodeData.ModuleMatrix.Count, pixelsPerModule * this.QrCodeData.ModuleMatrix.Count); + return this.GetGraphic(viewBox, Color.Black, Color.White); + } + + /// + /// Returns a QR code as SVG string with custom colors, optional quietzone and logo + /// + /// The pixel size each b/w module is drawn + /// Color of the dark modules + /// Color of the light modules + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as 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; + var edgeSize = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule); + var viewBox = new Size(edgeSize, edgeSize); + return this.GetGraphic(viewBox, darkColor, lightColor, drawQuietZones, sizingMode, logo); + } + + /// + /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo + /// + /// The pixel size each b/w module is drawn + /// The color of the dark/black modules in hex (e.g. #000000) representation + /// The color of the light/white modules in hex (e.g. #ffffff) representation + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(int pixelsPerModule, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) + { + var offset = drawQuietZones ? 0 : 4; + var edgeSize = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule - (offset * 2 * pixelsPerModule); + var viewBox = new Size(edgeSize, edgeSize); + return this.GetGraphic(viewBox, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo); + } + + /// + /// Returns a QR code as SVG string with optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(Size viewBox, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) + { + return this.GetGraphic(viewBox, Color.Black, Color.White, drawQuietZones, sizingMode, logo); + } + + /// + /// Returns a QR code as SVG string with custom colors and optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// Color of the dark modules + /// Color of the light modules + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(Size viewBox, Color darkColor, Color lightColor, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo logo = null) + { + return this.GetGraphic(viewBox, ColorTranslator.ToHtml(Color.FromArgb(darkColor.ToArgb())), ColorTranslator.ToHtml(Color.FromArgb(lightColor.ToArgb())), drawQuietZones, sizingMode, logo); + } + + /// + /// Returns a QR code as SVG string with custom colors (in HEX syntax), optional quietzone and logo + /// + /// The viewbox of the QR code graphic + /// The color of the dark/black modules in hex (e.g. #000000) representation + /// The color of the light/white modules in hex (e.g. #ffffff) representation + /// If true a white border is drawn around the whole QR Code + /// Defines if width/height or viewbox should be used for size definition + /// A (optional) logo to be rendered on the code (either Bitmap or SVG) + /// SVG as string + public string GetGraphic(Size viewBox, string darkColorHex, string lightColorHex, bool drawQuietZones = true, SizingMode sizingMode = SizingMode.WidthHeightAttribute, SvgLogo 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($@""); + svgFile.AppendLine($@""); + 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($@""); + } + } + } + + //Render logo, if set + if (logo != null) + { + if (!logo.IsEmbedded()) + { + svgFile.AppendLine($@""); + svgFile.AppendLine($@""); + svgFile.AppendLine(@""); + } + 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(@""); + return svgFile.ToString(); + } + + 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 logo, Size 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); + } + + /// + /// Mode of sizing attribution on svg root node + /// + public enum SizingMode + { + WidthHeightAttribute, + ViewBoxAttribute + } + + /// + /// Represents a logo graphic that can be rendered on a SvgQRCode + /// + public class SvgLogo + { + private string _logoData; + private MediaType _mediaType; + private int _iconSizePercent; + private bool _fillLogoBackground; + private object _logoRaw; + private bool _isEmbedded; + + + /// + /// Create a logo object to be used in SvgQRCode renderer + /// + /// Logo to be rendered as Bitmap/rasterized graphic + /// Degree of percentage coverage of the QR code by the logo + /// If true, the background behind the logo will be cleaned + public SvgLogo(Bitmap iconRasterized, int iconSizePercent = 15, bool fillLogoBackground = true) + { + _iconSizePercent = iconSizePercent; + using (var ms = new System.IO.MemoryStream()) + { + using (var bitmap = new Bitmap(iconRasterized)) + { + bitmap.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + _logoData = Convert.ToBase64String(ms.GetBuffer(), Base64FormattingOptions.None); + } + } + _mediaType = MediaType.PNG; + _fillLogoBackground = fillLogoBackground; + _logoRaw = iconRasterized; + _isEmbedded = false; + } + + /// + /// Create a logo object to be used in SvgQRCode renderer + /// + /// Logo to be rendered as SVG/vectorized graphic/string + /// Degree of percentage coverage of the QR code by the logo + /// If true, the background behind the logo will be cleaned + /// If true, the logo will embedded as native svg instead of embedding it as image-tag + public SvgLogo(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; + } + + /// + /// Returns the raw logo's data + /// + /// + public object GetRawLogo() + { + return _logoRaw; + } + + /// + /// Defines, if the logo shall be natively embedded. + /// true=native svg embedding, false=embedding via image-tag + /// + /// + public bool IsEmbedded() + { + return _isEmbedded; + } + + /// + /// Returns the media type of the logo + /// + /// + public MediaType GetMediaType() + { + return _mediaType; + } + + /// + /// Returns the logo as data-uri + /// + /// + public string GetDataUri() + { + return $"data:{_mediaType.GetStringValue()};base64,{_logoData}"; + } + + /// + /// Returns how much of the QR code should be covered by the logo (in percent) + /// + /// + public int GetIconSizePercent() + { + return _iconSizePercent; + } + + /// + /// Returns if the background of the logo should be cleaned (no QR modules will be rendered behind the logo) + /// + /// + public bool FillLogoBackground() + { + return _fillLogoBackground; + } + + /// + /// Media types for SvgLogos + /// + public enum MediaType : int + { + [StringValue("image/png")] + PNG = 0, + [StringValue("image/svg+xml")] + SVG = 1 + } + } + } + +#if NET6_0_WINDOWS + [System.Runtime.Versioning.SupportedOSPlatform("windows")] +#endif + public static class SvgQRCodeHelper + { + 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 logo = null) + { + using (var qrGenerator = new QRCodeGenerator()) + using (var qrCodeData = qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion)) + using (var qrCode = new SvgQRCode(qrCodeData)) + return qrCode.GetGraphic(pixelsPerModule, darkColorHex, lightColorHex, drawQuietZones, sizingMode, logo); + } + } +} + +#endif diff --git a/vCardEditor/View/QRDialog.Designer.cs b/vCardEditor/View/QRDialog.Designer.cs new file mode 100644 index 0000000..d4316e4 --- /dev/null +++ b/vCardEditor/View/QRDialog.Designer.cs @@ -0,0 +1,96 @@ + +namespace vCardEditor.View +{ + partial class QRDialog + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Windows Form Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.btnExport = new System.Windows.Forms.Button(); + this.btnClose = new System.Windows.Forms.Button(); + this.pictureBoxQRCode = new System.Windows.Forms.PictureBox(); + this.saveFileDialog1 = new System.Windows.Forms.SaveFileDialog(); + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxQRCode)).BeginInit(); + this.SuspendLayout(); + // + // btnExport + // + this.btnExport.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnExport.Location = new System.Drawing.Point(464, 385); + this.btnExport.Name = "btnExport"; + this.btnExport.Size = new System.Drawing.Size(75, 28); + this.btnExport.TabIndex = 5; + this.btnExport.Text = "Export.."; + this.btnExport.UseVisualStyleBackColor = true; + this.btnExport.Click += new System.EventHandler(this.btnExport_Click); + // + // btnClose + // + this.btnClose.Anchor = ((System.Windows.Forms.AnchorStyles)((System.Windows.Forms.AnchorStyles.Bottom | System.Windows.Forms.AnchorStyles.Right))); + this.btnClose.Location = new System.Drawing.Point(550, 386); + this.btnClose.Name = "btnClose"; + this.btnClose.Size = new System.Drawing.Size(75, 28); + this.btnClose.TabIndex = 4; + this.btnClose.Text = "Close"; + this.btnClose.UseVisualStyleBackColor = true; + this.btnClose.Click += new System.EventHandler(this.btnClose_Click); + // + // pictureBoxQRCode + // + this.pictureBoxQRCode.Anchor = ((System.Windows.Forms.AnchorStyles)((((System.Windows.Forms.AnchorStyles.Top | System.Windows.Forms.AnchorStyles.Bottom) + | System.Windows.Forms.AnchorStyles.Left) + | System.Windows.Forms.AnchorStyles.Right))); + this.pictureBoxQRCode.BackgroundImageLayout = System.Windows.Forms.ImageLayout.Zoom; + this.pictureBoxQRCode.BorderStyle = System.Windows.Forms.BorderStyle.FixedSingle; + this.pictureBoxQRCode.Location = new System.Drawing.Point(12, 12); + this.pictureBoxQRCode.Name = "pictureBoxQRCode"; + this.pictureBoxQRCode.Size = new System.Drawing.Size(613, 367); + this.pictureBoxQRCode.TabIndex = 3; + this.pictureBoxQRCode.TabStop = false; + // + // QRDialog + // + this.AutoScaleDimensions = new System.Drawing.SizeF(8F, 16F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.ClientSize = new System.Drawing.Size(640, 422); + this.Controls.Add(this.btnExport); + this.Controls.Add(this.btnClose); + this.Controls.Add(this.pictureBoxQRCode); + this.Name = "QRDialog"; + this.Text = "QR Generator"; + ((System.ComponentModel.ISupportInitialize)(this.pictureBoxQRCode)).EndInit(); + this.ResumeLayout(false); + + } + + #endregion + + private System.Windows.Forms.Button btnExport; + private System.Windows.Forms.Button btnClose; + private System.Windows.Forms.PictureBox pictureBoxQRCode; + private System.Windows.Forms.SaveFileDialog saveFileDialog1; + } +} \ No newline at end of file diff --git a/vCardEditor/View/QRDialog.cs b/vCardEditor/View/QRDialog.cs new file mode 100644 index 0000000..ce63c74 --- /dev/null +++ b/vCardEditor/View/QRDialog.cs @@ -0,0 +1,93 @@ +using QRCoder; +using System; +using System.Drawing; +using System.Drawing.Imaging; +using System.IO; +using System.Windows.Forms; + +namespace vCardEditor.View +{ + public partial class QRDialog : Form + { + public QRDialog() + { + InitializeComponent(); + } + + private void RenderQrCode() + { + + QRCodeGenerator.ECCLevel eccLevel = (QRCodeGenerator.ECCLevel)0; + + using (QRCodeGenerator qrGenerator = new QRCodeGenerator()) + using (QRCodeData qrCodeData = qrGenerator.CreateQrCode("BEGIN:VCARD\r\nVERSION:3.0\r\nN:Chlef université;sarah;;;\r\nFN:sarah Chlef université\r\nADR;TYPE=HOME:;;sdad;;das;;\r\nEMAIL;TYPE=INTERNET:sibri02@yahoo.fr\r\nNOTE:I am proficient in Tiger-Crane Style\\,\\nand I am more than proficient in the exquisite art of the Samurai sword.\r\nNOTE:eee\r\nORG:Google\\;GMail Team\\;Spam Detection Squad\r\nTEL;TYPE=CELL;TYPE=OTHER;TYPE=VOICE:+213560348275\r\nTEL;TYPE=OTHER;TYPE=FAX:+2131122\r\nEND:VCARD\r\n", eccLevel)) + using (QRCode qrCode = new QRCode(qrCodeData)) + { + pictureBoxQRCode.BackgroundImage = qrCode.GetGraphic(20, GetPrimaryColor(), GetBackgroundColor(), null, 1); + + pictureBoxQRCode.Size = new System.Drawing.Size(pictureBoxQRCode.Width, pictureBoxQRCode.Height); + //Set the SizeMode to center the image. + pictureBoxQRCode.SizeMode = PictureBoxSizeMode.CenterImage; + + pictureBoxQRCode.SizeMode = PictureBoxSizeMode.StretchImage; + } + } + + private Color GetPrimaryColor() + { + return Color.Black; + } + + private Color GetBackgroundColor() + { + return Color.White; + } + + private void btnClose_Click(object sender, EventArgs e) + { + this.Close(); + } + + private void btnExport_Click(object sender, EventArgs e) + { + // Displays a SaveFileDialog so the user can save the Image + SaveFileDialog saveFileDialog1 = new SaveFileDialog(); + saveFileDialog1.Filter = "Bitmap Image|*.bmp|PNG Image|*.png|JPeg Image|*.jpg|Gif Image|*.gif"; + saveFileDialog1.Title = "Save an Image File"; + saveFileDialog1.ShowDialog(); + + // If the file name is not an empty string open it for saving. + if (saveFileDialog1.FileName != "") + { + // Saves the Image via a FileStream created by the OpenFile method. + using (FileStream fs = (System.IO.FileStream)saveFileDialog1.OpenFile()) + { + // Saves the Image in the appropriate ImageFormat based upon the + // File type selected in the dialog box. + // NOTE that the FilterIndex property is one-based. + + ImageFormat imageFormat = null; + switch (saveFileDialog1.FilterIndex) + { + case 1: + imageFormat = ImageFormat.Bmp; + break; + case 2: + imageFormat = ImageFormat.Png; + break; + case 3: + imageFormat = ImageFormat.Jpeg; + break; + case 4: + imageFormat = ImageFormat.Gif; + break; + default: + throw new NotSupportedException("File extension is not supported"); + } + + pictureBoxQRCode.BackgroundImage.Save(fs, imageFormat); + } + } + } + } +} diff --git a/vCardEditor/View/QRDialog.resx b/vCardEditor/View/QRDialog.resx new file mode 100644 index 0000000..053da59 --- /dev/null +++ b/vCardEditor/View/QRDialog.resx @@ -0,0 +1,123 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + 17, 17 + + \ No newline at end of file diff --git a/vCardEditor/vCardEditor.csproj b/vCardEditor/vCardEditor.csproj index cc40b00..ac0fdce 100644 --- a/vCardEditor/vCardEditor.csproj +++ b/vCardEditor/vCardEditor.csproj @@ -66,6 +66,23 @@ + + + + + + + + + + + + + + + + + @@ -187,6 +204,12 @@ MainForm.cs + + Form + + + QRDialog.cs + Component @@ -238,7 +261,11 @@ ExtraTextGroup.cs + + QRDialog.cs + + SettingsSingleFileGenerator Settings.Designer.cs @@ -255,6 +282,7 @@ +