diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Json/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.Json/Shared.props
index 83919346..4d0e01ef 100644
--- a/Extras/Core/FastReport.Data/FastReport.Data.Json/Shared.props
+++ b/Extras/Core/FastReport.Data/FastReport.Data.Json/Shared.props
@@ -8,7 +8,7 @@
-
+
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/Shared.props
index 38a5f5e2..fa0babd3 100644
--- a/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/Shared.props
+++ b/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/Shared.props
@@ -12,9 +12,9 @@
-
-
-
+
+
+
diff --git a/Extras/Core/FastReport.Data/FastReport.Data.MsSql/Shared.props b/Extras/Core/FastReport.Data/FastReport.Data.MsSql/Shared.props
index 5cf69904..bf62f837 100644
--- a/Extras/Core/FastReport.Data/FastReport.Data.MsSql/Shared.props
+++ b/Extras/Core/FastReport.Data/FastReport.Data.MsSql/Shared.props
@@ -8,7 +8,7 @@
-
+
diff --git a/Extras/Core/FastReport.Data/WPF.props b/Extras/Core/FastReport.Data/WPF.props
index 0030f148..6bb29b99 100644
--- a/Extras/Core/FastReport.Data/WPF.props
+++ b/Extras/Core/FastReport.Data/WPF.props
@@ -12,7 +12,7 @@
..\..\..\..\FastReport.WPF\FastReport.WPF.csproj
FastReport.WPF.Demo
- ..\..\..\..\..\FastReport.Forms.WPF\src\FastReport.Forms.WPF.csproj
+ ..\..\..\..\FastReport.Forms.WPF\src\FastReport.Forms.WPF.csproj
FastReport.Forms.WPF.Demo
diff --git a/Extras/Core/FastReport.Plugin/FastReport.Plugins.WebP/Shared.props b/Extras/Core/FastReport.Plugin/FastReport.Plugins.WebP/Shared.props
index 7b257bd9..33923589 100644
--- a/Extras/Core/FastReport.Plugin/FastReport.Plugins.WebP/Shared.props
+++ b/Extras/Core/FastReport.Plugin/FastReport.Plugins.WebP/Shared.props
@@ -8,7 +8,7 @@
-
+
diff --git a/FastReport.Base/Barcode/BarcodeObject.cs b/FastReport.Base/Barcode/BarcodeObject.cs
index 32301eea..b4ec5225 100644
--- a/FastReport.Base/Barcode/BarcodeObject.cs
+++ b/FastReport.Base/Barcode/BarcodeObject.cs
@@ -644,12 +644,33 @@ public override void GetData()
}
}
- // without the second condition Text.StartsWith("SPC") the method will be executed for any QR
- // according to my observations, this does not break anything
+ if (Barcode is BarcodeQR)
+ {
+ //QRData.Parse(Text);
+
+ //QRSwiss qRSwiss = new QRSwiss(Text);
+ //Text = qRSwiss.Pack();
+
+ QRData qr = QRData.Parse(Text);
- if (Barcode is BarcodeQR/* && Text.StartsWith("SPC")*/)
- {
- QRData.Parse(Text);
+ if (qr is QRSwiss)
+ {
+ QRSwiss _qr = qr as QRSwiss;
+
+ QRSwissParameters parameters = new QRSwissParameters();
+ parameters.Iban = _qr._Iban;
+ parameters.Creditor = _qr.Creditor;
+ parameters.Reference = _qr._Reference;
+ parameters.AlternativeProcedure1 = _qr.AlternativeProcedure1;
+ parameters.AlternativeProcedure2 = _qr.AlternativeProcedure2;
+ parameters.AdditionalInformation = _qr._AdditionalInformation;
+ parameters.Amount = _qr.Amount;
+ parameters.Currency = _qr._Currency;
+ parameters.Debitor = _qr.Debitor;
+ QRSwiss swissQR = new QRSwiss(parameters);
+
+ Text = swissQR.Pack();
+ }
}
}
#endregion
diff --git a/FastReport.Base/Barcode/QRCode/QRData.cs b/FastReport.Base/Barcode/QRCode/QRData.cs
index eb25cf61..ff889ca0 100644
--- a/FastReport.Base/Barcode/QRCode/QRData.cs
+++ b/FastReport.Base/Barcode/QRCode/QRData.cs
@@ -1,6 +1,8 @@
using FastReport.Utils;
using System;
using System.Collections.Generic;
+using System.Globalization;
+using System.Linq;
using System.Security.Policy;
using System.Text;
using System.Text.RegularExpressions;
@@ -704,7 +706,7 @@ class QRSwiss : QRData
private Iban iban;
private string amount;
private Contact creditor, ultimateCreditor, debitor;
- private Currency currency;
+ private string currency;
private Reference reference;
private AdditionalInformation additionalInformation;
private MyRes res;
@@ -713,7 +715,7 @@ class QRSwiss : QRData
public Contact Creditor { get { return creditor; } set { creditor = value; } }
public Contact Debitor { get { return debitor; } set { debitor = value; } }
public string Amount { get { return amount; } }
- public Currency _Currency { get { return currency; } set { currency = value; } }
+ public string _Currency { get { return currency; } set { currency = value; } }
public Reference _Reference { get { return reference; } set { reference = value; } }
public AdditionalInformation _AdditionalInformation { get { return additionalInformation; } set { additionalInformation = value; } }
public string AlternativeProcedure1 { get { return alternativeProcedure1; } set { alternativeProcedure1 = value; } }
@@ -743,11 +745,53 @@ public QRSwiss(QRSwissParameters parameters)
if (!String.IsNullOrEmpty(parameters.Amount))
if (!parameters.Amount.StartsWith("[") || !parameters.Amount.EndsWith("]"))
- if (parameters.Amount.Length > 12)
- throw new SwissQrCodeException(res.Get("SwissAmountLength"));
+ {
+ //Amount has to use . as decimal separator in any case. See https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf page 27.
+ decimal.TryParse(parameters.Amount, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal decAmount);
+ decimal roundedAmount = decimal.Round(decAmount, 2, MidpointRounding.AwayFromZero);
+
+ // if the parameters.Amount contains "0", or "0.", or ".0"
+ if (Regex.IsMatch(parameters.Amount, @"^0(\.0+)?$"))
+ {
+ roundedAmount = 0m;
+ }
+ else
+ {
+ // If the rounded number is less than or equal to 0, then set it to the minimum allowed value
+ if (roundedAmount <= 0)
+ {
+ roundedAmount = 0.01m;
+ }
+
+ // If the rounded number is greater than 999999999.99, then set it to the maximum allowed value
+ if (roundedAmount > 999999999.99m)
+ {
+ roundedAmount = 999999999.99m;
+ }
+ }
+
+ parameters.Amount = roundedAmount.ToString("0.00", CultureInfo.InvariantCulture);
+
+ // in theory, this check is no longer needed
+ // if (parameters.Amount.Length > 12)
+ // throw new SwissQrCodeException(res.Get("SwissAmountLength"));
+ }
this.amount = parameters.Amount;
- this.currency = parameters.Currency.Value;
+ switch (parameters.Currency)
+ {
+ case nameof(Currency.EUR):
+ parameters.Currency = Currency.EUR.ToString();
+ break;
+ case nameof(Currency.CHF):
+ parameters.Currency = Currency.CHF.ToString();
+ break;
+ default:
+ if (!parameters.Currency.StartsWith("[") || !parameters.Currency.EndsWith("]"))
+ parameters.Currency = Currency.EUR.ToString();
+ break;
+ }
+ this.currency = parameters.Currency;
this.debitor = parameters.Debitor;
if (iban.IsQrIban && parameters.Reference.RefType != Reference.ReferenceType.QRR)
@@ -799,17 +843,30 @@ public override void Unpack(string data)
}
counter += 7;
if (!datas[counter].StartsWith("[") || !datas[counter].EndsWith("]"))
- amount = datas[counter] == String.Empty ? amount = null : Decimal.Parse(datas[counter].Replace('.', ',')).ToString();
+ {
+ //Amount has to use . as decimal separator in any case. See https://www.paymentstandards.ch/dam/downloads/ig-qr-bill-en.pdf page 27.
+ string invariantSeparator = CultureInfo.InvariantCulture.NumberFormat.NumberDecimalSeparator;
+ string clearQRSwissAmount = datas[counter].Replace(",", invariantSeparator).Replace("/", invariantSeparator);
+
+ decimal.TryParse(clearQRSwissAmount, NumberStyles.Number, CultureInfo.InvariantCulture, out decimal decAmount);
+ amount = datas[counter] == String.Empty ? amount = null : decAmount.ToString(CultureInfo.InvariantCulture);
+ }
else amount = datas[counter] == String.Empty ? amount = null : datas[counter];
counter++;
switch (datas[counter])
{
- case "EUR":
- this.currency = Currency.EUR;
+ case nameof(Currency.EUR):
+ this.currency = Currency.EUR.ToString();
+ break;
+ case nameof(Currency.CHF):
+ this.currency = Currency.CHF.ToString();
break;
- case "CHF":
- this.currency = Currency.CHF;
+ default:
+ if (datas[counter].StartsWith("[") && datas[counter].EndsWith("]"))
+ currency = datas[counter];
+ else
+ currency = Currency.EUR.ToString();
break;
}
counter++;
@@ -837,10 +894,20 @@ public override void Unpack(string data)
iban.TypeIban = Iban.IbanType.Iban;
if (!String.IsNullOrEmpty(reference.ReferenceText))
{
- if (reference.ChecksumMod10(reference.ReferenceText))
- reference._ReferenceTextType = Reference.ReferenceTextType.QrReference;
- else if (Regex.IsMatch(reference.ReferenceText, "^[a-zA-Z0-9 ]+$"))
- reference._ReferenceTextType = Reference.ReferenceTextType.CreditorReferenceIso11649;
+ if (reference.ReferenceText.StartsWith("[") && reference.ReferenceText.EndsWith("]"))
+ {
+ if (reference.RefType == Reference.ReferenceType.QRR)
+ reference._ReferenceTextType = Reference.ReferenceTextType.QrReference;
+ else
+ reference._ReferenceTextType = Reference.ReferenceTextType.CreditorReferenceIso11649;
+ }
+ else
+ {
+ if (reference.ChecksumMod10(reference.ReferenceText))
+ reference._ReferenceTextType = Reference.ReferenceTextType.QrReference;
+ else if (Regex.IsMatch(reference.ReferenceText, "^[a-zA-Z0-9 ]+$"))
+ reference._ReferenceTextType = Reference.ReferenceTextType.CreditorReferenceIso11649;
+ }
}
if (!iban._Iban.StartsWith("[") || !iban._Iban.EndsWith("]"))
@@ -892,18 +959,8 @@ public override string Pack()
//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.ToString().Replace(",", ".") : string.Empty) + br; //Amt
if (amount != null)
- {
- string strAmount = amount;
- if (!strAmount.StartsWith("[") || !strAmount.EndsWith("]"))
- if (!strAmount.Contains("."))
- strAmount = amount.ToString().Replace(",", ".");
- else
- strAmount += ".00";
- SwissQrCodePayload += strAmount;
- }
+ SwissQrCodePayload += amount;
else
SwissQrCodePayload += string.Empty;
SwissQrCodePayload += br;
@@ -922,7 +979,17 @@ public override string Pack()
//RmtInf "logical" element
SwissQrCodePayload += reference.RefType.ToString() + br; //Tp
- SwissQrCodePayload += (!string.IsNullOrEmpty(reference.ReferenceText) ? reference.ReferenceText : string.Empty) + br; //Ref
+ if (!string.IsNullOrEmpty(reference.ReferenceText))
+ {
+ if (reference.ReferenceText.StartsWith("[") && reference.ReferenceText.EndsWith("]"))
+ SwissQrCodePayload += reference.ReferenceText + br;
+ else
+ SwissQrCodePayload += new string(reference.ReferenceText.Where(c => char.IsLetterOrDigit(c)).ToArray()).ToUpper() + br; //Ref
+ }
+ else
+ {
+ SwissQrCodePayload += string.Empty + br; //Ref
+ }
//AddInf "logical" element
diff --git a/FastReport.Base/Barcode/SwissQRCode.cs b/FastReport.Base/Barcode/SwissQRCode.cs
index 76ec7833..1c3d70af 100644
--- a/FastReport.Base/Barcode/SwissQRCode.cs
+++ b/FastReport.Base/Barcode/SwissQRCode.cs
@@ -13,7 +13,7 @@ public class QRSwissParameters
{
#region private fields
private Iban iban;
- private Currency? currency;
+ private string currency;
private Contact creditor;
private Reference reference;
private AdditionalInformation additionalInformation;
@@ -23,6 +23,9 @@ public class QRSwissParameters
private string alternativeProcedure2;
#endregion
+ //Pattern extracted from https://qr-validation.iso-payments.ch as explained in https://github.com/codebude/QRCoder/issues/97
+ internal static string charsetPattern = @"^([a-zA-Z0-9\.,;:'\ \+\-/\(\)?\*\[\]\{\}\\`´~ ]|[!""#%&<>÷=@_$£]|[àáâäçèéêëìíîïñòóôöùúûüýßÀÁÂÄÇÈÉÊËÌÍÎÏÒÓÔÖÙÚÛÜÑ])*$";
+
#region public properties
///
/// IBAN object
@@ -32,7 +35,7 @@ public class QRSwissParameters
///
/// (either EUR or CHF)
///
- public Currency? Currency { get { return currency; } set { currency = value; } }
+ public string Currency { get { return currency; } set { currency = value; } }
///
/// Creditor (payee) information
@@ -85,6 +88,10 @@ public AdditionalInformation(string unstructuredMessage, string billInformation)
MyRes res = new MyRes("Messages,Swiss");
if (((unstructuredMessage != null ? unstructuredMessage.Length : 0) + (billInformation != null ? billInformation.Length : 0)) > 140)
throw new SwissQrCodeException(res.Get("SwissUnstructBillLength"));
+ if (!Regex.IsMatch(unstructuredMessage, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropUnstructuredMessage")) + QRSwissParameters.charsetPattern);
+ if (!Regex.IsMatch(billInformation, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropBillInformation")) + QRSwissParameters.charsetPattern);
this.unstructuredMessage = unstructuredMessage;
this.billInformation = billInformation;
this.trailer = "EPD";
@@ -155,10 +162,17 @@ public Reference(ReferenceType referenceType, string reference, ReferenceTextTyp
this.referenceType = referenceType;
this.referenceTextType = referenceTextType;
- string referenceCleaned = reference?.Replace(" ", "");
+ if (reference != null && reference.StartsWith("[") && reference.EndsWith("]"))
+ {
+ this.reference = reference;
+ return;
+ }
+ string referenceCleaned = reference is null ? null : new string(reference?.Where(c => char.IsLetterOrDigit(c)).ToArray());
if (referenceType == ReferenceType.NON && referenceCleaned != null)
throw new SwissQrCodeException(res.Get("SwissRefTypeNon"));
+ if (referenceType != ReferenceType.NON && referenceCleaned == null)
+ throw new SwissQrCodeException(res.Get("SwissRefTypeNotNon"));
if (referenceType != ReferenceType.NON && referenceCleaned != null && referenceTextType == null)
throw new SwissQrCodeException(res.Get("SwissRefTextTypeNon"));
if (referenceTextType == ReferenceTextType.QrReference && referenceCleaned != null && (referenceCleaned.Length > 27))
@@ -180,13 +194,13 @@ public Reference(string reference)
switch (data[0].Trim())
{
- case "QRR":
+ case nameof(ReferenceType.QRR):
this.referenceType = ReferenceType.QRR;
break;
- case "SCOR":
+ case nameof(ReferenceType.SCOR):
this.referenceType = ReferenceType.SCOR;
break;
- case "NON":
+ case nameof(ReferenceType.NON):
this.referenceType = ReferenceType.NON;
break;
}
@@ -272,64 +286,79 @@ private Contact(string name, string zipCode, string city, string country, string
{
twoLetterCodes = ValidTwoLetterCodes();
MyRes res = new MyRes("Messages,Swiss");
- //Pattern extracted from https://qr-validation.iso-payments.ch as explained in https://github.com/codebude/QRCoder/issues/97
- string charsetPattern = @"^([a-zA-Z0-9\.,;:'\ \+\-/\(\)?\*\[\]\{\}\\`´~ ]|[!""#%&<>÷=@_$£]|[àáâäçèéêëìíîïñòóôöùúûüýßÀÁÂÄÇÈÉÊËÌÍÎÏÒÓÔÖÙÚÛÜÑ])*$";
this.adrType = addressType;
if (string.IsNullOrEmpty(name))
throw new SwissQrCodeContactException(String.Format(res.Get("SwissEmptyProperty"), res.Get("SwissPropName")));
- if (name.Length > 70)
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropName"), 71));
- if (!Regex.IsMatch(name, charsetPattern))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropName")) + charsetPattern);
+ if (!name.StartsWith("[") || !name.EndsWith("]"))
+ {
+ if (name.Length > 70)
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropName"), 71));
+ if (!Regex.IsMatch(name, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropName")) + QRSwissParameters.charsetPattern);
+ }
this.name = name;
if (AddressType.StructuredAddress == this.adrType)
{
- if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropStreet"), 71));
- if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, charsetPattern))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropStreet")) + charsetPattern);
+ if (!streetOrAddressline1.StartsWith("[") || !streetOrAddressline1.EndsWith("]"))
+ {
+ if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropStreet"), 71));
+ if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropStreet")) + QRSwissParameters.charsetPattern);
+ }
this.streetOrAddressline1 = streetOrAddressline1;
- if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && houseNumberOrAddressline2.Length > 16)
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropHouseNumber"), 71));
+ if (!houseNumberOrAddressline2.StartsWith("[") || !houseNumberOrAddressline2.EndsWith("]"))
+ {
+ if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && houseNumberOrAddressline2.Length > 16)
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropHouseNumber"), 17));
+ if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && !Regex.IsMatch(houseNumberOrAddressline2, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropStreet")) + QRSwissParameters.charsetPattern);
+ }
this.houseNumberOrAddressline2 = houseNumberOrAddressline2;
}
else
{
if (!string.IsNullOrEmpty(streetOrAddressline1) && (streetOrAddressline1.Length > 70))
throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), "Address line 1", 71));
- if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, charsetPattern))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), "Address line 1") + charsetPattern);
+ if (!string.IsNullOrEmpty(streetOrAddressline1) && !Regex.IsMatch(streetOrAddressline1, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), "Address line 1") + QRSwissParameters.charsetPattern);
this.streetOrAddressline1 = streetOrAddressline1;
if (string.IsNullOrEmpty(houseNumberOrAddressline2))
throw new SwissQrCodeContactException(res.Get("SwissAddressLine2Error"));
if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && (houseNumberOrAddressline2.Length > 70))
throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), "Address line 2", 71));
- if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && !Regex.IsMatch(houseNumberOrAddressline2, charsetPattern))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), "Address line 2") + charsetPattern);
+ if (!string.IsNullOrEmpty(houseNumberOrAddressline2) && !Regex.IsMatch(houseNumberOrAddressline2, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), "Address line 2") + QRSwissParameters.charsetPattern);
this.houseNumberOrAddressline2 = houseNumberOrAddressline2;
}
if (AddressType.StructuredAddress == this.adrType)
{
- if (string.IsNullOrEmpty(zipCode))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissEmptyProperty"), res.Get("SwissPropZipCode")));
- if (zipCode.Length > 16)
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropZipCode"), 17));
- if (!Regex.IsMatch(zipCode, charsetPattern))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropZipCode")) + charsetPattern);
+ if (!zipCode.StartsWith("[") || !zipCode.EndsWith("]"))
+ {
+ if (string.IsNullOrEmpty(zipCode))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissEmptyProperty"), res.Get("SwissPropZipCode")));
+ if (zipCode.Length > 16)
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropZipCode"), 17));
+ if (!Regex.IsMatch(zipCode, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropZipCode")) + QRSwissParameters.charsetPattern);
+ }
this.zipCode = zipCode;
- if (string.IsNullOrEmpty(city))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissEmptyProperty"), res.Get("SwissPropCity")));
- if (city.Length > 35)
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropCity"), 36));
- if (!Regex.IsMatch(city, charsetPattern))
- throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropCity")) + charsetPattern);
+ if (!city.StartsWith("[") || !city.EndsWith("]"))
+ {
+ if (string.IsNullOrEmpty(city))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissEmptyProperty"), res.Get("SwissPropCity")));
+ if (city.Length > 35)
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissLengthMore"), res.Get("SwissPropCity"), 36));
+ if (!Regex.IsMatch(city, QRSwissParameters.charsetPattern))
+ throw new SwissQrCodeContactException(String.Format(res.Get("SwissPatternError"), res.Get("SwissPropCity")) + QRSwissParameters.charsetPattern);
+ }
this.city = city;
}
else
@@ -436,8 +465,8 @@ public Iban(string iban, IbanType ibanType)
throw new SwissQrCodeException(res.Get("SwissIbanNotValid"));
if (ibanType == IbanType.QrIban && !IsValidQRIban(iban))
throw new SwissQrCodeException(res.Get("SwissQRIbanNotValid"));
- if (!iban.StartsWith("CH") && !iban.StartsWith("LI"))
- throw new SwissQrCodeException("SwissQRStartNotValid");
+ if (!iban.StartsWith("CH", StringComparison.OrdinalIgnoreCase) && !iban.StartsWith("LI", StringComparison.OrdinalIgnoreCase))
+ throw new SwissQrCodeException(res.Get("SwissQRStartNotValid"));
this.iban = iban;
this.ibanType = ibanType;
}
@@ -454,7 +483,10 @@ public Iban(string iban)
public override string ToString()
{
- return iban.Replace("-", " ").Replace("\n", " ");
+ if (iban.StartsWith("[") && iban.EndsWith("]"))
+ return iban;
+ else
+ return new string(iban.Where(c => char.IsLetterOrDigit(c)).ToArray()).ToUpper();
}
public enum IbanType
@@ -466,7 +498,7 @@ public enum IbanType
private bool IsValidIban(string iban)
{
//Clean IBAN
- string ibanCleared = iban.ToUpper().Replace(" ", "").Replace("-", "");
+ string ibanCleared = new string(iban.Where(c => char.IsLetterOrDigit(c)).ToArray()).ToUpper();
//Check for general structure
bool structurallyValid = Regex.IsMatch(ibanCleared, @"^[a-zA-Z]{2}[0-9]{2}([a-zA-Z0-9]?){16,30}$");
@@ -492,7 +524,7 @@ private bool IsValidQRIban(string iban)
bool foundQrIid = false;
try
{
- string ibanCleared = iban.ToUpper().Replace(" ", "").Replace("-", "");
+ string ibanCleared = new string(iban.Where(c => char.IsLetterOrDigit(c)).ToArray()).ToUpper();
int possibleQrIid = Convert.ToInt32(ibanCleared.Substring(4, 5));
foundQrIid = possibleQrIid >= 30000 && possibleQrIid <= 31999;
}
diff --git a/FastReport.Base/Export/Html/HTMLExportLayers.cs b/FastReport.Base/Export/Html/HTMLExportLayers.cs
index e55d2f3a..76b869ac 100644
--- a/FastReport.Base/Export/Html/HTMLExportLayers.cs
+++ b/FastReport.Base/Export/Html/HTMLExportLayers.cs
@@ -489,7 +489,7 @@ private string GetLayerPicture(ReportComponentBase obj, out float Width, out flo
if (needClear)
{
- g.Clear(Color.Transparent);
+ g.Clear(imageFormat == ImageFormat.Bmp ? Color.White : Color.Transparent);
g.TextRenderingHint = TextRenderingHint.AntiAlias;
}
diff --git a/FastReport.Compat/FastReport.Compat.sln b/FastReport.Compat/FastReport.Compat.sln
new file mode 100644
index 00000000..22ad9956
--- /dev/null
+++ b/FastReport.Compat/FastReport.Compat.sln
@@ -0,0 +1,51 @@
+
+Microsoft Visual Studio Solution File, Format Version 12.00
+# Visual Studio Version 17
+VisualStudioVersion = 17.2.32519.379
+MinimumVisualStudioVersion = 10.0.40219.1
+Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "src", "src", "{C137F644-DCB8-424D-A49D-4ABE28E4BA72}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.Compat", "src\FastReport.Compat\FastReport.Compat.csproj", "{9A315AD7-3470-426C-B0B2-B9ECFA76C64B}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.Compat-Windows", "src\FastReport.Compat-Windows\FastReport.Compat-Windows.csproj", "{AB9F228F-5B37-436D-968F-37266B445B64}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.Compat.Skia", "src\FastReport.Compat.Skia\FastReport.Compat.Skia.csproj", "{C0A7385F-9AC6-4CC7-8BAA-E740F0C726D4}"
+EndProject
+Global
+ GlobalSection(SolutionConfigurationPlatforms) = preSolution
+ Debug|Any CPU = Debug|Any CPU
+ Demo|Any CPU = Demo|Any CPU
+ Release|Any CPU = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(ProjectConfigurationPlatforms) = postSolution
+ {9A315AD7-3470-426C-B0B2-B9ECFA76C64B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {9A315AD7-3470-426C-B0B2-B9ECFA76C64B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {9A315AD7-3470-426C-B0B2-B9ECFA76C64B}.Demo|Any CPU.ActiveCfg = Release|Any CPU
+ {9A315AD7-3470-426C-B0B2-B9ECFA76C64B}.Demo|Any CPU.Build.0 = Release|Any CPU
+ {9A315AD7-3470-426C-B0B2-B9ECFA76C64B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {9A315AD7-3470-426C-B0B2-B9ECFA76C64B}.Release|Any CPU.Build.0 = Release|Any CPU
+ {AB9F228F-5B37-436D-968F-37266B445B64}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {AB9F228F-5B37-436D-968F-37266B445B64}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {AB9F228F-5B37-436D-968F-37266B445B64}.Demo|Any CPU.ActiveCfg = Release|Any CPU
+ {AB9F228F-5B37-436D-968F-37266B445B64}.Demo|Any CPU.Build.0 = Release|Any CPU
+ {AB9F228F-5B37-436D-968F-37266B445B64}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {AB9F228F-5B37-436D-968F-37266B445B64}.Release|Any CPU.Build.0 = Release|Any CPU
+ {C0A7385F-9AC6-4CC7-8BAA-E740F0C726D4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {C0A7385F-9AC6-4CC7-8BAA-E740F0C726D4}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {C0A7385F-9AC6-4CC7-8BAA-E740F0C726D4}.Demo|Any CPU.ActiveCfg = Demo|Any CPU
+ {C0A7385F-9AC6-4CC7-8BAA-E740F0C726D4}.Demo|Any CPU.Build.0 = Demo|Any CPU
+ {C0A7385F-9AC6-4CC7-8BAA-E740F0C726D4}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {C0A7385F-9AC6-4CC7-8BAA-E740F0C726D4}.Release|Any CPU.Build.0 = Release|Any CPU
+ EndGlobalSection
+ GlobalSection(SolutionProperties) = preSolution
+ HideSolutionNode = FALSE
+ EndGlobalSection
+ GlobalSection(NestedProjects) = preSolution
+ {9A315AD7-3470-426C-B0B2-B9ECFA76C64B} = {C137F644-DCB8-424D-A49D-4ABE28E4BA72}
+ {AB9F228F-5B37-436D-968F-37266B445B64} = {C137F644-DCB8-424D-A49D-4ABE28E4BA72}
+ {C0A7385F-9AC6-4CC7-8BAA-E740F0C726D4} = {C137F644-DCB8-424D-A49D-4ABE28E4BA72}
+ EndGlobalSection
+ GlobalSection(ExtensibilityGlobals) = postSolution
+ SolutionGuid = {C3FF3494-9BB7-4A9B-BE84-B3A9AA93C2D3}
+ EndGlobalSection
+EndGlobal
diff --git a/FastReport.Compat/FastReport.OpenSource.snk b/FastReport.Compat/FastReport.OpenSource.snk
new file mode 100644
index 00000000..43376c7e
Binary files /dev/null and b/FastReport.Compat/FastReport.OpenSource.snk differ
diff --git a/FastReport.Compat/LICENSE.md b/FastReport.Compat/LICENSE.md
new file mode 100644
index 00000000..2af2d487
--- /dev/null
+++ b/FastReport.Compat/LICENSE.md
@@ -0,0 +1,7 @@
+Copyright (c) 2018-2023 Fast Reports Inc
+
+Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
+
+The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
+
+THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
\ No newline at end of file
diff --git a/FastReport.Compat/Nuget/FastReport.Compat/buildTransitive/netcoreapp3.0/FastReport.Compat.targets b/FastReport.Compat/Nuget/FastReport.Compat/buildTransitive/netcoreapp3.0/FastReport.Compat.targets
new file mode 100644
index 00000000..eac500f5
--- /dev/null
+++ b/FastReport.Compat/Nuget/FastReport.Compat/buildTransitive/netcoreapp3.0/FastReport.Compat.targets
@@ -0,0 +1,3 @@
+
+
+
diff --git a/FastReport.Compat/Nuget/_._ b/FastReport.Compat/Nuget/_._
new file mode 100644
index 00000000..e69de29b
diff --git a/FastReport.Compat/Nuget/template.nuspec b/FastReport.Compat/Nuget/template.nuspec
new file mode 100644
index 00000000..f87989fa
--- /dev/null
+++ b/FastReport.Compat/Nuget/template.nuspec
@@ -0,0 +1,10 @@
+
+
+
+
+
+
+
+
+
+
\ No newline at end of file
diff --git a/FastReport.Compat/UsedPackages.version b/FastReport.Compat/UsedPackages.version
new file mode 100644
index 00000000..3a5d3392
--- /dev/null
+++ b/FastReport.Compat/UsedPackages.version
@@ -0,0 +1,17 @@
+
+
+
+
+ 2024.1.0
+
+ 2024.1.0
+
+ [4.7.0,)
+
+ [3.3.1,)
+
+ [3.3.1,)
+
+
+
+
\ No newline at end of file
diff --git a/FastReport.Compat/_Build_Compat_Packages.bat b/FastReport.Compat/_Build_Compat_Packages.bat
new file mode 100644
index 00000000..e0cb91b2
--- /dev/null
+++ b/FastReport.Compat/_Build_Compat_Packages.bat
@@ -0,0 +1,14 @@
+@echo off
+setlocal ENABLEDELAYEDEXPANSION
+rem HELP FOR THIS FILE
+rem CLONE the fr if need
+rem clear the fr if need
+rem next run scripts from the fr tools
+rem that's all
+
+ECHO TRY TO BUILD FR.Compat
+
+pushd .\build\Cake
+ dotnet run --target=PackCompat --solution-filename=FastReport.Compat.sln --config=Release --vers=2024.1.0
+ dotnet run --target=PackCompatSkia --solution-filename=FastReport.Compat.sln --config=Release --vers=2024.1.0
+popd
\ No newline at end of file
diff --git a/FastReport.Compat/build_WPF.bat b/FastReport.Compat/build_WPF.bat
new file mode 100644
index 00000000..1f66efc4
--- /dev/null
+++ b/FastReport.Compat/build_WPF.bat
@@ -0,0 +1,2 @@
+dotnet pack src\FastReport.Compat-Windows\FastReport.Compat.WPF.csproj --configuration=Release /p:Version=2024.1.0
+dotnet pack src\FastReport.Compat-Windows\FastReport.Compat.WPF.csproj --configuration=Demo /p:Version=2024.1.0
diff --git a/FastReport.Compat/frlogo192.png b/FastReport.Compat/frlogo192.png
new file mode 100644
index 00000000..3d34b580
Binary files /dev/null and b/FastReport.Compat/frlogo192.png differ
diff --git a/FastReport.Compat/src/Directory.Build.props b/FastReport.Compat/src/Directory.Build.props
new file mode 100644
index 00000000..3e2c97b2
--- /dev/null
+++ b/FastReport.Compat/src/Directory.Build.props
@@ -0,0 +1,53 @@
+
+
+
+
+ $(MSBuildThisFileDirectory)..
+ $(MSBuildProjectDirectory)\bin
+ Fast Reports Inc.
+ FastReport
+ FastReport.Compat
+ Fast Reports Inc.
+ LICENSE.md
+ https://www.fast-report.com/en/product/fast-report-net
+ reporting, reports, pdf, html, mvc, core
+ https://github.com/FastReports/FastReport.Compat
+ GIT
+ true
+ Common compatible types for FastReport .Net, Core and Mono
+ frlogo192.png
+
+ true
+ $(SlnDir)\FastReport.OpenSource.snk
+ $(MSBuildThisFileDirectory)shared
+
+
+
+ none
+ true
+ false
+ false
+ false
+
+
+
+
+
+
+
+
+ True
+
+
+
+ True
+
+
+
+ True
+
+ false
+
+
+
+
\ No newline at end of file
diff --git a/FastReport.Compat/src/FastReport.Compat-Windows/FastReport.Compat-Windows.csproj b/FastReport.Compat/src/FastReport.Compat-Windows/FastReport.Compat-Windows.csproj
new file mode 100644
index 00000000..170ec86c
--- /dev/null
+++ b/FastReport.Compat/src/FastReport.Compat-Windows/FastReport.Compat-Windows.csproj
@@ -0,0 +1,42 @@
+
+
+
+ net462;$(CustomTargetFrameworks)
+ $(DefineConstants);NETCOREWIN
+ true
+ FastReport.Compat
+ $(AssemblyName)
+ false
+
+
+
+ $(TargetFrameworks);netcoreapp3.0;net5.0-windows7.0;
+
+
+
+
+
+
+ $(BaseOutputPath)\lib
+
+
+
+ true
+
+
+
+ $(BaseOutputPath)\build\$(TargetFramework)\lib\Win
+
+
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/FastReport.Compat/src/FastReport.Compat/FastReport.Compat.csproj b/FastReport.Compat/src/FastReport.Compat/FastReport.Compat.csproj
new file mode 100644
index 00000000..88c1572d
--- /dev/null
+++ b/FastReport.Compat/src/FastReport.Compat/FastReport.Compat.csproj
@@ -0,0 +1,23 @@
+
+
+
+ netstandard2.0;netstandard2.1;netcoreapp3.0
+ true
+
+
+
+ $(BaseOutputPath)\lib
+
+
+
+ $(BaseOutputPath)\build\$(TargetFramework)\lib\Any
+
+
+
+
+
+
+
+
+
+
diff --git a/FastReport.Compat/src/shared/Compiler/CSharpCodeProvider.cs b/FastReport.Compat/src/shared/Compiler/CSharpCodeProvider.cs
new file mode 100644
index 00000000..cb198e52
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/CSharpCodeProvider.cs
@@ -0,0 +1,54 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.CSharp;
+
+using System;
+using System.Collections.Generic;
+using System.Collections.Immutable;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using FastReport.Code.CodeDom.Compiler;
+using System.Runtime.InteropServices;
+using System.Threading.Tasks;
+using System.Threading;
+
+namespace FastReport.Code.CSharp
+{
+ public class CSharpCodeProvider : CodeDomProvider
+ {
+
+ protected override SyntaxTree ParseTree(string text, CancellationToken ct = default)
+ => CSharpSyntaxTree.ParseText(text,
+ cancellationToken: ct);
+
+
+ private static CSharpCompilationOptions GetCompilationOptions()
+ {
+ CSharpCompilationOptions options = new CSharpCompilationOptions(
+ OutputKind.DynamicallyLinkedLibrary,
+ optimizationLevel: OptimizationLevel.Release,
+ generalDiagnosticOption: ReportDiagnostic.Default,
+ reportSuppressedDiagnostics: true);
+ return options;
+ }
+
+ protected override Compilation CreateCompilation(SyntaxTree codeTree, ICollection references)
+ {
+ CSharpCompilationOptions options = GetCompilationOptions();
+ Compilation compilation = CSharpCompilation.Create(
+ "_" + Guid.NewGuid().ToString("D"), new SyntaxTree[] { codeTree },
+ references: references, options: options
+ );
+ return compilation;
+ }
+
+
+ public override void Dispose()
+ {
+
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/CodeDomProvider.cs b/FastReport.Compat/src/shared/Compiler/CodeDomProvider.cs
new file mode 100644
index 00000000..8452f61e
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/CodeDomProvider.cs
@@ -0,0 +1,834 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+using Microsoft.CodeAnalysis;
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Reflection;
+using System.Linq;
+using System.Reflection.Metadata;
+using System.Diagnostics;
+using System.ComponentModel;
+using System.Runtime.InteropServices;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.CodeAnalysis.Emit;
+using System.Globalization;
+#if NETCOREAPP
+using System.Runtime.Loader;
+#endif
+
+namespace FastReport.Code.CodeDom.Compiler
+{
+ public abstract class CodeDomProvider : IDisposable
+ {
+ static readonly Dictionary cache = new Dictionary();
+
+
+ ///
+ /// Throws before compilation emit
+ ///
+ public event EventHandler BeforeEmitCompilation;
+
+ ///
+ /// Manual resolve MetadataReference
+ ///
+ [Obsolete("Use AssemblyLoadResolver")]
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static Func ResolveMetadataReference { get; set; }
+
+ ///
+ /// Manual resolve MetadataReference
+ ///
+ public static IAssemblyLoadResolver AssemblyLoadResolver { get; set; }
+
+ ///
+ /// For developers only
+ ///
+ [EditorBrowsable(EditorBrowsableState.Never)]
+ public static event EventHandler Log;
+
+#if NETCOREAPP
+ ///
+ /// If these assemblies were not found when 'trimmed', then skip them
+ ///
+ private static readonly string[] SkippedAssemblies = new string[] {
+ "System",
+
+ "System.Core",
+
+ "System.Drawing",
+
+ //"System.Drawing.Primitives",
+
+ "System.Data",
+
+ "System.Xml",
+
+ "System.Private.CoreLib",
+ };
+#endif
+
+ private static readonly string[] _additionalAssemblies = new[] {
+ "mscorlib",
+ "netstandard",
+ "System.Core",
+ "System.Collections.Concurrent",
+ "System.Collections",
+ "System.Collections.NonGeneric",
+ "System.Collections.Specialized",
+ "System.ComponentModel",
+ "System.ComponentModel.Primitives",
+ "System.Data.Common",
+#if !SKIA
+ "System.Drawing.Common",
+#endif
+ "System.Globalization",
+ "System.IO",
+ "System.Linq",
+ "System.Linq.Expressions",
+ "System.Linq.Parallel",
+ "System.Linq.Queryable",
+ "System.Numerics",
+ "System.Runtime",
+ "System.Text.Encoding",
+ "System.Text.RegularExpressions"
+ };
+
+ [Conditional("DEBUG")] // Comment for use log messages in Release configuration
+ protected static void DebugMessage(string message)
+ {
+ Debug.WriteLine(message);
+ Console.WriteLine(message);
+
+ Log?.Invoke(null, message);
+ }
+
+ protected void AddReferences(CompilerParameters cp, List references)
+ {
+ foreach (string reference in cp.ReferencedAssemblies)
+ {
+ DebugMessage($"TRY ADD '{reference}'");
+#if NETCOREAPP
+ try
+ {
+#endif
+ var metadata = GetReference(reference);
+ references.Add(metadata);
+#if NETCOREAPP
+ }
+ catch (FileNotFoundException)
+ {
+ DebugMessage($"{reference} FileNotFound");
+
+ string assemblyName = GetCorrectAssemblyName(reference);
+ if (SkippedAssemblies.Contains(assemblyName))
+ {
+ DebugMessage($"{reference} FileNotFound. SKIPPED");
+ continue;
+ }
+ else
+ {
+ throw;
+ }
+ }
+#endif
+
+ DebugMessage($"{reference} ADDED");
+ }
+ DebugMessage("AFTER ADDING ReferencedAssemblies");
+
+ AddExtraAssemblies(cp.ReferencedAssemblies, references);
+ }
+
+ protected async ValueTask AddReferencesAsync(CompilerParameters cp, List references, CancellationToken cancellationToken)
+ {
+ foreach (string reference in cp.ReferencedAssemblies)
+ {
+ DebugMessage($"TRY ADD '{reference}'");
+#if NETCOREAPP
+ try
+ {
+#endif
+ var metadata = await GetReferenceAsync(reference, cancellationToken);
+ references.Add(metadata);
+#if NETCOREAPP
+ }
+ catch (FileNotFoundException)
+ {
+ DebugMessage($"{reference} FileNotFound");
+
+ string assemblyName = GetCorrectAssemblyName(reference);
+ if (SkippedAssemblies.Contains(assemblyName))
+ {
+ DebugMessage($"{reference} FileNotFound. SKIPPED");
+ continue;
+ }
+ else
+ {
+ throw;
+ }
+ }
+#endif
+
+ DebugMessage($"{reference} ADDED");
+ }
+ DebugMessage("AFTER ADDING ReferencedAssemblies");
+
+ await AddExtraAssembliesAsync(cp.ReferencedAssemblies, references, cancellationToken);
+ }
+
+
+ protected void AddExtraAssemblies(StringCollection referencedAssemblies, List references)
+ {
+ DebugMessage("Add Extra Assemblies...");
+ foreach(string assembly in _additionalAssemblies)
+ {
+ if (!referencedAssemblies.Contains(assembly))
+ {
+#if NETCOREAPP
+ try
+ {
+#endif
+ var metadata = GetReference(assembly);
+ references.Add(metadata);
+#if NETCOREAPP
+ }
+ // If user run 'dotnet publish' with Trimmed - dotnet cut some extra assemblies.
+ // We skip this error, because some assemblies in 'assemblies' array may not be needed
+ catch (FileNotFoundException)
+ {
+ DebugMessage($"{assembly} FILENOTFOUND. SKIPPED");
+ continue;
+ }
+#endif
+ }
+ }
+ }
+
+ protected async ValueTask AddExtraAssembliesAsync(StringCollection referencedAssemblies, List references, CancellationToken ct)
+ {
+ DebugMessage("Add Extra Assemblies...");
+ foreach (string assembly in _additionalAssemblies)
+ {
+ if (!referencedAssemblies.Contains(assembly))
+ {
+#if NETCOREAPP
+ try
+ {
+#endif
+ var metadata = await GetReferenceAsync(assembly, ct);
+ references.Add(metadata);
+#if NETCOREAPP
+ }
+ // If user run 'dotnet publish' with Trimmed - dotnet cut some extra assemblies.
+ // We skip this error, because some assemblies in 'assemblies' array may not be needed
+ catch (FileNotFoundException)
+ {
+ DebugMessage($"{assembly} FILENOTFOUND. SKIPPED");
+ continue;
+ }
+#endif
+ }
+ }
+ }
+
+
+ private void OnBeforeEmitCompilation(Compilation compilation)
+ {
+ if (BeforeEmitCompilation != null)
+ {
+ var eventArgs = new CompilationEventArgs(compilation);
+ BeforeEmitCompilation(this, eventArgs);
+ }
+ }
+
+ public MetadataReference GetReference(string refDll)
+ {
+ if (cache.ContainsKey(refDll))
+ return cache[refDll];
+
+ MetadataReference result;
+ string reference = GetCorrectAssemblyName(refDll);
+
+ try
+ {
+ if (!refDll.Contains(Path.DirectorySeparatorChar))
+ {
+#if NETCOREAPP
+ // try find in AssemblyLoadContext
+ foreach (AssemblyLoadContext assemblyLoadContext in AssemblyLoadContext.All)
+ {
+ foreach (Assembly loadedAssembly in assemblyLoadContext.Assemblies)
+ {
+ if (loadedAssembly.GetName().Name == reference)
+ {
+ DebugMessage($"FIND {reference} IN AssemblyLoadContext");
+
+ result = ProcessAssembly(loadedAssembly);
+
+ AddToCache(refDll, result);
+ return result;
+ }
+ }
+ }
+#else
+ foreach (Assembly currAssembly in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ if (string.Compare(currAssembly.GetName().Name, reference, true) == 0)
+ {
+ DebugMessage("FIND IN AppDomain");
+
+ // Found it, return the location as the full reference.
+ result = ProcessAssembly(currAssembly);
+ AddToCache(refDll, result);
+ return result;
+ }
+ }
+#endif
+ // try find in ReferencedAssemblies
+ foreach (AssemblyName name in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
+ {
+ if (name.Name == reference)
+ {
+ DebugMessage($"FIND {reference} IN ReferencedAssemblies");
+#if NETCOREAPP
+ // try load Assembly in runtime (for user script with custom assembly)
+ var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(name);
+#else
+ var assembly = Assembly.Load(name);
+#endif
+ result = ProcessAssembly(assembly);
+
+ AddToCache(refDll, result);
+ return result;
+ }
+ }
+ }
+
+
+ result = MetadataReference.CreateFromFile(refDll);
+#if NETCOREAPP
+ try
+ {
+ // try load Assembly in runtime (for user script with custom assembly)
+ AssemblyLoadContext.Default.LoadFromAssemblyPath(refDll);
+ }
+ catch(ArgumentException) {
+ var fullpath = Path.Combine(Environment.CurrentDirectory, refDll);
+ try
+ {
+ AssemblyLoadContext.Default.LoadFromAssemblyPath(fullpath);
+ }
+ catch { }
+ }
+ catch { }
+#endif
+ AddToCache(refDll, result);
+
+ return result;
+ }
+ catch
+ {
+ DebugMessage("IN AssemblyName");
+ var assemblyName = new AssemblyName(reference);
+
+ result = UserResolveMetadataReference(assemblyName);
+ if(result != null)
+ {
+ DebugMessage($"MetadataReference for assembly {reference} resolved by user");
+ AddToCache(refDll, result);
+ return result;
+ }
+
+#if NETCOREAPP
+ // try load Assembly in runtime (for user script with custom assembly)
+ var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
+#else
+ var assembly = Assembly.Load(assemblyName);
+#endif
+ DebugMessage("After LoadFromAssemblyName");
+
+ result = ProcessAssembly(assembly);
+
+ AddToCache(refDll, result);
+ return result;
+ }
+ }
+
+ private static async ValueTask GetReferenceAsync(string refDll, CancellationToken cancellationToken)
+ {
+ DebugMessage($"GetReferenceAsync: {refDll}");
+
+ if (cache.ContainsKey(refDll))
+ return cache[refDll];
+
+ MetadataReference result;
+ string reference = GetCorrectAssemblyName(refDll);
+
+ try
+ {
+ if (!refDll.Contains(Path.DirectorySeparatorChar))
+ {
+#if NETCOREAPP
+ // try find in AssemblyLoadContext
+ foreach (AssemblyLoadContext assemblyLoadContext in AssemblyLoadContext.All)
+ {
+ foreach (Assembly loadedAssembly in assemblyLoadContext.Assemblies)
+ {
+ if (loadedAssembly.GetName().Name == reference)
+ {
+ DebugMessage($"FIND {reference} IN AssemblyLoadContext");
+
+ result = await ProcessAssemblyAsync(loadedAssembly, cancellationToken);
+
+ AddToCache(refDll, result);
+ return result;
+ }
+ }
+ }
+#else
+ foreach (Assembly currAssembly in AppDomain.CurrentDomain.GetAssemblies())
+ {
+ if (string.Compare(currAssembly.GetName().Name, reference, true) == 0)
+ {
+ DebugMessage("FIND IN AppDomain");
+
+ // Found it, return the location as the full reference.
+ result = await ProcessAssemblyAsync(currAssembly, cancellationToken);
+ AddToCache(refDll, result);
+ return result;
+ }
+ }
+#endif
+ // try find in ReferencedAssemblies
+ foreach (AssemblyName name in Assembly.GetExecutingAssembly().GetReferencedAssemblies())
+ {
+ if (name.Name == reference)
+ {
+ DebugMessage($"FIND {reference} IN ReferencedAssemblies");
+#if NETCOREAPP
+ // try load Assembly in runtime (for user script with custom assembly)
+ var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(name);
+#else
+ var assembly = Assembly.Load(name);
+#endif
+ result = await ProcessAssemblyAsync(assembly, cancellationToken);
+
+ AddToCache(refDll, result);
+ return result;
+ }
+ }
+ }
+
+
+ result = MetadataReference.CreateFromFile(refDll);
+#if NETCOREAPP
+ try
+ {
+ // try load Assembly in runtime (for user script with custom assembly)
+ AssemblyLoadContext.Default.LoadFromAssemblyPath(refDll);
+ }
+ catch(ArgumentException) {
+ var fullpath = Path.Combine(Environment.CurrentDirectory, refDll);
+ try
+ {
+ AssemblyLoadContext.Default.LoadFromAssemblyPath(fullpath);
+ }
+ catch { }
+ }
+ catch { }
+#endif
+ AddToCache(refDll, result);
+
+ return result;
+ }
+ catch
+ {
+ DebugMessage("IN AssemblyName");
+ var assemblyName = new AssemblyName(reference);
+
+ result = await UserResolveMetadataReferenceAsync(assemblyName, cancellationToken);
+ if (result != null)
+ {
+ DebugMessage($"MetadataReference for assembly {reference} resolved by user");
+ AddToCache(refDll, result);
+ return result;
+ }
+
+#if NETCOREAPP
+ // try load Assembly in runtime (for user script with custom assembly)
+ var assembly = AssemblyLoadContext.Default.LoadFromAssemblyName(assemblyName);
+#else
+ var assembly = Assembly.Load(assemblyName);
+#endif
+ DebugMessage("After LoadFromAssemblyName");
+
+ result = await ProcessAssemblyAsync(assembly, cancellationToken);
+
+ AddToCache(refDll, result);
+ return result;
+ }
+ }
+
+
+ private static MetadataReference UserResolveMetadataReference(AssemblyName assembly)
+ {
+ if(AssemblyLoadResolver != null)
+ {
+ return AssemblyLoadResolver.LoadManagedLibrary(assembly);
+ }
+
+ if (ResolveMetadataReference == null)
+ return null;
+
+ return ResolveMetadataReference(assembly);
+ }
+
+ private static async ValueTask UserResolveMetadataReferenceAsync(AssemblyName assembly, CancellationToken ct)
+ {
+ if (AssemblyLoadResolver != null)
+ {
+ return await AssemblyLoadResolver.LoadManagedLibraryAsync(assembly, ct);
+ }
+
+ return null;
+ }
+
+ private static MetadataReference ProcessAssembly(Assembly assembly)
+ {
+ MetadataReference result;
+ DebugMessage($"Location: {assembly.Location}");
+
+#if NETCOREAPP
+ // In SFA location is empty
+ // In WASM location is empty
+ // In Android DEBUG location is correct (not empty)
+ // In Android RELEASE (AOT) location is not empty but incorrect
+ if (SpecialCondition(assembly))
+ {
+ DebugMessage("SpecialCondition is true");
+ result = GetMetadataReferenceSpecialized(assembly);
+ return result;
+ }
+#endif
+ result = MetadataReference.CreateFromFile(assembly.Location);
+
+ return result;
+ }
+
+ private static async ValueTask ProcessAssemblyAsync(Assembly assembly, CancellationToken ct)
+ {
+ MetadataReference result;
+ DebugMessage($"Location: {assembly.Location}");
+
+
+#if NETCOREAPP
+ // In SFA location is empty
+ // In WASM location is empty
+ // In Android DEBUG location is correct (not empty)
+ // In Android RELEASE (AOT) location is not empty but incorrect
+ if (SpecialCondition(assembly))
+ {
+ DebugMessage("SpecialCondition is true");
+ result = await GetMetadataReferenceSpecializedAsync(assembly, ct);
+ return result;
+ }
+#endif
+ result = MetadataReference.CreateFromFile(assembly.Location);
+
+ return result;
+ }
+
+
+#if NETCOREAPP
+
+ private static bool SpecialCondition(Assembly assembly)
+ {
+ string location = assembly.Location;
+
+ DebugMessage($"assemblyName Name {assembly.GetName().Name}");
+
+ bool result = string.IsNullOrEmpty(location)
+#if NET6_0_OR_GREATER // ANDROID_BUILD || IOS_BUILD
+ || location.StartsWith(assembly.GetName().Name)
+#endif
+ ;
+ return result;
+ }
+
+
+ private static MetadataReference GetMetadataReferenceSpecialized(Assembly assembly)
+ {
+ MetadataReference result;
+ try
+ {
+ result = GetMetadataReferenceInSingleFileApp(assembly);
+ }
+ catch (NotImplementedException)
+ {
+ DebugMessage("Not implemented assembly load from SFA");
+ // try load from external source
+ result = UserResolveMetadataReference(assembly.GetName());
+
+ if(result == null)
+ throw;
+ }
+ return result;
+ }
+
+ private static async ValueTask GetMetadataReferenceSpecializedAsync(Assembly assembly, CancellationToken ct)
+ {
+ MetadataReference result;
+ try
+ {
+ result = GetMetadataReferenceInSingleFileApp(assembly);
+ }
+ catch (NotImplementedException)
+ {
+ DebugMessage("Not implemented assembly load from SFA");
+ // try load from external source
+ result = await UserResolveMetadataReferenceAsync(assembly.GetName(), ct);
+
+ if (result == null)
+ throw;
+ }
+ return result;
+ }
+
+
+ private static unsafe MetadataReference GetMetadataReferenceInSingleFileApp(Assembly assembly)
+ {
+ DebugMessage($"TRY IN UNSAFE METHOD {assembly.GetName().Name}");
+ assembly.TryGetRawMetadata(out byte* blob, out int length);
+ var moduleMetadata = ModuleMetadata.CreateFromMetadata((IntPtr)blob, length);
+ var assemblyMetadata = AssemblyMetadata.Create(moduleMetadata);
+ return assemblyMetadata.GetReference();
+ }
+
+#endif
+
+ public static string TryFixReferenceInSingeFileApp(Assembly assembly)
+ {
+#if NETCOREAPP
+ try
+ {
+ string assemblyName = assembly.GetName().Name;
+ if (!cache.ContainsKey(assemblyName))
+ {
+ MetadataReference metadataReference = GetMetadataReferenceSpecialized(assembly);
+ AddToCache(assemblyName, metadataReference);
+ }
+ return assemblyName;
+ }
+ catch (Exception ex)
+ {
+ DebugMessage(ex.ToString());
+ }
+#endif
+ return null;
+ }
+
+ public static async ValueTask TryFixReferenceInSingeFileAppAsync(Assembly assembly, CancellationToken ct)
+ {
+#if NETCOREAPP
+ try
+ {
+ string assemblyName = assembly.GetName().Name;
+ if (!cache.ContainsKey(assemblyName))
+ {
+ MetadataReference metadataReference = await GetMetadataReferenceSpecializedAsync(assembly, ct);
+ AddToCache(assemblyName, metadataReference);
+ }
+ return assemblyName;
+ }
+ catch (Exception ex)
+ {
+ DebugMessage(ex.ToString());
+ }
+#endif
+ return null;
+ }
+
+
+ private static void AddToCache(string refDll, MetadataReference metadata)
+ {
+ cache[refDll] = metadata;
+ }
+
+ private static string GetCorrectAssemblyName(string reference)
+ {
+ string assemblyName = reference.EndsWith(".dll") || reference.EndsWith(".exe") ?
+ reference.Substring(0, reference.Length - 4) : reference;
+ return assemblyName;
+ }
+
+ // backward compabilty
+ public CompilerResults CompileAssemblyFromSource(CompilerParameters cp, string code)
+ {
+ return CompileAssemblyFromSource(cp, code, null);
+ }
+
+ public CompilerResults CompileAssemblyFromSource(CompilerParameters cp, string code, CultureInfo cultureInfo = null)
+ {
+ DebugMessage(typeof(SyntaxTree).Assembly.FullName);
+ DebugMessage($"threadId: {Environment.CurrentManagedThreadId}");
+
+#if NET6_0_OR_GREATER
+ DebugMessage($"rid: {RuntimeInformation.RuntimeIdentifier} " +
+ $"arch: {RuntimeInformation.ProcessArchitecture} " +
+ $"os-arch: {RuntimeInformation.OSArchitecture} " +
+ $"os: {RuntimeInformation.OSDescription}");
+#endif
+
+ DebugMessage("FR.Compat: " +
+#if NETSTANDARD
+ "NETSTANDARD"
+#elif NETCOREAPP
+ "NETCOREAPP"
+#endif
+ );
+
+ SyntaxTree codeTree = ParseTree(code);
+
+ List references = new List();
+
+ AddReferences(cp, references);
+
+ DebugMessage($"References count: {references.Count}");
+ //foreach (var reference in references)
+ // DebugMessage($"{reference.Display}");
+
+ Compilation compilation = CreateCompilation(codeTree, references);
+
+
+ OnBeforeEmitCompilation(compilation);
+
+ return Emit(compilation, cultureInfo);
+ }
+
+
+ public async Task CompileAssemblyFromSourceAsync(CompilerParameters cp, string code, CultureInfo cultureInfo = null, CancellationToken cancellationToken = default)
+ {
+ DebugMessage(typeof(SyntaxTree).Assembly.FullName);
+ DebugMessage($"threadId: {Environment.CurrentManagedThreadId}");
+
+#if NET6_0_OR_GREATER
+ DebugMessage($"rid: {RuntimeInformation.RuntimeIdentifier} " +
+ $"arch: {RuntimeInformation.ProcessArchitecture} " +
+ $"os-arch: {RuntimeInformation.OSArchitecture} " +
+ $"os: {RuntimeInformation.OSDescription}");
+#endif
+
+ DebugMessage("FR.Compat: " +
+#if NETSTANDARD
+ "NETSTANDARD"
+#elif NETCOREAPP
+ "NETCOREAPP"
+#endif
+ );
+
+ SyntaxTree codeTree = ParseTree(code, cancellationToken);
+
+ List references = new List();
+
+ await AddReferencesAsync(cp, references, cancellationToken);
+
+ DebugMessage($"References count: {references.Count}");
+ //foreach (var reference in references)
+ // DebugMessage($"{reference.Display}");
+
+ Compilation compilation = CreateCompilation(codeTree, references);
+ OnBeforeEmitCompilation(compilation);
+
+ return await EmitAsync(compilation, cultureInfo, cancellationToken);
+ }
+
+ protected abstract Compilation CreateCompilation(SyntaxTree codeTree, ICollection references);
+
+ protected abstract SyntaxTree ParseTree(string text, CancellationToken ct = default);
+
+ public abstract void Dispose();
+
+
+ private static CompilerResults Emit(Compilation compilation, CultureInfo cultureInfo, CancellationToken ct = default)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ DebugMessage("Emit...");
+ //DebugMessage(code);
+ DebugMessage($"threadId: {Environment.CurrentManagedThreadId}");
+ EmitResult results = compilation.Emit(ms,
+ cancellationToken: ct);
+ return HandleEmitResult(results, ms, cultureInfo);
+ }
+ }
+
+ private static async Task EmitAsync(Compilation compilation, CultureInfo cultureInfo, CancellationToken ct = default)
+ {
+ using (MemoryStream ms = new MemoryStream())
+ {
+ DebugMessage("Emit...");
+ //DebugMessage(code);
+ // we use Task.Run because in Wasm Emit throws an exception in current context
+ EmitResult results = await Task.Run(() => compilation.Emit(ms,
+ cancellationToken: ct), ct);
+ return HandleEmitResult(results, ms, cultureInfo);
+ }
+ }
+
+ private static CompilerResults HandleEmitResult(EmitResult results, MemoryStream ms, CultureInfo cultureInfo)
+ {
+ if (results.Success)
+ {
+#if DEBUG
+ foreach (Diagnostic d in results.Diagnostics)
+ if (d.Severity > DiagnosticSeverity.Hidden)
+ DebugMessage($"Compiler {d.Severity}: {d.GetMessage()}. Line: {d.Location}");
+#endif
+
+ ms.Position = 0;
+ Assembly compiledAssembly = null;
+
+#if NETCOREAPP
+ foreach (var assemblyLoadContext in AssemblyLoadContext.All)
+ {
+ if (assemblyLoadContext.Assemblies.Any(assembly => assembly == typeof(CodeDomProvider).Assembly))
+ {
+ compiledAssembly = assemblyLoadContext.LoadFromStream(ms);
+ }
+ }
+
+ if (compiledAssembly == null)
+ compiledAssembly = AssemblyLoadContext.Default.LoadFromStream(ms);
+#else
+ compiledAssembly = Assembly.Load(ms.ToArray());
+#endif
+ return new CompilerResults(compiledAssembly);
+ }
+ else
+ {
+ DebugMessage($"results not success, {ms.Length}");
+ CompilerResults result = new CompilerResults();
+ foreach (Diagnostic d in results.Diagnostics)
+ {
+ if (d.Severity == DiagnosticSeverity.Error)
+ {
+ DebugMessage(d.GetMessage());
+ var position = d.Location.GetLineSpan().StartLinePosition;
+ result.Errors.Add(new CompilerError()
+ {
+ ErrorText = d.GetMessage(cultureInfo),
+ ErrorNumber = d.Id,
+ Line = position.Line,
+ Column = position.Character,
+ });
+ }
+ }
+ return result;
+ }
+ }
+
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/CodeGenerator.cs b/FastReport.Compat/src/shared/Compiler/CodeGenerator.cs
new file mode 100644
index 00000000..c964fed1
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/CodeGenerator.cs
@@ -0,0 +1,17 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace FastReport.Code.CodeDom.Compiler
+{
+ public class CodeGenerator
+ {
+ public static bool IsValidLanguageIndependentIdentifier(string value)
+ {
+ //TODO o_0 what????
+ return true;
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/CompilationEventArgs.cs b/FastReport.Compat/src/shared/Compiler/CompilationEventArgs.cs
new file mode 100644
index 00000000..4c234ede
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/CompilationEventArgs.cs
@@ -0,0 +1,21 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+using Microsoft.CodeAnalysis;
+using System;
+using System.IO;
+using System.Collections.Generic;
+using System.Linq;
+
+namespace FastReport.Code.CodeDom.Compiler
+{
+ public class CompilationEventArgs : EventArgs
+ {
+ public CompilationEventArgs(Compilation compilation)
+ {
+ Compilation = compilation;
+ }
+
+ public Compilation Compilation { get; }
+
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/CompilerError.cs b/FastReport.Compat/src/shared/Compiler/CompilerError.cs
new file mode 100644
index 00000000..7a0131cc
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/CompilerError.cs
@@ -0,0 +1,18 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace FastReport.Code.CodeDom.Compiler
+{
+ public class CompilerError
+ {
+ public int Line { get; set; }
+ public int Column { get; set; }
+ public string ErrorText { get; set; }
+ public string ErrorNumber { get; set; }
+
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/CompilerParameters.cs b/FastReport.Compat/src/shared/Compiler/CompilerParameters.cs
new file mode 100644
index 00000000..d4857b2c
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/CompilerParameters.cs
@@ -0,0 +1,17 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+using System;
+using System.Collections.Generic;
+using System.Collections.Specialized;
+using System.Text;
+
+namespace FastReport.Code.CodeDom.Compiler
+{
+ public class CompilerParameters
+ {
+ public bool GenerateInMemory { get; set; }
+ public StringCollection ReferencedAssemblies { get; } = new StringCollection();
+ public TempFileCollection TempFiles { get; set; } = new TempFileCollection("", false);
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/CompilerResults.cs b/FastReport.Compat/src/shared/Compiler/CompilerResults.cs
new file mode 100644
index 00000000..d8ebd45e
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/CompilerResults.cs
@@ -0,0 +1,25 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+using System;
+using System.Collections.Generic;
+using System.Reflection;
+using System.Text;
+
+namespace FastReport.Code.CodeDom.Compiler
+{
+ public class CompilerResults
+ {
+ public CompilerResults()
+ {
+ }
+
+ public CompilerResults(Assembly compiledAssembly)
+ {
+ CompiledAssembly = compiledAssembly;
+ }
+
+ public List Errors { get; } = new List();
+ public Assembly CompiledAssembly { get; }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/IAssemblyLoadResolver.cs b/FastReport.Compat/src/shared/Compiler/IAssemblyLoadResolver.cs
new file mode 100644
index 00000000..693ca8df
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/IAssemblyLoadResolver.cs
@@ -0,0 +1,18 @@
+#if NETSTANDARD || NETCOREAPP
+using Microsoft.CodeAnalysis;
+
+using System.Reflection;
+using System.Threading;
+using System.Threading.Tasks;
+
+namespace FastReport.Code
+{
+ public interface IAssemblyLoadResolver
+ {
+
+ MetadataReference LoadManagedLibrary(AssemblyName assemblyName);
+
+ Task LoadManagedLibraryAsync(AssemblyName assemblyName, CancellationToken ct);
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/TempFileCollection.cs b/FastReport.Compat/src/shared/Compiler/TempFileCollection.cs
new file mode 100644
index 00000000..a84284a8
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/TempFileCollection.cs
@@ -0,0 +1,22 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+
+using System;
+using System.Collections.Generic;
+using System.Text;
+
+namespace FastReport.Code.CodeDom.Compiler
+{
+ public class TempFileCollection
+ {
+ public string tempFolder;
+ public bool v;
+
+ public TempFileCollection(string tempFolder, bool v)
+ {
+ this.tempFolder = tempFolder;
+ this.v = v;
+ }
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/Compiler/VBCodeProvider.cs b/FastReport.Compat/src/shared/Compiler/VBCodeProvider.cs
new file mode 100644
index 00000000..de0c69f4
--- /dev/null
+++ b/FastReport.Compat/src/shared/Compiler/VBCodeProvider.cs
@@ -0,0 +1,44 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+using Microsoft.CodeAnalysis;
+using Microsoft.CodeAnalysis.Emit;
+using Microsoft.CodeAnalysis.VisualBasic;
+using System;
+using System.Collections.Generic;
+using System.IO;
+using System.Reflection;
+using System.Text;
+using FastReport.Code.CodeDom.Compiler;
+using System.Threading;
+
+namespace FastReport.Code.VisualBasic
+{
+ public class VBCodeProvider : CodeDomProvider
+ {
+
+ public override void Dispose()
+ {
+
+ }
+
+ protected override Compilation CreateCompilation(SyntaxTree codeTree, ICollection references)
+ {
+ VisualBasicCompilationOptions options = new VisualBasicCompilationOptions(
+ OutputKind.DynamicallyLinkedLibrary,
+ true,
+ embedVbCoreRuntime: true,
+ optimizationLevel: OptimizationLevel.Release,
+ generalDiagnosticOption: ReportDiagnostic.Default);
+
+ Compilation compilation = VisualBasicCompilation.Create(
+ "_" + Guid.NewGuid().ToString("D"), new SyntaxTree[] { codeTree },
+ references: references, options: options
+ );
+ return compilation;
+ }
+
+ protected override SyntaxTree ParseTree(string text, CancellationToken ct = default)
+ => VisualBasicSyntaxTree.ParseText(text,
+ cancellationToken: ct);
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/DotNetClasses/Color.Full.cs b/FastReport.Compat/src/shared/DotNetClasses/Color.Full.cs
new file mode 100644
index 00000000..85a4b250
--- /dev/null
+++ b/FastReport.Compat/src/shared/DotNetClasses/Color.Full.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Text;
+#if !NETSTANDARD
+namespace System.Drawing
+{
+ public class ColorExt
+ {
+ public static bool IsKnownColor(Color color)
+ {
+ return color.IsKnownColor;
+ }
+
+ public static KnownColor ToKnownColor(Color c)
+ {
+ return c.ToKnownColor();
+ }
+
+ public static Color FromKnownColor(KnownColor knownColor)
+ {
+ return Color.FromKnownColor(knownColor);
+ }
+
+ public static bool IsSystemColor(Color c)
+ {
+ return c.IsSystemColor;
+ }
+ }
+}
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/DotNetClasses/GdiGraphics.cs b/FastReport.Compat/src/shared/DotNetClasses/GdiGraphics.cs
new file mode 100644
index 00000000..0aa707e0
--- /dev/null
+++ b/FastReport.Compat/src/shared/DotNetClasses/GdiGraphics.cs
@@ -0,0 +1,443 @@
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.Drawing.Text;
+
+namespace FastReport
+{
+ ///
+ /// Drawing objects to a standard Graphics or Bitmap
+ ///
+ public class GdiGraphics : IGraphics
+ {
+ private Graphics graphics;
+ private readonly bool haveToDispose;
+
+ #region Properties
+ public Graphics Graphics
+ {
+ get { return graphics; }
+ }
+
+ float IGraphics.DpiX => this.graphics.DpiX;
+ float IGraphics.DpiY => this.graphics.DpiY;
+
+ TextRenderingHint IGraphics.TextRenderingHint { get => this.graphics.TextRenderingHint; set => this.graphics.TextRenderingHint = value; }
+ InterpolationMode IGraphics.InterpolationMode { get => this.graphics.InterpolationMode; set => this.graphics.InterpolationMode = value; }
+ SmoothingMode IGraphics.SmoothingMode { get => this.graphics.SmoothingMode; set => this.graphics.SmoothingMode = value; }
+ System.Drawing.Drawing2D.Matrix IGraphics.Transform { get => this.graphics.Transform; set => this.graphics.Transform = value; }
+ GraphicsUnit IGraphics.PageUnit { get => this.graphics.PageUnit; set => this.graphics.PageUnit = value; }
+ bool IGraphics.IsClipEmpty => this.graphics.IsClipEmpty;
+ Region IGraphics.Clip { get => this.graphics.Clip; set => this.graphics.Clip = value; }
+ CompositingQuality IGraphics.CompositingQuality { get => this.graphics.CompositingQuality; set => this.graphics.CompositingQuality = value; }
+ #endregion
+
+ public GdiGraphics(Image image)
+ : this(Graphics.FromImage(image), true)
+ {
+
+ }
+
+ public GdiGraphics(Graphics graphics, bool haveToDispose)
+ {
+ this.graphics = graphics;
+ this.haveToDispose = haveToDispose;
+ }
+
+
+ #region IDisposable Support
+ private bool disposedValue = false; // To detect redundant calls
+
+ protected virtual void Dispose(bool disposing)
+ {
+ if (!disposedValue)
+ {
+ if (disposing)
+ {
+ if (graphics != null && haveToDispose)
+ graphics.Dispose();
+ graphics = null;
+ // TODO: dispose managed state (managed objects).
+ }
+
+ // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below.
+ // TODO: set large fields to null.
+
+ disposedValue = true;
+ }
+ }
+
+ // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources.
+ // ~ImageGraphicsRenderer() {
+ // // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ // Dispose(false);
+ // }
+
+ // This code added to correctly implement the disposable pattern.
+ public void Dispose()
+ {
+ // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
+ Dispose(true);
+ // TODO: uncomment the following line if the finalizer is overridden above.
+ // GC.SuppressFinalize(this);
+ }
+ #endregion
+
+ #region Draw and measure text
+ public void DrawString(string text, Font drawFont, Brush brush, float left, float top)
+ {
+ this.graphics.DrawString(text, drawFont, brush, left, top);
+ }
+
+ public void DrawString(string text, Font drawFont, Brush brush, RectangleF rectangleF)
+ {
+ this.graphics.DrawString(text, drawFont, brush, rectangleF);
+ }
+
+ public void DrawString(string text, Font font, Brush textBrush, RectangleF textRect, StringFormat format)
+ {
+ this.graphics.DrawString(text, font, textBrush, textRect, format);
+ }
+
+ public void DrawString(string text, Font font, Brush brush, float left, float top, StringFormat format)
+ {
+ this.graphics.DrawString(text, font, brush, left, top, format);
+ }
+
+ void IGraphics.DrawString(string s, Font font, Brush brush, PointF point, StringFormat format)
+ {
+ this.graphics.DrawString(s, font, brush, point, format);
+ }
+
+ public Region[] MeasureCharacterRanges(string text, Font font, RectangleF rect, StringFormat format)
+ {
+ return this.graphics.MeasureCharacterRanges(text, font, rect, format);
+ }
+
+ public SizeF MeasureString(string text, Font font, SizeF size)
+ {
+ return this.graphics.MeasureString(text, font, size);
+ }
+
+ public SizeF MeasureString(string text, Font font, int width, StringFormat format)
+ {
+ return this.graphics.MeasureString(text, font, width, format);
+ }
+
+ public void MeasureString(string text, Font font, SizeF size, StringFormat format, out int charsFit, out int linesFit)
+ {
+ this.graphics.MeasureString(text, font, size, format, out charsFit, out linesFit);
+ }
+
+ public SizeF MeasureString(string text, Font drawFont)
+ {
+ return this.graphics.MeasureString(text, drawFont);
+ }
+
+ public SizeF MeasureString(string text, Font font, SizeF layoutArea, StringFormat format)
+ {
+ return this.graphics.MeasureString(text, font, layoutArea, format);
+ }
+ #endregion
+
+ #region Draw images
+ public void DrawImage(Image image, float x, float y)
+ {
+ this.graphics.DrawImage(image, x, y);
+ }
+
+ public void DrawImage(Image image, RectangleF destRect, RectangleF srcRect, GraphicsUnit unit)
+ {
+ this.graphics.DrawImage(image, destRect, srcRect, unit);
+ }
+
+ public void DrawImage(Image image, RectangleF rect)
+ {
+ this.graphics.DrawImage(image, rect);
+ }
+
+ public void DrawImage(Image image, float x, float y, float width, float height)
+ {
+ this.graphics.DrawImage(image, x, y, width, height);
+ }
+
+ public void DrawImage(Image image, PointF[] points)
+ {
+ this.graphics.DrawImage(image, points);
+ }
+
+ public void DrawImage(Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttr)
+ {
+ this.graphics.DrawImage(image, destRect, srcX, srcY, srcWidth, srcHeight, srcUnit, imageAttr);
+ }
+
+ public void DrawImage(Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs)
+ {
+ this.graphics.DrawImage(image, destRect, srcX, srcY, srcWidth, srcHeight, srcUnit, imageAttrs);
+ }
+
+ public void DrawImageUnscaled(Image image, Rectangle rect)
+ {
+ this.graphics.DrawImageUnscaled(image, rect);
+ }
+
+ #endregion
+
+ #region Draw geometry
+ public void DrawArc(Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle)
+ {
+ this.graphics.DrawArc(pen, x, y, width, height, startAngle, sweepAngle);
+ }
+
+ public void DrawCurve(Pen pen, PointF[] points, int offset, int numberOfSegments, float tension)
+ {
+ this.graphics.DrawCurve(pen, points, offset, numberOfSegments, tension);
+ }
+
+ public void DrawEllipse(Pen pen, float left, float top, float width, float height)
+ {
+ this.graphics.DrawEllipse(pen, left, top, width, height);
+ }
+
+ public void DrawEllipse(Pen pen, RectangleF rect)
+ {
+ this.graphics.DrawEllipse(pen, rect);
+ }
+
+ public void DrawLine(Pen pen, float x1, float y1, float x2, float y2)
+ {
+ this.graphics.DrawLine(pen, x1, y1, x2, y2);
+ }
+
+ public void DrawLine(Pen pen, PointF p1, PointF p2)
+ {
+ this.graphics.DrawLine(pen, p1, p2);
+ }
+
+ public void DrawLines(Pen pen, PointF[] points)
+ {
+ this.graphics.DrawLines(pen, points);
+ }
+
+ public void DrawPath(Pen outlinePen, GraphicsPath path)
+ {
+ this.graphics.DrawPath(outlinePen, path);
+ }
+
+ public void DrawPie(Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle)
+ {
+ this.graphics.DrawPie(pen, x, y, width, height, startAngle, sweepAngle);
+ }
+
+ public void DrawPolygon(Pen pen, PointF[] points)
+ {
+ this.graphics.DrawPolygon(pen, points);
+ }
+
+ public void DrawPolygon(Pen pen, Point[] points)
+ {
+ this.graphics.DrawPolygon(pen, points);
+ }
+
+ public void DrawRectangle(Pen pen, float left, float top, float width, float height)
+ {
+ this.graphics.DrawRectangle(pen, left, top, width, height);
+ }
+
+ public void DrawRectangle(Pen pen, Rectangle rect)
+ {
+ this.graphics.DrawRectangle(pen, rect);
+ }
+
+ public void PathAddRectangle(GraphicsPath path, RectangleF rect)
+ {
+ path.AddRectangle(rect);
+ }
+ #endregion
+
+ #region Fill geometry
+ public void FillEllipse(Brush brush, float x, float y, float dx, float dy)
+ {
+ this.graphics.FillEllipse(brush, x, y, dx, dy);
+ }
+
+ public void FillEllipse(Brush brush, RectangleF rect)
+ {
+ this.graphics.FillEllipse(brush, rect);
+ }
+
+ public void FillPath(Brush brush, GraphicsPath path)
+ {
+
+ this.graphics.FillPath(brush, path);
+ }
+
+ public void FillPie(Brush brush, float x, float y, float width, float height, float startAngle, float sweepAngle)
+ {
+ this.graphics.FillPie(brush, x, y, width, height, startAngle, sweepAngle);
+ }
+
+ public void FillPolygon(Brush brush, PointF[] points)
+ {
+ this.graphics.FillPolygon(brush, points);
+ }
+
+ public void FillPolygon(Brush brush, Point[] points)
+ {
+ this.graphics.FillPolygon(brush, points);
+ }
+
+ public void FillRectangle(Brush brush, RectangleF rect)
+ {
+ this.graphics.FillRectangle(brush, rect);
+ }
+
+ public void FillRectangle(Brush brush, float left, float top, float width, float height)
+ {
+ this.graphics.FillRectangle(brush, left, top, width, height);
+ }
+
+ public void FillRegion(Brush brush, Region region)
+ {
+ this.graphics.FillRegion(brush, region);
+ }
+ #endregion
+
+ #region Fill And Draw
+
+ public void FillAndDrawPath(Pen pen, Brush brush, GraphicsPath path)
+ {
+ FillPath(brush, path);
+ DrawPath(pen, path);
+ }
+
+ public void FillAndDrawEllipse(Pen pen, Brush brush, RectangleF rect)
+ {
+ FillEllipse(brush, rect);
+ DrawEllipse(pen, rect);
+ }
+
+ public void FillAndDrawEllipse(Pen pen, Brush brush, float left, float top, float width, float height)
+ {
+ FillEllipse(brush, left, top, width, height);
+ DrawEllipse(pen, left, top, width, height);
+ }
+
+ public void FillAndDrawPolygon(Pen pen, Brush brush, Point[] points)
+ {
+ FillPolygon(brush, points);
+ DrawPolygon(pen, points);
+ }
+
+ public void FillAndDrawPolygon(Pen pen, Brush brush, PointF[] points)
+ {
+ FillPolygon(brush, points);
+ DrawPolygon(pen, points);
+ }
+
+ public void FillAndDrawRectangle(Pen pen, Brush brush, float left, float top, float width, float height)
+ {
+ FillRectangle(brush, left, top, width, height);
+ DrawRectangle(pen, left, top, width, height);
+ }
+
+ #endregion
+
+ #region Transform
+ public void MultiplyTransform(System.Drawing.Drawing2D.Matrix matrix, MatrixOrder order)
+ {
+ this.graphics.MultiplyTransform(matrix, order);
+ }
+
+ public void RotateTransform(float angle)
+ {
+ this.graphics.RotateTransform(angle);
+ }
+
+ public void ScaleTransform(float scaleX, float scaleY)
+ {
+ this.graphics.ScaleTransform(scaleX, scaleY);
+ }
+
+ public void TranslateTransform(float left, float top)
+ {
+ this.graphics.TranslateTransform(left, top);
+ }
+ #endregion
+
+ #region State
+ public void Restore(IGraphicsState state)
+ {
+ if (state is ImageGraphicsRendererState)
+ this.graphics.Restore((state as ImageGraphicsRendererState).GraphicsState);
+ }
+
+ public IGraphicsState Save()
+ {
+ return new ImageGraphicsRendererState(this.graphics.Save());
+ }
+ #endregion
+
+ #region Clip
+ public bool IsVisible(RectangleF rect)
+ {
+ return this.graphics.IsVisible(rect);
+ }
+
+ public void ResetClip()
+ {
+ this.graphics.ResetClip();
+ }
+
+ public void SetClip(RectangleF rect)
+ {
+ this.graphics.SetClip(rect);
+ }
+
+ public void SetClip(RectangleF rect, CombineMode combineMode)
+ {
+ this.graphics.SetClip(rect, combineMode);
+ }
+
+ public void SetClip(GraphicsPath path, CombineMode combineMode)
+ {
+ this.graphics.SetClip(path, combineMode);
+ }
+ #endregion
+
+ public class ImageGraphicsRendererState : IGraphicsState
+ {
+ private readonly GraphicsState graphicsState;
+
+ public GraphicsState GraphicsState
+ {
+ get
+ {
+ return graphicsState;
+ }
+ }
+ public ImageGraphicsRendererState(GraphicsState state)
+ {
+ this.graphicsState = state;
+ }
+ }
+
+ public static GdiGraphics FromImage(Image image)
+ {
+ return new GdiGraphics(image);
+ }
+
+ public static GdiGraphics FromGraphics(Graphics graphics)
+ {
+ return new GdiGraphics(graphics, false);
+ }
+
+ public static GdiGraphics FromHdc(IntPtr hdc)
+ {
+ return FromGraphics(Graphics.FromHdc(hdc));
+ }
+
+ }
+
+}
diff --git a/FastReport.Compat/src/shared/DotNetClasses/IGraphics.cs b/FastReport.Compat/src/shared/DotNetClasses/IGraphics.cs
new file mode 100644
index 00000000..57f25eb3
--- /dev/null
+++ b/FastReport.Compat/src/shared/DotNetClasses/IGraphics.cs
@@ -0,0 +1,124 @@
+using System;
+using System.Drawing;
+using System.Drawing.Drawing2D;
+using System.Drawing.Imaging;
+using System.Drawing.Text;
+
+namespace FastReport
+{
+ ///
+ /// The interface for unifying methods for drawing objects into different graphics
+ ///
+ public interface IGraphics : IDisposable
+ {
+ #region Properties
+ Graphics Graphics { get; }
+ float DpiY { get; }
+ TextRenderingHint TextRenderingHint { get; set; }
+ InterpolationMode InterpolationMode { get; set; }
+ SmoothingMode SmoothingMode { get; set; }
+ System.Drawing.Drawing2D.Matrix Transform { get; set; }
+ GraphicsUnit PageUnit { get; set; }
+ bool IsClipEmpty { get; }
+ Region Clip { get; set; }
+ float DpiX { get; }
+ CompositingQuality CompositingQuality { get; set; }
+ #endregion
+
+ #region Draw and measure text
+ void DrawString(string text, Font font, Brush brush, float left, float top);
+ void DrawString(string text, Font font, Brush brush, float left, float top, StringFormat format);
+ // in this case if a baseline is needed, it will not be calculated
+ void DrawString(string text, Font font, Brush brush, RectangleF rectangleF);
+ void DrawString(string text, Font font, Brush textBrush, RectangleF textRect, StringFormat format);
+ void DrawString(string s, Font font, Brush brush, PointF point, StringFormat format);
+ Region[] MeasureCharacterRanges(string text, Font font, RectangleF textRect, StringFormat format);
+ SizeF MeasureString(string text, Font font);
+ SizeF MeasureString(string text, Font font, SizeF size);
+ SizeF MeasureString(string text, Font font, int v, StringFormat format);
+ void MeasureString(string text, Font font, SizeF size, StringFormat format, out int charsFit, out int linesFit);
+ SizeF MeasureString(string text, Font font, SizeF layoutArea, StringFormat stringFormat);
+ #endregion
+
+ #region Draw images
+ void DrawImage(Image image, float x, float y);
+ void DrawImage(Image image, RectangleF rect1, RectangleF rect2, GraphicsUnit unit);
+ void DrawImage(Image image, RectangleF rect);
+ void DrawImage(Image image, float x, float y, float width, float height);
+ void DrawImage(Image image, PointF[] points);
+ void DrawImage(Image image, Rectangle destRect, int srcX, int srcY, int srcWidth, int srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttr);
+ void DrawImage(Image image, Rectangle destRect, float srcX, float srcY, float srcWidth, float srcHeight, GraphicsUnit srcUnit, ImageAttributes imageAttrs);
+ void DrawImageUnscaled(Image image, Rectangle rect);
+ #endregion
+
+ #region Draw geometry
+ void DrawArc(Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle);
+ void DrawCurve(Pen pen, PointF[] points, int offset, int numberOfSegments, float tension);
+ void DrawEllipse(Pen pen, float left, float top, float width, float height);
+ void DrawEllipse(Pen pen, RectangleF rect);
+ void DrawLine(Pen pen, float x1, float y1, float x2, float y2);
+ void DrawLine(Pen pen, PointF p1, PointF p2);
+ void DrawLines(Pen pen, PointF[] points);
+ void DrawPath(Pen outlinePen, GraphicsPath path);
+ void DrawPie(Pen pen, float x, float y, float width, float height, float startAngle, float sweepAngle);
+ void DrawPolygon(Pen pen, PointF[] points);
+ void DrawPolygon(Pen pen, Point[] points);
+ void DrawRectangle(Pen pen, float left, float top, float width, float height);
+ void DrawRectangle(Pen pen, Rectangle rectangle);
+ #endregion
+
+ #region Fill geometry
+ void FillEllipse(Brush brush, float left, float top, float width, float height);
+ void FillEllipse(Brush brush, RectangleF rect);
+ // Works with polygons only
+ void FillPath(Brush brush, GraphicsPath path);
+ void FillPie(Brush brush, float x, float y, float width, float height, float startAngle, float sweepAngle);
+ void FillPolygon(Brush brush, PointF[] points);
+ void FillPolygon(Brush brush, Point[] points);
+ // Add rectangle to the graphics path
+ void FillRectangle(Brush brush, RectangleF rect);
+ void FillRectangle(Brush brush, float left, float top, float width, float height);
+ void FillRegion(Brush brush, Region region);
+ #endregion
+
+ #region Fill and Draw
+
+ void FillAndDrawPath(Pen pen, Brush brush, GraphicsPath path);
+ void FillAndDrawEllipse(Pen pen, Brush brush, RectangleF rect);
+ void FillAndDrawEllipse(Pen pen, Brush brush, float left, float top, float width, float height);
+ void FillAndDrawPolygon(Pen pen, Brush brush, Point[] points);
+ void FillAndDrawPolygon(Pen pen, Brush brush, PointF[] points);
+ void FillAndDrawRectangle(Pen pen, Brush brush, float left, float top, float width, float height);
+
+ #endregion
+
+ #region Transform
+ void MultiplyTransform(System.Drawing.Drawing2D.Matrix matrix, MatrixOrder prepend);
+ void RotateTransform(float angle);
+ void ScaleTransform(float scaleX, float scaleY);
+ void TranslateTransform(float left, float top);
+ #endregion
+
+ #region State
+ void Restore(IGraphicsState state);
+ IGraphicsState Save();
+ #endregion
+
+ #region Clip
+ bool IsVisible(RectangleF rect);
+ void ResetClip();
+ void SetClip(RectangleF rect);
+ void SetClip(RectangleF rect, CombineMode combineMode);
+ void SetClip(GraphicsPath path, CombineMode combineMode);
+ #endregion
+ }
+
+ ///
+ /// the interface for saving and restoring state
+ ///
+ public interface IGraphicsState
+ {
+
+ }
+
+}
diff --git a/FastReport.Compat/src/shared/DotNetClasses/UITypeEditor.cs b/FastReport.Compat/src/shared/DotNetClasses/UITypeEditor.cs
new file mode 100644
index 00000000..c2128e9e
--- /dev/null
+++ b/FastReport.Compat/src/shared/DotNetClasses/UITypeEditor.cs
@@ -0,0 +1,11 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1 || NETCOREAPP
+
+namespace System.Drawing.Design
+{
+ public class UITypeEditor
+ {
+
+ }
+}
+
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/TypeConverters/Color.Core.cs b/FastReport.Compat/src/shared/TypeConverters/Color.Core.cs
new file mode 100644
index 00000000..127a5b70
--- /dev/null
+++ b/FastReport.Compat/src/shared/TypeConverters/Color.Core.cs
@@ -0,0 +1,576 @@
+#if NETSTANDARD2_0 || NETSTANDARD2_1
+using System;
+using System.Collections.Concurrent;
+using System.Collections.Generic;
+using System.Text;
+
+namespace System.Drawing
+{
+ #if NETSTANDARD2_0 && !SKIA
+ public enum KnownColor
+ {
+ ActiveBorder = 1,
+ ActiveCaption = 2,
+ ActiveCaptionText = 3,
+ AppWorkspace = 4,
+ Control = 5,
+ ControlDark = 6,
+ ControlDarkDark = 7,
+ ControlLight = 8,
+ ControlLightLight = 9,
+ ControlText = 10,
+ Desktop = 11,
+ GrayText = 12,
+ Highlight = 13,
+ HighlightText = 14,
+ HotTrack = 15,
+ InactiveBorder = 16,
+ InactiveCaption = 17,
+ InactiveCaptionText = 18,
+ Info = 19,
+ InfoText = 20,
+ Menu = 21,
+ MenuText = 22,
+ ScrollBar = 23,
+ Window = 24,
+ WindowFrame = 25,
+ WindowText = 26,
+ Transparent = 27,
+ AliceBlue = 28,
+ AntiqueWhite = 29,
+ Aqua = 30,
+ Aquamarine = 31,
+ Azure = 32,
+ Beige = 33,
+ Bisque = 34,
+ Black = 35,
+ BlanchedAlmond = 36,
+ Blue = 37,
+ BlueViolet = 38,
+ Brown = 39,
+ BurlyWood = 40,
+ CadetBlue = 41,
+ Chartreuse = 42,
+ Chocolate = 43,
+ Coral = 44,
+ CornflowerBlue = 45,
+ Cornsilk = 46,
+ Crimson = 47,
+ Cyan = 48,
+ DarkBlue = 49,
+ DarkCyan = 50,
+ DarkGoldenrod = 51,
+ DarkGray = 52,
+ DarkGreen = 53,
+ DarkKhaki = 54,
+ DarkMagenta = 55,
+ DarkOliveGreen = 56,
+ DarkOrange = 57,
+ DarkOrchid = 58,
+ DarkRed = 59,
+ DarkSalmon = 60,
+ DarkSeaGreen = 61,
+ DarkSlateBlue = 62,
+ DarkSlateGray = 63,
+ DarkTurquoise = 64,
+ DarkViolet = 65,
+ DeepPink = 66,
+ DeepSkyBlue = 67,
+ DimGray = 68,
+ DodgerBlue = 69,
+ Firebrick = 70,
+ FloralWhite = 71,
+ ForestGreen = 72,
+ Fuchsia = 73,
+ Gainsboro = 74,
+ GhostWhite = 75,
+ Gold = 76,
+ Goldenrod = 77,
+ Gray = 78,
+ Green = 79,
+ GreenYellow = 80,
+ Honeydew = 81,
+ HotPink = 82,
+ IndianRed = 83,
+ Indigo = 84,
+ Ivory = 85,
+ Khaki = 86,
+ Lavender = 87,
+ LavenderBlush = 88,
+ LawnGreen = 89,
+ LemonChiffon = 90,
+ LightBlue = 91,
+ LightCoral = 92,
+ LightCyan = 93,
+ LightGoldenrodYellow = 94,
+ LightGray = 95,
+ LightGreen = 96,
+ LightPink = 97,
+ LightSalmon = 98,
+ LightSeaGreen = 99,
+ LightSkyBlue = 100,
+ LightSlateGray = 101,
+ LightSteelBlue = 102,
+ LightYellow = 103,
+ Lime = 104,
+ LimeGreen = 105,
+ Linen = 106,
+ Magenta = 107,
+ Maroon = 108,
+ MediumAquamarine = 109,
+ MediumBlue = 110,
+ MediumOrchid = 111,
+ MediumPurple = 112,
+ MediumSeaGreen = 113,
+ MediumSlateBlue = 114,
+ MediumSpringGreen = 115,
+ MediumTurquoise = 116,
+ MediumVioletRed = 117,
+ MidnightBlue = 118,
+ MintCream = 119,
+ MistyRose = 120,
+ Moccasin = 121,
+ NavajoWhite = 122,
+ Navy = 123,
+ OldLace = 124,
+ Olive = 125,
+ OliveDrab = 126,
+ Orange = 127,
+ OrangeRed = 128,
+ Orchid = 129,
+ PaleGoldenrod = 130,
+ PaleGreen = 131,
+ PaleTurquoise = 132,
+ PaleVioletRed = 133,
+ PapayaWhip = 134,
+ PeachPuff = 135,
+ Peru = 136,
+ Pink = 137,
+ Plum = 138,
+ PowderBlue = 139,
+ Purple = 140,
+ Red = 141,
+ RosyBrown = 142,
+ RoyalBlue = 143,
+ SaddleBrown = 144,
+ Salmon = 145,
+ SandyBrown = 146,
+ SeaGreen = 147,
+ SeaShell = 148,
+ Sienna = 149,
+ Silver = 150,
+ SkyBlue = 151,
+ SlateBlue = 152,
+ SlateGray = 153,
+ Snow = 154,
+ SpringGreen = 155,
+ SteelBlue = 156,
+ Tan = 157,
+ Teal = 158,
+ Thistle = 159,
+ Tomato = 160,
+ Turquoise = 161,
+ Violet = 162,
+ Wheat = 163,
+ White = 164,
+ WhiteSmoke = 165,
+ Yellow = 166,
+ YellowGreen = 167,
+ ButtonFace = 168,
+ ButtonHighlight = 169,
+ ButtonShadow = 170,
+ GradientActiveCaption = 171,
+ GradientInactiveCaption = 172,
+ MenuBar = 173,
+ MenuHighlight = 174
+ }
+#endif
+ public class ColorExt
+ {
+ private static readonly ConcurrentDictionary ColorToKnownColor = new ConcurrentDictionary();
+ private static readonly ConcurrentDictionary KnownColorToColor = new ConcurrentDictionary();
+
+ static ColorExt()
+ {
+ ColorToKnownColor[-4934476] = KnownColor.ActiveBorder;
+ KnownColorToColor[KnownColor.ActiveBorder] = Color.FromArgb(-4934476);
+ ColorToKnownColor[-6703919] = KnownColor.ActiveCaption;
+ KnownColorToColor[KnownColor.ActiveCaption] = Color.FromArgb(-6703919);
+ ColorToKnownColor[-16777216] = KnownColor.ActiveCaptionText;
+ KnownColorToColor[KnownColor.ActiveCaptionText] = Color.FromArgb(-16777216);
+ ColorToKnownColor[-5526613] = KnownColor.AppWorkspace;
+ KnownColorToColor[KnownColor.AppWorkspace] = Color.FromArgb(-5526613);
+ ColorToKnownColor[-986896] = KnownColor.Control;
+ KnownColorToColor[KnownColor.Control] = Color.FromArgb(-986896);
+ ColorToKnownColor[-6250336] = KnownColor.ControlDark;
+ KnownColorToColor[KnownColor.ControlDark] = Color.FromArgb(-6250336);
+ ColorToKnownColor[-9868951] = KnownColor.ControlDarkDark;
+ KnownColorToColor[KnownColor.ControlDarkDark] = Color.FromArgb(-9868951);
+ ColorToKnownColor[-1842205] = KnownColor.ControlLight;
+ KnownColorToColor[KnownColor.ControlLight] = Color.FromArgb(-1842205);
+ ColorToKnownColor[-1] = KnownColor.ControlLightLight;
+ KnownColorToColor[KnownColor.ControlLightLight] = Color.FromArgb(-1);
+ ColorToKnownColor[-16777216] = KnownColor.ControlText;
+ KnownColorToColor[KnownColor.ControlText] = Color.FromArgb(-16777216);
+ ColorToKnownColor[-16777216] = KnownColor.Desktop;
+ KnownColorToColor[KnownColor.Desktop] = Color.FromArgb(-16777216);
+ ColorToKnownColor[-9605779] = KnownColor.GrayText;
+ KnownColorToColor[KnownColor.GrayText] = Color.FromArgb(-9605779);
+ ColorToKnownColor[-16746281] = KnownColor.Highlight;
+ KnownColorToColor[KnownColor.Highlight] = Color.FromArgb(-16746281);
+ ColorToKnownColor[-1] = KnownColor.HighlightText;
+ KnownColorToColor[KnownColor.HighlightText] = Color.FromArgb(-1);
+ ColorToKnownColor[-16750900] = KnownColor.HotTrack;
+ KnownColorToColor[KnownColor.HotTrack] = Color.FromArgb(-16750900);
+ ColorToKnownColor[-722948] = KnownColor.InactiveBorder;
+ KnownColorToColor[KnownColor.InactiveBorder] = Color.FromArgb(-722948);
+ ColorToKnownColor[-4207141] = KnownColor.InactiveCaption;
+ KnownColorToColor[KnownColor.InactiveCaption] = Color.FromArgb(-4207141);
+ ColorToKnownColor[-16777216] = KnownColor.InactiveCaptionText;
+ KnownColorToColor[KnownColor.InactiveCaptionText] = Color.FromArgb(-16777216);
+ ColorToKnownColor[-31] = KnownColor.Info;
+ KnownColorToColor[KnownColor.Info] = Color.FromArgb(-31);
+ ColorToKnownColor[-16777216] = KnownColor.InfoText;
+ KnownColorToColor[KnownColor.InfoText] = Color.FromArgb(-16777216);
+ ColorToKnownColor[-986896] = KnownColor.Menu;
+ KnownColorToColor[KnownColor.Menu] = Color.FromArgb(-986896);
+ ColorToKnownColor[-16777216] = KnownColor.MenuText;
+ KnownColorToColor[KnownColor.MenuText] = Color.FromArgb(-16777216);
+ ColorToKnownColor[-3618616] = KnownColor.ScrollBar;
+ KnownColorToColor[KnownColor.ScrollBar] = Color.FromArgb(-3618616);
+ ColorToKnownColor[-1] = KnownColor.Window;
+ KnownColorToColor[KnownColor.Window] = Color.FromArgb(-1);
+ ColorToKnownColor[-10197916] = KnownColor.WindowFrame;
+ KnownColorToColor[KnownColor.WindowFrame] = Color.FromArgb(-10197916);
+ ColorToKnownColor[-16777216] = KnownColor.WindowText;
+ KnownColorToColor[KnownColor.WindowText] = Color.FromArgb(-16777216);
+ ColorToKnownColor[16777215] = KnownColor.Transparent;
+ KnownColorToColor[KnownColor.Transparent] = Color.FromArgb(16777215);
+ ColorToKnownColor[-984833] = KnownColor.AliceBlue;
+ KnownColorToColor[KnownColor.AliceBlue] = Color.FromArgb(-984833);
+ ColorToKnownColor[-332841] = KnownColor.AntiqueWhite;
+ KnownColorToColor[KnownColor.AntiqueWhite] = Color.FromArgb(-332841);
+ ColorToKnownColor[-16711681] = KnownColor.Aqua;
+ KnownColorToColor[KnownColor.Aqua] = Color.FromArgb(-16711681);
+ ColorToKnownColor[-8388652] = KnownColor.Aquamarine;
+ KnownColorToColor[KnownColor.Aquamarine] = Color.FromArgb(-8388652);
+ ColorToKnownColor[-983041] = KnownColor.Azure;
+ KnownColorToColor[KnownColor.Azure] = Color.FromArgb(-983041);
+ ColorToKnownColor[-657956] = KnownColor.Beige;
+ KnownColorToColor[KnownColor.Beige] = Color.FromArgb(-657956);
+ ColorToKnownColor[-6972] = KnownColor.Bisque;
+ KnownColorToColor[KnownColor.Bisque] = Color.FromArgb(-6972);
+ ColorToKnownColor[-16777216] = KnownColor.Black;
+ KnownColorToColor[KnownColor.Black] = Color.FromArgb(-16777216);
+ ColorToKnownColor[-5171] = KnownColor.BlanchedAlmond;
+ KnownColorToColor[KnownColor.BlanchedAlmond] = Color.FromArgb(-5171);
+ ColorToKnownColor[-16776961] = KnownColor.Blue;
+ KnownColorToColor[KnownColor.Blue] = Color.FromArgb(-16776961);
+ ColorToKnownColor[-7722014] = KnownColor.BlueViolet;
+ KnownColorToColor[KnownColor.BlueViolet] = Color.FromArgb(-7722014);
+ ColorToKnownColor[-5952982] = KnownColor.Brown;
+ KnownColorToColor[KnownColor.Brown] = Color.FromArgb(-5952982);
+ ColorToKnownColor[-2180985] = KnownColor.BurlyWood;
+ KnownColorToColor[KnownColor.BurlyWood] = Color.FromArgb(-2180985);
+ ColorToKnownColor[-10510688] = KnownColor.CadetBlue;
+ KnownColorToColor[KnownColor.CadetBlue] = Color.FromArgb(-10510688);
+ ColorToKnownColor[-8388864] = KnownColor.Chartreuse;
+ KnownColorToColor[KnownColor.Chartreuse] = Color.FromArgb(-8388864);
+ ColorToKnownColor[-2987746] = KnownColor.Chocolate;
+ KnownColorToColor[KnownColor.Chocolate] = Color.FromArgb(-2987746);
+ ColorToKnownColor[-32944] = KnownColor.Coral;
+ KnownColorToColor[KnownColor.Coral] = Color.FromArgb(-32944);
+ ColorToKnownColor[-10185235] = KnownColor.CornflowerBlue;
+ KnownColorToColor[KnownColor.CornflowerBlue] = Color.FromArgb(-10185235);
+ ColorToKnownColor[-1828] = KnownColor.Cornsilk;
+ KnownColorToColor[KnownColor.Cornsilk] = Color.FromArgb(-1828);
+ ColorToKnownColor[-2354116] = KnownColor.Crimson;
+ KnownColorToColor[KnownColor.Crimson] = Color.FromArgb(-2354116);
+ ColorToKnownColor[-16711681] = KnownColor.Cyan;
+ KnownColorToColor[KnownColor.Cyan] = Color.FromArgb(-16711681);
+ ColorToKnownColor[-16777077] = KnownColor.DarkBlue;
+ KnownColorToColor[KnownColor.DarkBlue] = Color.FromArgb(-16777077);
+ ColorToKnownColor[-16741493] = KnownColor.DarkCyan;
+ KnownColorToColor[KnownColor.DarkCyan] = Color.FromArgb(-16741493);
+ ColorToKnownColor[-4684277] = KnownColor.DarkGoldenrod;
+ KnownColorToColor[KnownColor.DarkGoldenrod] = Color.FromArgb(-4684277);
+ ColorToKnownColor[-5658199] = KnownColor.DarkGray;
+ KnownColorToColor[KnownColor.DarkGray] = Color.FromArgb(-5658199);
+ ColorToKnownColor[-16751616] = KnownColor.DarkGreen;
+ KnownColorToColor[KnownColor.DarkGreen] = Color.FromArgb(-16751616);
+ ColorToKnownColor[-4343957] = KnownColor.DarkKhaki;
+ KnownColorToColor[KnownColor.DarkKhaki] = Color.FromArgb(-4343957);
+ ColorToKnownColor[-7667573] = KnownColor.DarkMagenta;
+ KnownColorToColor[KnownColor.DarkMagenta] = Color.FromArgb(-7667573);
+ ColorToKnownColor[-11179217] = KnownColor.DarkOliveGreen;
+ KnownColorToColor[KnownColor.DarkOliveGreen] = Color.FromArgb(-11179217);
+ ColorToKnownColor[-29696] = KnownColor.DarkOrange;
+ KnownColorToColor[KnownColor.DarkOrange] = Color.FromArgb(-29696);
+ ColorToKnownColor[-6737204] = KnownColor.DarkOrchid;
+ KnownColorToColor[KnownColor.DarkOrchid] = Color.FromArgb(-6737204);
+ ColorToKnownColor[-7667712] = KnownColor.DarkRed;
+ KnownColorToColor[KnownColor.DarkRed] = Color.FromArgb(-7667712);
+ ColorToKnownColor[-1468806] = KnownColor.DarkSalmon;
+ KnownColorToColor[KnownColor.DarkSalmon] = Color.FromArgb(-1468806);
+ ColorToKnownColor[-7357301] = KnownColor.DarkSeaGreen;
+ KnownColorToColor[KnownColor.DarkSeaGreen] = Color.FromArgb(-7357301);
+ ColorToKnownColor[-12042869] = KnownColor.DarkSlateBlue;
+ KnownColorToColor[KnownColor.DarkSlateBlue] = Color.FromArgb(-12042869);
+ ColorToKnownColor[-13676721] = KnownColor.DarkSlateGray;
+ KnownColorToColor[KnownColor.DarkSlateGray] = Color.FromArgb(-13676721);
+ ColorToKnownColor[-16724271] = KnownColor.DarkTurquoise;
+ KnownColorToColor[KnownColor.DarkTurquoise] = Color.FromArgb(-16724271);
+ ColorToKnownColor[-7077677] = KnownColor.DarkViolet;
+ KnownColorToColor[KnownColor.DarkViolet] = Color.FromArgb(-7077677);
+ ColorToKnownColor[-60269] = KnownColor.DeepPink;
+ KnownColorToColor[KnownColor.DeepPink] = Color.FromArgb(-60269);
+ ColorToKnownColor[-16728065] = KnownColor.DeepSkyBlue;
+ KnownColorToColor[KnownColor.DeepSkyBlue] = Color.FromArgb(-16728065);
+ ColorToKnownColor[-9868951] = KnownColor.DimGray;
+ KnownColorToColor[KnownColor.DimGray] = Color.FromArgb(-9868951);
+ ColorToKnownColor[-14774017] = KnownColor.DodgerBlue;
+ KnownColorToColor[KnownColor.DodgerBlue] = Color.FromArgb(-14774017);
+ ColorToKnownColor[-5103070] = KnownColor.Firebrick;
+ KnownColorToColor[KnownColor.Firebrick] = Color.FromArgb(-5103070);
+ ColorToKnownColor[-1296] = KnownColor.FloralWhite;
+ KnownColorToColor[KnownColor.FloralWhite] = Color.FromArgb(-1296);
+ ColorToKnownColor[-14513374] = KnownColor.ForestGreen;
+ KnownColorToColor[KnownColor.ForestGreen] = Color.FromArgb(-14513374);
+ ColorToKnownColor[-65281] = KnownColor.Fuchsia;
+ KnownColorToColor[KnownColor.Fuchsia] = Color.FromArgb(-65281);
+ ColorToKnownColor[-2302756] = KnownColor.Gainsboro;
+ KnownColorToColor[KnownColor.Gainsboro] = Color.FromArgb(-2302756);
+ ColorToKnownColor[-460545] = KnownColor.GhostWhite;
+ KnownColorToColor[KnownColor.GhostWhite] = Color.FromArgb(-460545);
+ ColorToKnownColor[-10496] = KnownColor.Gold;
+ KnownColorToColor[KnownColor.Gold] = Color.FromArgb(-10496);
+ ColorToKnownColor[-2448096] = KnownColor.Goldenrod;
+ KnownColorToColor[KnownColor.Goldenrod] = Color.FromArgb(-2448096);
+ ColorToKnownColor[-8355712] = KnownColor.Gray;
+ KnownColorToColor[KnownColor.Gray] = Color.FromArgb(-8355712);
+ ColorToKnownColor[-16744448] = KnownColor.Green;
+ KnownColorToColor[KnownColor.Green] = Color.FromArgb(-16744448);
+ ColorToKnownColor[-5374161] = KnownColor.GreenYellow;
+ KnownColorToColor[KnownColor.GreenYellow] = Color.FromArgb(-5374161);
+ ColorToKnownColor[-983056] = KnownColor.Honeydew;
+ KnownColorToColor[KnownColor.Honeydew] = Color.FromArgb(-983056);
+ ColorToKnownColor[-38476] = KnownColor.HotPink;
+ KnownColorToColor[KnownColor.HotPink] = Color.FromArgb(-38476);
+ ColorToKnownColor[-3318692] = KnownColor.IndianRed;
+ KnownColorToColor[KnownColor.IndianRed] = Color.FromArgb(-3318692);
+ ColorToKnownColor[-11861886] = KnownColor.Indigo;
+ KnownColorToColor[KnownColor.Indigo] = Color.FromArgb(-11861886);
+ ColorToKnownColor[-16] = KnownColor.Ivory;
+ KnownColorToColor[KnownColor.Ivory] = Color.FromArgb(-16);
+ ColorToKnownColor[-989556] = KnownColor.Khaki;
+ KnownColorToColor[KnownColor.Khaki] = Color.FromArgb(-989556);
+ ColorToKnownColor[-1644806] = KnownColor.Lavender;
+ KnownColorToColor[KnownColor.Lavender] = Color.FromArgb(-1644806);
+ ColorToKnownColor[-3851] = KnownColor.LavenderBlush;
+ KnownColorToColor[KnownColor.LavenderBlush] = Color.FromArgb(-3851);
+ ColorToKnownColor[-8586240] = KnownColor.LawnGreen;
+ KnownColorToColor[KnownColor.LawnGreen] = Color.FromArgb(-8586240);
+ ColorToKnownColor[-1331] = KnownColor.LemonChiffon;
+ KnownColorToColor[KnownColor.LemonChiffon] = Color.FromArgb(-1331);
+ ColorToKnownColor[-5383962] = KnownColor.LightBlue;
+ KnownColorToColor[KnownColor.LightBlue] = Color.FromArgb(-5383962);
+ ColorToKnownColor[-1015680] = KnownColor.LightCoral;
+ KnownColorToColor[KnownColor.LightCoral] = Color.FromArgb(-1015680);
+ ColorToKnownColor[-2031617] = KnownColor.LightCyan;
+ KnownColorToColor[KnownColor.LightCyan] = Color.FromArgb(-2031617);
+ ColorToKnownColor[-329006] = KnownColor.LightGoldenrodYellow;
+ KnownColorToColor[KnownColor.LightGoldenrodYellow] = Color.FromArgb(-329006);
+ ColorToKnownColor[-2894893] = KnownColor.LightGray;
+ KnownColorToColor[KnownColor.LightGray] = Color.FromArgb(-2894893);
+ ColorToKnownColor[-7278960] = KnownColor.LightGreen;
+ KnownColorToColor[KnownColor.LightGreen] = Color.FromArgb(-7278960);
+ ColorToKnownColor[-18751] = KnownColor.LightPink;
+ KnownColorToColor[KnownColor.LightPink] = Color.FromArgb(-18751);
+ ColorToKnownColor[-24454] = KnownColor.LightSalmon;
+ KnownColorToColor[KnownColor.LightSalmon] = Color.FromArgb(-24454);
+ ColorToKnownColor[-14634326] = KnownColor.LightSeaGreen;
+ KnownColorToColor[KnownColor.LightSeaGreen] = Color.FromArgb(-14634326);
+ ColorToKnownColor[-7876870] = KnownColor.LightSkyBlue;
+ KnownColorToColor[KnownColor.LightSkyBlue] = Color.FromArgb(-7876870);
+ ColorToKnownColor[-8943463] = KnownColor.LightSlateGray;
+ KnownColorToColor[KnownColor.LightSlateGray] = Color.FromArgb(-8943463);
+ ColorToKnownColor[-5192482] = KnownColor.LightSteelBlue;
+ KnownColorToColor[KnownColor.LightSteelBlue] = Color.FromArgb(-5192482);
+ ColorToKnownColor[-32] = KnownColor.LightYellow;
+ KnownColorToColor[KnownColor.LightYellow] = Color.FromArgb(-32);
+ ColorToKnownColor[-16711936] = KnownColor.Lime;
+ KnownColorToColor[KnownColor.Lime] = Color.FromArgb(-16711936);
+ ColorToKnownColor[-13447886] = KnownColor.LimeGreen;
+ KnownColorToColor[KnownColor.LimeGreen] = Color.FromArgb(-13447886);
+ ColorToKnownColor[-331546] = KnownColor.Linen;
+ KnownColorToColor[KnownColor.Linen] = Color.FromArgb(-331546);
+ ColorToKnownColor[-65281] = KnownColor.Magenta;
+ KnownColorToColor[KnownColor.Magenta] = Color.FromArgb(-65281);
+ ColorToKnownColor[-8388608] = KnownColor.Maroon;
+ KnownColorToColor[KnownColor.Maroon] = Color.FromArgb(-8388608);
+ ColorToKnownColor[-10039894] = KnownColor.MediumAquamarine;
+ KnownColorToColor[KnownColor.MediumAquamarine] = Color.FromArgb(-10039894);
+ ColorToKnownColor[-16777011] = KnownColor.MediumBlue;
+ KnownColorToColor[KnownColor.MediumBlue] = Color.FromArgb(-16777011);
+ ColorToKnownColor[-4565549] = KnownColor.MediumOrchid;
+ KnownColorToColor[KnownColor.MediumOrchid] = Color.FromArgb(-4565549);
+ ColorToKnownColor[-7114533] = KnownColor.MediumPurple;
+ KnownColorToColor[KnownColor.MediumPurple] = Color.FromArgb(-7114533);
+ ColorToKnownColor[-12799119] = KnownColor.MediumSeaGreen;
+ KnownColorToColor[KnownColor.MediumSeaGreen] = Color.FromArgb(-12799119);
+ ColorToKnownColor[-8689426] = KnownColor.MediumSlateBlue;
+ KnownColorToColor[KnownColor.MediumSlateBlue] = Color.FromArgb(-8689426);
+ ColorToKnownColor[-16713062] = KnownColor.MediumSpringGreen;
+ KnownColorToColor[KnownColor.MediumSpringGreen] = Color.FromArgb(-16713062);
+ ColorToKnownColor[-12004916] = KnownColor.MediumTurquoise;
+ KnownColorToColor[KnownColor.MediumTurquoise] = Color.FromArgb(-12004916);
+ ColorToKnownColor[-3730043] = KnownColor.MediumVioletRed;
+ KnownColorToColor[KnownColor.MediumVioletRed] = Color.FromArgb(-3730043);
+ ColorToKnownColor[-15132304] = KnownColor.MidnightBlue;
+ KnownColorToColor[KnownColor.MidnightBlue] = Color.FromArgb(-15132304);
+ ColorToKnownColor[-655366] = KnownColor.MintCream;
+ KnownColorToColor[KnownColor.MintCream] = Color.FromArgb(-655366);
+ ColorToKnownColor[-6943] = KnownColor.MistyRose;
+ KnownColorToColor[KnownColor.MistyRose] = Color.FromArgb(-6943);
+ ColorToKnownColor[-6987] = KnownColor.Moccasin;
+ KnownColorToColor[KnownColor.Moccasin] = Color.FromArgb(-6987);
+ ColorToKnownColor[-8531] = KnownColor.NavajoWhite;
+ KnownColorToColor[KnownColor.NavajoWhite] = Color.FromArgb(-8531);
+ ColorToKnownColor[-16777088] = KnownColor.Navy;
+ KnownColorToColor[KnownColor.Navy] = Color.FromArgb(-16777088);
+ ColorToKnownColor[-133658] = KnownColor.OldLace;
+ KnownColorToColor[KnownColor.OldLace] = Color.FromArgb(-133658);
+ ColorToKnownColor[-8355840] = KnownColor.Olive;
+ KnownColorToColor[KnownColor.Olive] = Color.FromArgb(-8355840);
+ ColorToKnownColor[-9728477] = KnownColor.OliveDrab;
+ KnownColorToColor[KnownColor.OliveDrab] = Color.FromArgb(-9728477);
+ ColorToKnownColor[-23296] = KnownColor.Orange;
+ KnownColorToColor[KnownColor.Orange] = Color.FromArgb(-23296);
+ ColorToKnownColor[-47872] = KnownColor.OrangeRed;
+ KnownColorToColor[KnownColor.OrangeRed] = Color.FromArgb(-47872);
+ ColorToKnownColor[-2461482] = KnownColor.Orchid;
+ KnownColorToColor[KnownColor.Orchid] = Color.FromArgb(-2461482);
+ ColorToKnownColor[-1120086] = KnownColor.PaleGoldenrod;
+ KnownColorToColor[KnownColor.PaleGoldenrod] = Color.FromArgb(-1120086);
+ ColorToKnownColor[-6751336] = KnownColor.PaleGreen;
+ KnownColorToColor[KnownColor.PaleGreen] = Color.FromArgb(-6751336);
+ ColorToKnownColor[-5247250] = KnownColor.PaleTurquoise;
+ KnownColorToColor[KnownColor.PaleTurquoise] = Color.FromArgb(-5247250);
+ ColorToKnownColor[-2396013] = KnownColor.PaleVioletRed;
+ KnownColorToColor[KnownColor.PaleVioletRed] = Color.FromArgb(-2396013);
+ ColorToKnownColor[-4139] = KnownColor.PapayaWhip;
+ KnownColorToColor[KnownColor.PapayaWhip] = Color.FromArgb(-4139);
+ ColorToKnownColor[-9543] = KnownColor.PeachPuff;
+ KnownColorToColor[KnownColor.PeachPuff] = Color.FromArgb(-9543);
+ ColorToKnownColor[-3308225] = KnownColor.Peru;
+ KnownColorToColor[KnownColor.Peru] = Color.FromArgb(-3308225);
+ ColorToKnownColor[-16181] = KnownColor.Pink;
+ KnownColorToColor[KnownColor.Pink] = Color.FromArgb(-16181);
+ ColorToKnownColor[-2252579] = KnownColor.Plum;
+ KnownColorToColor[KnownColor.Plum] = Color.FromArgb(-2252579);
+ ColorToKnownColor[-5185306] = KnownColor.PowderBlue;
+ KnownColorToColor[KnownColor.PowderBlue] = Color.FromArgb(-5185306);
+ ColorToKnownColor[-8388480] = KnownColor.Purple;
+ KnownColorToColor[KnownColor.Purple] = Color.FromArgb(-8388480);
+ ColorToKnownColor[-65536] = KnownColor.Red;
+ KnownColorToColor[KnownColor.Red] = Color.FromArgb(-65536);
+ ColorToKnownColor[-4419697] = KnownColor.RosyBrown;
+ KnownColorToColor[KnownColor.RosyBrown] = Color.FromArgb(-4419697);
+ ColorToKnownColor[-12490271] = KnownColor.RoyalBlue;
+ KnownColorToColor[KnownColor.RoyalBlue] = Color.FromArgb(-12490271);
+ ColorToKnownColor[-7650029] = KnownColor.SaddleBrown;
+ KnownColorToColor[KnownColor.SaddleBrown] = Color.FromArgb(-7650029);
+ ColorToKnownColor[-360334] = KnownColor.Salmon;
+ KnownColorToColor[KnownColor.Salmon] = Color.FromArgb(-360334);
+ ColorToKnownColor[-744352] = KnownColor.SandyBrown;
+ KnownColorToColor[KnownColor.SandyBrown] = Color.FromArgb(-744352);
+ ColorToKnownColor[-13726889] = KnownColor.SeaGreen;
+ KnownColorToColor[KnownColor.SeaGreen] = Color.FromArgb(-13726889);
+ ColorToKnownColor[-2578] = KnownColor.SeaShell;
+ KnownColorToColor[KnownColor.SeaShell] = Color.FromArgb(-2578);
+ ColorToKnownColor[-6270419] = KnownColor.Sienna;
+ KnownColorToColor[KnownColor.Sienna] = Color.FromArgb(-6270419);
+ ColorToKnownColor[-4144960] = KnownColor.Silver;
+ KnownColorToColor[KnownColor.Silver] = Color.FromArgb(-4144960);
+ ColorToKnownColor[-7876885] = KnownColor.SkyBlue;
+ KnownColorToColor[KnownColor.SkyBlue] = Color.FromArgb(-7876885);
+ ColorToKnownColor[-9807155] = KnownColor.SlateBlue;
+ KnownColorToColor[KnownColor.SlateBlue] = Color.FromArgb(-9807155);
+ ColorToKnownColor[-9404272] = KnownColor.SlateGray;
+ KnownColorToColor[KnownColor.SlateGray] = Color.FromArgb(-9404272);
+ ColorToKnownColor[-1286] = KnownColor.Snow;
+ KnownColorToColor[KnownColor.Snow] = Color.FromArgb(-1286);
+ ColorToKnownColor[-16711809] = KnownColor.SpringGreen;
+ KnownColorToColor[KnownColor.SpringGreen] = Color.FromArgb(-16711809);
+ ColorToKnownColor[-12156236] = KnownColor.SteelBlue;
+ KnownColorToColor[KnownColor.SteelBlue] = Color.FromArgb(-12156236);
+ ColorToKnownColor[-2968436] = KnownColor.Tan;
+ KnownColorToColor[KnownColor.Tan] = Color.FromArgb(-2968436);
+ ColorToKnownColor[-16744320] = KnownColor.Teal;
+ KnownColorToColor[KnownColor.Teal] = Color.FromArgb(-16744320);
+ ColorToKnownColor[-2572328] = KnownColor.Thistle;
+ KnownColorToColor[KnownColor.Thistle] = Color.FromArgb(-2572328);
+ ColorToKnownColor[-40121] = KnownColor.Tomato;
+ KnownColorToColor[KnownColor.Tomato] = Color.FromArgb(-40121);
+ ColorToKnownColor[-12525360] = KnownColor.Turquoise;
+ KnownColorToColor[KnownColor.Turquoise] = Color.FromArgb(-12525360);
+ ColorToKnownColor[-1146130] = KnownColor.Violet;
+ KnownColorToColor[KnownColor.Violet] = Color.FromArgb(-1146130);
+ ColorToKnownColor[-663885] = KnownColor.Wheat;
+ KnownColorToColor[KnownColor.Wheat] = Color.FromArgb(-663885);
+ ColorToKnownColor[-1] = KnownColor.White;
+ KnownColorToColor[KnownColor.White] = Color.FromArgb(-1);
+ ColorToKnownColor[-657931] = KnownColor.WhiteSmoke;
+ KnownColorToColor[KnownColor.WhiteSmoke] = Color.FromArgb(-657931);
+ ColorToKnownColor[-256] = KnownColor.Yellow;
+ KnownColorToColor[KnownColor.Yellow] = Color.FromArgb(-256);
+ ColorToKnownColor[-6632142] = KnownColor.YellowGreen;
+ KnownColorToColor[KnownColor.YellowGreen] = Color.FromArgb(-6632142);
+ ColorToKnownColor[-986896] = KnownColor.ButtonFace;
+ KnownColorToColor[KnownColor.ButtonFace] = Color.FromArgb(-986896);
+ ColorToKnownColor[-1] = KnownColor.ButtonHighlight;
+ KnownColorToColor[KnownColor.ButtonHighlight] = Color.FromArgb(-1);
+ ColorToKnownColor[-6250336] = KnownColor.ButtonShadow;
+ KnownColorToColor[KnownColor.ButtonShadow] = Color.FromArgb(-6250336);
+ ColorToKnownColor[-4599318] = KnownColor.GradientActiveCaption;
+ KnownColorToColor[KnownColor.GradientActiveCaption] = Color.FromArgb(-4599318);
+ ColorToKnownColor[-2628366] = KnownColor.GradientInactiveCaption;
+ KnownColorToColor[KnownColor.GradientInactiveCaption] = Color.FromArgb(-2628366);
+ ColorToKnownColor[-986896] = KnownColor.MenuBar;
+ KnownColorToColor[KnownColor.MenuBar] = Color.FromArgb(-986896);
+ ColorToKnownColor[-16746281] = KnownColor.MenuHighlight;
+ KnownColorToColor[KnownColor.MenuHighlight] = Color.FromArgb(-16746281);
+ }
+
+ public static bool IsKnownColor(Color color)
+ {
+ return ColorToKnownColor.ContainsKey(color.ToArgb());
+ }
+
+ public static KnownColor ToKnownColor(Color color)
+ {
+ if (ColorToKnownColor.TryGetValue(color.ToArgb(), out KnownColor result))
+ return result;
+ return KnownColor.Transparent;
+ }
+
+ public static Color FromKnownColor(KnownColor knownColor)
+ {
+ if (KnownColorToColor.TryGetValue(knownColor, out Color result))
+ return result;
+ return Color.Transparent;
+ }
+
+ public static bool IsSystemColor(Color c)
+ {
+ if (IsKnownColor(c))
+ {
+ KnownColor knownColor = ToKnownColor(c);
+ return ((((KnownColor)knownColor) <= KnownColor.WindowText) || (((KnownColor)knownColor) > KnownColor.YellowGreen));
+ }
+ return false;
+ }
+ }
+}
+#endif
+
diff --git a/FastReport.Compat/src/shared/TypeConverters/ColorConverter.cs b/FastReport.Compat/src/shared/TypeConverters/ColorConverter.cs
new file mode 100644
index 00000000..969137f8
--- /dev/null
+++ b/FastReport.Compat/src/shared/TypeConverters/ColorConverter.cs
@@ -0,0 +1,480 @@
+#if NETSTANDARD2_0
+using System;
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.ComponentModel.Design.Serialization;
+using System.Diagnostics.CodeAnalysis;
+using System.Globalization;
+using System.Reflection;
+using System.Text;
+
+namespace System.Drawing
+{
+ public class ColorConverter : TypeConverter
+ {
+ private static readonly string ColorConstantsLock = "colorConstants";
+ private static Hashtable colorConstants;
+ private static readonly string SystemColorConstantsLock = "systemColorConstants";
+ private static Hashtable systemColorConstants;
+ private static readonly string ValuesLock = "values";
+ private static StandardValuesCollection values;
+
+ ///
+ ///
+ /// [To be supplied.]
+ ///
+ public ColorConverter()
+ {
+ }
+
+ ///
+ ///
+ /// Hashtable of color / value pairs (color name is key)
+ /// for standard colors.
+ ///
+ private static Hashtable Colors
+ {
+ get
+ {
+ if (colorConstants == null)
+ {
+ lock (ColorConstantsLock)
+ {
+ if (colorConstants == null)
+ {
+ Hashtable tempHash = new Hashtable(StringComparer.OrdinalIgnoreCase);
+ FillConstants(tempHash, typeof(Color));
+ colorConstants = tempHash;
+ }
+ }
+ }
+
+ return colorConstants;
+ }
+ }
+
+ ///
+ ///
+ /// Hashtable of color / value pairs (color name is key)
+ /// for system colors.
+ ///
+ private static Hashtable SystemColors
+ {
+ get
+ {
+ if (systemColorConstants == null)
+ {
+ lock (SystemColorConstantsLock)
+ {
+ if (systemColorConstants == null)
+ {
+ Hashtable tempHash = new Hashtable(StringComparer.OrdinalIgnoreCase);
+ FillConstants(tempHash, typeof(System.Drawing.SystemColors));
+ systemColorConstants = tempHash;
+ }
+ }
+ }
+
+ return systemColorConstants;
+ }
+ }
+
+ ///
+ ///
+ /// Determines if this converter can convert an object in the given source
+ /// type to the native type of the converter.
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == typeof(string))
+ {
+ return true;
+ }
+ return base.CanConvertFrom(context, sourceType);
+ }
+
+ ///
+ ///
+ /// Gets a value indicating whether this converter can
+ /// convert an object to the given destination type using the context.
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(InstanceDescriptor))
+ {
+ return true;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
+ internal static object GetNamedColor(string name)
+ {
+ object color = null;
+ // First, check to see if this is a standard name.
+ //
+ color = Colors[name];
+ if (color != null)
+ {
+ return color;
+ }
+ // Ok, how about a system color?
+ //
+ color = SystemColors[name];
+ return color;
+ }
+
+ ///
+ ///
+ /// Converts the given object to the converter's native type.
+ ///
+ [SuppressMessage("Microsoft.Performance", "CA1808:AvoidCallsThatBoxValueTypes")]
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ string strValue = value as string;
+ if (strValue != null)
+ {
+ object obj = null;
+ string text = strValue.Trim();
+
+ if (text.Length == 0)
+ {
+ obj = Color.Empty;
+ }
+ else
+ {
+ // First, check to see if this is a standard name.
+ //
+ obj = GetNamedColor(text);
+
+ if (obj == null)
+ {
+ if (culture == null)
+ {
+ culture = CultureInfo.CurrentCulture;
+ }
+
+ char sep = culture.TextInfo.ListSeparator[0];
+ bool tryMappingToKnownColor = true;
+
+ TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
+
+ // If the value is a 6 digit hex number only, then
+ // we want to treat the Alpha as 255, not 0
+ //
+ if (text.IndexOf(sep) == -1)
+ {
+
+ // text can be '' (empty quoted string)
+ if (text.Length >= 2 && (text[0] == '\'' || text[0] == '"') && text[0] == text[text.Length - 1])
+ {
+ // In quotes means a named value
+ string colorName = text.Substring(1, text.Length - 2);
+ obj = Color.FromName(colorName);
+ tryMappingToKnownColor = false;
+ }
+ else if ((text.Length == 7 && text[0] == '#') ||
+ (text.Length == 8 && (text.StartsWith("0x") || text.StartsWith("0X"))) ||
+ (text.Length == 8 && (text.StartsWith("&h") || text.StartsWith("&H"))))
+ {
+ // Note: ConvertFromString will raise exception if value cannot be converted.
+ obj = Color.FromArgb(unchecked((int)(0xFF000000 | (uint)(int)intConverter.ConvertFromString(context, culture, text))));
+ }
+ }
+
+ // Nope. Parse the RGBA from the text.
+ //
+ if (obj == null)
+ {
+ string[] tokens = text.Split(new char[] { sep });
+ int[] values = new int[tokens.Length];
+ for (int i = 0; i < values.Length; i++)
+ {
+ values[i] = unchecked((int)intConverter.ConvertFromString(context, culture, tokens[i]));
+ }
+
+ // We should now have a number of parsed integer values.
+ // We support 1, 3, or 4 arguments:
+ //
+ // 1 -- full ARGB encoded
+ // 3 -- RGB
+ // 4 -- ARGB
+ //
+ switch (values.Length)
+ {
+ case 1:
+ obj = Color.FromArgb(values[0]);
+ break;
+
+ case 3:
+ obj = Color.FromArgb(values[0], values[1], values[2]);
+ break;
+
+ case 4:
+ obj = Color.FromArgb(values[0], values[1], values[2], values[3]);
+ break;
+ }
+ tryMappingToKnownColor = true;
+ }
+
+ if ((obj != null) && tryMappingToKnownColor)
+ {
+
+ // Now check to see if this color matches one of our known colors.
+ // If it does, then substitute it. We can only do this for "Colors"
+ // because system colors morph with user settings.
+ //
+ int targetARGB = ((Color)obj).ToArgb();
+
+ foreach (Color c in Colors.Values)
+ {
+ if (c.ToArgb() == targetARGB)
+ {
+ obj = c;
+ break;
+ }
+ }
+ }
+ }
+
+ if (obj == null)
+ {
+ //throw new ArgumentException(SR.GetString(SR.InvalidColor, text));
+ }
+ }
+ return obj;
+ }
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ ///
+ ///
+ /// Converts the given object to another type. The most common types to convert
+ /// are to and from a string object. The default implementation will make a call
+ /// to ToString on the object if the object is valid and if the destination
+ /// type is string. If this cannot convert to the desitnation type, this will
+ /// throw a NotSupportedException.
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (destinationType == null)
+ {
+ throw new ArgumentNullException("destinationType");
+ }
+
+ if (value is Color)
+ {
+ if (destinationType == typeof(string))
+ {
+ Color c = (Color)value;
+
+ if (c == Color.Empty)
+ {
+ return string.Empty;
+ }
+ else
+ {
+ // If this is a known color, then Color can provide its own
+ // name. Otherwise, we fabricate an ARGB value for it.
+ //
+ if (ColorExt.IsKnownColor( c))
+ {
+ return c.Name;
+ }
+ else if (c.IsNamedColor)
+ {
+ return "'" + c.Name + "'";
+ }
+ else
+ {
+ if (culture == null)
+ {
+ culture = CultureInfo.CurrentCulture;
+ }
+ string sep = culture.TextInfo.ListSeparator + " ";
+ TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
+ string[] args;
+ int nArg = 0;
+
+ if (c.A < 255)
+ {
+ args = new string[4];
+ args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.A);
+ }
+ else
+ {
+ args = new string[3];
+ }
+
+ // Note: ConvertToString will raise exception if value cannot be converted.
+ args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.R);
+ args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.G);
+ args[nArg++] = intConverter.ConvertToString(context, culture, (object)c.B);
+
+ // Now slam all of these together with the fantastic Join
+ // method.
+ //
+ return string.Join(sep, args);
+ }
+ }
+ }
+ if (destinationType == typeof(InstanceDescriptor))
+ {
+ MemberInfo member = null;
+ object[] args = null;
+
+ Color c = (Color)value;
+
+ if (c.IsEmpty)
+ {
+ member = typeof(Color).GetField("Empty");
+ }
+ else if (ColorExt.IsSystemColor(c))
+ {
+ member = typeof(SystemColors).GetProperty(c.Name);
+ }
+ else if (ColorExt.IsKnownColor(c))
+ {
+ member = typeof(Color).GetProperty(c.Name);
+ }
+ else if (c.A != 255)
+ {
+ member = typeof(Color).GetMethod("FromArgb", new Type[] { typeof(int), typeof(int), typeof(int), typeof(int) });
+ args = new object[] { c.A, c.R, c.G, c.B };
+ }
+ else if (c.IsNamedColor)
+ {
+ member = typeof(Color).GetMethod("FromName", new Type[] { typeof(string) });
+ args = new object[] { c.Name };
+ }
+ else
+ {
+ member = typeof(Color).GetMethod("FromArgb", new Type[] { typeof(int), typeof(int), typeof(int) });
+ args = new object[] { c.R, c.G, c.B };
+ }
+
+ //Debug.Assert(member != null, "Could not convert color to member. Did someone change method name / signature and not update Colorconverter?");
+ if (member != null)
+ {
+ return new InstanceDescriptor(member, args);
+ }
+ else
+ {
+ return null;
+ }
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+
+ ///
+ ///
+ /// Fills the given hashtable with field name / value pairs. It walks all public static
+ /// properties of enumType that have a property type of Color.
+ ///
+ private static void FillConstants(Hashtable hash, Type enumType)
+ {
+ MethodAttributes attrs = MethodAttributes.Public | MethodAttributes.Static;
+ PropertyInfo[] props = enumType.GetProperties();
+
+ for (int i = 0; i < props.Length; i++)
+ {
+ PropertyInfo prop = props[i];
+ if (prop.PropertyType == typeof(Color))
+ {
+ MethodInfo method = prop.GetGetMethod();
+ if (method != null && (method.Attributes & attrs) == attrs)
+ {
+ object[] tempIndex = null;
+ hash[prop.Name] = prop.GetValue(null, tempIndex);
+ }
+ }
+ }
+ }
+
+ ///
+ ///
+ /// Retrieves a collection containing a set of standard values
+ /// for the data type this validator is designed for. This
+ /// will return null if the data type does not support a
+ /// standard set of values.
+ ///
+ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
+ {
+ if (values == null)
+ {
+ lock (ValuesLock)
+ {
+ if (values == null)
+ {
+
+ // We must take the value from each hashtable and combine them.
+ //
+ ArrayList arrayValues = new ArrayList();
+ arrayValues.AddRange(Colors.Values);
+ arrayValues.AddRange(SystemColors.Values);
+
+ // Now, we have a couple of colors that have the same names but
+ // are identical values. Look for these and remove them. Too
+ // bad this is n^2.
+ //
+ int count = arrayValues.Count;
+ for (int i = 0; i < count - 1; i++)
+ {
+ for (int j = i + 1; j < count; j++)
+ {
+ if (arrayValues[i].Equals(arrayValues[j]))
+ {
+ // Remove this item!
+ //
+ arrayValues.RemoveAt(j);
+ count--;
+ j--;
+ }
+ }
+ }
+
+ // Sort the array.
+ //
+ arrayValues.Sort(0, arrayValues.Count, new ColorComparer());
+ values = new StandardValuesCollection(arrayValues.ToArray());
+ }
+ }
+ }
+
+ return values;
+ }
+
+ ///
+ ///
+ /// Determines if this object supports a standard set of values
+ /// that can be picked from a list.
+ ///
+ public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
+ {
+ return true;
+ }
+
+ ///
+ ///
+ /// IComparer for color values. This takes color values but compares their
+ /// names.
+ ///
+ private class ColorComparer : IComparer
+ {
+
+ public int Compare(object left, object right)
+ {
+ Color cLeft = (Color)left;
+ Color cRight = (Color)right;
+ return string.Compare(cLeft.Name, cRight.Name, false, CultureInfo.InvariantCulture);
+ }
+ }
+ }
+ //internal Color ConvertFromInvariantString(string value)
+ //{
+ // return (Color)TypeDescriptor.GetConverter(typeof(Color)).ConvertFromInvariantString(value);
+ //}
+}
+#endif
\ No newline at end of file
diff --git a/FastReport.Compat/src/shared/TypeConverters/FontConverter.cs b/FastReport.Compat/src/shared/TypeConverters/FontConverter.cs
new file mode 100644
index 00000000..a213a541
--- /dev/null
+++ b/FastReport.Compat/src/shared/TypeConverters/FontConverter.cs
@@ -0,0 +1,553 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+// Note: the original file was modified by AlexTZ:
+// - code changed to support .Net 2.0
+// - PrivateFontCollection property added
+// - Instance property added
+// - Up priority of PrivateFontCollection
+
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.ComponentModel.Design.Serialization;
+using System.Drawing.Text;
+using System.Globalization;
+using System.Reflection;
+using System.Text;
+using System.Drawing;
+
+namespace FastReport.TypeConverters
+{
+ public class FontConverter : TypeConverter
+ {
+ private const string StylePrefix = "style=";
+
+
+ [Obsolete]
+ public static FontConverter Instance = new FontConverter();
+
+ ///
+ /// Gets a PrivateFontCollection instance.
+ ///
+ ///
+ /// NOT THREAD SAFE!
+ ///
+ public static PrivateFontCollection PrivateFontCollection { get; } = new PrivateFontCollection();
+
+ ///
+ /// Do not update PrivateFontCollection at realtime, you must update property value then dispose previous.
+ ///
+ ///
+ /// NOT THREAD SAFE!
+ ///
+ public static PrivateFontCollection TemporaryFontCollection { get; set; } = null;
+
+ public static InstalledFontCollection InstalledFontCollection { get; } = new InstalledFontCollection();
+
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string) ? true : base.CanConvertFrom(context, sourceType);
+ }
+
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ return (destinationType == typeof(string)) || (destinationType == typeof(InstanceDescriptor));
+ }
+
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (value is Font)
+ {
+ Font font = value as Font;
+ if (destinationType == typeof(string))
+ {
+ if (culture == null)
+ {
+ culture = CultureInfo.CurrentCulture;
+ }
+
+ StringBuilder sb = new StringBuilder();
+ sb.Append(font.Name);
+ sb.Append(culture.TextInfo.ListSeparator[0]);
+ sb.Append(' ');
+ sb.Append(font.Size.ToString(culture.NumberFormat));
+
+ switch (font.Unit)
+ {
+ // MS throws ArgumentException, if unit is set
+ // to GraphicsUnit.Display
+ // Don't know what to append for GraphicsUnit.Display
+ case GraphicsUnit.Display:
+ sb.Append("display");
+ break;
+
+ case GraphicsUnit.Document:
+ sb.Append("doc");
+ break;
+
+ case GraphicsUnit.Point:
+ sb.Append("pt");
+ break;
+
+ case GraphicsUnit.Inch:
+ sb.Append("in");
+ break;
+
+ case GraphicsUnit.Millimeter:
+ sb.Append("mm");
+ break;
+
+ case GraphicsUnit.Pixel:
+ sb.Append("px");
+ break;
+
+ case GraphicsUnit.World:
+ sb.Append("world");
+ break;
+ }
+
+ if (font.Style != FontStyle.Regular)
+ {
+ sb.Append(culture.TextInfo.ListSeparator[0]);
+ sb.Append(" style=");
+ sb.Append(font.Style.ToString());
+ }
+
+ return sb.ToString();
+ }
+
+ if (destinationType == typeof(InstanceDescriptor))
+ {
+ ConstructorInfo met = typeof(Font).GetConstructor(new Type[] { typeof(string), typeof(float), typeof(FontStyle), typeof(GraphicsUnit) });
+ object[] args = new object[4];
+ args[0] = font.Name;
+ args[1] = font.Size;
+ args[2] = font.Style;
+ args[3] = font.Unit;
+
+ return new InstanceDescriptor(met, args);
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+
+ ///
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ if (!(value is string))
+ {
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ string font = value as string;
+ font = font.Trim();
+
+ // Expected string format: "name[, size[, units[, style=style1[, style2[...]]]]]"
+ // Example using 'vi-VN' culture: "Microsoft Sans Serif, 8,25pt, style=Italic, Bold"
+ if (font.Length == 0)
+ {
+ return null;
+ }
+
+ if (culture == null)
+ {
+ culture = CultureInfo.CurrentCulture;
+ }
+
+ char separator = culture.TextInfo.ListSeparator[0]; // For vi-VN: ','
+ string fontName = font; // start with the assumption that only the font name was provided.
+ string style = null;
+ string sizeStr = null;
+ float fontSize = 8.25f;
+ FontStyle fontStyle = FontStyle.Regular;
+ GraphicsUnit units = GraphicsUnit.Point;
+
+ // Get the index of the first separator (would indicate the end of the name in the string).
+ int nameIndex = font.IndexOf(separator);
+
+ if (nameIndex < 0)
+ {
+ return new Font(fontName, fontSize, fontStyle, units);
+ }
+
+ // Some parameters are provided in addition to name.
+ fontName = font.Substring(0, nameIndex);
+
+ if (nameIndex < font.Length - 1)
+ {
+ // Get the style index (if any). The size is a bit problematic because it can be formatted differently
+ // depending on the culture, we'll parse it last.
+ int styleIndex = culture.CompareInfo.IndexOf(font, StylePrefix, CompareOptions.IgnoreCase);
+
+ if (styleIndex != -1)
+ {
+ // style found.
+ style = font.Substring(styleIndex, font.Length - styleIndex);
+
+ // Get the mid-substring containing the size information.
+ sizeStr = font.Substring(nameIndex + 1, styleIndex - nameIndex - 1);
+ }
+ else
+ {
+ // no style.
+ sizeStr = font.Substring(nameIndex + 1);
+ }
+
+ // Parse size.
+ string unitTokensSize = null;
+ string unitTokensUnit = null;
+ ParseSizeTokens(sizeStr, separator, ref unitTokensSize, ref unitTokensUnit);
+
+ if (unitTokensSize != null)
+ {
+ try
+ {
+ fontSize = (float)TypeDescriptor.GetConverter(typeof(float)).ConvertFromString(context, culture, unitTokensSize);
+ }
+ catch
+ {
+ // Exception from converter is too generic.
+ throw new ArgumentException("Invalid font string: " + font);
+ }
+ }
+
+ if (unitTokensUnit != null)
+ {
+ // ParseGraphicsUnits throws an ArgumentException if format is invalid.
+ units = ParseGraphicsUnits(unitTokensUnit);
+ }
+
+ if (style != null)
+ {
+ // Parse FontStyle
+ style = style.Substring(6); // style string always starts with style=
+ string[] styleTokens = style.Split(separator);
+
+ for (int tokenCount = 0; tokenCount < styleTokens.Length; tokenCount++)
+ {
+ string styleText = styleTokens[tokenCount];
+ styleText = styleText.Trim();
+
+ fontStyle |= (FontStyle)Enum.Parse(typeof(FontStyle), styleText, true);
+
+ // Enum.IsDefined doesn't do what we want on flags enums...
+ FontStyle validBits = FontStyle.Regular | FontStyle.Bold | FontStyle.Italic | FontStyle.Underline | FontStyle.Strikeout;
+ if ((fontStyle | validBits) != validBits)
+ {
+ throw new InvalidEnumArgumentException("FontStyle", (int)fontStyle, typeof(FontStyle));
+ }
+ }
+ }
+ }
+
+ var fontFamily = FindFontFamily(fontName, null);
+
+ Font result = new Font(fontFamily, fontSize, fontStyle, units);
+
+ return result;
+ }
+
+ private void ParseSizeTokens(string text, char separator, ref string size, ref string units)
+ {
+ text = text.Trim();
+
+ int length = text.Length;
+ int splitPoint;
+
+ if (length > 0)
+ {
+ // text is expected to have a format like " 8,25pt, ". Leading and trailing spaces (trimmed above),
+ // last comma, unit and decimal value may not appear. We need to make it ####.##CC
+ for (splitPoint = 0; splitPoint < length; splitPoint++)
+ {
+ if (char.IsLetter(text[splitPoint]))
+ {
+ break;
+ }
+ }
+
+ char[] trimChars = new char[] { separator, ' ' };
+
+ if (splitPoint > 0)
+ {
+ size = text.Substring(0, splitPoint);
+ // Trimming spaces between size and units.
+ size = size.Trim(trimChars);
+ }
+
+ if (splitPoint < length)
+ {
+ units = text.Substring(splitPoint);
+ units = units.TrimEnd(trimChars);
+ }
+ }
+ }
+
+ private GraphicsUnit ParseGraphicsUnits(string units)
+ {
+ if (units == "display") return GraphicsUnit.Display;
+ else if (units == "doc") return GraphicsUnit.Document;
+ else if (units == "pt") return GraphicsUnit.Point;
+ else if (units == "in") return GraphicsUnit.Inch;
+ else if (units == "mm") return GraphicsUnit.Millimeter;
+ else if (units == "px") return GraphicsUnit.Pixel;
+ else if (units == "world") return GraphicsUnit.World;
+ else throw new ArgumentException("Invalid font units: " + units);
+ }
+
+ ///
+ public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
+ {
+ object value;
+ byte charSet = 1;
+ float size = 8;
+ string name = null;
+ bool vertical = false;
+ FontStyle style = FontStyle.Regular;
+ FontFamily fontFamily = null;
+ GraphicsUnit unit = GraphicsUnit.Point;
+
+ if ((value = propertyValues["GdiCharSet"]) != null)
+ charSet = (byte)value;
+
+ if ((value = propertyValues["Size"]) != null)
+ size = (float)value;
+
+ if ((value = propertyValues["Unit"]) != null)
+ unit = (GraphicsUnit)value;
+
+ if ((value = propertyValues["Name"]) != null)
+ name = (string)value;
+
+ if ((value = propertyValues["GdiVerticalFont"]) != null)
+ vertical = (bool)value;
+
+ if ((value = propertyValues["Bold"]) != null)
+ {
+ if ((bool)value == true)
+ style |= FontStyle.Bold;
+ }
+
+ if ((value = propertyValues["Italic"]) != null)
+ {
+ if ((bool)value == true)
+ style |= FontStyle.Italic;
+ }
+
+ if ((value = propertyValues["Strikeout"]) != null)
+ {
+ if ((bool)value == true)
+ style |= FontStyle.Strikeout;
+ }
+
+ if ((value = propertyValues["Underline"]) != null)
+ {
+ if ((bool)value == true)
+ style |= FontStyle.Underline;
+ }
+
+ if (name == null)
+ {
+ fontFamily = new FontFamily("Tahoma");
+ }
+ else
+ {
+ fontFamily = FindFontFamily(name, fontFamily);
+ }
+
+ return new Font(fontFamily, size, style, unit, charSet, vertical);
+ }
+
+ private static FontFamily FindFontFamily(string name, FontFamily fontFamily)
+ {
+ FontCollection collection;
+
+#if SKIA
+ collection = TemporaryFontCollection;
+
+ if (collection != null)
+ {
+ fontFamily = collection.FindInternalByGDIFontFamilyName(name);
+ }
+
+ // font family not found in temp list
+ if (fontFamily == null)
+ {
+ collection = PrivateFontCollection;
+ fontFamily = collection.FindInternalByGDIFontFamilyName(name);
+
+ // font family not found in private list
+ if (fontFamily == null)
+ {
+ collection = InstalledFontCollection;
+ fontFamily = collection.FindInternalByGDIFontFamilyName(name);
+ }
+ }
+#else
+ collection = TemporaryFontCollection;
+
+ if (collection != null)
+ {
+ foreach (FontFamily font in collection.Families)
+ {
+ if (name.Equals(font.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ return font;
+ }
+ }
+ }
+
+ // font family not found in temp list
+ collection = PrivateFontCollection;
+ foreach (FontFamily font in collection.Families)
+ {
+ if (name.Equals(font.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ return font;
+ }
+ }
+
+ // font family not found in private list
+ collection = InstalledFontCollection;
+ FontFamily[] privateFontList = collection.Families;
+ foreach (FontFamily font in privateFontList)
+ {
+ if (name.Equals(font.Name, StringComparison.OrdinalIgnoreCase))
+ {
+ return font;
+ }
+ }
+
+#endif
+
+ // font family not found in private fonts also
+ if (fontFamily == null)
+ fontFamily = FontFamily.GenericSansSerif;
+ return fontFamily;
+ }
+
+ ///
+ public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
+ {
+ return true;
+ }
+
+ ///
+ public override PropertyDescriptorCollection GetProperties(
+ ITypeDescriptorContext context,
+ object value,
+ Attribute[] attributes)
+ {
+ return value is Font ? TypeDescriptor.GetProperties(value, attributes) : base.GetProperties(context, value, attributes);
+ }
+
+ ///
+ public override bool GetPropertiesSupported(ITypeDescriptorContext context)
+ {
+ return true;
+ }
+
+ public sealed class FontNameConverter : TypeConverter, IDisposable
+ {
+ private readonly FontFamily[] _fonts;
+
+ public FontNameConverter()
+ {
+ _fonts = FontFamily.Families;
+ }
+
+ void IDisposable.Dispose()
+ {
+ }
+
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ return sourceType == typeof(string) ? true : base.CanConvertFrom(context, sourceType);
+ }
+
+ ///
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+ return value is string ? MatchFontName((value as string), context) : base.ConvertFrom(context, culture, value);
+ }
+
+ ///
+ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
+ {
+ string[] values = new string[_fonts.Length];
+ for (int i = 0; i < _fonts.Length; i++)
+ {
+ values[i] = _fonts[i].Name;
+ }
+ Array.Sort(values, Comparer.Default);
+
+ return new TypeConverter.StandardValuesCollection(values);
+ }
+
+ // We allow other values other than those in the font list.
+ ///
+ public override bool GetStandardValuesExclusive(ITypeDescriptorContext context)
+ {
+ return false;
+ }
+
+ // Yes, we support picking an element from the list.
+ ///
+ public override bool GetStandardValuesSupported(ITypeDescriptorContext context)
+ {
+ return true;
+ }
+
+ private string MatchFontName(string name, ITypeDescriptorContext context)
+ {
+ // Try a partial match
+ string bestMatch = null;
+
+ foreach (string fontName in GetStandardValues(context))
+ {
+ if (fontName.Equals(name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ // For an exact match, return immediately
+ return fontName;
+ }
+ if (fontName.StartsWith(name, StringComparison.InvariantCultureIgnoreCase))
+ {
+ if (bestMatch == null || fontName.Length <= bestMatch.Length)
+ {
+ bestMatch = fontName;
+ }
+ }
+ }
+
+ // No match... fall back on whatever was provided
+ return bestMatch != null ? bestMatch : name;
+ }
+ }
+
+ public class FontUnitConverter : EnumConverter
+ {
+ public FontUnitConverter() : base(typeof(GraphicsUnit)) { }
+
+ ///
+ public override StandardValuesCollection GetStandardValues(ITypeDescriptorContext context)
+ {
+ // display graphic unit is not supported.
+ if (Values == null)
+ {
+ base.GetStandardValues(context); // sets "values"
+ ArrayList filteredValues = new ArrayList(Values);
+ filteredValues.Remove(GraphicsUnit.Display);
+ Values = new StandardValuesCollection(filteredValues);
+ }
+ return Values;
+ }
+ }
+ }
+}
diff --git a/FastReport.Compat/src/shared/TypeConverters/SizeConverter.cs b/FastReport.Compat/src/shared/TypeConverters/SizeConverter.cs
new file mode 100644
index 00000000..0e64f482
--- /dev/null
+++ b/FastReport.Compat/src/shared/TypeConverters/SizeConverter.cs
@@ -0,0 +1,206 @@
+using System;
+using System.Collections;
+using System.ComponentModel;
+using System.Globalization;
+using System.Drawing;
+using System.Reflection;
+using System.ComponentModel.Design.Serialization;
+
+#if NETSTANDARD2_0
+namespace FastReport.TypeConverters
+{
+ public class SizeConverter : TypeConverter
+ {
+
+ ///
+ ///
+ /// Determines if this converter can convert an object in the given source
+ /// type to the native type of the converter.
+ ///
+ public override bool CanConvertFrom(ITypeDescriptorContext context, Type sourceType)
+ {
+ if (sourceType == typeof(string))
+ {
+ return true;
+ }
+ return base.CanConvertFrom(context, sourceType);
+ }
+
+ ///
+ ///
+ /// Gets a value indicating whether this converter can
+ /// convert an object to the given destination type using the context.
+ ///
+ public override bool CanConvertTo(ITypeDescriptorContext context, Type destinationType)
+ {
+ if (destinationType == typeof(InstanceDescriptor))
+ {
+ return true;
+ }
+ return base.CanConvertTo(context, destinationType);
+ }
+
+ ///
+ ///
+ /// Converts the given object to the converter's native type.
+ ///
+ public override object ConvertFrom(ITypeDescriptorContext context, CultureInfo culture, object value)
+ {
+
+ string strValue = value as string;
+
+ if (strValue != null)
+ {
+
+ string text = strValue.Trim();
+
+ if (text.Length == 0)
+ {
+ return null;
+ }
+ else
+ {
+
+ // Parse 2 integer values.
+ //
+ if (culture == null)
+ {
+ culture = CultureInfo.CurrentCulture;
+ }
+ char sep = culture.TextInfo.ListSeparator[0];
+ string[] tokens = text.Split(new char[] { sep });
+ int[] values = new int[tokens.Length];
+ TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
+ for (int i = 0; i < values.Length; i++)
+ {
+ // Note: ConvertFromString will raise exception if value cannot be converted.
+ values[i] = (int)intConverter.ConvertFromString(context, culture, tokens[i]);
+ }
+
+ if (values.Length == 2)
+ {
+ return new Size(values[0], values[1]);
+ }
+ else
+ {
+ throw new ArgumentException();
+ }
+ }
+ }
+
+ return base.ConvertFrom(context, culture, value);
+ }
+
+ ///
+ ///
+ /// Converts the given object to another type. The most common types to convert
+ /// are to and from a string object. The default implementation will make a call
+ /// to ToString on the object if the object is valid and if the destination
+ /// type is string. If this cannot convert to the desitnation type, this will
+ /// throw a NotSupportedException.
+ ///
+ public override object ConvertTo(ITypeDescriptorContext context, CultureInfo culture, object value, Type destinationType)
+ {
+ if (destinationType == null)
+ {
+ throw new ArgumentNullException("destinationType");
+ }
+
+ if (value is Size)
+ {
+ if (destinationType == typeof(string))
+ {
+ Size size = (Size)value;
+
+ if (culture == null)
+ {
+ culture = CultureInfo.CurrentCulture;
+ }
+ string sep = culture.TextInfo.ListSeparator + " ";
+ TypeConverter intConverter = TypeDescriptor.GetConverter(typeof(int));
+ string[] args = new string[2];
+ int nArg = 0;
+
+ // Note: ConvertToString will raise exception if value cannot be converted.
+ args[nArg++] = intConverter.ConvertToString(context, culture, size.Width);
+ args[nArg++] = intConverter.ConvertToString(context, culture, size.Height);
+
+ return string.Join(sep, args);
+ }
+ if (destinationType == typeof(InstanceDescriptor))
+ {
+ Size size = (Size)value;
+
+ ConstructorInfo ctor = typeof(Size).GetConstructor(new Type[] { typeof(int), typeof(int) });
+ if (ctor != null)
+ {
+ return new InstanceDescriptor(ctor, new object[] { size.Width, size.Height });
+ }
+ }
+ }
+
+ return base.ConvertTo(context, culture, value, destinationType);
+ }
+
+ ///
+ ///
+ /// Creates an instance of this type given a set of property values
+ /// for the object. This is useful for objects that are immutable, but still
+ /// want to provide changable properties.
+ ///
+ public override object CreateInstance(ITypeDescriptorContext context, IDictionary propertyValues)
+ {
+ if (propertyValues == null)
+ {
+ throw new ArgumentNullException("propertyValues");
+ }
+
+
+ object width = propertyValues["Width"];
+ object height = propertyValues["Height"];
+
+ if (width == null || height == null ||
+ !(width is int) || !(height is int))
+ {
+ throw new ArgumentException();
+ }
+ return new Size((int)width,
+ (int)height);
+ }
+
+ ///
+ ///
+ /// Determines if changing a value on this object should require a call to
+ /// CreateInstance to create a new value.
+ ///
+ public override bool GetCreateInstanceSupported(ITypeDescriptorContext context)
+ {
+ return true;
+ }
+
+ ///
+ ///
+ /// Retrieves the set of properties for this type. By default, a type has
+ /// does not return any properties. An easy implementation of this method
+ /// can just call TypeDescriptor.GetProperties for the correct data type.
+ ///
+ public override PropertyDescriptorCollection GetProperties(ITypeDescriptorContext context, object value, Attribute[] attributes)
+ {
+ PropertyDescriptorCollection props = TypeDescriptor.GetProperties(typeof(Size), attributes);
+ return props.Sort(new string[] { "Width", "Height" });
+ }
+
+
+ ///
+ ///
+ /// Determines if this object supports properties. By default, this
+ /// is false.
+ ///
+ public override bool GetPropertiesSupported(ITypeDescriptorContext context)
+ {
+ return true;
+ }
+ }
+
+}
+#endif
diff --git a/FastReport.Compat/src/shared/WindowsForms/ComboBox.ObjectCollection.cs b/FastReport.Compat/src/shared/WindowsForms/ComboBox.ObjectCollection.cs
new file mode 100644
index 00000000..52936b95
--- /dev/null
+++ b/FastReport.Compat/src/shared/WindowsForms/ComboBox.ObjectCollection.cs
@@ -0,0 +1,367 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace System.Windows.Forms
+{
+ public partial class ComboBox
+ {
+ public class ObjectCollection : IList
+ {
+ private readonly ComboBox owner;
+ private ArrayList innerList;
+ private IComparer comparer;
+
+ public ObjectCollection(ComboBox owner)
+ {
+ this.owner = owner;
+ }
+
+ private IComparer Comparer
+ {
+ get
+ {
+ if (comparer == null)
+ {
+ comparer = new ItemComparer(owner);
+ }
+ return comparer;
+ }
+ }
+
+ private ArrayList InnerList
+ {
+ get
+ {
+ if (innerList == null)
+ {
+ innerList = new ArrayList();
+ }
+ return innerList;
+ }
+ }
+
+ ///
+ /// Retrieves the number of items.
+ ///
+ public int Count
+ {
+ get
+ {
+ return InnerList.Count;
+ }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ return this;
+ }
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ bool IList.IsFixedSize
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Adds an item to the combo box. For an unsorted combo box, the item is
+ /// added to the end of the existing list of items. For a sorted combo box,
+ /// the item is inserted into the list according to its sorted position.
+ /// The item's toString() method is called to obtain the string that is
+ /// displayed in the combo box.
+ /// A SystemException occurs if there is insufficient space available to
+ /// store the new item.
+ ///
+ public int Add(object item)
+ {
+ int index = AddInternal(item);
+ return index;
+ }
+
+ private int AddInternal(object item)
+ {
+
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+ int index = -1;
+ if (!owner.sorted)
+ {
+ InnerList.Add(item);
+ }
+ else
+ {
+ index = InnerList.BinarySearch(item, Comparer);
+ if (index < 0)
+ {
+ index = ~index; // getting the index of the first element that is larger than the search value
+ }
+
+ InnerList.Insert(index, item);
+ }
+ bool successful = false;
+
+ try
+ {
+ if (owner.sorted)
+ {
+ }
+ else
+ {
+ index = InnerList.Count - 1;
+ }
+ successful = true;
+ }
+ finally
+ {
+ if (!successful)
+ {
+ InnerList.Remove(item);
+ }
+ }
+
+ return index;
+ }
+
+ int IList.Add(object item)
+ {
+ return Add(item);
+ }
+
+ ///
+ /// Retrieves the item with the specified index.
+ ///
+ [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public virtual object this[int index]
+ {
+ get
+ {
+ if (index < 0 || index >= InnerList.Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return InnerList[index];
+ }
+ set
+ {
+ SetItemInternal(index, value);
+ }
+ }
+
+ public void AddRange(object[] items)
+ {
+ try
+ {
+ AddRangeInternal(items);
+ }
+ finally
+ {
+ }
+ }
+
+ internal void AddRangeInternal(IList items)
+ {
+
+ if (items == null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+ foreach (object item in items)
+ {
+ // adding items one-by-one for performance (especially for sorted combobox)
+ // we can not rely on ArrayList.Sort since its worst case complexity is n*n
+ // AddInternal is based on BinarySearch and ensures n*log(n) complexity
+ AddInternal(item);
+ }
+ }
+
+ ///
+ /// Removes all items from the ComboBox.
+ ///
+ public void Clear()
+ {
+ ClearInternal();
+ }
+
+ internal void ClearInternal()
+ {
+
+ InnerList.Clear();
+ owner.selectedIndex = -1;
+ }
+
+ public bool Contains(object value)
+ {
+ return IndexOf(value) != -1;
+ }
+
+ ///
+ /// Copies the ComboBox Items collection to a destination array.
+ ///
+ public void CopyTo(object[] destination, int arrayIndex)
+ {
+ InnerList.CopyTo(destination, arrayIndex);
+ }
+
+ void ICollection.CopyTo(Array destination, int index)
+ {
+ InnerList.CopyTo(destination, index);
+ }
+
+ ///
+ /// Returns an enumerator for the ComboBox Items collection.
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return InnerList.GetEnumerator();
+ }
+
+ public int IndexOf(object value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ return InnerList.IndexOf(value);
+ }
+
+ ///
+ /// Adds an item to the combo box. For an unsorted combo box, the item is
+ /// added to the end of the existing list of items. For a sorted combo box,
+ /// the item is inserted into the list according to its sorted position.
+ /// The item's toString() method is called to obtain the string that is
+ /// displayed in the combo box.
+ /// A SystemException occurs if there is insufficient space available to
+ /// store the new item.
+ ///
+ public void Insert(int index, object item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ if (index < 0 || index > InnerList.Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ // If the combo box is sorted, then nust treat this like an add
+ // because we are going to twiddle the index anyway.
+ //
+ if (owner.sorted)
+ {
+ Add(item);
+ }
+ else
+ {
+ InnerList.Insert(index, item);
+ }
+ }
+
+ ///
+ /// Removes an item from the ComboBox at the given index.
+ ///
+ public void RemoveAt(int index)
+ {
+ if (index < 0 || index >= InnerList.Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ InnerList.RemoveAt(index);
+ if (index < owner.selectedIndex)
+ {
+ owner.selectedIndex--;
+ }
+ }
+
+ ///
+ /// Removes the given item from the ComboBox, provided that it is
+ /// actually in the list.
+ ///
+ public void Remove(object value)
+ {
+
+ int index = InnerList.IndexOf(value);
+
+ if (index != -1)
+ {
+ RemoveAt(index);
+ }
+ }
+
+ internal void SetItemInternal(int index, object value)
+ {
+ if (index < 0 || index >= InnerList.Count)
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ InnerList[index] = value ?? throw new ArgumentNullException(nameof(value));
+ }
+ }
+
+ private sealed class ItemComparer : IComparer
+ {
+ private readonly ComboBox comboBox;
+
+ public ItemComparer(ComboBox comboBox)
+ {
+ this.comboBox = comboBox;
+ }
+
+ public int Compare(object item1, object item2)
+ {
+ if (item1 == null)
+ {
+ if (item2 == null)
+ {
+ return 0; //both null, then they are equal
+ }
+
+ return -1; //item1 is null, but item2 is valid (greater)
+ }
+ if (item2 == null)
+ {
+ return 1; //item2 is null, so item 1 is greater
+ }
+
+ string itemName1 = comboBox.GetItemText(item1);
+ string itemName2 = comboBox.GetItemText(item2);
+
+ CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo;
+ return compInfo.Compare(itemName1, itemName2, CompareOptions.StringSort);
+ }
+ }
+ }
+}
diff --git a/FastReport.Compat/src/shared/WindowsForms/ItemArray.cs b/FastReport.Compat/src/shared/WindowsForms/ItemArray.cs
new file mode 100644
index 00000000..97d0f77b
--- /dev/null
+++ b/FastReport.Compat/src/shared/WindowsForms/ItemArray.cs
@@ -0,0 +1,502 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace System.Windows.Forms
+{
+ internal class ItemArray : IComparer
+ {
+ private static int lastMask = 1;
+
+ private readonly ListControl listControl;
+ private Entry[] entries;
+ private int count;
+ private int version;
+
+ public ItemArray(ListControl listControl)
+ {
+ this.listControl = listControl;
+ }
+
+ internal IReadOnlyList Entries => entries;
+
+ ///
+ /// The version of this array. This number changes with each
+ /// change to the item list.
+ ///
+ public int Version
+ {
+ get
+ {
+ return version;
+ }
+ }
+
+ ///
+ /// Adds the given item to the array. The state is initially
+ /// zero.
+ ///
+ public object Add(object item)
+ {
+ EnsureSpace(1);
+ version++;
+ entries[count] = new Entry(item);
+ return entries[count++];
+ }
+
+ ///
+ /// Adds the given collection of items to the array.
+ ///
+ public void AddRange(ICollection items)
+ {
+ if (items == null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+ EnsureSpace(items.Count);
+ foreach (object i in items)
+ {
+ entries[count++] = new Entry(i);
+ }
+ version++;
+ }
+
+ ///
+ /// Clears this array.
+ ///
+ public void Clear()
+ {
+ if (count > 0)
+ {
+ Array.Clear(entries, 0, count);
+ }
+
+ count = 0;
+ version++;
+ }
+
+ ///
+ /// Allocates a new bitmask for use.
+ ///
+ public static int CreateMask()
+ {
+ int mask = lastMask;
+ lastMask <<= 1;
+ return mask;
+ }
+
+ ///
+ /// Ensures that our internal array has space for
+ /// the requested # of elements.
+ ///
+ private void EnsureSpace(int elements)
+ {
+ if (entries == null)
+ {
+ entries = new Entry[Math.Max(elements, 4)];
+ }
+ else if (count + elements >= entries.Length)
+ {
+ int newLength = Math.Max(entries.Length * 2, entries.Length + elements);
+ Entry[] newEntries = new Entry[newLength];
+ entries.CopyTo(newEntries, 0);
+ entries = newEntries;
+ }
+ }
+
+ ///
+ /// Turns a virtual index into an actual index.
+ ///
+ public int GetActualIndex(int virtualIndex, int stateMask)
+ {
+ if (stateMask == 0)
+ {
+ return virtualIndex;
+ }
+
+ // More complex; we must compute this index.
+ int calcIndex = -1;
+ for (int i = 0; i < count; i++)
+ {
+ if ((entries[i].state & stateMask) != 0)
+ {
+ calcIndex++;
+ if (calcIndex == virtualIndex)
+ {
+ return i;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Gets the count of items matching the given mask.
+ ///
+ public int GetCount(int stateMask)
+ {
+ // If mask is zero, then just give the main count
+ if (stateMask == 0)
+ {
+ return count;
+ }
+
+ // more complex: must provide a count of items
+ // based on a mask.
+
+ int filteredCount = 0;
+
+ for (int i = 0; i < count; i++)
+ {
+ if ((entries[i].state & stateMask) != 0)
+ {
+ filteredCount++;
+ }
+ }
+
+ return filteredCount;
+ }
+
+ ///
+ /// Retrieves an enumerator that will enumerate based on
+ /// the given mask.
+ ///
+ public IEnumerator GetEnumerator(int stateMask)
+ {
+ return GetEnumerator(stateMask, false);
+ }
+
+ ///
+ /// Retrieves an enumerator that will enumerate based on
+ /// the given mask.
+ ///
+ public IEnumerator GetEnumerator(int stateMask, bool anyBit)
+ {
+ return new EntryEnumerator(this, stateMask, anyBit);
+ }
+
+ ///
+ /// Gets the item at the given index. The index is
+ /// virtualized against the given mask value.
+ ///
+ public object GetItem(int virtualIndex, int stateMask)
+ {
+ int actualIndex = GetActualIndex(virtualIndex, stateMask);
+
+ if (actualIndex == -1)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ return entries[actualIndex].item;
+ }
+ ///
+ /// Gets the item at the given index. The index is
+ /// virtualized against the given mask value.
+ ///
+ internal object GetEntryObject(int virtualIndex, int stateMask)
+ {
+ int actualIndex = GetActualIndex(virtualIndex, stateMask);
+
+ if (actualIndex == -1)
+ {
+ throw new IndexOutOfRangeException();
+ }
+
+ return entries[actualIndex];
+ }
+ ///
+ /// Returns true if the requested state mask is set.
+ /// The index is the actual index to the array.
+ ///
+ public bool GetState(int index, int stateMask)
+ {
+ return ((entries[index].state & stateMask) == stateMask);
+ }
+
+ ///
+ /// Returns the virtual index of the item based on the
+ /// state mask.
+ ///
+ public int IndexOf(object item, int stateMask)
+ {
+
+ int virtualIndex = -1;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (stateMask == 0 || (entries[i].state & stateMask) != 0)
+ {
+ virtualIndex++;
+ if (entries[i].item.Equals(item))
+ {
+ return virtualIndex;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Returns the virtual index of the item based on the
+ /// state mask. Uses reference equality to identify the
+ /// given object in the list.
+ ///
+ public int IndexOfIdentifier(object identifier, int stateMask)
+ {
+ int virtualIndex = -1;
+
+ for (int i = 0; i < count; i++)
+ {
+ if (stateMask == 0 || (entries[i].state & stateMask) != 0)
+ {
+ virtualIndex++;
+ if (entries[i] == identifier)
+ {
+ return virtualIndex;
+ }
+ }
+ }
+
+ return -1;
+ }
+
+ ///
+ /// Inserts item at the given index. The index
+ /// is not virtualized.
+ ///
+ public void Insert(int index, object item)
+ {
+ EnsureSpace(1);
+
+ if (index < count)
+ {
+ System.Array.Copy(entries, index, entries, index + 1, count - index);
+ }
+
+ entries[index] = new Entry(item);
+ count++;
+ version++;
+ }
+
+ ///
+ /// Removes the given item from the array. If
+ /// the item is not in the array, this does nothing.
+ ///
+ public void Remove(object item)
+ {
+ int index = IndexOf(item, 0);
+
+ if (index != -1)
+ {
+ RemoveAt(index);
+ }
+ }
+
+ ///
+ /// Removes the item at the given index.
+ ///
+ public void RemoveAt(int index)
+ {
+ count--;
+ for (int i = index; i < count; i++)
+ {
+ entries[i] = entries[i + 1];
+ }
+ entries[count] = null;
+ version++;
+ }
+
+ ///
+ /// Sets the item at the given index to a new value.
+ ///
+ public void SetItem(int index, object item)
+ {
+ entries[index].item = item;
+ }
+
+ ///
+ /// Sets the state data for the given index.
+ ///
+ public void SetState(int index, int stateMask, bool value)
+ {
+ if (value)
+ {
+ entries[index].state |= stateMask;
+ }
+ else
+ {
+ entries[index].state &= ~stateMask;
+ }
+ version++;
+ }
+
+ ///
+ /// Find element in sorted array. If element is not found returns a binary complement of index for inserting
+ ///
+ public int BinarySearch(object element)
+ {
+ return Array.BinarySearch(entries, 0, count, element, this);
+ }
+
+ ///
+ /// Sorts our array.
+ ///
+ public void Sort()
+ {
+ Array.Sort(entries, 0, count, this);
+ }
+
+ public void Sort(Array externalArray)
+ {
+ Array.Sort(externalArray, this);
+ }
+
+ int IComparer.Compare(object item1, object item2)
+ {
+ if (item1 == null)
+ {
+ if (item2 == null)
+ {
+ return 0; //both null, then they are equal
+ }
+
+ return -1; //item1 is null, but item2 is valid (greater)
+ }
+ if (item2 == null)
+ {
+ return 1; //item2 is null, so item 1 is greater
+ }
+
+ if (item1 is Entry)
+ {
+ item1 = ((Entry)item1).item;
+ }
+
+ if (item2 is Entry)
+ {
+ item2 = ((Entry)item2).item;
+ }
+
+ string itemName1 = listControl.GetItemText(item1);
+ string itemName2 = listControl.GetItemText(item2);
+
+ CompareInfo compInfo = CultureInfo.CurrentCulture.CompareInfo;
+ return compInfo.Compare(itemName1, itemName2, CompareOptions.StringSort);
+ }
+
+ ///
+ /// This is a single entry in our item array.
+ ///
+ internal class Entry
+ {
+ public object item;
+ public int state;
+
+ public Entry(object item)
+ {
+ this.item = item;
+ state = 0;
+ }
+ }
+
+ ///
+ /// EntryEnumerator is an enumerator that will enumerate over
+ /// a given state mask.
+ ///
+ private class EntryEnumerator : IEnumerator
+ {
+ private readonly ItemArray items;
+ private readonly bool anyBit;
+ private readonly int state;
+ private int current;
+ private readonly int version;
+
+ ///
+ /// Creates a new enumerator that will enumerate over the given state.
+ ///
+ public EntryEnumerator(ItemArray items, int state, bool anyBit)
+ {
+ this.items = items;
+ this.state = state;
+ this.anyBit = anyBit;
+ version = items.version;
+ current = -1;
+ }
+
+ ///
+ /// Moves to the next element, or returns false if at the end.
+ ///
+ bool IEnumerator.MoveNext()
+ {
+ if (version != items.version)
+ {
+ throw new InvalidOperationException();
+ }
+
+ while (true)
+ {
+ if (current < items.count - 1)
+ {
+ current++;
+ if (anyBit)
+ {
+ if ((items.entries[current].state & state) != 0)
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if ((items.entries[current].state & state) == state)
+ {
+ return true;
+ }
+ }
+ }
+ else
+ {
+ current = items.count;
+ return false;
+ }
+ }
+ }
+
+ ///
+ /// Resets the enumeration back to the beginning.
+ ///
+ void IEnumerator.Reset()
+ {
+ if (version != items.version)
+ {
+ throw new InvalidOperationException();
+ }
+
+ current = -1;
+ }
+
+ ///
+ /// Retrieves the current value in the enumerator.
+ ///
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (current == -1 || current == items.count)
+ {
+ throw new InvalidOperationException();
+ }
+
+ return items.entries[current].item;
+ }
+ }
+ }
+ }
+}
diff --git a/FastReport.Compat/src/shared/WindowsForms/ListBox.ObjectCollection.cs b/FastReport.Compat/src/shared/WindowsForms/ListBox.ObjectCollection.cs
new file mode 100644
index 00000000..fc1fff8d
--- /dev/null
+++ b/FastReport.Compat/src/shared/WindowsForms/ListBox.ObjectCollection.cs
@@ -0,0 +1,375 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.Collections.Generic;
+using System.ComponentModel;
+using System.Globalization;
+
+namespace System.Windows.Forms
+{
+ public partial class ListBox
+ {
+ public class ObjectCollection : IList
+ {
+ private readonly ListBox owner;
+ private ItemArray items;
+
+ public ObjectCollection(ListBox owner)
+ {
+ this.owner = owner;
+ }
+
+ ///
+ /// Initializes a new instance of ListBox.ObjectCollection based on another ListBox.ObjectCollection.
+ ///
+ public ObjectCollection(ListBox owner, ObjectCollection value)
+ {
+ this.owner = owner;
+ AddRange(value);
+ }
+
+ ///
+ /// Initializes a new instance of ListBox.ObjectCollection containing any array of objects.
+ ///
+ public ObjectCollection(ListBox owner, object[] value)
+ {
+ this.owner = owner;
+ AddRange(value);
+ }
+
+ ///
+ /// Retrieves the number of items.
+ ///
+ public int Count
+ {
+ get
+ {
+ return InnerArray.GetCount(0);
+ }
+ }
+
+ ///
+ /// Internal access to the actual data store.
+ ///
+ internal ItemArray InnerArray
+ {
+ get
+ {
+ if (items == null)
+ {
+ items = new ItemArray(owner);
+ }
+ return items;
+ }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ return this;
+ }
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ bool IList.IsFixedSize
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ ///
+ /// Adds an item to the List box. For an unsorted List box, the item is
+ /// added to the end of the existing list of items. For a sorted List box,
+ /// the item is inserted into the list according to its sorted position.
+ /// The item's toString() method is called to obtain the string that is
+ /// displayed in the List box.
+ /// A SystemException occurs if there is insufficient space available to
+ /// store the new item.
+ ///
+ public int Add(object item)
+ {
+ int index = AddInternal(item);
+ return index;
+ }
+
+ private int AddInternal(object item)
+ {
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+ int index = -1;
+ if (!owner.sorted)
+ {
+ InnerArray.Add(item);
+ }
+ else
+ {
+ if (Count > 0)
+ {
+ index = InnerArray.BinarySearch(item);
+ if (index < 0)
+ {
+ index = ~index; // getting the index of the first element that is larger than the search value
+ //this index will be used for insert
+ }
+ }
+ else
+ {
+ index = 0;
+ }
+
+ InnerArray.Insert(index, item);
+ }
+ bool successful = false;
+
+ try
+ {
+ if (owner.sorted)
+ {
+ }
+ else
+ {
+ index = Count - 1;
+ }
+ successful = true;
+ }
+ finally
+ {
+ if (!successful)
+ {
+ InnerArray.Remove(item);
+ }
+ }
+
+ return index;
+ }
+
+ int IList.Add(object item)
+ {
+ return Add(item);
+ }
+
+ public void AddRange(ObjectCollection value)
+ {
+ AddRangeInternal((ICollection)value);
+ }
+
+ public void AddRange(object[] items)
+ {
+ AddRangeInternal((ICollection)items);
+ }
+
+ internal void AddRangeInternal(ICollection items)
+ {
+ if (items == null)
+ {
+ throw new ArgumentNullException(nameof(items));
+ }
+ try
+ {
+ foreach (object item in items)
+ {
+ // adding items one-by-one for performance
+ // not using sort because after the array is sorted index of each newly added item will need to be found
+ // AddInternal is based on BinarySearch and finds index without any additional cost
+ AddInternal(item);
+ }
+ }
+ finally
+ {
+ }
+ }
+
+ ///
+ /// Retrieves the item with the specified index.
+ ///
+ [Browsable(false), DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public virtual object this[int index]
+ {
+ get
+ {
+ if (index < 0 || index >= InnerArray.GetCount(0))
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ return InnerArray.GetItem(index, 0);
+ }
+ set
+ {
+ SetItemInternal(index, value);
+ }
+ }
+
+ ///
+ /// Removes all items from the ListBox.
+ ///
+ public virtual void Clear()
+ {
+ ClearInternal();
+ }
+
+ ///
+ /// Removes all items from the ListBox. Bypasses the data source check.
+ ///
+ internal void ClearInternal()
+ {
+ InnerArray.Clear();
+ }
+
+ public bool Contains(object value)
+ {
+ return IndexOf(value) != -1;
+ }
+
+ ///
+ /// Copies the ListBox Items collection to a destination array.
+ ///
+ public void CopyTo(object[] destination, int arrayIndex)
+ {
+ int count = InnerArray.GetCount(0);
+ for (int i = 0; i < count; i++)
+ {
+ destination[i + arrayIndex] = InnerArray.GetItem(i, 0);
+ }
+ }
+
+ void ICollection.CopyTo(Array destination, int index)
+ {
+ int count = InnerArray.GetCount(0);
+ for (int i = 0; i < count; i++)
+ {
+ destination.SetValue(InnerArray.GetItem(i, 0), i + index);
+ }
+ }
+
+ ///
+ /// Returns an enumerator for the ListBox Items collection.
+ ///
+ public IEnumerator GetEnumerator()
+ {
+ return InnerArray.GetEnumerator(0);
+ }
+
+ public int IndexOf(object value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ return InnerArray.IndexOf(value, 0);
+ }
+
+ internal int IndexOfIdentifier(object value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ return InnerArray.IndexOfIdentifier(value, 0);
+ }
+
+ ///
+ /// Adds an item to the List box. For an unsorted List box, the item is
+ /// added to the end of the existing list of items. For a sorted List box,
+ /// the item is inserted into the list according to its sorted position.
+ /// The item's toString() method is called to obtain the string that is
+ /// displayed in the List box.
+ /// A SystemException occurs if there is insufficient space available to
+ /// store the new item.
+ ///
+ public void Insert(int index, object item)
+ {
+ if (index < 0 || index > InnerArray.GetCount(0))
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ if (item == null)
+ {
+ throw new ArgumentNullException(nameof(item));
+ }
+
+ // If the List box is sorted, then nust treat this like an add
+ // because we are going to twiddle the index anyway.
+ //
+ if (owner.sorted)
+ {
+ Add(item);
+ }
+ else
+ {
+ InnerArray.Insert(index, item);
+ }
+ }
+
+ ///
+ /// Removes the given item from the ListBox, provided that it is
+ /// actually in the list.
+ ///
+ public void Remove(object value)
+ {
+
+ int index = InnerArray.IndexOf(value, 0);
+
+ if (index != -1)
+ {
+ RemoveAt(index);
+ }
+ }
+
+ ///
+ /// Removes an item from the ListBox at the given index.
+ ///
+ public void RemoveAt(int index)
+ {
+ if (index < 0 || index >= InnerArray.GetCount(0))
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ // Update InnerArray before calling NativeRemoveAt to ensure that when
+ // SelectedIndexChanged is raised (by NativeRemoveAt), InnerArray's state matches wrapped LB state.
+ InnerArray.RemoveAt(index);
+ }
+
+ internal void SetItemInternal(int index, object value)
+ {
+ if (value == null)
+ {
+ throw new ArgumentNullException(nameof(value));
+ }
+
+ if (index < 0 || index >= InnerArray.GetCount(0))
+ {
+ throw new ArgumentOutOfRangeException();
+ }
+
+ InnerArray.SetItem(index, value);
+ }
+ } // end ObjectCollection}
+ }
+}
diff --git a/FastReport.Compat/src/shared/WindowsForms/ListBox.SelectedIndexCollection.cs b/FastReport.Compat/src/shared/WindowsForms/ListBox.SelectedIndexCollection.cs
new file mode 100644
index 00000000..9db89d3f
--- /dev/null
+++ b/FastReport.Compat/src/shared/WindowsForms/ListBox.SelectedIndexCollection.cs
@@ -0,0 +1,285 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.ComponentModel;
+
+namespace System.Windows.Forms
+{
+ public partial class ListBox
+ {
+ public class SelectedIndexCollection : IList
+ {
+ private readonly ListBox _owner;
+
+ public SelectedIndexCollection(ListBox owner)
+ {
+ _owner = owner ?? throw new ArgumentNullException(nameof(owner));
+ }
+
+ ///
+ /// Number of current selected items.
+ ///
+ [Browsable(false)]
+ public int Count
+ {
+ get
+ {
+ return _owner.SelectedItems.Count;
+ }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ return this;
+ }
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ bool IList.IsFixedSize
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public bool Contains(int selectedIndex)
+ {
+ return IndexOf(selectedIndex) != -1;
+ }
+
+ bool IList.Contains(object selectedIndex)
+ {
+ if (selectedIndex is int)
+ {
+ return Contains((int)selectedIndex);
+ }
+ else
+ {
+ return false;
+ }
+ }
+
+ public int IndexOf(int selectedIndex)
+ {
+ // Just what does this do? The selectedIndex parameter above is the index into the
+ // main object collection. We look at the state of that item, and if the state indicates
+ // that it is selected, we get back the virtualized index into this collection. Indexes on
+ // this collection match those on the SelectedObjectCollection.
+ if (selectedIndex >= 0 &&
+ selectedIndex < InnerArray.GetCount(0) &&
+ InnerArray.GetState(selectedIndex, SelectedObjectCollection.SelectedObjectMask))
+ {
+ return InnerArray.IndexOf(InnerArray.GetItem(selectedIndex, 0), SelectedObjectCollection.SelectedObjectMask);
+ }
+
+ return -1;
+ }
+
+ int IList.IndexOf(object selectedIndex)
+ {
+ if (selectedIndex is int)
+ {
+ return IndexOf((int)selectedIndex);
+ }
+ else
+ {
+ return -1;
+ }
+ }
+
+ int IList.Add(object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Remove(object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ throw new NotSupportedException();
+ }
+
+ ///
+ /// Retrieves the specified selected item.
+ ///
+ public int this[int index]
+ {
+ get
+ {
+ object identifier = InnerArray.GetEntryObject(index, SelectedObjectCollection.SelectedObjectMask);
+ return InnerArray.IndexOfIdentifier(identifier, 0);
+ }
+ }
+
+ object IList.this[int index]
+ {
+ get
+ {
+ return this[index];
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ ///
+ /// This is the item array that stores our data. We share this backing store
+ /// with the main object collection.
+ ///
+ private ItemArray InnerArray
+ {
+ get
+ {
+ _owner.SelectedItems.EnsureUpToDate();
+ return ((ObjectCollection)_owner.Items).InnerArray;
+ }
+ }
+
+ public void CopyTo(Array destination, int index)
+ {
+ int cnt = Count;
+ for (int i = 0; i < cnt; i++)
+ {
+ destination.SetValue(this[i], i + index);
+ }
+ }
+
+ public void Clear()
+ {
+ if (_owner != null)
+ {
+ _owner.ClearSelected();
+ }
+ }
+
+ public void Add(int index)
+ {
+ if (_owner != null)
+ {
+ ObjectCollection items = _owner.Items;
+ if (items != null)
+ {
+ if (index != -1 && !Contains(index))
+ {
+ _owner.SetSelected(index, true);
+ }
+ }
+ }
+ }
+
+ public void Remove(int index)
+ {
+ if (_owner != null)
+ {
+ ObjectCollection items = _owner.Items;
+ if (items != null)
+ {
+ if (index != -1 && Contains(index))
+ {
+ _owner.SetSelected(index, false);
+ }
+ }
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return new SelectedIndexEnumerator(this);
+ }
+
+ ///
+ /// EntryEnumerator is an enumerator that will enumerate over
+ /// a given state mask.
+ ///
+ private class SelectedIndexEnumerator : IEnumerator
+ {
+ private readonly SelectedIndexCollection items;
+ private int current;
+
+ ///
+ /// Creates a new enumerator that will enumerate over the given state.
+ ///
+ public SelectedIndexEnumerator(SelectedIndexCollection items)
+ {
+ this.items = items;
+ current = -1;
+ }
+
+ ///
+ /// Moves to the next element, or returns false if at the end.
+ ///
+ bool IEnumerator.MoveNext()
+ {
+ if (current < items.Count - 1)
+ {
+ current++;
+ return true;
+ }
+ else
+ {
+ current = items.Count;
+ return false;
+ }
+ }
+
+ ///
+ /// Resets the enumeration back to the beginning.
+ ///
+ void IEnumerator.Reset()
+ {
+ current = -1;
+ }
+
+ ///
+ /// Retrieves the current value in the enumerator.
+ ///
+ object IEnumerator.Current
+ {
+ get
+ {
+ if (current == -1 || current == items.Count)
+ {
+ throw new InvalidOperationException();
+ }
+
+ return items[current];
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/FastReport.Compat/src/shared/WindowsForms/ListBox.SelectedObjectCollection.cs b/FastReport.Compat/src/shared/WindowsForms/ListBox.SelectedObjectCollection.cs
new file mode 100644
index 00000000..7e7401aa
--- /dev/null
+++ b/FastReport.Compat/src/shared/WindowsForms/ListBox.SelectedObjectCollection.cs
@@ -0,0 +1,250 @@
+// Licensed to the .NET Foundation under one or more agreements.
+// The .NET Foundation licenses this file to you under the MIT license.
+// See the LICENSE file in the project root for more information.
+
+using System.Collections;
+using System.ComponentModel;
+
+namespace System.Windows.Forms
+{
+ public partial class ListBox
+ {
+ // Should be "ObjectCollection", except we already have one of those.
+ public class SelectedObjectCollection : IList
+ {
+ // This is the bitmask used within ItemArray to identify selected objects.
+ internal static int SelectedObjectMask = ItemArray.CreateMask();
+
+ private readonly ListBox _owner;
+ private bool stateDirty;
+ private int lastVersion;
+ private int count;
+
+ public SelectedObjectCollection(ListBox owner)
+ {
+ _owner = owner ?? throw new ArgumentNullException(nameof(owner));
+ stateDirty = true;
+ lastVersion = -1;
+ }
+
+ ///
+ /// Number of current selected items.
+ ///
+ public int Count
+ {
+ get
+ {
+ // If the handle hasn't been created, we must do this the hard way.
+ // Getting the count when using a mask is expensive, so cache it.
+ //
+ if (lastVersion != InnerArray.Version)
+ {
+ lastVersion = InnerArray.Version;
+ count = InnerArray.GetCount(SelectedObjectMask);
+ }
+
+ return count;
+ }
+ }
+
+ object ICollection.SyncRoot
+ {
+ get
+ {
+ return this;
+ }
+ }
+
+ bool ICollection.IsSynchronized
+ {
+ get
+ {
+ return false;
+ }
+ }
+
+ bool IList.IsFixedSize
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ ///
+ /// Called by the list box to dirty the selected item state.
+ ///
+ internal void Dirty()
+ {
+ stateDirty = true;
+ }
+
+ ///
+ /// This is the item array that stores our data. We share this backing store
+ /// with the main object collection.
+ ///
+ private ItemArray InnerArray
+ {
+ get
+ {
+ EnsureUpToDate();
+ return ((ObjectCollection)_owner.Items).InnerArray;
+ }
+ }
+
+ ///
+ /// This is the function that Ensures that the selections are uptodate with
+ /// current listbox handle selections.
+ ///
+ internal void EnsureUpToDate()
+ {
+ if (stateDirty)
+ {
+ stateDirty = false;
+ }
+ }
+
+ public bool IsReadOnly
+ {
+ get
+ {
+ return true;
+ }
+ }
+
+ public bool Contains(object selectedObject)
+ {
+ return IndexOf(selectedObject) != -1;
+ }
+
+ public int IndexOf(object selectedObject)
+ {
+ return InnerArray.IndexOf(selectedObject, SelectedObjectMask);
+ }
+
+ int IList.Add(object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Clear()
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Insert(int index, object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.Remove(object value)
+ {
+ throw new NotSupportedException();
+ }
+
+ void IList.RemoveAt(int index)
+ {
+ throw new NotSupportedException();
+ }
+
+ // A new internal method used in SelectedIndex getter...
+ // For a Multi select ListBox there can be two items with the same name ...
+ // and hence a object comparison is required...
+ // This method returns the "object" at the passed index rather than the "item" ...
+ // this "object" is then compared in the IndexOf( ) method of the itemsCollection.
+ //
+ internal object GetObjectAt(int index)
+ {
+ return InnerArray.GetEntryObject(index, SelectedObjectCollection.SelectedObjectMask);
+ }
+
+ ///
+ /// Retrieves the specified selected item.
+ ///
+ [Browsable(false)]
+ [DesignerSerializationVisibility(DesignerSerializationVisibility.Hidden)]
+ public object this[int index]
+ {
+ get
+ {
+ return InnerArray.GetItem(index, SelectedObjectMask);
+ }
+ set
+ {
+ throw new NotSupportedException();
+ }
+ }
+
+ public void CopyTo(Array destination, int index)
+ {
+ int cnt = InnerArray.GetCount(SelectedObjectMask);
+ for (int i = 0; i < cnt; i++)
+ {
+ destination.SetValue(InnerArray.GetItem(i, SelectedObjectMask), i + index);
+ }
+ }
+
+ public IEnumerator GetEnumerator()
+ {
+ return InnerArray.GetEnumerator(SelectedObjectMask);
+ }
+
+ ///
+ /// This method returns if the actual item index is selected. The index is the index to the MAIN
+ /// collection, not this one.
+ ///
+ internal bool GetSelected(int index)
+ {
+ return InnerArray.GetState(index, SelectedObjectMask);
+ }
+
+ ///
+ /// Same thing for GetSelected.
+ ///
+ internal void SetSelected(int index, bool value)
+ {
+ InnerArray.SetState(index, SelectedObjectMask, value);
+ }
+
+ public void Clear()
+ {
+ if (_owner != null)
+ {
+ _owner.ClearSelected();
+ }
+ }
+
+ public void Add(object value)
+ {
+ if (_owner != null)
+ {
+ ObjectCollection items = _owner.Items;
+ if (items != null && value != null)
+ {
+ int index = items.IndexOf(value);
+ if (index != -1 && !GetSelected(index))
+ {
+ _owner.SelectedIndex = index;
+ }
+ }
+ }
+ }
+
+ public void Remove(object value)
+ {
+ if (_owner != null)
+ {
+ ObjectCollection items = _owner.Items;
+ if (items != null & value != null)
+ {
+ int index = items.IndexOf(value);
+ if (index != -1 && GetSelected(index))
+ {
+ _owner.SetSelected(index, false);
+ }
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/FastReport.Compat/src/shared/WindowsForms/WindowsFormsReplacement.BindingSource.cs b/FastReport.Compat/src/shared/WindowsForms/WindowsFormsReplacement.BindingSource.cs
new file mode 100644
index 00000000..4d005ae3
--- /dev/null
+++ b/FastReport.Compat/src/shared/WindowsForms/WindowsFormsReplacement.BindingSource.cs
@@ -0,0 +1,174 @@
+// Permission is hereby granted, free of charge, to any person obtaining
+// a copy of this software and associated documentation files (the
+// "Software"), to deal in the Software without restriction, including
+// without limitation the rights to use, copy, modify, merge, publish,
+// distribute, sublicense, and/or sell copies of the Software, and to
+// permit persons to whom the Software is furnished to do so, subject to
+// the following conditions:
+//
+// The above copyright notice and this permission notice shall be
+// included in all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND,
+// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
+// MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
+// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE
+// LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION
+// OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
+// WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
+//
+// Copyright (c) 2007 Novell, Inc.
+//
+
+// Modified by: Alexander Tzyganenko
+
+using System;
+using System.ComponentModel;
+using System.Collections;
+
+namespace System.Windows.Forms
+{
+ public class BindingSource : Component, IList, ICollection, IEnumerable
+ {
+ IList list;
+ object datasource;
+
+ public object DataSource
+ {
+ get { return datasource; }
+ set
+ {
+ if (datasource != value)
+ {
+ datasource = value;
+ ResetList();
+ }
+ }
+ }
+
+ public bool IsFixedSize => list.IsFixedSize;
+
+ public bool IsReadOnly => list.IsReadOnly;
+
+ public int Count => list.Count;
+
+ public bool IsSynchronized => list.IsSynchronized;
+
+ public object SyncRoot => list.SyncRoot;
+
+ public object this[int index] { get => list[index]; set => list[index] = value; }
+
+ IList GetListFromEnumerable(IEnumerable enumerable)
+ {
+ IList l;
+
+ IEnumerator e = enumerable.GetEnumerator();
+
+ if (enumerable is string)
+ {
+ /* special case this.. seems to be the only one .net special cases? */
+ l = new BindingList();
+ }
+ else
+ {
+ /* try to figure out the type based on
+ * the first element, if there is
+ * one */
+ object first = null;
+ if (e.MoveNext())
+ {
+ first = e.Current;
+ }
+
+ if (first == null)
+ {
+ return null;
+ }
+ else
+ {
+ Type t = typeof(BindingList<>).MakeGenericType(new Type[] { first.GetType() });
+ l = (IList)Activator.CreateInstance(t);
+ }
+ }
+
+ e.Reset();
+ while (e.MoveNext())
+ {
+ l.Add(e.Current);
+ }
+
+ return l;
+ }
+
+ void ResetList()
+ {
+ IList l;
+ object source = ListBindingHelper.GetList(datasource, null);
+
+ //
+ // If original source is null, then create a new object list
+ // Otherwise, try to infer the list item type
+ //
+
+ if (datasource == null)
+ {
+ l = new BindingList