diff --git a/QRCoder.sln b/QRCoder.sln
index 2df93136..909b787b 100644
--- a/QRCoder.sln
+++ b/QRCoder.sln
@@ -13,6 +13,10 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QRCoderDemoUWP", "QRCoderDe
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "QRCoderTests", "QRCoderTests\QRCoderTests.csproj", "{1B51624B-9915-4ED6-8FC1-1B7C472246E5}"
EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QRCoder2", "QRCoder2\QRCoder2.csproj", "{990202D3-C323-433A-881D-382C4F5BD2D5}"
+EndProject
+Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "QRCoder2.Renderers.ImageSharp", "QRCoder2.Renderers.ImageSharp\QRCoder2.Renderers.ImageSharp.csproj", "{F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -101,6 +105,38 @@ Global
{1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|x64.Build.0 = Release|Any CPU
{1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|x86.ActiveCfg = Release|Any CPU
{1B51624B-9915-4ED6-8FC1-1B7C472246E5}.Release|x86.Build.0 = Release|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Debug|ARM.Build.0 = Debug|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Debug|x64.Build.0 = Debug|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Debug|x86.Build.0 = Debug|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Release|Any CPU.Build.0 = Release|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Release|ARM.ActiveCfg = Release|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Release|ARM.Build.0 = Release|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Release|x64.ActiveCfg = Release|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Release|x64.Build.0 = Release|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Release|x86.ActiveCfg = Release|Any CPU
+ {990202D3-C323-433A-881D-382C4F5BD2D5}.Release|x86.Build.0 = Release|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Debug|ARM.ActiveCfg = Debug|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Debug|ARM.Build.0 = Debug|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Debug|x64.ActiveCfg = Debug|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Debug|x64.Build.0 = Debug|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Debug|x86.ActiveCfg = Debug|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Debug|x86.Build.0 = Debug|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Release|ARM.ActiveCfg = Release|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Release|ARM.Build.0 = Release|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Release|x64.ActiveCfg = Release|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Release|x64.Build.0 = Release|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Release|x86.ActiveCfg = Release|Any CPU
+ {F2BEC8CB-B151-40BC-80B0-99FED2BEDD05}.Release|x86.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
diff --git a/QRCoder2.Renderers.ImageSharp/ImageSharpByteQRCode.cs b/QRCoder2.Renderers.ImageSharp/ImageSharpByteQRCode.cs
new file mode 100644
index 00000000..b5a76ce8
--- /dev/null
+++ b/QRCoder2.Renderers.ImageSharp/ImageSharpByteQRCode.cs
@@ -0,0 +1,7 @@
+namespace QRCoder2.Renderers.ImageSharp
+{
+ public class ImageSharpByteQRCode
+ {
+
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2.Renderers.ImageSharp/QRCoder2.Renderers.ImageSharp.csproj b/QRCoder2.Renderers.ImageSharp/QRCoder2.Renderers.ImageSharp.csproj
new file mode 100644
index 00000000..cfc582f2
--- /dev/null
+++ b/QRCoder2.Renderers.ImageSharp/QRCoder2.Renderers.ImageSharp.csproj
@@ -0,0 +1,12 @@
+
+
+
+ net6.0
+
+
+
+
+
+
+
+
diff --git a/QRCoder2/Exceptions/DataTooLongException.cs b/QRCoder2/Exceptions/DataTooLongException.cs
new file mode 100644
index 00000000..c4327607
--- /dev/null
+++ b/QRCoder2/Exceptions/DataTooLongException.cs
@@ -0,0 +1,16 @@
+using System;
+
+namespace QRCoder2.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/QRCoder2/Payloads/BezahlCodePayload.cs b/QRCoder2/Payloads/BezahlCodePayload.cs
new file mode 100644
index 00000000..cf81c769
--- /dev/null
+++ b/QRCoder2/Payloads/BezahlCodePayload.cs
@@ -0,0 +1,567 @@
+using System;
+using System.Text.RegularExpressions;
+
+namespace QRCoder2.Payloads
+{
+ public class BezahlCodePayload : PayloadBase
+ {
+ //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 BezahlCodePayload(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 BezahlCodePayload(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 BezahlCodePayload(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 BezahlCodePayload(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)
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/BitcoinLikeCryptoCurrencyAddressPayload.cs b/QRCoder2/Payloads/BitcoinLikeCryptoCurrencyAddressPayload.cs
new file mode 100644
index 00000000..327bca9a
--- /dev/null
+++ b/QRCoder2/Payloads/BitcoinLikeCryptoCurrencyAddressPayload.cs
@@ -0,0 +1,86 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+
+namespace QRCoder2.Payloads
+{
+ public class BitcoinLikeCryptoCurrencyAddressPayload : PayloadBase
+ {
+ 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 BitcoinLikeCryptoCurrencyAddressPayload(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.EscapeUriString(label);
+ }
+
+ if (!string.IsNullOrEmpty(message))
+ {
+ this.message = Uri.EscapeUriString(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 BitcoinAddressPayload : BitcoinLikeCryptoCurrencyAddressPayload
+ {
+ public BitcoinAddressPayload(string address, double? amount, string label = null, string message = null)
+ : base(BitcoinLikeCryptoCurrencyType.Bitcoin, address, amount, label, message) { }
+ }
+
+ public class BitcoinCashAddressPayload : BitcoinLikeCryptoCurrencyAddressPayload
+ {
+ public BitcoinCashAddressPayload(string address, double? amount, string label = null, string message = null)
+ : base(BitcoinLikeCryptoCurrencyType.BitcoinCash, address, amount, label, message) { }
+ }
+
+ public class LitecoinAddressPayload : BitcoinLikeCryptoCurrencyAddressPayload
+ {
+ public LitecoinAddressPayload(string address, double? amount, string label = null, string message = null)
+ : base(BitcoinLikeCryptoCurrencyType.Litecoin, address, amount, label, message) { }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/BookmarkPayload.cs b/QRCoder2/Payloads/BookmarkPayload.cs
new file mode 100644
index 00000000..b7b3b0fa
--- /dev/null
+++ b/QRCoder2/Payloads/BookmarkPayload.cs
@@ -0,0 +1,23 @@
+namespace QRCoder2.Payloads
+{
+ public class BookmarkPayload : PayloadBase
+ {
+ 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 BookmarkPayload(string url, string title)
+ {
+ this.url = EscapeInput(url);
+ this.title = EscapeInput(title);
+ }
+
+ public override string ToString()
+ {
+ return $"MEBKM:TITLE:{this.title};URL:{this.url};;";
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/CalendarEventPayload.cs b/QRCoder2/Payloads/CalendarEventPayload.cs
new file mode 100644
index 00000000..0d349163
--- /dev/null
+++ b/QRCoder2/Payloads/CalendarEventPayload.cs
@@ -0,0 +1,53 @@
+using System;
+
+namespace QRCoder2.Payloads
+{
+ public class CalendarEventPayload : PayloadBase
+ {
+ 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 CalendarEventPayload(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
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/ContactDataPayload.cs b/QRCoder2/Payloads/ContactDataPayload.cs
new file mode 100644
index 00000000..ed9c910a
--- /dev/null
+++ b/QRCoder2/Payloads/ContactDataPayload.cs
@@ -0,0 +1,224 @@
+using System;
+
+namespace QRCoder2.Payloads
+{
+ public class ContactDataPayload : PayloadBase
+ {
+ private readonly string firstname;
+ private readonly string lastname;
+ private readonly string nickname;
+ private readonly string org;
+ private readonly string phone;
+ private readonly string mobilePhone;
+ private readonly string workPhone;
+ private readonly string email;
+ private readonly DateOnly? 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
+ public ContactDataPayload(ContactOutputType outputType, string firstname, string lastname, string nickname = null, string phone = null, string mobilePhone = null, string workPhone = null, string email = null, DateOnly? 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)
+ {
+ this.firstname = firstname;
+ this.lastname = lastname;
+ this.nickname = nickname;
+ this.org = org;
+ 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(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:{((DateOnly)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(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:{((DateOnly)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
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/ECCLevel.cs b/QRCoder2/Payloads/ECCLevel.cs
new file mode 100644
index 00000000..0bef4cfd
--- /dev/null
+++ b/QRCoder2/Payloads/ECCLevel.cs
@@ -0,0 +1,25 @@
+namespace QRCoder2.Payloads
+{
+ ///
+ /// 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,
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/EciMode.cs b/QRCoder2/Payloads/EciMode.cs
new file mode 100644
index 00000000..2d4f2e0d
--- /dev/null
+++ b/QRCoder2/Payloads/EciMode.cs
@@ -0,0 +1,10 @@
+namespace QRCoder2.Payloads
+{
+ public enum EciMode
+ {
+ Default = 0,
+ Iso8859_1 = 3,
+ Iso8859_2 = 4,
+ Utf8 = 26
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/GeolocationPayload.cs b/QRCoder2/Payloads/GeolocationPayload.cs
new file mode 100644
index 00000000..54bfbe0a
--- /dev/null
+++ b/QRCoder2/Payloads/GeolocationPayload.cs
@@ -0,0 +1,40 @@
+namespace QRCoder2.Payloads
+{
+ public class GeolocationPayload : PayloadBase
+ {
+ 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 GeolocationPayload(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
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/GirocodePayload.cs b/QRCoder2/Payloads/GirocodePayload.cs
new file mode 100644
index 00000000..89015c63
--- /dev/null
+++ b/QRCoder2/Payloads/GirocodePayload.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Text;
+
+namespace QRCoder2.Payloads
+{
+ public class GirocodePayload : PayloadBase
+ {
+ //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 GirocodePayload(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("_","-"));
+ }
+
+ 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);
+ }
+
+ 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)
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/MMSPayload.cs b/QRCoder2/Payloads/MMSPayload.cs
new file mode 100644
index 00000000..9d90ed28
--- /dev/null
+++ b/QRCoder2/Payloads/MMSPayload.cs
@@ -0,0 +1,52 @@
+namespace QRCoder2.Payloads
+{
+ public class MMSPayload : PayloadBase
+ {
+ private readonly string number, subject;
+ private readonly MMSEncoding encoding;
+
+ ///
+ /// Creates a MMS payload without text
+ ///
+ /// Receiver phone number
+ /// Encoding type
+ public MMSPayload(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 MMSPayload(string number, string subject, MMSEncoding encoding = MMSEncoding.MMS)
+ {
+ this.number = number;
+ this.subject = subject;
+ this.encoding = encoding;
+ }
+
+ public override string ToString()
+ {
+ switch (this.encoding)
+ {
+ case MMSEncoding.MMSTO:
+ return $"mmsto:{this.number}?subject={System.Uri.EscapeDataString(this.subject)}";
+ case MMSEncoding.MMS:
+ return $"mms:{this.number}?body={System.Uri.EscapeDataString(this.subject)}";
+ default:
+ return "mms:";
+ }
+ }
+
+ public enum MMSEncoding
+ {
+ MMS,
+ MMSTO
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/MailPayload.cs b/QRCoder2/Payloads/MailPayload.cs
new file mode 100644
index 00000000..fd169b59
--- /dev/null
+++ b/QRCoder2/Payloads/MailPayload.cs
@@ -0,0 +1,74 @@
+namespace QRCoder2.Payloads
+{
+ public class MailPayload : PayloadBase
+ {
+ private readonly string mailReceiver, subject, message;
+ private readonly MailEncoding encoding;
+
+ ///
+ /// Creates an empty email payload
+ ///
+ /// Receiver's email address
+ /// Payload encoding type. Choose dependent on your QR Code scanner app.
+ public MailPayload(string mailReceiver, MailEncoding encoding = MailEncoding.MAILTO)
+ {
+ this.mailReceiver = mailReceiver;
+ this.subject = this.message = string.Empty;
+ this.encoding = encoding;
+ }
+
+ ///
+ /// Creates an email payload with subject
+ ///
+ /// Receiver's email address
+ /// Subject line of the email
+ /// Payload encoding type. Choose dependent on your QR Code scanner app.
+ public MailPayload(string mailReceiver, string subject, MailEncoding encoding = MailEncoding.MAILTO)
+ {
+ this.mailReceiver = mailReceiver;
+ this.subject = subject;
+ this.message = string.Empty;
+ this.encoding = 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 MailPayload(string mailReceiver, string subject, string message, MailEncoding encoding = MailEncoding.MAILTO)
+ {
+ this.mailReceiver = mailReceiver;
+ this.subject = subject;
+ this.message = message;
+ this.encoding = encoding;
+ }
+
+ public override string ToString()
+ {
+ switch (this.encoding)
+ {
+ case MailEncoding.MAILTO:
+ return
+ $"mailto:{this.mailReceiver}?subject={System.Uri.EscapeDataString(this.subject)}&body={System.Uri.EscapeDataString(this.message)}";
+ case MailEncoding.MATMSG:
+ return
+ $"MATMSG:TO:{this.mailReceiver};SUB:{EscapeInput(this.subject)};BODY:{EscapeInput(this.message)};;";
+ case MailEncoding.SMTP:
+ return
+ $"SMTP:{this.mailReceiver}:{EscapeInput(this.subject, true)}:{EscapeInput(this.message, true)}";
+ default:
+ return this.mailReceiver;
+ }
+ }
+
+ public enum MailEncoding
+ {
+ MAILTO,
+ MATMSG,
+ SMTP
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/MoneroTransactionPayload.cs b/QRCoder2/Payloads/MoneroTransactionPayload.cs
new file mode 100644
index 00000000..03287137
--- /dev/null
+++ b/QRCoder2/Payloads/MoneroTransactionPayload.cs
@@ -0,0 +1,59 @@
+using System;
+
+namespace QRCoder2.Payloads
+{
+ public class MoneroTransactionPayload : PayloadBase
+ {
+ 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 MoneroTransactionPayload(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)
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/OneTimePasswordPayload.cs b/QRCoder2/Payloads/OneTimePasswordPayload.cs
new file mode 100644
index 00000000..ccfc18c2
--- /dev/null
+++ b/QRCoder2/Payloads/OneTimePasswordPayload.cs
@@ -0,0 +1,149 @@
+using System;
+using System.Text;
+
+namespace QRCoder2.Payloads
+{
+ public class OneTimePasswordPayload : PayloadBase
+ {
+ //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 (string.IsNullOrWhiteSpace(Secret))
+ {
+ throw new Exception("Secret must be a filled out base32 encoded string");
+ }
+ string strippedSecret = Secret.Replace(" ", "");
+ string escapedIssuer = null;
+ string escapedLabel = null;
+
+ if (!string.IsNullOrWhiteSpace(Issuer))
+ {
+ if (Issuer.Contains(":"))
+ {
+ throw new Exception("Issuer must not have a ':'");
+ }
+ escapedIssuer = Uri.EscapeUriString(Issuer);
+ }
+
+ if (!string.IsNullOrWhiteSpace(Label))
+ {
+ if (Label.Contains(":"))
+ {
+ throw new Exception("Label must not have a ':'");
+ }
+ escapedLabel = Uri.EscapeUriString(Label);
+ }
+
+ if (escapedLabel != null)
+ {
+ if (escapedIssuer != null)
+ {
+ escapedLabel = escapedIssuer + ":" + escapedLabel;
+ }
+ }
+ else if (escapedIssuer != null)
+ {
+ escapedLabel = escapedIssuer;
+ }
+
+ if (escapedLabel != null)
+ {
+ sb.Append(escapedLabel);
+ }
+
+ sb.Append("?secret=" + strippedSecret);
+
+ if (escapedIssuer != null)
+ {
+ sb.Append("&issuer=" + escapedIssuer);
+ }
+
+ if (Digits != 6)
+ {
+ sb.Append("&digits=" + Digits);
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/PayloadBase.cs b/QRCoder2/Payloads/PayloadBase.cs
new file mode 100644
index 00000000..903e11c8
--- /dev/null
+++ b/QRCoder2/Payloads/PayloadBase.cs
@@ -0,0 +1,67 @@
+using System;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace QRCoder2.Payloads
+{
+ public abstract class PayloadBase
+ {
+ public virtual int Version => -1;
+ public virtual ECCLevel EccLevel => ECCLevel.M;
+ public virtual EciMode EciMode => EciMode.Default;
+ public abstract override string ToString();
+
+ protected 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;
+ }
+
+ protected 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"));
+ }
+
+ protected 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;
+ }
+
+ protected 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})?)$");
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/PhoneNumberPayload.cs b/QRCoder2/Payloads/PhoneNumberPayload.cs
new file mode 100644
index 00000000..6532e70c
--- /dev/null
+++ b/QRCoder2/Payloads/PhoneNumberPayload.cs
@@ -0,0 +1,21 @@
+namespace QRCoder2.Payloads
+{
+ public class PhoneNumberPayload : PayloadBase
+ {
+ private readonly string number;
+
+ ///
+ /// Generates a phone call payload
+ ///
+ /// Phonenumber of the receiver
+ public PhoneNumberPayload(string number)
+ {
+ this.number = number;
+ }
+
+ public override string ToString()
+ {
+ return $"tel:{this.number}";
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/PlainTextPayload.cs b/QRCoder2/Payloads/PlainTextPayload.cs
new file mode 100644
index 00000000..c6c67c7a
--- /dev/null
+++ b/QRCoder2/Payloads/PlainTextPayload.cs
@@ -0,0 +1,21 @@
+namespace QRCoder2.Payloads
+{
+ public class PlainTextPayload : PayloadBase
+ {
+ private readonly string text;
+
+ ///
+ /// Generates a plain text payload
+ ///
+ ///
+ public PlainTextPayload(string text)
+ {
+ this.text = text;
+ }
+
+ public override string ToString()
+ {
+ return text;
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/SMSPayload.cs b/QRCoder2/Payloads/SMSPayload.cs
new file mode 100644
index 00000000..3c14f850
--- /dev/null
+++ b/QRCoder2/Payloads/SMSPayload.cs
@@ -0,0 +1,55 @@
+namespace QRCoder2.Payloads
+{
+ public class SMSPayload : PayloadBase
+ {
+ private readonly string number, subject;
+ private readonly SMSEncoding encoding;
+
+ ///
+ /// Creates a SMS payload without text
+ ///
+ /// Receiver phone number
+ /// Encoding type
+ public SMSPayload(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 SMSPayload(string number, string subject, SMSEncoding encoding = SMSEncoding.SMS)
+ {
+ this.number = number;
+ this.subject = subject;
+ this.encoding = encoding;
+ }
+
+ public override string ToString()
+ {
+ switch (this.encoding)
+ {
+ case SMSEncoding.SMS:
+ return $"sms:{this.number}?body={System.Uri.EscapeDataString(this.subject)}";
+ case SMSEncoding.SMS_iOS:
+ return $"sms:{this.number};body={System.Uri.EscapeDataString(this.subject)}";
+ case SMSEncoding.SMSTO:
+ return $"SMSTO:{this.number}:{this.subject}";
+ default:
+ return "sms:";
+ }
+ }
+
+ public enum SMSEncoding
+ {
+ SMS,
+ SMSTO,
+ SMS_iOS
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/ShadowSocksConfigPayload.cs b/QRCoder2/Payloads/ShadowSocksConfigPayload.cs
new file mode 100644
index 00000000..5c88f24e
--- /dev/null
+++ b/QRCoder2/Payloads/ShadowSocksConfigPayload.cs
@@ -0,0 +1,227 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+namespace QRCoder2.Payloads
+{
+ public class ShadowSocksConfigPayload : PayloadBase
+ {
+ 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 ShadowSocksConfigPayload(string hostname, int port, string password, Method method, string tag = null) :
+ this(hostname, port, password, method, null, tag)
+ { }
+
+ public ShadowSocksConfigPayload(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 ShadowSocksConfigPayload(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)
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/SkypeCallPayload.cs b/QRCoder2/Payloads/SkypeCallPayload.cs
new file mode 100644
index 00000000..4814c2be
--- /dev/null
+++ b/QRCoder2/Payloads/SkypeCallPayload.cs
@@ -0,0 +1,21 @@
+namespace QRCoder2.Payloads
+{
+ public class SkypeCallPayload : PayloadBase
+ {
+ private readonly string skypeUsername;
+
+ ///
+ /// Generates a Skype call payload
+ ///
+ /// Skype username which will be called
+ public SkypeCallPayload(string skypeUsername)
+ {
+ this.skypeUsername = skypeUsername;
+ }
+
+ public override string ToString()
+ {
+ return $"skype:{this.skypeUsername}?call";
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/SlovenianUpnQrPayload.cs b/QRCoder2/Payloads/SlovenianUpnQrPayload.cs
new file mode 100644
index 00000000..b9e0025b
--- /dev/null
+++ b/QRCoder2/Payloads/SlovenianUpnQrPayload.cs
@@ -0,0 +1,101 @@
+using System;
+using System.Text;
+
+namespace QRCoder2.Payloads
+{
+ public class SlovenianUpnQrPayload : PayloadBase
+ {
+ //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 ECCLevel EccLevel { get { return ECCLevel.M; } }
+ public override EciMode EciMode { get { return EciMode.Iso8859_2; } }
+
+ private string LimitLength(string value, int maxLength)
+ {
+ return (value.Length <= maxLength) ? value : value.Substring(0, maxLength);
+ }
+
+ public SlovenianUpnQrPayload(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 SlovenianUpnQrPayload(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();
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/SwissQrCodePayload.cs b/QRCoder2/Payloads/SwissQrCodePayload.cs
new file mode 100644
index 00000000..47e82e83
--- /dev/null
+++ b/QRCoder2/Payloads/SwissQrCodePayload.cs
@@ -0,0 +1,599 @@
+using System;
+using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
+using System.Text.RegularExpressions;
+
+namespace QRCoder2.Payloads
+{
+ public class SwissQrCodePayload : PayloadBase
+ {
+ //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 SwissQrCodePayload(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
+ }
+
+ private 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;
+ }
+
+ 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
+ }
+
+ 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;
+ }
+
+ 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)
+ {
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/UrlPayload.cs b/QRCoder2/Payloads/UrlPayload.cs
new file mode 100644
index 00000000..8807080b
--- /dev/null
+++ b/QRCoder2/Payloads/UrlPayload.cs
@@ -0,0 +1,21 @@
+namespace QRCoder2.Payloads
+{
+ public class UrlPayload : PayloadBase
+ {
+ private readonly string url;
+
+ ///
+ /// Generates a link. If not given, http/https protocol will be added.
+ ///
+ /// Link url target
+ public UrlPayload(string url)
+ {
+ this.url = url;
+ }
+
+ public override string ToString()
+ {
+ return (!this.url.StartsWith("http") ? "http://" + this.url : this.url);
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/WhatsAppMessagePayload.cs b/QRCoder2/Payloads/WhatsAppMessagePayload.cs
new file mode 100644
index 00000000..7f6cb8f6
--- /dev/null
+++ b/QRCoder2/Payloads/WhatsAppMessagePayload.cs
@@ -0,0 +1,35 @@
+using System;
+
+namespace QRCoder2.Payloads
+{
+ public class WhatsAppMessagePayload : PayloadBase
+ {
+ private readonly string number, message;
+
+ ///
+ /// Let's you compose a WhatApp message and send it the receiver number.
+ ///
+ /// Receiver phone number
+ /// The message
+ public WhatsAppMessagePayload(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 WhatsAppMessagePayload(string message)
+ {
+ this.number = string.Empty;
+ this.message = message;
+ }
+
+ public override string ToString()
+ {
+ return ($"whatsapp://send?phone={this.number}&text={Uri.EscapeDataString(message)}");
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/Payloads/WiFiPayload.cs b/QRCoder2/Payloads/WiFiPayload.cs
new file mode 100644
index 00000000..5c7daa86
--- /dev/null
+++ b/QRCoder2/Payloads/WiFiPayload.cs
@@ -0,0 +1,38 @@
+namespace QRCoder2.Payloads
+{
+ public class WiFiPayload : PayloadBase
+ {
+ 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
+ /// Authentication mode (WEP, WPA, WPA2)
+ /// Set flag, if the WiFi network hides its SSID
+ public WiFiPayload(string ssid, string password, Authentication authenticationMode, bool isHiddenSSID = false)
+ {
+ this.ssid = EscapeInput(ssid);
+ this.ssid = IsHexStyle(this.ssid) ? "\"" + this.ssid + "\"" : this.ssid;
+ this.password = EscapeInput(password);
+ this.password = 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
+ }
+ }
+}
\ No newline at end of file
diff --git a/QRCoder2/QRCodeData.cs b/QRCoder2/QRCodeData.cs
new file mode 100644
index 00000000..f39a8654
--- /dev/null
+++ b/QRCoder2/QRCodeData.cs
@@ -0,0 +1,172 @@
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.IO;
+using System.IO.Compression;
+using System.Linq;
+
+namespace QRCoder2
+{
+ 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));
+ }
+
+ 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))
+ {
+ dstream.CopyTo(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))
+ {
+ dstream.CopyTo(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;
+ }
+
+ 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/QRCoder2/QRCodeGenerator.cs b/QRCoder2/QRCodeGenerator.cs
new file mode 100644
index 00000000..18bbb69a
--- /dev/null
+++ b/QRCoder2/QRCodeGenerator.cs
@@ -0,0 +1,1566 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Collections;
+using QRCoder2.Payloads;
+
+namespace QRCoder2
+{
+ 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();
+
+ ///
+ /// 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(PayloadBase 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(PayloadBase 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(PayloadBase 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(PayloadBase 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 QRCoder2.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 QRCoder2.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;
+ }
+
+ 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/QRCoder2/QRCoder2.csproj b/QRCoder2/QRCoder2.csproj
new file mode 100644
index 00000000..7e24b13a
--- /dev/null
+++ b/QRCoder2/QRCoder2.csproj
@@ -0,0 +1,8 @@
+
+
+
+ net6.0
+
+
+
+
diff --git a/QRCoder2/Renderers/BitmapByteQRCode.cs b/QRCoder2/Renderers/BitmapByteQRCode.cs
new file mode 100644
index 00000000..0e0f92e7
--- /dev/null
+++ b/QRCoder2/Renderers/BitmapByteQRCode.cs
@@ -0,0 +1,123 @@
+using System.Collections.Generic;
+using System.Linq;
+using QRCoder2.Payloads;
+
+namespace QRCoder2.Renderers
+{
+
+ // ReSharper disable once InconsistentNaming
+ public class BitmapByteQRCode : QRCodeRendererBase
+ {
+ 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))
+ {
+ var qrCode = new BitmapByteQRCode(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))
+ {
+ var qrBmp = new BitmapByteQRCode(qrCode);
+ return qrBmp.GetGraphic(size);
+ }
+ }
+ }
+}
diff --git a/QRCoder2/Renderers/PngByteQRCode.cs b/QRCoder2/Renderers/PngByteQRCode.cs
new file mode 100644
index 00000000..78b69b77
--- /dev/null
+++ b/QRCoder2/Renderers/PngByteQRCode.cs
@@ -0,0 +1,338 @@
+using System;
+using System.IO;
+using System.IO.Compression;
+using QRCoder2.Payloads;
+
+namespace QRCoder2.Renderers
+{
+ public sealed class PngByteQRCode : QRCodeRendererBase
+ {
+ public PngByteQRCode(QRCodeData data) : base(data)
+ {
+ }
+
+
+ ///
+ /// Creates a black & white PNG of the QR code, using 1-bit grayscale.
+ ///
+ public byte[] GetGraphic(int pixelsPerModule)
+ {
+ using (var png = new PngBuilder())
+ {
+ var size = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule;
+ png.WriteHeader(size, size, 1, PngBuilder.ColorType.Greyscale);
+ png.WriteScanlines(this.DrawScanlines(pixelsPerModule));
+ 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)
+ {
+ using (var png = new PngBuilder())
+ {
+ var size = this.QrCodeData.ModuleMatrix.Count * pixelsPerModule;
+ png.WriteHeader(size, size, 1, PngBuilder.ColorType.Indexed);
+ png.WritePalette(darkColorRgba, lightColorRgba);
+ png.WriteScanlines(this.DrawScanlines(pixelsPerModule));
+ 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)
+ {
+ var moduleMatrix = this.QrCodeData.ModuleMatrix;
+ var matrixSize = moduleMatrix.Count;
+ 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];
+ 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])
+ {
+ 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)
+ {
+ using (var qrGenerator = new QRCodeGenerator())
+ using (var qrCodeData =
+ qrGenerator.CreateQrCode(plainText, eccLevel, forceUtf8, utf8BOM, eciMode, requestedVersion))
+ {
+ var qrCode = new PngByteQRCode(qrCodeData);
+ return qrCode.GetGraphic(pixelsPerModule, darkColorRgba, lightColorRgba);
+ }
+ }
+
+ public static byte[] GetQRCode(string txt, ECCLevel eccLevel, int size)
+ {
+ using (var qrGen = new QRCodeGenerator())
+ using (var qrCode = qrGen.CreateQrCode(txt, eccLevel))
+ {
+ var qrPng = new PngByteQRCode(qrCode);
+ return qrPng.GetGraphic(size);
+ }
+ }
+ }
+}
diff --git a/QRCoder2/Renderers/RendererBase.cs b/QRCoder2/Renderers/RendererBase.cs
new file mode 100644
index 00000000..62be13a0
--- /dev/null
+++ b/QRCoder2/Renderers/RendererBase.cs
@@ -0,0 +1,12 @@
+namespace QRCoder2.Renderers
+{
+ public abstract class QRCodeRendererBase
+ {
+ protected QRCodeData QrCodeData { get; set; }
+
+ protected QRCodeRendererBase(QRCodeData data)
+ {
+ this.QrCodeData = data;
+ }
+ }
+}
\ No newline at end of file