From d4e613ccb0373814708d342dac5560375ddbf0ce Mon Sep 17 00:00:00 2001 From: Fast Reports Date: Wed, 10 Mar 2021 14:40:29 +0300 Subject: [PATCH 1/2] * sync 3/10/2021 --- .../ADO/Adapters/ClickHouseDataAdapter.cs | 11 + .../ADO/ClickHouseCommand.cs | 132 + .../ADO/ClickHouseConnection.cs | 325 ++ .../ADO/ClickHouseConnectionFactory.cs | 19 + .../ADO/ClickHouseConnectionStringBuilder.cs | 87 + .../ADO/Parameters/ClickHouseDbParameter.cs | 30 + .../ClickHouseParameterCollection.cs | 68 + .../ADO/Readers/ClickHouseDataReader.cs | 181 + .../ADO/Readers/ClickHouseRawResult.cs | 27 + .../ClickHouse.Client.csproj | 59 + .../ClickHouseServerException.cs | 67 + .../ClickHouse.Client/ClickHouseUriBuilder.cs | 52 + .../Copy/ClickHouseBulkCopy.cs | 168 + .../Formats/BinaryStreamReader.cs | 127 + .../Formats/BinaryStreamWriter.cs | 206 + .../Formats/ExtendedBinaryReader.cs | 43 + .../Formats/ExtendedBinaryWriter.cs | 13 + .../Formats/HttpParameterFormatter.cs | 87 + .../Formats/InlineParameterFormatter.cs | 123 + .../Formats/PeekableStreamWrapper.cs | 79 + .../ClickHouse.Client/GlobalSuppressions.cs | 6 + .../ClickHouse.Client/IClickHouseCommand.cs | 15 + .../IClickHouseConnection.cs | 10 + .../Properties/Resources.Designer.cs | 108 + .../Properties/Resources.resx | 135 + .../Types/AbstractDateTimeType.cs | 53 + .../ClickHouse.Client/Types/ArrayType.cs | 32 + .../ClickHouse.Client/Types/ClickHouseType.cs | 17 + .../Types/ClickHouseTypeCode.cs | 43 + .../ClickHouse.Client/Types/DateTime64Type.cs | 38 + .../ClickHouse.Client/Types/DateTimeType.cs | 23 + .../ClickHouse.Client/Types/DateType.cs | 15 + .../ClickHouse.Client/Types/Decimal128Type.cs | 27 + .../ClickHouse.Client/Types/Decimal32Type.cs | 27 + .../ClickHouse.Client/Types/Decimal64Type.cs | 24 + .../ClickHouse.Client/Types/DecimalType.cs | 76 + .../ClickHouse.Client/Types/Enum16Type.cs | 13 + .../ClickHouse.Client/Types/Enum8Type.cs | 11 + .../ClickHouse.Client/Types/EnumType.cs | 46 + .../Types/FixedStringType.cs | 30 + .../ClickHouse.Client/Types/Float32Type.cs | 15 + .../ClickHouse.Client/Types/Float64Type.cs | 15 + .../ClickHouse.Client/Types/Grammar/Parser.cs | 36 + .../Types/Grammar/SyntaxTreeNode.cs | 28 + .../Types/Grammar/Tokenizer.cs | 35 + .../ClickHouse.Client/Types/IPv4Type.cs | 16 + .../ClickHouse.Client/Types/IPv6Type.cs | 16 + .../ISerializationTypeVisitorAcceptor.cs | 9 + .../Types/ISerializationTypeVisitorReader.cs | 61 + .../Types/ISerializationTypeVisitorWriter.cs | 61 + .../ClickHouse.Client/Types/Int16Type.cs | 15 + .../ClickHouse.Client/Types/Int32Type.cs | 15 + .../ClickHouse.Client/Types/Int64Type.cs | 15 + .../ClickHouse.Client/Types/Int8Type.cs | 15 + .../Types/LowCardinalityType.cs | 30 + .../ClickHouse.Client/Types/NestedType.cs | 25 + .../ClickHouse.Client/Types/NothingType.cs | 17 + .../ClickHouse.Client/Types/NullableType.cs | 37 + .../Types/ParameterizedType.cs | 12 + .../ClickHouse.Client/Types/StringType.cs | 15 + .../ClickHouse.Client/Types/TupleType.cs | 77 + .../ClickHouse.Client/Types/TypeConverter.cs | 152 + .../ClickHouse.Client/Types/UInt16Type.cs | 15 + .../ClickHouse.Client/Types/UInt32Type.cs | 15 + .../ClickHouse.Client/Types/UInt64Type.cs | 15 + .../ClickHouse.Client/Types/UInt8Type.cs | 15 + .../ClickHouse.Client/Types/UuidType.cs | 15 + .../Utility/CommandExtensions.cs | 27 + .../Utility/CompressedContent.cs | 63 + .../Utility/ConnectionExtensions.cs | 42 + .../Utility/DataReaderExtensions.cs | 44 + .../Utility/DictionaryExtensions.cs | 23 + .../Utility/EnumerableExtensions.cs | 41 + .../ClickHouse.Client/Utility/LargeTuple.cs | 25 + .../ClickHouse.Client/Utility/MathUtils.cs | 50 + .../Utility/NameValueCollectionExtensions.cs | 19 + .../Utility/SchemaDescriber.cs | 152 + .../Utility/StringExtensions.cs | 29 + .../ClickHouseAssemblyInitializer.cs | 17 + .../ClickHouseConnection.DesignExt.cs | 37 + .../ClickHouseConnectionEditor.Designer.cs | 196 + .../ClickHouseConnectionEditor.cs | 55 + .../ClickHouseConnectionEditor.resx | 120 + .../ClickHouseDataConnection.cs | 120 + .../Directory.Build.targets | 11 + .../FastReport.ClickHouse.csproj | 51 + .../FastReport.Core.Data.ClickHouse.csproj | 43 + ...stReport.OpenSource.Data.ClickHouse.csproj | 43 + .../CouchbaseConnectionEditor.Designer.cs | 2 +- .../JsonConnectionEditor.cs | 2 +- .../JsonDataConnection.cs | 1 + .../FastReport.Data.MongoDB.csproj | 4 +- .../MongoDBConnectionEditor.Designer.cs | 2 +- .../MongoDBDataConnection.cs | 9 +- .../MySqlConnectionEditor.Designer.cs | 2 +- .../OracleConnectionEditor.Designer.cs | 2 +- .../FastReport.Data.Postgres.csproj | 4 +- .../PostgresConnectionEditor.Designer.cs | 2 +- .../FastReport.Data.RavenDB.csproj | 6 +- .../RavenDBConnectionEditor.Designer.cs | 2 +- .../RavenDBConnectionEditor.cs | 2 +- .../SQLiteConnectionEditor.Designer.cs | 2 +- .../Core/FastReport.Data/FastReport.Data.sln | 6 + FastReport.Base/AssemblyInitializer.cs | 2 +- FastReport.Base/Base.cs | 1 + FastReport.Base/Data/CsvDataConnection.cs | 5 + FastReport.Base/Data/DataConnectionBase.cs | 43 + .../JsonDataSourceConnection.cs | 2 + FastReport.Base/Data/Total.cs | 774 +-- FastReport.Base/Data/XmlDataConnection.cs | 100 +- FastReport.Base/Engine/ReportEngine.Bands.cs | 2 +- FastReport.Base/Engine/ReportEngine.cs | 10 - FastReport.Base/Export/ExportBase.cs | 56 +- FastReport.Base/Export/ExportUtils.cs | 10 - FastReport.Base/Export/Image/ImageExport.cs | 12 +- FastReport.Base/Fills.cs | 3 +- FastReport.Base/Functions/NumToWordsPl.cs | 128 + FastReport.Base/Functions/StdFunctions.cs | 38 + .../Matrix/MatrixCellDescriptor.cs | 15 +- FastReport.Base/Matrix/MatrixHelper.cs | 10 + FastReport.Base/Matrix/MatrixObject.cs | 1 + FastReport.Base/PictureObject.cs | 23 +- FastReport.Base/PictureObjectBase.cs | 98 - FastReport.Base/Preview/PreparedPage.cs | 73 +- FastReport.Base/Preview/PreparedPages.cs | 9 +- FastReport.Base/Preview/SourcePages.cs | 16 +- FastReport.Base/TextObject.cs | 14 +- FastReport.Base/Utils/Compressor.cs | 15 +- FastReport.Base/Utils/Config.cs | 72 +- FastReport.Base/Utils/DrawUtils.cs | 25 +- FastReport.Base/Utils/FastNameCreator.cs | 2 +- FastReport.Base/Utils/FileUtils.cs | 8 - FastReport.Base/Utils/HtmlTextRenderer.cs | 5 +- FastReport.Base/Utils/ImageHelper.cs | 3 +- FastReport.Base/Utils/Res.cs | 11 - FastReport.Base/Utils/ResourceLoader.cs | 13 +- FastReport.Base/Utils/TextRenderer.cs | 4764 ++++++++--------- FastReport.Base/Utils/Zip.cs | 2 +- .../Application/Dialog/WebComboBox.cs | 2 + .../Application/WebReportDesigner.cs | 29 +- .../Controllers/DesignerController.cs | 2 +- .../FastReport.OpenSource.Web.csproj | 18 +- FastReport.Core.Web/FastReport.Web.csproj | 11 +- FastReport.Core.Web/Templates/style.cs | 5 + FastReport.OpenSource.sln | 40 +- .../Code/AssemblyDescriptor.Core.cs | 20 +- .../Code/ExpressionDescriptor.Core.cs | 2 +- .../CrossVeiw/CrossViewHelper.Core.cs | 5 +- .../CrossVeiw/CrossViewObject.Core.cs | 5 +- .../Data/DataConnectionBase.Core.cs | 8 +- .../Data/TableDataSource.Core.cs | 4 +- .../Dialog/DialogPage.Core.cs | 4 +- .../Engine/ReportEngine.Core.cs | 4 +- .../Engine/ReportEngine.Rich.cs | 15 + .../Export/ExportBase.Core.cs | 5 +- .../Export/Html/HTMLExport.Core.cs | 5 +- .../FastReport.OpenSource.csproj | 13 +- FastReport.OpenSource/HtmlObject.Core.cs | 5 +- .../Matrix/MatrixObject.Core.cs | 8 +- .../PictureObjectBase.Core.cs | 5 +- FastReport.OpenSource/PolyLineObject.Core.cs | 12 +- .../Preview/SourcePages.OpenSource.cs | 15 + FastReport.OpenSource/Report.Core.cs | 20 +- FastReport.OpenSource/ReportPage.Core.cs | 12 +- FastReport.OpenSource/ReportSettings.Core.cs | 2 +- FastReport.OpenSource/Table/TableBase.Core.cs | 20 +- FastReport.OpenSource/TextObject.Core.cs | 5 +- FastReport.OpenSource/Utils/Config.Core.cs | 20 +- .../Utils/ExportsOptions.Core.cs | 4 +- .../Utils/RegisteredObjects.Core.cs | 8 +- FastReport/Resources/en.xml | 23 +- Localization/Arabic.frl | 2258 ++++++++ Localization/Armenian.frl | 1992 +++++++ Localization/Chinese (Simplified).frl | 1509 ++++++ Localization/Chinese (Traditional).frl | 1509 ++++++ Localization/Croatian.frl | 1576 ++++++ Localization/Czech.frl | 1693 ++++++ Localization/Danish.frl | 1593 ++++++ Localization/Dutch.frl | 1593 ++++++ Localization/French.frl | 2203 ++++++++ Localization/German.frl | 2239 ++++++++ Localization/Greek.frl | 2564 +++++++++ Localization/Hungarian.frl | 1774 ++++++ Localization/Italian.frl | 1150 ++++ Localization/Persian.frl | 1576 ++++++ Localization/Polish.frl | 1129 ++++ Localization/Portuguese (Brazil).frl | 1479 +++++ Localization/Portuguese.frl | 1530 ++++++ Localization/Romanian.frl | 1610 ++++++ Localization/Russian.frl | 2479 +++++++++ Localization/Serbian.frl | 1143 ++++ Localization/Slovak.frl | 1601 ++++++ Localization/Slovenian.frl | 2043 +++++++ Localization/Spanish.frl | 1876 +++++++ Localization/Swedish.frl | 1515 ++++++ Localization/Thai.frl | 1472 +++++ Localization/Turkish.frl | 1543 ++++++ Localization/Ukrainian.frl | 1592 ++++++ Localization/readme.txt | 1 + UsedPackages.version | 12 +- 200 files changed, 54395 insertions(+), 3281 deletions(-) create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Adapters/ClickHouseDataAdapter.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseCommand.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnection.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnectionFactory.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnectionStringBuilder.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Parameters/ClickHouseDbParameter.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Parameters/ClickHouseParameterCollection.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Readers/ClickHouseDataReader.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Readers/ClickHouseRawResult.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouse.Client.csproj create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouseServerException.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouseUriBuilder.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Copy/ClickHouseBulkCopy.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/BinaryStreamReader.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/BinaryStreamWriter.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/ExtendedBinaryReader.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/ExtendedBinaryWriter.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/HttpParameterFormatter.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/InlineParameterFormatter.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/PeekableStreamWrapper.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/GlobalSuppressions.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/IClickHouseCommand.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/IClickHouseConnection.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Properties/Resources.Designer.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Properties/Resources.resx create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/AbstractDateTimeType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ArrayType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ClickHouseType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ClickHouseTypeCode.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateTime64Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateTimeType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal128Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal32Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal64Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DecimalType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Enum16Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Enum8Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/EnumType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/FixedStringType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Float32Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Float64Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/Parser.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/SyntaxTreeNode.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/Tokenizer.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/IPv4Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/IPv6Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorAcceptor.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorReader.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorWriter.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int16Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int32Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int64Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int8Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/LowCardinalityType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NestedType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NothingType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NullableType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ParameterizedType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/StringType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/TupleType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/TypeConverter.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt16Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt32Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt64Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt8Type.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UuidType.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/CommandExtensions.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/CompressedContent.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/ConnectionExtensions.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/DataReaderExtensions.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/DictionaryExtensions.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/EnumerableExtensions.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/LargeTuple.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/MathUtils.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/NameValueCollectionExtensions.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/SchemaDescriber.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/StringExtensions.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseAssemblyInitializer.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnection.DesignExt.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.Designer.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.resx create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseDataConnection.cs create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/Directory.Build.targets create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.ClickHouse.csproj create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.Core.Data.ClickHouse.csproj create mode 100644 Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.OpenSource.Data.ClickHouse.csproj create mode 100644 FastReport.Base/Functions/NumToWordsPl.cs create mode 100644 FastReport.OpenSource/Engine/ReportEngine.Rich.cs create mode 100644 FastReport.OpenSource/Preview/SourcePages.OpenSource.cs create mode 100644 Localization/Arabic.frl create mode 100644 Localization/Armenian.frl create mode 100644 Localization/Chinese (Simplified).frl create mode 100644 Localization/Chinese (Traditional).frl create mode 100644 Localization/Croatian.frl create mode 100644 Localization/Czech.frl create mode 100644 Localization/Danish.frl create mode 100644 Localization/Dutch.frl create mode 100644 Localization/French.frl create mode 100644 Localization/German.frl create mode 100644 Localization/Greek.frl create mode 100644 Localization/Hungarian.frl create mode 100644 Localization/Italian.frl create mode 100644 Localization/Persian.frl create mode 100644 Localization/Polish.frl create mode 100644 Localization/Portuguese (Brazil).frl create mode 100644 Localization/Portuguese.frl create mode 100644 Localization/Romanian.frl create mode 100644 Localization/Russian.frl create mode 100644 Localization/Serbian.frl create mode 100644 Localization/Slovak.frl create mode 100644 Localization/Slovenian.frl create mode 100644 Localization/Spanish.frl create mode 100644 Localization/Swedish.frl create mode 100644 Localization/Thai.frl create mode 100644 Localization/Turkish.frl create mode 100644 Localization/Ukrainian.frl create mode 100644 Localization/readme.txt diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Adapters/ClickHouseDataAdapter.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Adapters/ClickHouseDataAdapter.cs new file mode 100644 index 00000000..1eaa5cd7 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Adapters/ClickHouseDataAdapter.cs @@ -0,0 +1,11 @@ +using System.Data.Common; + +namespace ClickHouse.Client.ADO.Adapters +{ + /// + /// Dummy adapter class - to maintain backward compatibility + /// + public class ClickHouseDataAdapter : DbDataAdapter + { + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseCommand.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseCommand.cs new file mode 100644 index 00000000..78247d23 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseCommand.cs @@ -0,0 +1,132 @@ +using System; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ClickHouse.Client.ADO.Parameters; +using ClickHouse.Client.ADO.Readers; + +namespace ClickHouse.Client.ADO +{ + public class ClickHouseCommand : DbCommand, IClickHouseCommand, IDisposable + { + private readonly CancellationTokenSource cts = new CancellationTokenSource(); + private readonly ClickHouseParameterCollection commandParameters = new ClickHouseParameterCollection(); + private ClickHouseConnection connection; + + public ClickHouseCommand() + { + } + + public ClickHouseCommand(ClickHouseConnection connection) + { + this.connection = connection; + } + + public override string CommandText { get; set; } + + public override int CommandTimeout { get; set; } + + public override CommandType CommandType { get; set; } + + public override bool DesignTimeVisible { get; set; } + + public override UpdateRowSource UpdatedRowSource { get; set; } + + protected override DbConnection DbConnection + { + get => connection; + set => connection = (ClickHouseConnection)value; + } + + protected override DbParameterCollection DbParameterCollection => commandParameters; + + protected override DbTransaction DbTransaction { get; set; } + + public new void Dispose() + { + cts?.Dispose(); + base.Dispose(); + } + + public override void Cancel() => cts.Cancel(); + + public override int ExecuteNonQuery() => ExecuteNonQueryAsync(CancellationToken.None).GetAwaiter().GetResult(); + + public override async Task ExecuteNonQueryAsync(CancellationToken cancellationToken) + { + if (connection == null) + throw new InvalidOperationException("Connection is not set"); + + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken); + using var response = await connection.PostSqlQueryAsync(CommandText, linkedCancellationTokenSource.Token, commandParameters).ConfigureAwait(false); + var result = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + return int.TryParse(result, NumberStyles.Integer, CultureInfo.InvariantCulture, out var r) ? r : 0; + } + + /// + /// Allows to return raw result from a query (with custom FORMAT) + /// + /// Cancellation token + /// ClickHouseRawResult object containing response stream + public async Task ExecuteRawResultAsync(CancellationToken cancellationToken) + { + if (connection == null) + throw new InvalidOperationException("Connection is not set"); + + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken); + var response = await connection.PostSqlQueryAsync(CommandText, linkedCancellationTokenSource.Token, commandParameters).ConfigureAwait(false); + return new ClickHouseRawResult(response); + } + + public override object ExecuteScalar() => ExecuteScalarAsync(CancellationToken.None).GetAwaiter().GetResult(); + + public override async Task ExecuteScalarAsync(CancellationToken cancellationToken) + { + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken); + using var reader = await ExecuteDbDataReaderAsync(CommandBehavior.Default, linkedCancellationTokenSource.Token).ConfigureAwait(false); + return reader.Read() ? reader.GetValue(0) : null; + } + + public override void Prepare() { /* ClickHouse has no notion of prepared statements */ } + + public new ClickHouseDbParameter CreateParameter() => (ClickHouseDbParameter)CreateDbParameter(); + + protected override DbParameter CreateDbParameter() => new ClickHouseDbParameter(); + + protected override void Dispose(bool disposing) + { + if (disposing) + { + cts.Dispose(); + } + } + + protected override DbDataReader ExecuteDbDataReader(CommandBehavior behavior) => ExecuteDbDataReaderAsync(behavior, CancellationToken.None).GetAwaiter().GetResult(); + + protected override async Task ExecuteDbDataReaderAsync(CommandBehavior behavior, CancellationToken cancellationToken) + { + if (connection == null) + throw new InvalidOperationException("Connection is not set"); + + using var linkedCancellationTokenSource = CancellationTokenSource.CreateLinkedTokenSource(cts.Token, cancellationToken); + var sqlBuilder = new StringBuilder(CommandText); + switch (behavior) + { + case CommandBehavior.SingleRow: + case CommandBehavior.SingleResult: + sqlBuilder.Append(" LIMIT 1"); + break; + case CommandBehavior.SchemaOnly: + sqlBuilder.Append(" LIMIT 0"); + break; + default: + break; + } + var result = await connection.PostSqlQueryAsync(sqlBuilder.ToString(), linkedCancellationTokenSource.Token, commandParameters).ConfigureAwait(false); + return new ClickHouseDataReader(result); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnection.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnection.cs new file mode 100644 index 00000000..1790be15 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnection.cs @@ -0,0 +1,325 @@ +using System; +using System.Collections.Concurrent; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.IO; +using System.Linq; +using System.Net; +using System.Net.Http; +using System.Net.Http.Headers; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ClickHouse.Client.ADO.Parameters; +using ClickHouse.Client.Formats; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client.ADO +{ + public class ClickHouseConnection : DbConnection, IClickHouseConnection, ICloneable + { + private const string CustomSettingPrefix = "set_"; + + private readonly HttpClient httpClient; + private readonly ConcurrentDictionary customSettings = new ConcurrentDictionary(); + private ConnectionState state = ConnectionState.Closed; // Not an autoproperty because of interface implementation + private Version serverVersion; + private string database = "default"; + private string username; + private string password; + private bool useCompression; + private string session; + private TimeSpan timeout; + private Uri serverUri; + + public ClickHouseConnection() + : this(string.Empty) + { + } + + public ClickHouseConnection(string connectionString) + { + ConnectionString = connectionString; + var httpClientHandler = new HttpClientHandler() { AutomaticDecompression = DecompressionMethods.GZip | DecompressionMethods.Deflate }; + httpClient = new HttpClient(httpClientHandler, true) + { + Timeout = timeout, + }; + // Connection string has to be initialized after HttpClient, as it may set HttpClient.Timeout + } + + /// + /// Initializes a new instance of the class using provided HttpClient. + /// Note that HttpClient must have AutomaticDecompression enabled if compression is not disabled in connection string + /// + /// Connection string + /// instance of HttpClient + public ClickHouseConnection(string connectionString, HttpClient httpClient) + { + ConnectionString = connectionString; + this.httpClient = httpClient; + } + + /// + /// Gets or sets string defining connection settings for ClickHouse server + /// Example: Host=localhost;Port=8123;Username=default;Password=123;Compression=true + /// + public sealed override string ConnectionString + { + get + { + var builder = new ClickHouseConnectionStringBuilder + { + Database = database, + Username = username, + Password = password, + Host = serverUri?.Host, + Port = (ushort)serverUri?.Port, + Compression = useCompression, + UseSession = session != null, + Timeout = timeout, + }; + + foreach (var kvp in CustomSettings) + builder[CustomSettingPrefix + kvp.Key] = kvp.Value; + + return builder.ToString(); + } + + set + { + var builder = new ClickHouseConnectionStringBuilder() { ConnectionString = value }; + database = builder.Database; + username = builder.Username; + password = builder.Password; + serverUri = new UriBuilder(builder.Protocol, builder.Host, builder.Port).Uri; + useCompression = builder.Compression; + session = builder.UseSession ? builder.SessionId ?? Guid.NewGuid().ToString() : null; + timeout = builder.Timeout; + + foreach (var key in builder.Keys.Cast().Where(k => k.StartsWith(CustomSettingPrefix))) + { + CustomSettings.Set(key.Replace(CustomSettingPrefix, string.Empty), builder[key]); + } + } + } + + public IDictionary CustomSettings => customSettings; + + public override ConnectionState State => state; + + public override string Database => database; + + public override string DataSource { get; } + + public override string ServerVersion => serverVersion?.ToString(); + + public override DataTable GetSchema() => GetSchema(null, null); + + public override DataTable GetSchema(string type) => GetSchema(type, null); + + public override DataTable GetSchema(string type, string[] restrictions) => SchemaDescriber.DescribeSchema(this, type, restrictions); + + internal async Task PostSqlQueryAsync(string sqlQuery, CancellationToken token, ClickHouseParameterCollection parameters = null) + { + var uriBuilder = CreateUriBuilder(); + if (parameters != null) + { + var httpParametersSupported = await SupportsHttpParameters(); + + if (httpParametersSupported) + { + foreach (ClickHouseDbParameter parameter in parameters) + uriBuilder.AddQueryParameter(parameter.ParameterName, HttpParameterFormatter.Format(parameter)); + } + else + { + var formattedParameters = new Dictionary(parameters.Count); + foreach (ClickHouseDbParameter parameter in parameters) + formattedParameters.TryAdd(parameter.ParameterName, InlineParameterFormatter.Format(parameter)); + sqlQuery = SubstituteParameters(sqlQuery, formattedParameters); + } + } + string uri = uriBuilder.ToString(); + + using var postMessage = new HttpRequestMessage(HttpMethod.Post, uri); + + AddDefaultHttpHeaders(postMessage.Headers); + HttpContent content = new StringContent(sqlQuery); + content.Headers.ContentType = new MediaTypeHeaderValue("text/sql"); + if (useCompression) + { + content = new CompressedContent(content, DecompressionMethods.GZip); + } + + postMessage.Content = content; + + var response = await httpClient.SendAsync(postMessage, HttpCompletionOption.ResponseHeadersRead, token).ConfigureAwait(false); + return await HandleError(response, sqlQuery).ConfigureAwait(false); + } + + internal async Task PostStreamAsync(string sql, Stream data, bool isCompressed, CancellationToken token) + { + var builder = CreateUriBuilder(sql); + using var postMessage = new HttpRequestMessage(HttpMethod.Post, builder.ToString()); + AddDefaultHttpHeaders(postMessage.Headers); + + postMessage.Content = new StreamContent(data); + postMessage.Content.Headers.ContentType = new MediaTypeHeaderValue("application/octet-stream"); + if (isCompressed) + { + postMessage.Content.Headers.Add("Content-Encoding", "gzip"); + } + + using var response = await httpClient.SendAsync(postMessage, HttpCompletionOption.ResponseContentRead, token).ConfigureAwait(false); + await HandleError(response, sql).ConfigureAwait(false); + } + + private static async Task HandleError(HttpResponseMessage response, string query) + { + if (!response.IsSuccessStatusCode) + { + var error = await response.Content.ReadAsStringAsync().ConfigureAwait(false); + throw ClickHouseServerException.FromServerResponse(error, query); + } + return response; + } + + private static string SubstituteParameters(string query, IDictionary parameters) + { + var builder = new StringBuilder(query.Length); + + var paramStartPos = query.IndexOf('{'); + var paramEndPos = -1; + + while (paramStartPos != -1) + { + builder.Append(query.Substring(paramEndPos + 1, paramStartPos - paramEndPos - 1)); + + paramStartPos += 1; + paramEndPos = query.IndexOf('}', paramStartPos); + var param = query.Substring(paramStartPos, paramEndPos - paramStartPos); + var delimiterPos = param.LastIndexOf(':'); + if (delimiterPos == -1) + throw new NotSupportedException($"param {param} doesn`t have data type"); + var name = param.Substring(0, delimiterPos); + + if (!parameters.TryGetValue(name, out var value)) + throw new ArgumentOutOfRangeException($"Parameter {name} not found in parameters list"); + + builder.Append(value); + + paramStartPos = query.IndexOf('{', paramEndPos); + } + + builder.Append(query.Substring(paramEndPos + 1, query.Length - paramEndPos - 1)); + + return builder.ToString(); + } + + public override void ChangeDatabase(string databaseName) => database = databaseName; + + public object Clone() => new ClickHouseConnection(ConnectionString); + + public override void Close() => state = ConnectionState.Closed; + + public override void Open() => OpenAsync().ConfigureAwait(false).GetAwaiter().GetResult(); + + public override async Task OpenAsync(CancellationToken token) + { + if (State == ConnectionState.Open) + return; + const string versionQuery = "SELECT version() FORMAT TSV"; + try + { + var response = await PostSqlQueryAsync(versionQuery, token).ConfigureAwait(false); + response = await HandleError(response, versionQuery); + var data = await response.Content.ReadAsByteArrayAsync().ConfigureAwait(false); + + if (data.Length > 2 && data[0] == 0x1F && data[1] == 0x8B) // Check if response starts with GZip marker + throw new InvalidOperationException("ClickHouse server returned compressed result but HttpClient did not decompress it. Check HttpClient settings"); + + if (data.Length == 0) + throw new InvalidOperationException("ClickHouse server did not return version, check if the server is functional"); + + serverVersion = Version.Parse(Encoding.UTF8.GetString(data).Trim()); + state = ConnectionState.Open; + } + catch + { + state = ConnectionState.Broken; + throw; + } + } + + public new ClickHouseCommand CreateCommand() => new ClickHouseCommand(this); + + /// + /// Detects whether server supports parameters through URI + /// ClickHouse Release 19.11.3.11, 2019-07-18: New Feature: Added support for prepared statements. #5331 (Alexander) #5630 (alexey-milovidov) + /// + /// whether parameters are supported + internal virtual async Task SupportsHttpParameters() + { + if (State != ConnectionState.Open) + await OpenAsync(); + if (serverVersion == null) + throw new InvalidOperationException("Connection does not define server version"); + return serverVersion >= new Version(19, 11, 3, 11); + } + + /// + /// Detects whether server supports putting query into POST body along with binary data + /// Added somewhere in ClickHouse 20.5 + /// + /// whether parameters are supported + internal virtual async Task SupportsInlineQuery() + { + if (State != ConnectionState.Open) + await OpenAsync(); + if (serverVersion == null) + throw new InvalidOperationException("Connection does not define server version"); + return serverVersion >= new Version(20, 5); + } + + /// + /// 20.1.2.4 Add DateTime64 datatype with configurable sub-second precision. #7170 (Vasily Nemkov) + /// + /// whether DateTime64 is supported + internal virtual async Task SupportsDateTime64() + { + if (State != ConnectionState.Open) + await OpenAsync(); + if (serverVersion == null) + throw new InvalidOperationException("Connection does not define server version"); + return serverVersion >= new Version(20, 1, 2, 4); + } + + protected override DbTransaction BeginDbTransaction(IsolationLevel isolationLevel) => throw new NotSupportedException(); + + protected override DbCommand CreateDbCommand() => CreateCommand(); + + private void AddDefaultHttpHeaders(HttpRequestHeaders headers) + { + headers.Authorization = new AuthenticationHeaderValue("Basic", Convert.ToBase64String(Encoding.UTF8.GetBytes($"{username}:{password}"))); + headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/json")); + headers.Accept.Add(new MediaTypeWithQualityHeaderValue("text/csv")); + headers.Accept.Add(new MediaTypeWithQualityHeaderValue("application/octet-stream")); + if (useCompression) + { + headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("gzip")); + headers.AcceptEncoding.Add(new StringWithQualityHeaderValue("deflate")); + } + } + + private ClickHouseUriBuilder CreateUriBuilder(string sql = null) => new ClickHouseUriBuilder(serverUri) + { + Database = database, + SessionId = session, + UseCompression = useCompression, + CustomParameters = customSettings, + Sql = sql, + }; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnectionFactory.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnectionFactory.cs new file mode 100644 index 00000000..12592c53 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnectionFactory.cs @@ -0,0 +1,19 @@ +using System.Data.Common; +using ClickHouse.Client.ADO.Adapters; +using ClickHouse.Client.ADO.Parameters; + +namespace ClickHouse.Client.ADO +{ + public class ClickHouseConnectionFactory : DbProviderFactory + { + public override DbConnection CreateConnection() => new ClickHouseConnection(); + + public override DbDataAdapter CreateDataAdapter() => new ClickHouseDataAdapter(); + + public override DbConnectionStringBuilder CreateConnectionStringBuilder() => new ClickHouseConnectionStringBuilder(); + + public override DbParameter CreateParameter() => new ClickHouseDbParameter(); + + public override DbCommand CreateCommand() => new ClickHouseCommand(); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnectionStringBuilder.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnectionStringBuilder.cs new file mode 100644 index 00000000..979436e7 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/ClickHouseConnectionStringBuilder.cs @@ -0,0 +1,87 @@ +using System; +using System.Data.Common; +using System.Globalization; + +namespace ClickHouse.Client.ADO +{ + public class ClickHouseConnectionStringBuilder : DbConnectionStringBuilder + { + public ClickHouseConnectionStringBuilder() + { + } + + public ClickHouseConnectionStringBuilder(string connectionString) => ConnectionString = connectionString; + + public string Database + { + get => TryGetValue("Database", out var value) ? value as string : "default"; + set => this["Database"] = value; + } + + public string Username + { + get => TryGetValue("Username", out var value) ? value as string : "default"; + set => this["Username"] = value; + } + + public string Password + { + get => TryGetValue("Password", out var value) ? value as string : string.Empty; + set => this["Password"] = value; + } + + public string Protocol + { + get => TryGetValue("Protocol", out var value) ? value as string : "http"; + set => this["Protocol"] = value; + } + + public string Host + { + get => TryGetValue("Host", out var value) ? value as string : "localhost"; + set => this["Host"] = value; + } + + public bool Compression + { + get => TryGetValue("Compression", out var value) ? "true".Equals(value as string, StringComparison.OrdinalIgnoreCase) : true; + set => this["Compression"] = value; + } + + public bool UseSession + { + get => TryGetValue("UseSession", out var value) ? "true".Equals(value as string, StringComparison.OrdinalIgnoreCase) : false; + set => this["UseSession"] = value; + } + + public string SessionId + { + get => TryGetValue("SessionId", out var value) ? value as string : null; + set => this["SessionId"] = value; + } + + public ushort Port + { + get => TryGetValue("Port", out var value) && value is string @string && ushort.TryParse(@string, out var @ushort) ? @ushort : (ushort)8123; + set => this["Port"] = value; + } + + [Obsolete] + public string Driver + { + get => TryGetValue("Driver", out var value) ? value as string : null; + set => this["Driver"] = value; + } + + public TimeSpan Timeout + { + get + { + return TryGetValue("Timeout", out var value) && value is string @string && double.TryParse(@string, NumberStyles.Any, CultureInfo.InvariantCulture, out var timeout) + ? TimeSpan.FromSeconds(timeout) + : TimeSpan.FromMinutes(2); + } + set => this["Timeout"] = value.TotalSeconds; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Parameters/ClickHouseDbParameter.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Parameters/ClickHouseDbParameter.cs new file mode 100644 index 00000000..b6af641e --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Parameters/ClickHouseDbParameter.cs @@ -0,0 +1,30 @@ +using System.Data; +using System.Data.Common; + +namespace ClickHouse.Client.ADO.Parameters +{ + public class ClickHouseDbParameter : DbParameter + { + public override DbType DbType { get; set; } + + public string ClickHouseType { get; set; } + + public override ParameterDirection Direction { get => ParameterDirection.Input; set { } } + + public override bool IsNullable { get; set; } + + public override string ParameterName { get; set; } + + public override int Size { get; set; } + + public override string SourceColumn { get; set; } + + public override bool SourceColumnNullMapping { get; set; } + + public override object Value { get; set; } + + public override void ResetDbType() { } + + public override string ToString() => $"{ParameterName}:{Value}"; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Parameters/ClickHouseParameterCollection.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Parameters/ClickHouseParameterCollection.cs new file mode 100644 index 00000000..ce4c280d --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Parameters/ClickHouseParameterCollection.cs @@ -0,0 +1,68 @@ +using System; +using System.Collections; +using System.Collections.Generic; +using System.Data.Common; +using System.Linq; + +namespace ClickHouse.Client.ADO.Parameters +{ + internal class ClickHouseParameterCollection : DbParameterCollection + { + private readonly List parameters = new List(); + + public override int Count => parameters.Count; + + public override object SyncRoot { get; } + + public override int Add(object value) + { + parameters.Add((ClickHouseDbParameter)value); + return parameters.Count - 1; + } + + public override void AddRange(Array values) => parameters.AddRange(values.Cast()); + + public override void Clear() => parameters.Clear(); + + public override bool Contains(object value) => parameters.Contains(value as ClickHouseDbParameter); + + public override bool Contains(string value) => parameters.Any(p => p.ParameterName == value); + + public override void CopyTo(Array array, int index) + { + for (int i = 0; i < parameters.Count; i++) + { + array.SetValue(parameters[i].Value, index + i); + } + } + + public override IEnumerator GetEnumerator() => parameters.GetEnumerator(); + + public override int IndexOf(object value) => parameters.IndexOf(value as ClickHouseDbParameter); + + public override int IndexOf(string parameterName) => parameters.FindIndex(x => x.ParameterName == parameterName); + + public override void Insert(int index, object value) => parameters.Insert(index, (ClickHouseDbParameter)value); + + public override void Remove(object value) => parameters.Remove(value as ClickHouseDbParameter); + + public override void RemoveAt(int index) => parameters.RemoveAt(index); + + public override void RemoveAt(string parameterName) => parameters.RemoveAll(p => p.ParameterName == parameterName); + + protected override DbParameter GetParameter(int index) => parameters[index]; + + protected override DbParameter GetParameter(string parameterName) => parameters[IndexOf(parameterName)]; + + protected override void SetParameter(int index, DbParameter value) => parameters[index] = (ClickHouseDbParameter)value; + + protected override void SetParameter(string parameterName, DbParameter value) + { + var index = IndexOf(parameterName); + if (index < 0) + Add(value); + else + SetParameter(index, value); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Readers/ClickHouseDataReader.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Readers/ClickHouseDataReader.cs new file mode 100644 index 00000000..e1ed709f --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Readers/ClickHouseDataReader.cs @@ -0,0 +1,181 @@ +using System; +using System.Collections; +using System.Data; +using System.Data.Common; +using System.Globalization; +using System.IO; +using System.Net.Http; +using System.Threading; +using System.Threading.Tasks; +using ClickHouse.Client.Formats; +using ClickHouse.Client.Types; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client.ADO.Readers +{ + public class ClickHouseDataReader : DbDataReader + { + private const int BufferSize = 512 * 1024; + + private readonly HttpResponseMessage httpResponse; // Used to dispose at the end of reader + private readonly ExtendedBinaryReader reader; + private readonly BinaryStreamReader streamReader; + + internal ClickHouseDataReader(HttpResponseMessage httpResponse) + { + this.httpResponse = httpResponse ?? throw new ArgumentNullException(nameof(httpResponse)); + var stream = new BufferedStream(httpResponse.Content.ReadAsStreamAsync().GetAwaiter().GetResult(), BufferSize); + reader = new ExtendedBinaryReader(stream); // will dispose of stream + streamReader = new BinaryStreamReader(reader); + ReadHeaders(); + } + + internal ClickHouseType GetClickHouseType(int ordinal) => RawTypes[ordinal]; + + public override object this[int ordinal] => GetValue(ordinal); + + public override object this[string name] => this[GetOrdinal(name)]; + + public override int Depth { get; } + + public override int FieldCount => RawTypes?.Length ?? throw new InvalidOperationException(); + + public override bool IsClosed => false; + + public override sealed bool HasRows => true; + + public override int RecordsAffected { get; } + + protected object[] CurrentRow { get; set; } + + protected string[] FieldNames { get; set; } + + private protected ClickHouseType[] RawTypes { get; set; } + + public override bool GetBoolean(int ordinal) => Convert.ToBoolean(GetValue(ordinal), CultureInfo.InvariantCulture); + + public override byte GetByte(int ordinal) => (byte)GetValue(ordinal); + + public override long GetBytes(int ordinal, long dataOffset, byte[] buffer, int bufferOffset, int length) => throw new NotImplementedException(); + + public override char GetChar(int ordinal) => (char)GetValue(ordinal); + + public override long GetChars(int ordinal, long dataOffset, char[] buffer, int bufferOffset, int length) => throw new NotImplementedException(); + + public override string GetDataTypeName(int ordinal) => RawTypes[ordinal].ToString(); + + public override DateTime GetDateTime(int ordinal) => (DateTime)GetValue(ordinal); + + public virtual DateTimeOffset GetDateTimeOffset(int ordinal) + { + var dt = GetDateTime(ordinal); + return ((AbstractDateTimeType)RawTypes[ordinal]).ToDateTimeOffset(dt); + } + + public override decimal GetDecimal(int ordinal) => (decimal)GetValue(ordinal); + + public override double GetDouble(int ordinal) => (double)GetValue(ordinal); + + public override IEnumerator GetEnumerator() => CurrentRow.GetEnumerator(); + + public override Type GetFieldType(int ordinal) + { + var rawType = RawTypes[ordinal]; + return rawType is NullableType nt ? nt.UnderlyingType.FrameworkType : rawType.FrameworkType; + } + + public override float GetFloat(int ordinal) => (float)GetValue(ordinal); + + public override Guid GetGuid(int ordinal) => (Guid)GetValue(ordinal); + + public override short GetInt16(int ordinal) => (short)GetValue(ordinal); + + public override int GetInt32(int ordinal) => (int)GetValue(ordinal); + + public override long GetInt64(int ordinal) => (long)GetValue(ordinal); + + public override string GetName(int ordinal) => FieldNames[ordinal]; + + public override int GetOrdinal(string name) + { + var index = Array.FindIndex(FieldNames, (fn) => fn == name); + if (index == -1) + { + throw new IndexOutOfRangeException(); + } + + return index; + } + + public override string GetString(int ordinal) => (string)GetValue(ordinal); + + public override object GetValue(int ordinal) => CurrentRow[ordinal]; + + public override int GetValues(object[] values) + { + if (CurrentRow == null) + { + throw new InvalidOperationException(); + } + + CurrentRow.CopyTo(values, 0); + return CurrentRow.Length; + } + + public override bool IsDBNull(int ordinal) => GetValue(ordinal) is DBNull || GetValue(ordinal) is null; + + public override bool NextResult() => false; + + public override void Close() => Dispose(); + + public override T GetFieldValue(int ordinal) => (T)GetValue(ordinal); + + public override DataTable GetSchemaTable() => SchemaDescriber.DescribeSchema(this); + + public override Task NextResultAsync(CancellationToken cancellationToken) => Task.FromResult(false); + + public override bool Read() + { + if (reader.PeekChar() == -1) + return false; // End of stream reached + + var count = RawTypes.Length; + var data = CurrentRow; + for (var i = 0; i < count; i++) + { + var rawType = RawTypes[i]; + data[i] = streamReader.Read(rawType); + } + return true; + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + httpResponse?.Dispose(); + reader?.Dispose(); + streamReader?.Dispose(); + } + } + + private void ReadHeaders() + { + var count = reader.Read7BitEncodedInt(); + FieldNames = new string[count]; + RawTypes = new ClickHouseType[count]; + CurrentRow = new object[count]; + + for (var i = 0; i < count; i++) + { + FieldNames[i] = reader.ReadString(); + } + + for (var i = 0; i < count; i++) + { + var chType = reader.ReadString(); + RawTypes[i] = TypeConverter.ParseClickHouseType(chType); + } + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Readers/ClickHouseRawResult.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Readers/ClickHouseRawResult.cs new file mode 100644 index 00000000..75b9a6f2 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ADO/Readers/ClickHouseRawResult.cs @@ -0,0 +1,27 @@ +using System; +using System.IO; +using System.Net.Http; +using System.Threading.Tasks; + +namespace ClickHouse.Client.ADO +{ + public class ClickHouseRawResult : IDisposable + { + private readonly HttpResponseMessage response; + + internal ClickHouseRawResult(HttpResponseMessage response) + { + this.response = response; + } + + public Task ReadAsStreamAsync() => response.Content.ReadAsStreamAsync(); + + public Task ReadAsByteArrayAsync() => response.Content.ReadAsByteArrayAsync(); + + public Task ReadAsStringAsync() => response.Content.ReadAsStringAsync(); + + public Task CopyToAsync(Stream stream) => response.Content.CopyToAsync(stream); + + public void Dispose() => response?.Dispose(); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouse.Client.csproj b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouse.Client.csproj new file mode 100644 index 00000000..2cd0df5a --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouse.Client.csproj @@ -0,0 +1,59 @@ + + + + 8.0 + net472;netstandard2.1;netcoreapp2.2 + false + https://github.com/DarkWanderer/ClickHouse.Client + https://github.com/DarkWanderer/ClickHouse.Client + clickhouse, ado.net, client + Oleg Kozlyuk + Fast binary-over-HTTP ADO.NET provider for ClickHouse + MIT + 1.0.0 + 1.0.0 + 1.0.0 + true + true + snupkg + + + + + + + + + + + + + + + + all + runtime; build; native; contentfiles; analyzers; buildtransitive + + + + + + + + + + + True + True + Resources.resx + + + + + + ResXFileCodeGenerator + Resources.Designer.cs + + + + diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouseServerException.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouseServerException.cs new file mode 100644 index 00000000..64672831 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouseServerException.cs @@ -0,0 +1,67 @@ +using System; +using System.Data.Common; +using System.Runtime.Serialization; + +namespace ClickHouse.Client +{ + /// + /// Exception class representing error which happened on ClickHouse server + /// + [Serializable] + public class ClickHouseServerException : DbException + { + public ClickHouseServerException() + { + } + + public ClickHouseServerException(string error, string query, int errorCode) + : base(error, errorCode) + { + Query = query; + } + + public string Query { get; } = null; + + public static ClickHouseServerException FromServerResponse(string error, string query) + { + var errorCode = ParseErrorCode(error) ?? -1; + return new ClickHouseServerException(error, query, errorCode); + } + + protected ClickHouseServerException(SerializationInfo info, StreamingContext context) + : base(info, context) + { + } + + private static int? ParseErrorCode(string error) + { + int start = -1; + int end = error.Length - 1; + + for (int i = 0; i < error.Length; i++) + { + if (char.IsDigit(error[i])) + { + start = i; + break; + } + } + + if (start == -1) + { + return null; + } + + for (int i = start; i < error.Length; i++) + { + if (!char.IsDigit(error[i])) + { + end = i; + break; + } + } + + return int.TryParse(error.Substring(start, end - start), out int result) ? result : (int?)null; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouseUriBuilder.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouseUriBuilder.cs new file mode 100644 index 00000000..ec4cd824 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/ClickHouseUriBuilder.cs @@ -0,0 +1,52 @@ +using System; +using System.Collections.Generic; +using System.Globalization; +using System.Web; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client +{ + internal class ClickHouseUriBuilder + { + private readonly IDictionary queryParameters = new Dictionary(); + + public ClickHouseUriBuilder(Uri baseUri) => BaseUri = baseUri; + + public Uri BaseUri { get; } + + public string Sql { get; set; } + + public bool UseCompression { get; set; } + + public string Database { get; set; } + + public string SessionId { get; set; } + + public string DefaultFormat => "RowBinaryWithNamesAndTypes"; + + public IReadOnlyDictionary CustomParameters { get; set; } + + public bool AddQueryParameter(string name, string value) => DictionaryExtensions.TryAdd(queryParameters, name, value); + + public override string ToString() + { + var parameters = HttpUtility.ParseQueryString(string.Empty); // NameValueCollection but a special one + parameters.Set("enable_http_compression", UseCompression.ToString(CultureInfo.InvariantCulture).ToLowerInvariant()); + parameters.Set("default_format", DefaultFormat); + parameters.SetOrRemove("database", Database); + parameters.SetOrRemove("session_id", SessionId); + parameters.SetOrRemove("query", Sql); + + foreach (var parameter in queryParameters) + parameters.Set("param_" + parameter.Key, parameter.Value); + + if (CustomParameters != null) + { + foreach (var parameter in CustomParameters) + parameters.Set(parameter.Key, Convert.ToString(parameter.Value, CultureInfo.InvariantCulture)); + } + var uriBuilder = new UriBuilder(BaseUri) { Query = parameters.ToString() }; + return uriBuilder.ToString(); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Copy/ClickHouseBulkCopy.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Copy/ClickHouseBulkCopy.cs new file mode 100644 index 00000000..284d428b --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Copy/ClickHouseBulkCopy.cs @@ -0,0 +1,168 @@ +using System; +using System.Collections.Generic; +using System.Data; +using System.IO; +using System.IO.Compression; +using System.Linq; +using System.Text; +using System.Threading; +using System.Threading.Tasks; +using ClickHouse.Client.ADO; +using ClickHouse.Client.ADO.Readers; +using ClickHouse.Client.Formats; +using ClickHouse.Client.Properties; +using ClickHouse.Client.Types; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client.Copy +{ + public class ClickHouseBulkCopy : IDisposable + { + private readonly ClickHouseConnection connection; + private long rowsWritten = 0; + + public ClickHouseBulkCopy(ClickHouseConnection connection) + { + this.connection = connection ?? throw new ArgumentNullException(nameof(connection)); + } + + /// + /// Gets or sets size of batch in rows. + /// + public int BatchSize { get; set; } = 100000; + + /// + /// Gets or sets maximum number of parallel processing tasks. + /// + public int MaxDegreeOfParallelism { get; set; } = 4; + + /// + /// Gets or sets name of destination table to insert to. "SELECT ..columns.. LIMIT 0" query is performed before insertion. + /// + public string DestinationTableName { get; set; } + + /// + /// Gets total number of rows written by this instance. + /// + public long RowsWritten => Interlocked.Read(ref rowsWritten); + + public Task WriteToServerAsync(IDataReader reader) => WriteToServerAsync(reader, CancellationToken.None); + + public Task WriteToServerAsync(IDataReader reader, CancellationToken token) + { + if (reader is null) + { + throw new ArgumentNullException(nameof(reader)); + } + + return WriteToServerAsync(reader.AsEnumerable(), reader.GetColumnNames(), token); + } + + public Task WriteToServerAsync(DataTable table, CancellationToken token) + { + if (table is null) + { + throw new ArgumentNullException(nameof(table)); + } + + var rows = table.Rows.Cast().Select(r => r.ItemArray); // enumerable + var columns = table.Columns.Cast().Select(c => c.ColumnName).ToArray(); + return WriteToServerAsync(rows, columns, token); + } + + public Task WriteToServerAsync(IEnumerable rows) => WriteToServerAsync(rows, null, CancellationToken.None); + + public Task WriteToServerAsync(IEnumerable rows, IReadOnlyCollection columns) => WriteToServerAsync(rows, columns, CancellationToken.None); + + public Task WriteToServerAsync(IEnumerable rows, CancellationToken token) => WriteToServerAsync(rows, null, token); + + public async Task WriteToServerAsync(IEnumerable rows, IReadOnlyCollection columns, CancellationToken token) + { + if (rows is null) + { + throw new ArgumentNullException(nameof(rows)); + } + + if (string.IsNullOrWhiteSpace(DestinationTableName)) + { + throw new InvalidOperationException(Resources.DestinationTableNotSetMessage); + } + + ClickHouseType[] columnTypes = null; + string[] columnNames = columns?.ToArray(); + + using (var reader = (ClickHouseDataReader)await connection.ExecuteReaderAsync($"SELECT {GetColumnsExpression(columns)} FROM {DestinationTableName} LIMIT 0")) + { + columnTypes = reader.GetClickHouseColumnTypes(); + columnNames ??= reader.GetColumnNames(); + } + for (int i = 0; i < columnNames.Length; i++) + columnNames[i] = columnNames[i].EncloseColumnName(); + + var tasks = new Task[MaxDegreeOfParallelism]; + for (var i = 0; i < tasks.Length; i++) + { + tasks[i] = Task.CompletedTask; + } + + foreach (var batch in rows.Batch(BatchSize)) + { + token.ThrowIfCancellationRequested(); + while (true) + { + var completedTaskIndex = Array.FindIndex(tasks, t => t.IsCompleted); + if (completedTaskIndex >= 0) + { + // propagate exception if one happens + // 'await' instead of 'Wait()' to avoid dealing with AggregateException + await tasks[completedTaskIndex].ConfigureAwait(false); + var task = PushBatch(batch, columnTypes, columnNames, token); + tasks[completedTaskIndex] = task; + break; // while (true); go to next batch + } + else + { + await Task.WhenAny(tasks).ConfigureAwait(false); + } + } + } + await Task.WhenAll(tasks).ConfigureAwait(false); + } + + // This code added to correctly implement the disposable pattern. + public void Dispose() => connection?.Dispose(); + + private string GetColumnsExpression(IReadOnlyCollection columns) => columns == null || columns.Count == 0 ? "*" : string.Join(",", columns); + + private async Task PushBatch(ICollection rows, ClickHouseType[] columnTypes, string[] columnNames, CancellationToken token) + { + var query = $"INSERT INTO {DestinationTableName} ({string.Join(", ", columnNames)}) FORMAT RowBinary"; + bool useInlineQuery = await connection.SupportsInlineQuery(); + + using var stream = new MemoryStream() { Capacity = 512 * 1024 }; + using (var gzipStream = new BufferedStream(new GZipStream(stream, CompressionLevel.Fastest, true), 256 * 1024)) + { + if (useInlineQuery) + { + using var textWriter = new StreamWriter(gzipStream, Encoding.UTF8, 4 * 1024, true); + textWriter.WriteLine(query); + query = null; // Query was already written to POST body + } + + using var writer = new ExtendedBinaryWriter(gzipStream); + using var streamer = new BinaryStreamWriter(writer); + foreach (var row in rows) + { + for (var i = 0; i < row.Length; i++) + { + streamer.Write(columnTypes[i], row[i]); + } + } + } + stream.Seek(0, SeekOrigin.Begin); + + await connection.PostStreamAsync(query, stream, true, token).ConfigureAwait(false); + Interlocked.Add(ref rowsWritten, rows.Count); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/BinaryStreamReader.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/BinaryStreamReader.cs new file mode 100644 index 00000000..ac9a6819 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/BinaryStreamReader.cs @@ -0,0 +1,127 @@ +using System; +using System.Net; +using System.Numerics; +using System.Text; +using ClickHouse.Client.Types; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client.Formats +{ + internal class BinaryStreamReader : IDisposable, ISerializationTypeVisitorReader + { + private readonly ExtendedBinaryReader reader; + + public BinaryStreamReader(ExtendedBinaryReader reader) + { + this.reader = reader ?? throw new ArgumentNullException(nameof(reader)); + } + + public void Dispose() => reader.Dispose(); + + public object Read(ClickHouseType type) => type.AcceptRead(this); + + public object Read(LowCardinalityType lowCardinalityType) => Read(lowCardinalityType.UnderlyingType); + + public object Read(FixedStringType fixedStringType) => Encoding.UTF8.GetString(reader.ReadBytes(fixedStringType.Length)); + + public object Read(Int8Type int8Type) => reader.ReadSByte(); + + public object Read(UInt32Type uInt32Type) => reader.ReadUInt32(); + + public object Read(Int32Type int32Type) => reader.ReadInt32(); + + public object Read(UInt16Type uInt16Type) => reader.ReadUInt16(); + + public object Read(Int16Type int16Type) => reader.ReadInt16(); + + public object Read(UInt8Type uInt8Type) => reader.ReadByte(); + + public object Read(NothingType nothingType) => null; + + public object Read(ArrayType arrayType) + { + var length = reader.Read7BitEncodedInt(); + var data = arrayType.MakeArray(length); + for (var i = 0; i < length; i++) + { + data.SetValue(ClearDBNull(Read(arrayType.UnderlyingType)), i); + } + return data; + } + + public object Read(DateTimeType dateTimeType) => TypeConverter.DateTimeEpochStart.AddSeconds(reader.ReadUInt32()); + + public object Read(DecimalType decimalType) + { + switch (decimalType.Size) + { + case 4: + return (decimal)reader.ReadInt32() / decimalType.Exponent; + case 8: + return (decimal)reader.ReadInt64() / decimalType.Exponent; + default: + var bigInt = new BigInteger(reader.ReadBytes(decimalType.Size)); + return (decimal)bigInt / decimalType.Exponent; + } + } + + public object Read(NullableType nullableType) => reader.ReadByte() > 0 ? DBNull.Value : Read(nullableType.UnderlyingType); + + public object Read(TupleType tupleType) + { + var count = tupleType.UnderlyingTypes.Length; + var contents = new object[count]; + for (var i = 0; i < count; i++) + { + var value = Read(tupleType.UnderlyingTypes[i]); + contents[i] = ClearDBNull(value); + } + return tupleType.MakeTuple(contents); + } + + public object Read(StringType stringType) => reader.ReadString(); + + public object Read(UuidType uuidType) + { + // Byte manipulation because of ClickHouse's weird GUID/UUID implementation + var bytes = new byte[16]; + reader.Read(bytes, 6, 2); + reader.Read(bytes, 4, 2); + reader.Read(bytes, 0, 4); + reader.Read(bytes, 8, 8); + Array.Reverse(bytes, 8, 8); + return new Guid(bytes); + } + + public object Read(Float32Type float32Type) => reader.ReadSingle(); + + public object Read(Int64Type int64Type) => reader.ReadInt64(); + + public object Read(UInt64Type uInt64Type) => reader.ReadUInt64(); + + public object Read(Float64Type float64Type) => reader.ReadDouble(); + + public object Read(IPv4Type pv4Type) + { + var ipv4bytes = reader.ReadBytes(4); + Array.Reverse(ipv4bytes); + return new IPAddress(ipv4bytes); + } + + public object Read(IPv6Type pv6Type) => new IPAddress(reader.ReadBytes(16)); + + public object Read(DateTime64Type dateTimeType) => TypeConverter.DateTimeEpochStart.AddTicks(MathUtils.ShiftDecimalPlaces(reader.ReadInt64(), 7 - dateTimeType.Scale)); + + public object Read(DateType dateType) => TypeConverter.DateTimeEpochStart.AddDays(reader.ReadUInt16()); + + public object Read(Enum8Type enumType) => enumType.Lookup(reader.ReadSByte()); + + public object Read(Enum16Type enumType) => enumType.Lookup(reader.ReadInt16()); + + public object Read(EnumType enumType) => enumType.Lookup(reader.ReadSByte()); + + public object Read(NestedType tupleType) => throw new NotSupportedException(); + + private static object ClearDBNull(object value) => value is DBNull ? null : value; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/BinaryStreamWriter.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/BinaryStreamWriter.cs new file mode 100644 index 00000000..63ba0e53 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/BinaryStreamWriter.cs @@ -0,0 +1,206 @@ +using System; +using System.Collections; +using System.Globalization; +using System.Net; +using System.Numerics; +using System.Runtime.CompilerServices; +using System.Text; +using ClickHouse.Client.Types; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client.Formats +{ + internal class BinaryStreamWriter : IDisposable, ISerializationTypeVisitorWriter + { + private readonly ExtendedBinaryWriter writer; + + public BinaryStreamWriter(ExtendedBinaryWriter writer) + { + this.writer = writer ?? throw new ArgumentNullException(nameof(writer)); + } + + public void Dispose() => writer.Dispose(); + + internal void Write(ClickHouseType clickHouseType, object value) => clickHouseType.AcceptWrite(this, value); + + private void WriteLargeDecimal(DecimalType dti, decimal value) + { + var bigInt = new BigInteger(value); + byte[] bigIntBytes = bigInt.ToByteArray(); + byte[] decimalBytes = new byte[dti.Size]; + bigIntBytes.CopyTo(decimalBytes, 0); + + // If a negative BigInteger is not long enough to fill the whole buffer, the remainder needs to be filled with 0xFF + if (bigInt < 0) + { + for (int i = bigIntBytes.Length; i < dti.Size; i++) + decimalBytes[i] = 0xFF; + } + writer.Write(decimalBytes); + } + + public void Write(LowCardinalityType lowCardinalityType, object value) => Write(lowCardinalityType.UnderlyingType, value); + + public void Write(FixedStringType fixedStringType, object value) + { + var @string = Convert.ToString(value, CultureInfo.InvariantCulture); + var stringBytes = new byte[fixedStringType.Length]; + Encoding.UTF8.GetBytes(@string, 0, @string.Length, stringBytes, 0); + writer.Write(stringBytes); + } + + public void Write(Int8Type int8Type, object value) => writer.Write(Convert.ToSByte(value)); + + public void Write(UInt32Type uInt32Type, object value) => writer.Write(Convert.ToUInt32(value)); + + public void Write(Enum8Type enumType, object value) + { + var enumIndex = value is string enumStr ? (sbyte)enumType.Lookup(enumStr) : Convert.ToSByte(value); + writer.Write(enumIndex); + } + + public void Write(Int32Type int32Type, object value) => writer.Write(Convert.ToInt32(value)); + + public void Write(Float32Type float32Type, object value) => writer.Write(Convert.ToSingle(value)); + + public void Write(IPv4Type pv4Type, object value) + { + var address4 = ExtractIPAddress(value); + if (address4.AddressFamily != System.Net.Sockets.AddressFamily.InterNetwork) + { + throw new ArgumentException($"Expected IPv4, got {address4.AddressFamily}"); + } + + var ipv4bytes = address4.GetAddressBytes(); + Array.Reverse(ipv4bytes); + writer.Write(ipv4bytes, 0, ipv4bytes.Length); + } + + public void Write(Float64Type float64Type, object value) => writer.Write(Convert.ToDouble(value)); + + public void Write(StringType stringType, object value) => writer.Write(Convert.ToString(value, CultureInfo.InvariantCulture)); + + public void Write(UuidType uuidType, object value) + { + var guid = ExtractGuid(value); + var bytes = guid.ToByteArray(); + Array.Reverse(bytes, 8, 8); + writer.Write(bytes, 6, 2); + writer.Write(bytes, 4, 2); + writer.Write(bytes, 0, 4); + writer.Write(bytes, 8, 8); + } + + public void Write(Enum16Type enumType, object value) + { + var enumIndex = value is string enumStr ? (short)enumType.Lookup(enumStr) : Convert.ToInt16(value); + writer.Write(enumIndex); + } + + public void Write(UInt16Type uInt16Type, object value) => writer.Write(Convert.ToUInt16(value)); + + public void Write(NothingType nothingType, object value) { } + + public void Write(Int16Type int16Type, object value) => writer.Write(Convert.ToInt16(value)); + + public void Write(UInt8Type uInt8Type, object value) => writer.Write(Convert.ToByte(value)); + + public void Write(Int64Type int64Type, object value) => writer.Write(Convert.ToInt64(value)); + + public void Write(UInt64Type uInt64Type, object value) => writer.Write(Convert.ToUInt64(value)); + + public void Write(ArrayType arrayType, object value) + { + var collection = (IList)value; + writer.Write7BitEncodedInt(collection.Count); + for (var i = 0; i < collection.Count; i++) + { + Write(arrayType.UnderlyingType, collection[i]); + } + } + + public void Write(DateTime64Type dateTime64Type, object value) + { + var dateTimeOffset = dateTime64Type.ToDateTimeOffset((DateTime)value); + var ticks = (dateTimeOffset.UtcDateTime - TypeConverter.DateTimeEpochStart).Ticks; + // 7 is a 'magic constant' - Log10 of TimeSpan.TicksInSecond + writer.Write(MathUtils.ShiftDecimalPlaces(ticks, dateTime64Type.Scale - 7)); + } + + public void Write(NullableType nullableType, object value) + { + if (value == null || value is DBNull) + { + writer.Write((byte)1); + } + else + { + writer.Write((byte)0); + Write(nullableType.UnderlyingType, value); + } + } + + public void Write(IPv6Type pv6Type, object value) + { + var address6 = ExtractIPAddress(value); + if (address6.AddressFamily != System.Net.Sockets.AddressFamily.InterNetworkV6) + { + throw new ArgumentException($"Expected IPv6, got {address6.AddressFamily}"); + } + + var ipv6bytes = address6.GetAddressBytes(); + writer.Write(ipv6bytes, 0, ipv6bytes.Length); + } + + public void Write(TupleType tupleType, object value) + { + var tuple = (ITuple)value; + for (var i = 0; i < tuple.Length; i++) + { + Write(tupleType.UnderlyingTypes[i], tuple[i]); + } + } + + public void Write(DateTimeType dateTimeType, object value) + { + var dateTimeOffset = dateTimeType.ToDateTimeOffset((DateTime)value); + var seconds = (uint)(dateTimeOffset.UtcDateTime - TypeConverter.DateTimeEpochStart).TotalSeconds; + writer.Write(seconds); + } + + public void Write(DecimalType decimalType, object value) + { + decimal multipliedValue = Convert.ToDecimal(value) * decimalType.Exponent; + switch (decimalType.Size) + { + case 4: + writer.Write((int)multipliedValue); + break; + case 8: + writer.Write((long)multipliedValue); + break; + default: + WriteLargeDecimal(decimalType, multipliedValue); + break; + } + } + + public void Write(DateType dateTimeType, object value) + { + var days = (ushort)(((DateTime)value).Date - TypeConverter.DateTimeEpochStart).TotalDays; + writer.Write(days); + } + + public void Write(EnumType enumType, object value) + { + var enumIndex = value is string enumStr ? (sbyte)enumType.Lookup(enumStr) : Convert.ToSByte(value); + writer.Write(enumIndex); + } + + public void Write(NestedType nestedType, object value) => throw new NotSupportedException("Writing Nested values directly is not supported, see documentation"); + + private static IPAddress ExtractIPAddress(object data) => data is IPAddress a ? a : IPAddress.Parse((string)data); + + private static Guid ExtractGuid(object data) => data is Guid g ? g : new Guid((string)data); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/ExtendedBinaryReader.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/ExtendedBinaryReader.cs new file mode 100644 index 00000000..9294a9f0 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/ExtendedBinaryReader.cs @@ -0,0 +1,43 @@ +using System.IO; +using System.Text; + +namespace ClickHouse.Client.Formats +{ + internal class ExtendedBinaryReader : BinaryReader + { + private readonly PeekableStreamWrapper streamWrapper; + + public ExtendedBinaryReader(Stream stream) + : base(new PeekableStreamWrapper(stream), Encoding.UTF8, false) + { + streamWrapper = (PeekableStreamWrapper)BaseStream; + } + + public new int Read7BitEncodedInt() => base.Read7BitEncodedInt(); + + public override byte[] ReadBytes(int count) + { + var buffer = new byte[count]; + var bytesRead = base.Read(buffer, 0, count); + if (bytesRead < count) + { + throw new EndOfStreamException($"Expected to read {count} bytes, got {bytesRead}"); + } + + return buffer; + } + + public override int Read(byte[] buffer, int index, int count) + { + var bytesRead = base.Read(buffer, index, count); + if (bytesRead < count) + { + throw new EndOfStreamException($"Expected to read {count} bytes, got {bytesRead}"); + } + + return bytesRead; + } + + public override int PeekChar() => streamWrapper.Peek(); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/ExtendedBinaryWriter.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/ExtendedBinaryWriter.cs new file mode 100644 index 00000000..9bb29b0f --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/ExtendedBinaryWriter.cs @@ -0,0 +1,13 @@ +using System.IO; +using System.Text; + +namespace ClickHouse.Client.Formats +{ + public class ExtendedBinaryWriter : BinaryWriter + { + public ExtendedBinaryWriter(Stream stream) + : base(stream, Encoding.UTF8, false) { } + + public new void Write7BitEncodedInt(int i) => base.Write7BitEncodedInt(i); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/HttpParameterFormatter.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/HttpParameterFormatter.cs new file mode 100644 index 00000000..bbcd8556 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/HttpParameterFormatter.cs @@ -0,0 +1,87 @@ +using System; +using System.Collections; +using System.Globalization; +using System.Linq; +using System.Runtime.CompilerServices; +using ClickHouse.Client.ADO.Parameters; +using ClickHouse.Client.Types; + +namespace ClickHouse.Client.Formats +{ + public static class HttpParameterFormatter + { + public static string Format(ClickHouseDbParameter parameter) + { + if (parameter.Value is null) + return string.Empty; + var type = string.IsNullOrWhiteSpace(parameter.ClickHouseType) + ? TypeConverter.ToClickHouseType(parameter.Value.GetType()) + : TypeConverter.ParseClickHouseType(parameter.ClickHouseType); + return Format(type, parameter.Value); + } + + internal static string Format(ClickHouseType type, object value) + { + return type.TypeCode switch + { + var simpleType when + simpleType == ClickHouseTypeCode.UInt8 || + simpleType == ClickHouseTypeCode.UInt16 || + simpleType == ClickHouseTypeCode.UInt32 || + simpleType == ClickHouseTypeCode.UInt64 || + simpleType == ClickHouseTypeCode.Int8 || + simpleType == ClickHouseTypeCode.Int16 || + simpleType == ClickHouseTypeCode.Int32 || + simpleType == ClickHouseTypeCode.Int64 => Convert.ToString(value, CultureInfo.InvariantCulture), + + var floatType when + floatType == ClickHouseTypeCode.Float32 || + floatType == ClickHouseTypeCode.Float64 => FormatFloat(value), + + ClickHouseTypeCode.Decimal when value is decimal decimalValue => decimalValue.ToString(CultureInfo.InvariantCulture), + + var stringType when + stringType == ClickHouseTypeCode.String || + stringType == ClickHouseTypeCode.FixedString || + stringType == ClickHouseTypeCode.LowCardinality || + stringType == ClickHouseTypeCode.Enum8 || + stringType == ClickHouseTypeCode.Enum16 || + stringType == ClickHouseTypeCode.UUID || + stringType == ClickHouseTypeCode.IPv4 || + stringType == ClickHouseTypeCode.IPv6 => value.ToString(), + + ClickHouseTypeCode.Nothing => $"null", + + ClickHouseTypeCode.Date when value is DateTime date => $"{date:yyyy-MM-dd}", + ClickHouseTypeCode.DateTime when type is DateTimeType dateTimeType && value is DateTime dateTime => + dateTimeType.TimeZone == null + ? $"{dateTime:yyyy-MM-dd HH:mm:ss}" + : $"{dateTime.ToUniversalTime():yyyy-MM-dd HH:mm:ss}", + ClickHouseTypeCode.DateTime64 when type is DateTime64Type dateTimeType && value is DateTime dateTime => + dateTimeType.TimeZone == null + ? $"{dateTime:yyyy-MM-dd HH:mm:ss.fffffff}" + : $"{dateTime.ToUniversalTime():yyyy-MM-dd HH:mm:ss.fffffff}", + + ClickHouseTypeCode.Nullable when type is NullableType nullableType => value is null || value is DBNull ? "null" : $"{Format(nullableType.UnderlyingType, value)}", + + ClickHouseTypeCode.Array when type is ArrayType arrayType && value is IEnumerable enumerable => + $"[{string.Join(",", enumerable.Cast().Select(obj => InlineParameterFormatter.Format(arrayType.UnderlyingType, obj)))}]", + + ClickHouseTypeCode.Tuple when type is TupleType tupleType && value is ITuple tuple => + $"({string.Join(",", tupleType.UnderlyingTypes.Select((x, i) => InlineParameterFormatter.Format(x, tuple[i])))})", + + _ => throw new NotSupportedException($"Cannot convert value {value} to type {type.TypeCode}") + }; + } + + private static string FormatFloat(object value) + { + return value switch + { + float floatValue => floatValue.ToString(CultureInfo.InvariantCulture), + double doubleValue => doubleValue.ToString(CultureInfo.InvariantCulture), + _ => throw new NotSupportedException($"Cannot convert value {value} to float type") + }; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/InlineParameterFormatter.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/InlineParameterFormatter.cs new file mode 100644 index 00000000..7d0facb7 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/InlineParameterFormatter.cs @@ -0,0 +1,123 @@ +using System; +using System.Collections; +using System.Globalization; +using System.Linq; +using System.Net; +using System.Runtime.CompilerServices; +using ClickHouse.Client.ADO.Parameters; +using ClickHouse.Client.Types; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client.Formats +{ + public static class InlineParameterFormatter + { + public static string Format(ClickHouseDbParameter parameter) + { + if (parameter.Value is null) + return string.Empty; + var type = string.IsNullOrWhiteSpace(parameter.ClickHouseType) + ? TypeConverter.ToClickHouseType(parameter.Value.GetType()) + : TypeConverter.ParseClickHouseType(parameter.ClickHouseType); + return Format(type, parameter.Value); + } + + internal static string Format(ClickHouseType type, object value) + { + switch (type.TypeCode) + { + case ClickHouseTypeCode.UInt8: + case ClickHouseTypeCode.UInt16: + case ClickHouseTypeCode.UInt32: + case ClickHouseTypeCode.UInt64: + case ClickHouseTypeCode.Int8: + case ClickHouseTypeCode.Int16: + case ClickHouseTypeCode.Int32: + case ClickHouseTypeCode.Int64: + return Convert.ToString(value, CultureInfo.InvariantCulture); + + case ClickHouseTypeCode.Float32: + case ClickHouseTypeCode.Float64: + return FormatFloat(value); + + case ClickHouseTypeCode.Decimal: + return FormatDecimal(type, value); + + case ClickHouseTypeCode.String: + case ClickHouseTypeCode.FixedString: + case ClickHouseTypeCode.LowCardinality: + case ClickHouseTypeCode.Enum8: + case ClickHouseTypeCode.Enum16: + return value.ToString().Escape(); + case ClickHouseTypeCode.UUID: + return $"toUUID({value.ToString().Escape()})"; + + case ClickHouseTypeCode.Nothing: + return "null"; + + case ClickHouseTypeCode.Date when value is DateTime: + return $"toDate('{value:yyyy-MM-dd}')"; + + case ClickHouseTypeCode.DateTime when type is AbstractDateTimeType dateTimeType && value is DateTime dateTime: + if (dateTimeType.TimeZone != null) + { + dateTime = dateTime.ToUniversalTime(); + } + return $"toDateTime('{dateTime:yyyy-MM-dd HH:mm:ss}')"; + + case ClickHouseTypeCode.DateTime64 when type is DateTime64Type dateTimeType && value is DateTime dateTime: + if (dateTimeType.TimeZone != null) + dateTime = dateTime.ToUniversalTime(); + return $"toDateTime64('{dateTime:yyyy-MM-dd HH:mm:ss.fffffff}', 7)"; + + case ClickHouseTypeCode.IPv4: return $"toIPv4({FormatIPAddress(value)})"; + case ClickHouseTypeCode.IPv6: return $"toIPv6({FormatIPAddress(value)})"; + + case ClickHouseTypeCode.Nullable: + var nullableType = (NullableType)type; + return value is null || value is DBNull ? "null" : $"{Format(nullableType.UnderlyingType, value)}"; + + case ClickHouseTypeCode.Array: + var arrayType = (ArrayType)type; + var array = ((IEnumerable)value).Cast().Select(obj => Format(arrayType.UnderlyingType, obj)); + return $"[{string.Join(",", array)}]"; + + case ClickHouseTypeCode.Tuple: + var tupleType = (TupleType)type; + var tuple = (ITuple)value; + return $"({string.Join(",", tupleType.UnderlyingTypes.Select((x, i) => Format(x, tuple[i])))})"; + + default: + throw new NotSupportedException($"Cannot convert value {value} to type {type.TypeCode}"); + } + } + + private static object FormatIPAddress(object value) => value switch + { + IPAddress ipAddress => ipAddress.ToString().Escape(), + string str => str, + _ => throw new NotSupportedException($"Cannot convert value {value} to IP address") + }; + + private static string FormatFloat(object value) => value switch + { + float floatValue => floatValue.ToString(CultureInfo.InvariantCulture), + double doubleValue => doubleValue.ToString(CultureInfo.InvariantCulture), + _ => Convert.ToDouble(value).ToString(CultureInfo.InvariantCulture) + }; + + private static string FormatDecimal(ClickHouseType type, object value) + { + if (!(value is decimal decimalValue)) + decimalValue = Convert.ToDecimal(value); + return type switch + { + Decimal128Type decimal128Type => $"toDecimal128({decimalValue.ToString(CultureInfo.InvariantCulture)},{decimal128Type.Scale})", + Decimal64Type decimal64Type => $"toDecimal64({decimalValue.ToString(CultureInfo.InvariantCulture)},{decimal64Type.Scale})", + Decimal32Type decimal32Type => $"toDecimal32({decimalValue.ToString(CultureInfo.InvariantCulture)},{decimal32Type.Scale})", + DecimalType _ => decimalValue.ToString(CultureInfo.InvariantCulture), + _ => throw new NotSupportedException($"Cannot convert value {value} to decimal type") + }; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/PeekableStreamWrapper.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/PeekableStreamWrapper.cs new file mode 100644 index 00000000..8040e933 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Formats/PeekableStreamWrapper.cs @@ -0,0 +1,79 @@ +using System; +using System.IO; + +namespace ClickHouse.Client.Formats +{ + /// + /// Universal stream wrapper allowing to add 'PeekChar' support to streams with CanSeek=false + /// Suggestions for better solution wanted + /// + internal class PeekableStreamWrapper : Stream, IDisposable + { + private readonly Stream stream; + private bool hasReadAheadByte; + private int readAheadByte; + + public PeekableStreamWrapper(Stream stream) + { + this.stream = stream; + hasReadAheadByte = false; + readAheadByte = 0; + } + + public override bool CanRead => stream.CanRead; + + public override bool CanSeek => stream.CanSeek; + + public override bool CanWrite => stream.CanWrite; + + public override long Length => stream.Length; + + public override long Position { get => stream.Position; set => stream.Position = value; } + + public override void Flush() => stream.Flush(); + + public override int Read(byte[] buffer, int offset, int count) + { + if (buffer is null) + throw new ArgumentNullException(nameof(buffer)); + if (count == 0) + return 0; + var b = ReadByte(); + if (b == -1) + throw new EndOfStreamException(); + buffer[offset] = (byte)b; + var result = 1; + if (count > 1) + result += stream.Read(buffer, offset + 1, count - 1); + return result; + } + + public override long Seek(long offset, SeekOrigin origin) => stream.Seek(offset, origin); + + public override void SetLength(long value) => stream.SetLength(value); + + public override void Write(byte[] buffer, int offset, int count) => stream.Write(buffer, offset, count); + + public override int ReadByte() + { + if (!hasReadAheadByte) + { + return stream.ReadByte(); + } + hasReadAheadByte = false; + return readAheadByte; + } + + public int Peek() + { + if (!hasReadAheadByte) + { + readAheadByte = stream.ReadByte(); + hasReadAheadByte = true; + } + return readAheadByte; + } + + public new void Dispose() => stream.Dispose(); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/GlobalSuppressions.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/GlobalSuppressions.cs new file mode 100644 index 00000000..f5096dd3 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/GlobalSuppressions.cs @@ -0,0 +1,6 @@ +// This file is used by Code Analysis to maintain SuppressMessage +// attributes that are applied to this project. +// Project-level suppressions either have no target or are given +// a specific target and scoped to a namespace, type, member, etc. + +[assembly: System.Diagnostics.CodeAnalysis.SuppressMessage("StyleCop.CSharp.LayoutRules", "SA1503:Braces should not be omitted", Justification = "This is a valid code style")] diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/IClickHouseCommand.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/IClickHouseCommand.cs new file mode 100644 index 00000000..260a1866 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/IClickHouseCommand.cs @@ -0,0 +1,15 @@ +using System.Data; +using System.Threading; +using System.Threading.Tasks; +using ClickHouse.Client.ADO; +using ClickHouse.Client.ADO.Parameters; + +namespace ClickHouse.Client +{ + public interface IClickHouseCommand : IDbCommand + { + new ClickHouseDbParameter CreateParameter(); + + Task ExecuteRawResultAsync(CancellationToken cancellationToken); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/IClickHouseConnection.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/IClickHouseConnection.cs new file mode 100644 index 00000000..1d7fd1fe --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/IClickHouseConnection.cs @@ -0,0 +1,10 @@ +using System.Data; +using ClickHouse.Client.ADO; + +namespace ClickHouse.Client +{ + public interface IClickHouseConnection : IDbConnection + { + new ClickHouseCommand CreateCommand(); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Properties/Resources.Designer.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Properties/Resources.Designer.cs new file mode 100644 index 00000000..3754a746 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Properties/Resources.Designer.cs @@ -0,0 +1,108 @@ +//------------------------------------------------------------------------------ +// +// This code was generated by a tool. +// Runtime Version:4.0.30319.42000 +// +// Changes to this file may cause incorrect behavior and will be lost if +// the code is regenerated. +// +//------------------------------------------------------------------------------ + +namespace ClickHouse.Client.Properties { + using System; + + + /// + /// A strongly-typed resource class, for looking up localized strings, etc. + /// + // This class was auto-generated by the StronglyTypedResourceBuilder + // class via a tool like ResGen or Visual Studio. + // To add or remove a member, edit your .ResX file then rerun ResGen + // with the /str option, or rebuild your VS project. + [global::System.CodeDom.Compiler.GeneratedCodeAttribute("System.Resources.Tools.StronglyTypedResourceBuilder", "16.0.0.0")] + [global::System.Diagnostics.DebuggerNonUserCodeAttribute()] + [global::System.Runtime.CompilerServices.CompilerGeneratedAttribute()] + internal class Resources { + + private static global::System.Resources.ResourceManager resourceMan; + + private static global::System.Globalization.CultureInfo resourceCulture; + + [global::System.Diagnostics.CodeAnalysis.SuppressMessageAttribute("Microsoft.Performance", "CA1811:AvoidUncalledPrivateCode")] + internal Resources() { + } + + /// + /// Returns the cached ResourceManager instance used by this class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Resources.ResourceManager ResourceManager { + get { + if (object.ReferenceEquals(resourceMan, null)) { + global::System.Resources.ResourceManager temp = new global::System.Resources.ResourceManager("ClickHouse.Client.Properties.Resources", typeof(Resources).Assembly); + resourceMan = temp; + } + return resourceMan; + } + } + + /// + /// Overrides the current thread's CurrentUICulture property for all + /// resource lookups using this strongly typed resource class. + /// + [global::System.ComponentModel.EditorBrowsableAttribute(global::System.ComponentModel.EditorBrowsableState.Advanced)] + internal static global::System.Globalization.CultureInfo Culture { + get { + return resourceCulture; + } + set { + resourceCulture = value; + } + } + + /// + /// Looks up a localized string similar to Destination table not set. + /// + internal static string DestinationTableNotSetMessage { + get { + return ResourceManager.GetString("DestinationTableNotSetMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Internal error. + /// + internal static string InternalErrorMessage { + get { + return ResourceManager.GetString("InternalErrorMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to Invalid count of columns in row. + /// + internal static string InvalidNumberOfColumnsInRowMessage { + get { + return ResourceManager.GetString("InvalidNumberOfColumnsInRowMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to JSON driver does not support fetching schema. + /// + internal static string JsonDoesNotSupportSchemaMessage { + get { + return ResourceManager.GetString("JsonDoesNotSupportSchemaMessage", resourceCulture); + } + } + + /// + /// Looks up a localized string similar to String is too large to fit in FixedString. + /// + internal static string StringIsTooLargeForFixedStringMessage { + get { + return ResourceManager.GetString("StringIsTooLargeForFixedStringMessage", resourceCulture); + } + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Properties/Resources.resx b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Properties/Resources.resx new file mode 100644 index 00000000..28c239e9 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Properties/Resources.resx @@ -0,0 +1,135 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + Destination table not set + + + Internal error + + + Invalid count of columns in row + + + JSON driver does not support fetching schema + + + String is too large to fit in FixedString + + \ No newline at end of file diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/AbstractDateTimeType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/AbstractDateTimeType.cs new file mode 100644 index 00000000..2f4134be --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/AbstractDateTimeType.cs @@ -0,0 +1,53 @@ +using System; +using NodaTime; +using NodaTime.TimeZones; + +namespace ClickHouse.Client.Types +{ + internal abstract class AbstractDateTimeType : ParameterizedType + { + public override Type FrameworkType => typeof(DateTime); + + public DateTimeZone TimeZone { get; set; } + + public DateTimeOffset ToDateTimeOffset(DateTime dateTime) + { + switch (dateTime.Kind) + { + case DateTimeKind.Local: + case DateTimeKind.Utc: + var instant = Instant.FromDateTimeUtc(dateTime.ToUniversalTime()); + var offset = TimeZone.GetUtcOffset(instant); + return instant.WithOffset(offset).ToDateTimeOffset(); + case DateTimeKind.Unspecified: + if (TimeZone == null) + { + return dateTime; + } + + var zonedDateTime = TimeZone.ResolveLocal(LocalDateTime.FromDateTime(dateTime), Resolvers.LenientResolver); + return zonedDateTime.ToDateTimeOffset(); + } + throw new ArgumentOutOfRangeException("Unknown DateTime kind: " + dateTime.Kind.ToString()); + } + + //public DateTime ToUtc(DateTime dateTime) + //{ + // switch (dateTime.Kind) + // { + // case DateTimeKind.Local: + // case DateTimeKind.Utc: + // return dateTime.ToUniversalTime(); + // case DateTimeKind.Unspecified: + // if (TimeZone == null) + // return dateTime; + + // var zonedDateTime = TimeZone.ResolveLocal(LocalDateTime.FromDateTime(dateTime), Resolvers.LenientResolver); + // return zonedDateTime.ToDateTimeUtc(); + // } + // throw new ArgumentOutOfRangeException("Unknown DateTime kind: " + dateTime.Kind.ToString()); + //} + + public override string ToString() => TimeZone == null ? $"{Name}" : $"{Name}({TimeZone.Id})"; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ArrayType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ArrayType.cs new file mode 100644 index 00000000..ac237631 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ArrayType.cs @@ -0,0 +1,32 @@ +using System; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class ArrayType : ParameterizedType + { + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Array; + + public ClickHouseType UnderlyingType { get; set; } + + public override Type FrameworkType => UnderlyingType.FrameworkType.MakeArrayType(); + + public override string Name => "Array"; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + return new ArrayType + { + UnderlyingType = parseClickHouseTypeFunc(node.SingleChild), + }; + } + + public Array MakeArray(int length) => Array.CreateInstance(UnderlyingType.FrameworkType, length); + + public override string ToString() => $"Array({UnderlyingType})"; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ClickHouseType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ClickHouseType.cs new file mode 100644 index 00000000..5bc38ddb --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ClickHouseType.cs @@ -0,0 +1,17 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal abstract class ClickHouseType : ISerializationTypeVisitorAcceptor + { + public abstract ClickHouseTypeCode TypeCode { get; } + + public abstract Type FrameworkType { get; } + + public abstract object AcceptRead(ISerializationTypeVisitorReader reader); + + public abstract void AcceptWrite(ISerializationTypeVisitorWriter writer, object value); + + public override string ToString() => TypeCode.ToString(); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ClickHouseTypeCode.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ClickHouseTypeCode.cs new file mode 100644 index 00000000..e1761ace --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ClickHouseTypeCode.cs @@ -0,0 +1,43 @@ +namespace ClickHouse.Client.Types +{ +#pragma warning disable CA1720 // Identifier contains type name + public enum ClickHouseTypeCode + { + Nothing, + + UInt8, + UInt16, + UInt32, + UInt64, + + Int8, + Int16, + Int32, + Int64, + + Float32, + Float64, + Decimal, + + Date, + DateTime, + DateTime64, + + Enum8, + Enum16, + + String, + FixedString, + + UUID, + IPv4, + IPv6, + + Array, + Nested, + Tuple, + Nullable, + LowCardinality, + } +#pragma warning restore CA1720 // Identifier contains type name +} \ No newline at end of file diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateTime64Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateTime64Type.cs new file mode 100644 index 00000000..a13d42dd --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateTime64Type.cs @@ -0,0 +1,38 @@ +using System; +using ClickHouse.Client.Types.Grammar; +using NodaTime; + +namespace ClickHouse.Client.Types +{ + internal class DateTime64Type : AbstractDateTimeType + { + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.DateTime64; + + public override Type FrameworkType => typeof(DateTime); + + public int Scale { get; set; } + + public override string ToString() => TimeZone == null ? $"DateTime64({Scale})" : $"DateTime64({Scale}, {TimeZone.Id})"; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + var scale = int.Parse(node.ChildNodes[0].Value); + var timeZone = DateTimeZone.Utc; + if (node.ChildNodes.Count > 1) + { + var timeZoneName = node.ChildNodes[1].Value.Trim('\''); + timeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timeZoneName) ?? DateTimeZone.Utc; + } + + return new DateTime64Type + { + TimeZone = timeZone, + Scale = scale, + }; + } + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateTimeType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateTimeType.cs new file mode 100644 index 00000000..2e2d3ef0 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateTimeType.cs @@ -0,0 +1,23 @@ +using System; +using ClickHouse.Client.Types.Grammar; +using NodaTime; + +namespace ClickHouse.Client.Types +{ + internal class DateTimeType : AbstractDateTimeType + { + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.DateTime; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + var timeZoneName = node.ChildNodes.Count > 0 ? node.SingleChild.Value.Trim('\'') : string.Empty; + var timeZone = DateTimeZoneProviders.Tzdb.GetZoneOrNull(timeZoneName) ?? DateTimeZone.Utc; + + return new DateTimeType { TimeZone = timeZone }; + } + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateType.cs new file mode 100644 index 00000000..3ff59f35 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DateType.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class DateType : DateTimeType + { + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Date; + + public override Type FrameworkType => typeof(DateTime); + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal128Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal128Type.cs new file mode 100644 index 00000000..c47497aa --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal128Type.cs @@ -0,0 +1,27 @@ +using System; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class Decimal128Type : DecimalType + { + public Decimal128Type() + { + Precision = 38; + } + + public override int Size => 16; + + public override string Name => "Decimal128"; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + return new Decimal128Type + { + Scale = int.Parse(node.SingleChild.Value), + }; + } + + public override string ToString() => $"{Name}({Scale})"; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal32Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal32Type.cs new file mode 100644 index 00000000..350aa070 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal32Type.cs @@ -0,0 +1,27 @@ +using System; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class Decimal32Type : DecimalType + { + public Decimal32Type() + { + Precision = 9; + } + + public override string Name => "Decimal32"; + + public override int Size => 4; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + return new Decimal32Type + { + Scale = int.Parse(node.SingleChild.Value), + }; + } + + public override string ToString() => $"{Name}({Scale})"; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal64Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal64Type.cs new file mode 100644 index 00000000..0c2d49bf --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Decimal64Type.cs @@ -0,0 +1,24 @@ +using System; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class Decimal64Type : DecimalType + { + public Decimal64Type() + { + Precision = 18; + } + + public override int Size => 8; + + public override string Name => "Decimal64"; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) => new Decimal64Type + { + Scale = int.Parse(node.SingleChild.Value), + }; + + public override string ToString() => $"{Name}({Scale})"; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DecimalType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DecimalType.cs new file mode 100644 index 00000000..323dad4d --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/DecimalType.cs @@ -0,0 +1,76 @@ +using System; +using ClickHouse.Client.Types.Grammar; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client.Types +{ + internal class DecimalType : ParameterizedType + { + private int scale; + + public virtual int Precision { get; set; } + + /// + /// Gets or sets the decimal 'scale' (precision) in ClickHouse + /// + public int Scale + { + get => scale; + set + { + scale = value; + Exponent = MathUtils.ToPower(10, value); + } + } + + /// + /// Gets decimal exponent value based on Scale + /// + public long Exponent { get; private set; } + + public override string Name => "Decimal"; + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Decimal; + + /// + /// Gets size of type in bytes + /// + public virtual int Size => GetSizeFromPrecision(Precision); + + public override Type FrameworkType => typeof(decimal); + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + var precision = int.Parse(node.ChildNodes[0].Value); + var scale = int.Parse(node.ChildNodes[1].Value); + + var size = GetSizeFromPrecision(precision); + + switch (size) + { + case 4: + return new Decimal32Type { Precision = precision, Scale = scale }; + case 8: + return new Decimal64Type { Precision = precision, Scale = scale }; + case 16: + return new Decimal128Type { Precision = precision, Scale = scale }; + default: + return new DecimalType { Precision = precision, Scale = scale }; + } + } + + public override string ToString() => $"{Name}({Precision}, {Scale})"; + + private int GetSizeFromPrecision(int precision) => precision switch + { + int p when p >= 1 && p < 10 => 4, + int p when p >= 10 && p < 19 => 8, + int p when p >= 19 && p < 39 => 16, + _ => throw new ArgumentOutOfRangeException(nameof(Precision)), + }; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Enum16Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Enum16Type.cs new file mode 100644 index 00000000..3892cc6f --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Enum16Type.cs @@ -0,0 +1,13 @@ +namespace ClickHouse.Client.Types +{ + internal class Enum16Type : EnumType + { + public override string Name => "Enum16"; + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Enum16; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Enum8Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Enum8Type.cs new file mode 100644 index 00000000..9a2cf81e --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Enum8Type.cs @@ -0,0 +1,11 @@ +namespace ClickHouse.Client.Types +{ + internal class Enum8Type : EnumType + { + public override string Name => "Enum8"; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/EnumType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/EnumType.cs new file mode 100644 index 00000000..b81c436a --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/EnumType.cs @@ -0,0 +1,46 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class EnumType : ParameterizedType + { + private Dictionary values = new Dictionary(); + + public override string Name => "Enum"; + + public override Type FrameworkType => typeof(string); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Enum8; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + var parameters = node.ChildNodes + .Select(cn => cn.Value) + .Select(p => p.Split('=')) + .ToDictionary(kvp => kvp[0].Trim().Trim('\''), kvp => Convert.ToInt32(kvp[1].Trim())); + + switch (node.Value) + { + case "Enum": + case "Enum8": + return new Enum8Type { values = parameters }; + case "Enum16": + return new Enum16Type { values = parameters }; + default: throw new ArgumentOutOfRangeException(); + } + } + + public int Lookup(string key) => values[key]; + + public string Lookup(int value) => values.SingleOrDefault(kvp => kvp.Value == value).Key ?? throw new KeyNotFoundException(); + + public override string ToString() => $"{Name}({string.Join(",", values.Select(kvp => kvp.Key + "=" + kvp.Value))}"; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/FixedStringType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/FixedStringType.cs new file mode 100644 index 00000000..9eca03a6 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/FixedStringType.cs @@ -0,0 +1,30 @@ +using System; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class FixedStringType : ParameterizedType + { + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.FixedString; + + public int Length { get; set; } + + public override Type FrameworkType => typeof(string); + + public override string Name => "FixedString"; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + return new FixedStringType + { + Length = int.Parse(node.SingleChild.Value), + }; + } + + public override string ToString() => $"FixedString{Length}"; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Float32Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Float32Type.cs new file mode 100644 index 00000000..a87af532 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Float32Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class Float32Type : ClickHouseType + { + public override Type FrameworkType => typeof(float); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Float32; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Float64Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Float64Type.cs new file mode 100644 index 00000000..2ee536ac --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Float64Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class Float64Type : ClickHouseType + { + public override Type FrameworkType => typeof(double); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Float64; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/Parser.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/Parser.cs new file mode 100644 index 00000000..ae277eb0 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/Parser.cs @@ -0,0 +1,36 @@ +using System.Collections.Generic; +using System.Linq; + +namespace ClickHouse.Client.Types.Grammar +{ + public static class Parser + { + public static SyntaxTreeNode Parse(string input) + { + var tokens = Tokenizer.GetTokens(input).ToList(); + var stack = new Stack(); + SyntaxTreeNode current = null; + + foreach (var token in tokens) + { + switch (token) + { + case "(": + stack.Push(current); + break; + case ",": + stack.Peek().ChildNodes.Add(current); + break; + case ")": + stack.Peek().ChildNodes.Add(current); + current = stack.Pop(); + break; + default: + current = new SyntaxTreeNode { Value = token }; + break; + } + } + return current; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/SyntaxTreeNode.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/SyntaxTreeNode.cs new file mode 100644 index 00000000..40b6d502 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/SyntaxTreeNode.cs @@ -0,0 +1,28 @@ +using System; +using System.Collections.Generic; +using System.Text; + +namespace ClickHouse.Client.Types.Grammar +{ + public class SyntaxTreeNode + { + public string Value { get; set; } + + public IList ChildNodes { get; } = new List(); + + public SyntaxTreeNode SingleChild => ChildNodes.Count == 1 ? ChildNodes[0] : throw new ArgumentOutOfRangeException(); + + public override string ToString() + { + var builder = new StringBuilder(); + builder.Append(Value); + if (ChildNodes.Count > 0) + { + builder.Append("("); + builder.Append(string.Join(", ", ChildNodes)); + builder.Append(")"); + } + return builder.ToString(); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/Tokenizer.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/Tokenizer.cs new file mode 100644 index 00000000..5d350bd2 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Grammar/Tokenizer.cs @@ -0,0 +1,35 @@ +using System.Collections.Generic; + +namespace ClickHouse.Client.Types.Grammar +{ + public static class Tokenizer + { + private static char[] breaks = new[] { ',', '(', ')' }; + + public static IEnumerable GetTokens(string input) + { + var start = 0; + var len = input.Length; + + while (start < len) + { + var nextBreak = input.IndexOfAny(breaks, start); + if (nextBreak == start) + { + start++; + yield return input.Substring(nextBreak, 1); + } + else if (nextBreak == -1) + { + yield return input.Substring(start).Trim(); + yield break; + } + else + { + yield return input.Substring(start, nextBreak - start).Trim(); + start = nextBreak; + } + } + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/IPv4Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/IPv4Type.cs new file mode 100644 index 00000000..80b5b0a6 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/IPv4Type.cs @@ -0,0 +1,16 @@ +using System; +using System.Net; + +namespace ClickHouse.Client.Types +{ + internal class IPv4Type : ClickHouseType + { + public override Type FrameworkType => typeof(IPAddress); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.IPv4; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/IPv6Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/IPv6Type.cs new file mode 100644 index 00000000..6ea43468 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/IPv6Type.cs @@ -0,0 +1,16 @@ +using System; +using System.Net; + +namespace ClickHouse.Client.Types +{ + internal class IPv6Type : ClickHouseType + { + public override Type FrameworkType => typeof(IPAddress); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.IPv6; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorAcceptor.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorAcceptor.cs new file mode 100644 index 00000000..de5c3ba4 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorAcceptor.cs @@ -0,0 +1,9 @@ +namespace ClickHouse.Client.Types +{ + internal interface ISerializationTypeVisitorAcceptor + { + void AcceptWrite(ISerializationTypeVisitorWriter writer, object value); + + object AcceptRead(ISerializationTypeVisitorReader reader); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorReader.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorReader.cs new file mode 100644 index 00000000..7720a61b --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorReader.cs @@ -0,0 +1,61 @@ +namespace ClickHouse.Client.Types +{ + internal interface ISerializationTypeVisitorReader + { + object Read(LowCardinalityType lowCardinalityType); + + object Read(FixedStringType fixedStringType); + + object Read(Int8Type int8Type); + + object Read(UInt32Type uInt32Type); + + object Read(Int32Type int32Type); + + object Read(DateType dateType); + + object Read(UInt16Type uInt16Type); + + object Read(Int16Type int16Type); + + object Read(NothingType nothingType); + + object Read(UInt8Type uInt8Type); + + object Read(ArrayType arrayType); + + object Read(DateTimeType dateTimeType); + + object Read(DateTime64Type dateTimeType); + + object Read(DecimalType decimalType); + + object Read(NullableType nullableType); + + object Read(Enum8Type enumType); + + object Read(Enum16Type enumType); + + object Read(TupleType tupleType); + + object Read(NestedType nestedType); + + object Read(IPv4Type pv4Type); + + object Read(Float32Type float32Type); + + object Read(Int64Type int64Type); + + object Read(UuidType uuidType); + + object Read(StringType stringType); + + object Read(UInt64Type uInt64Type); + + object Read(Float64Type float64Type); + + object Read(IPv6Type pv6Type); + + object Read(EnumType enumType); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorWriter.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorWriter.cs new file mode 100644 index 00000000..7e9b0fa0 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ISerializationTypeVisitorWriter.cs @@ -0,0 +1,61 @@ +namespace ClickHouse.Client.Types +{ + internal interface ISerializationTypeVisitorWriter + { + void Write(LowCardinalityType lowCardinalityType, object value); + + void Write(Int8Type int8Type, object value); + + void Write(UInt32Type uInt32Type, object value); + + void Write(Enum8Type enum8Type, object value); + + void Write(Int32Type int32Type, object value); + + void Write(Float32Type float32Type, object value); + + void Write(IPv4Type pv4Type, object value); + + void Write(Float64Type float64Type, object value); + + void Write(StringType stringType, object value); + + void Write(UuidType uuidType, object value); + + void Write(Enum16Type enum16Type, object value); + + void Write(UInt16Type uInt16Type, object value); + + void Write(NothingType nothingType, object value); + + void Write(Int16Type int16Type, object value); + + void Write(UInt8Type uInt8Type, object value); + + void Write(FixedStringType fixedStringType, object value); + + void Write(Int64Type int64Type, object value); + + void Write(UInt64Type uInt64Type, object value); + + void Write(ArrayType arrayType, object value); + + void Write(DateTime64Type dateTime64Type, object value); + + void Write(NullableType nullableType, object value); + + void Write(IPv6Type pv6Type, object value); + + void Write(TupleType tupleType, object value); + + void Write(NestedType nestedType, object value); + + void Write(DateType dateTimeType, object value); + + void Write(DateTimeType dateTimeType, object value); + + void Write(DecimalType decimalType, object value); + + void Write(EnumType enumType, object value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int16Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int16Type.cs new file mode 100644 index 00000000..c07f9026 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int16Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class Int16Type : ClickHouseType + { + public override Type FrameworkType => typeof(short); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Int16; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int32Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int32Type.cs new file mode 100644 index 00000000..edd9f5d2 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int32Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class Int32Type : ClickHouseType + { + public override Type FrameworkType => typeof(int); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Int32; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int64Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int64Type.cs new file mode 100644 index 00000000..ab2cca98 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int64Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class Int64Type : ClickHouseType + { + public override Type FrameworkType => typeof(long); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Int64; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int8Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int8Type.cs new file mode 100644 index 00000000..eecc6ccd --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/Int8Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class Int8Type : ClickHouseType + { + public override Type FrameworkType => typeof(sbyte); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Int8; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/LowCardinalityType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/LowCardinalityType.cs new file mode 100644 index 00000000..1c18af35 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/LowCardinalityType.cs @@ -0,0 +1,30 @@ +using System; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class LowCardinalityType : ParameterizedType + { + public ClickHouseType UnderlyingType { get; set; } + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.LowCardinality; + + public override string Name => "LowCardinality"; + + public override Type FrameworkType => UnderlyingType.FrameworkType; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + return new LowCardinalityType + { + UnderlyingType = parseClickHouseTypeFunc(node.SingleChild), + }; + } + + public override string ToString() => $"{Name}({UnderlyingType})"; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NestedType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NestedType.cs new file mode 100644 index 00000000..8cfce6ac --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NestedType.cs @@ -0,0 +1,25 @@ +using System; +using System.Linq; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class NestedType : TupleType + { + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Nested; + + public override Type FrameworkType => base.FrameworkType.MakeArrayType(); + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + return new NestedType + { + UnderlyingTypes = node.ChildNodes.Select(parseClickHouseTypeFunc).ToArray(), + }; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NothingType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NothingType.cs new file mode 100644 index 00000000..6b5a4e75 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NothingType.cs @@ -0,0 +1,17 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class NothingType : ClickHouseType + { + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Nothing; + + public override Type FrameworkType => typeof(DBNull); + + public override string ToString() => "Nothing"; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NullableType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NullableType.cs new file mode 100644 index 00000000..a3bdc18b --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/NullableType.cs @@ -0,0 +1,37 @@ +using System; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal class NullableType : ParameterizedType + { + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Nullable; + + public ClickHouseType UnderlyingType { get; set; } + + public override Type FrameworkType + { + get + { + var underlyingFrameworkType = UnderlyingType.FrameworkType; + return underlyingFrameworkType.IsValueType ? typeof(Nullable<>).MakeGenericType(underlyingFrameworkType) : underlyingFrameworkType; + } + } + + public override string Name => "Nullable"; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + return new NullableType + { + UnderlyingType = parseClickHouseTypeFunc(node.SingleChild), + }; + } + + public override string ToString() => $"{Name}({UnderlyingType})"; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ParameterizedType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ParameterizedType.cs new file mode 100644 index 00000000..0078c2bb --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/ParameterizedType.cs @@ -0,0 +1,12 @@ +using System; +using ClickHouse.Client.Types.Grammar; + +namespace ClickHouse.Client.Types +{ + internal abstract class ParameterizedType : ClickHouseType + { + public virtual string Name => TypeCode.ToString(); + + public abstract ParameterizedType Parse(SyntaxTreeNode typeName, Func parseClickHouseTypeFunc); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/StringType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/StringType.cs new file mode 100644 index 00000000..d5457723 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/StringType.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class StringType : ClickHouseType + { + public override Type FrameworkType => typeof(string); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.String; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/TupleType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/TupleType.cs new file mode 100644 index 00000000..0fcc2f84 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/TupleType.cs @@ -0,0 +1,77 @@ +using System; +using System.Linq; +using System.Runtime.CompilerServices; +using ClickHouse.Client.Types.Grammar; +using ClickHouse.Client.Utility; + +namespace ClickHouse.Client.Types +{ + internal class TupleType : ParameterizedType + { + private Type frameworkType; + private ClickHouseType[] underlyingTypes; + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.Tuple; + + public ClickHouseType[] UnderlyingTypes + { + get => underlyingTypes; + set + { + underlyingTypes = value; + frameworkType = DeviseFrameworkType(underlyingTypes); + } + } + + private static Type DeviseFrameworkType(ClickHouseType[] underlyingTypes) + { + var count = underlyingTypes.Length; + if (count > 7) + return typeof(LargeTuple); + + var typeArgs = new Type[count]; + for (var i = 0; i < count; i++) + { + typeArgs[i] = underlyingTypes[i].FrameworkType; + } + var genericType = Type.GetType("System.Tuple`" + typeArgs.Length); + return genericType.MakeGenericType(typeArgs); + } + + public ITuple MakeTuple(params object[] values) + { + var count = values.Length; + if (underlyingTypes.Length != count) + throw new ArgumentException($"Count of tuple type elements ({underlyingTypes.Length}) does not match number of elements ({count})"); + + if (count > 7) + return new LargeTuple(values); + + var valuesCopy = new object[count]; + + // Coerce the values into types which can be stored in the tuple + for (int i = 0; i < count; i++) + { + valuesCopy[i] = UnderlyingTypes[i].FrameworkType.IsSubclassOf(typeof(IConvertible)) ? Convert.ChangeType(values[i], UnderlyingTypes[i].FrameworkType) : values[i]; + } + + return (ITuple)Activator.CreateInstance(frameworkType, valuesCopy); + } + + public override Type FrameworkType => frameworkType; + + public override ParameterizedType Parse(SyntaxTreeNode node, Func parseClickHouseTypeFunc) + { + return new TupleType + { + UnderlyingTypes = node.ChildNodes.Select(parseClickHouseTypeFunc).ToArray(), + }; + } + + public override string ToString() => $"{Name}({string.Join(",", UnderlyingTypes.Select(t => t.ToString()))})"; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/TypeConverter.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/TypeConverter.cs new file mode 100644 index 00000000..f22d7c60 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/TypeConverter.cs @@ -0,0 +1,152 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; +using ClickHouse.Client.Types.Grammar; + +[assembly: InternalsVisibleTo("ClickHouse.Client.Tests")] // assembly-level tag to expose below classes to tests + +namespace ClickHouse.Client.Types +{ + internal static class TypeConverter + { + public static readonly DateTime DateTimeEpochStart = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc); + + private static readonly IDictionary SimpleTypes = new Dictionary(); + private static readonly IDictionary ParameterizedTypes = new Dictionary(); + private static readonly IDictionary ReverseMapping = new Dictionary(); + + public static IEnumerable RegisteredTypes => SimpleTypes.Keys + .Concat(ParameterizedTypes.Values.Select(t => t.TypeCode)) + .OrderBy(x => x) + .ToArray(); + + static TypeConverter() + { + // Integral types + RegisterPlainType(); + RegisterPlainType(); + RegisterPlainType(); + RegisterPlainType(); + + RegisterPlainType(); + RegisterPlainType(); + RegisterPlainType(); + RegisterPlainType(); + + // Floating point types + RegisterPlainType(); + RegisterPlainType(); + + // Special types + RegisterPlainType(); + RegisterPlainType(); + RegisterPlainType(); + + // String types + RegisterPlainType(); + RegisterParameterizedType(); + + // DateTime types + RegisterPlainType(); + RegisterParameterizedType(); + RegisterParameterizedType(); + + // Special 'nothing' type + RegisterPlainType(); + + // complex types like Tuple/Array/Nested etc. + RegisterParameterizedType(); + RegisterParameterizedType(); + RegisterParameterizedType(); + RegisterParameterizedType(); + RegisterParameterizedType(); + + RegisterParameterizedType(); + RegisterParameterizedType(); + RegisterParameterizedType(); + RegisterParameterizedType(); + + RegisterParameterizedType(); + RegisterParameterizedType(); + RegisterParameterizedType(); + + ReverseMapping.Add(typeof(decimal), new Decimal128Type()); + ReverseMapping[typeof(DateTime)] = new DateTimeType(); + } + + private static void RegisterPlainType() + where T : ClickHouseType, new() + { + var type = new T(); + SimpleTypes.Add(type.TypeCode, type); + if (!ReverseMapping.ContainsKey(type.FrameworkType)) + { + ReverseMapping.Add(type.FrameworkType, type); + } + } + + private static void RegisterParameterizedType() + where T : ParameterizedType, new() + { + var t = new T(); + ParameterizedTypes.Add(t.Name, t); + } + + public static ClickHouseType ParseClickHouseType(string type) + { + var node = Parser.Parse(type); + return ParseClickHouseType(node); + } + + internal static ClickHouseType ParseClickHouseType(SyntaxTreeNode node) + { + if ( + node.ChildNodes.Count == 0 && + Enum.TryParse(node.Value, out var chType) && + SimpleTypes.TryGetValue(chType, out var typeInfo)) + { + return typeInfo; + } + + if (ParameterizedTypes.ContainsKey(node.Value)) + { + return ParameterizedTypes[node.Value].Parse(node, ParseClickHouseType); + } + + throw new ArgumentException("Unknown type: " + node.ToString()); + } + + /// + /// Recursively build ClickHouse type from .NET complex type + /// Supports nullable and arrays. + /// + /// framework type to map + /// Corresponding ClickHouse type + public static ClickHouseType ToClickHouseType(Type type) + { + if (ReverseMapping.ContainsKey(type)) + { + return ReverseMapping[type]; + } + + if (type.IsArray) + { + return new ArrayType() { UnderlyingType = ToClickHouseType(type.GetElementType()) }; + } + + var underlyingType = Nullable.GetUnderlyingType(type); + if (underlyingType != null) + { + return new NullableType() { UnderlyingType = ToClickHouseType(underlyingType) }; + } + + if (type.IsGenericType && type.GetGenericTypeDefinition().FullName.StartsWith("System.Tuple")) + { + return new TupleType { UnderlyingTypes = type.GetGenericArguments().Select(ToClickHouseType).ToArray() }; + } + + throw new ArgumentOutOfRangeException(nameof(type), "Unknown type: " + type.ToString()); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt16Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt16Type.cs new file mode 100644 index 00000000..de47065c --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt16Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class UInt16Type : ClickHouseType + { + public override Type FrameworkType => typeof(ushort); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.UInt16; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt32Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt32Type.cs new file mode 100644 index 00000000..65dcd8af --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt32Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class UInt32Type : ClickHouseType + { + public override Type FrameworkType => typeof(uint); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.UInt32; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt64Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt64Type.cs new file mode 100644 index 00000000..4d9f01d5 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt64Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class UInt64Type : ClickHouseType + { + public override Type FrameworkType => typeof(ulong); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.UInt64; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt8Type.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt8Type.cs new file mode 100644 index 00000000..47d4c4d3 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UInt8Type.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class UInt8Type : ClickHouseType + { + public override Type FrameworkType => typeof(byte); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.UInt8; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UuidType.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UuidType.cs new file mode 100644 index 00000000..2d254d35 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Types/UuidType.cs @@ -0,0 +1,15 @@ +using System; + +namespace ClickHouse.Client.Types +{ + internal class UuidType : ClickHouseType + { + public override Type FrameworkType => typeof(Guid); + + public override ClickHouseTypeCode TypeCode => ClickHouseTypeCode.UUID; + + public override object AcceptRead(ISerializationTypeVisitorReader reader) => reader.Read(this); + + public override void AcceptWrite(ISerializationTypeVisitorWriter writer, object value) => writer.Write(this, value); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/CommandExtensions.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/CommandExtensions.cs new file mode 100644 index 00000000..26cf890b --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/CommandExtensions.cs @@ -0,0 +1,27 @@ +using ClickHouse.Client.ADO; +using ClickHouse.Client.ADO.Parameters; + +namespace ClickHouse.Client.Utility +{ + public static class CommandExtensions + { + public static ClickHouseDbParameter AddParameter(this ClickHouseCommand command, string parameterName, object parameterValue) + { + var parameter = command.CreateParameter(); + parameter.ParameterName = parameterName; + parameter.Value = parameterValue; + command.Parameters.Add(parameter); + return parameter; + } + + public static ClickHouseDbParameter AddParameter(this ClickHouseCommand command, string parameterName, string clickHouseType, object parameterValue) + { + var parameter = command.CreateParameter(); + parameter.ParameterName = parameterName; + parameter.ClickHouseType = clickHouseType; + parameter.Value = parameterValue; + command.Parameters.Add(parameter); + return parameter; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/CompressedContent.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/CompressedContent.cs new file mode 100644 index 00000000..78e559b0 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/CompressedContent.cs @@ -0,0 +1,63 @@ +using System; +using System.IO; +using System.IO.Compression; +using System.Net; +using System.Net.Http; +using System.Threading.Tasks; + +/// +/// Originally sourced from https://stackoverflow.com/questions/16673714/how-to-compress-http-request-on-the-fly-and-without-loading-compressed-buffer-in +/// +namespace ClickHouse.Client.Utility +{ + public class CompressedContent : HttpContent + { + private readonly HttpContent originalContent; + private readonly DecompressionMethods compressionMethod; + + public CompressedContent(HttpContent content, DecompressionMethods compressionMethod) + { + originalContent = content ?? throw new ArgumentNullException("content"); + this.compressionMethod = compressionMethod; + + if (this.compressionMethod != DecompressionMethods.GZip && this.compressionMethod != DecompressionMethods.Deflate) + { + throw new ArgumentException(string.Format($"Compression '{compressionMethod}' is not supported. Valid types: GZip, Deflate"), nameof(compressionMethod)); + } + + foreach (var header in originalContent.Headers) + { + Headers.TryAddWithoutValidation(header.Key, header.Value); + } + + Headers.ContentEncoding.Add(this.compressionMethod.ToString().ToLowerInvariant()); + } + + protected override void Dispose(bool disposing) + { + if (disposing) + { + originalContent?.Dispose(); + } + base.Dispose(disposing); + } + + protected override bool TryComputeLength(out long length) + { + length = -1; + return false; + } + + protected override async Task SerializeToStreamAsync(Stream stream, TransportContext context) + { + using Stream compressedStream = compressionMethod switch + { + DecompressionMethods.GZip => new GZipStream(stream, CompressionLevel.Fastest, leaveOpen: true), + DecompressionMethods.Deflate => new DeflateStream(stream, CompressionMode.Compress, leaveOpen: true), + _ => throw new ArgumentOutOfRangeException(nameof(compressionMethod)) + }; + + await originalContent.CopyToAsync(compressedStream); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/ConnectionExtensions.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/ConnectionExtensions.cs new file mode 100644 index 00000000..5544db59 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/ConnectionExtensions.cs @@ -0,0 +1,42 @@ +using System.Data; +using System.Data.Common; +using System.Threading.Tasks; +using ClickHouse.Client.ADO.Adapters; + +namespace ClickHouse.Client.Utility +{ + public static class ConnectionExtensions + { + public static async Task ExecuteStatementAsync(this DbConnection connection, string sql) + { + using var command = connection.CreateCommand(); + command.CommandText = sql; + return await command.ExecuteNonQueryAsync().ConfigureAwait(false); + } + + public static async Task ExecuteScalarAsync(this DbConnection connection, string sql) + { + using var command = connection.CreateCommand(); + command.CommandText = sql; + return await command.ExecuteScalarAsync().ConfigureAwait(false); + } + + public static async Task ExecuteReaderAsync(this DbConnection connection, string sql) + { + using var command = connection.CreateCommand(); + command.CommandText = sql; + return await command.ExecuteReaderAsync().ConfigureAwait(false); + } + + public static DataTable ExecuteDataTable(this DbConnection connection, string sql) + { + using var command = connection.CreateCommand(); + using var adapter = new ClickHouseDataAdapter(); + command.CommandText = sql; + adapter.SelectCommand = command; + var dataTable = new DataTable(); + adapter.Fill(dataTable); + return dataTable; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/DataReaderExtensions.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/DataReaderExtensions.cs new file mode 100644 index 00000000..c53d175a --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/DataReaderExtensions.cs @@ -0,0 +1,44 @@ +using System.Collections.Generic; +using System.Data; +using ClickHouse.Client.ADO.Readers; +using ClickHouse.Client.Types; + +namespace ClickHouse.Client.Utility +{ + public static class DataReaderExtensions + { + public static string[] GetColumnNames(this IDataReader reader) + { + var count = reader.FieldCount; + var names = new string[count]; + for (int i = 0; i < count; i++) + { + names[i] = reader.GetName(i); + } + + return names; + } + + internal static ClickHouseType[] GetClickHouseColumnTypes(this ClickHouseDataReader reader) + { + var count = reader.FieldCount; + var names = new ClickHouseType[count]; + for (int i = 0; i < count; i++) + { + names[i] = reader.GetClickHouseType(i); + } + + return names; + } + + internal static IEnumerable AsEnumerable(this IDataReader reader) + { + while (reader.Read()) + { + var values = new object[reader.FieldCount]; + reader.GetValues(values); + yield return values; + } + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/DictionaryExtensions.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/DictionaryExtensions.cs new file mode 100644 index 00000000..9c780b01 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/DictionaryExtensions.cs @@ -0,0 +1,23 @@ +using System.Collections.Generic; + +namespace ClickHouse.Client.Utility +{ + public static class DictionaryExtensions + { + public static bool TryAdd(this IDictionary dictionary, TKey key, TValue value) + { + if (dictionary.ContainsKey(key)) + return false; + dictionary.Add(key, value); + return true; + } + + public static void Set(this IDictionary dictionary, TKey key, TValue value) + { + if (dictionary.ContainsKey(key)) + dictionary[key] = value; + else + dictionary.Add(key, value); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/EnumerableExtensions.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/EnumerableExtensions.cs new file mode 100644 index 00000000..ee7d29f9 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/EnumerableExtensions.cs @@ -0,0 +1,41 @@ +using System; +using System.Collections.Generic; + +namespace ClickHouse.Client.Utility +{ + public static class EnumerableExtensions + { + public static IEnumerable> Batch(this IEnumerable source, int size) + { + T[] bucket = null; + var count = 0; + + foreach (var item in source) + { + if (bucket == null) + { + bucket = new T[size]; + } + + bucket[count++] = item; + + if (count != size) + { + continue; + } + + yield return bucket; + + bucket = null; + count = 0; + } + + // Return the last bucket with all remaining elements + if (bucket != null && count > 0) + { + Array.Resize(ref bucket, count); + yield return bucket; + } + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/LargeTuple.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/LargeTuple.cs new file mode 100644 index 00000000..5949ed4d --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/LargeTuple.cs @@ -0,0 +1,25 @@ +using System.Collections; +using System.Collections.Generic; +using System.Linq; +using System.Runtime.CompilerServices; + +namespace ClickHouse.Client.Utility +{ + internal class LargeTuple : ITuple + { + private readonly object[] items; + + public LargeTuple(params object[] items) + { + this.items = items; + } + + public object this[int index] => items[index]; + + public int Length => items.Length; + + public int Count => items.Length; + + public IEnumerator GetEnumerator() => items.Cast().GetEnumerator(); + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/MathUtils.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/MathUtils.cs new file mode 100644 index 00000000..01115810 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/MathUtils.cs @@ -0,0 +1,50 @@ +using System; + +namespace ClickHouse.Client.Utility +{ + public static class MathUtils + { + public static long ToPower(int value, int power) + { + long result = 1; + while (power > 0) + { + if ((power & 1) == 1) + { + result *= value; + } + + power >>= 1; + if (power <= 0) + { + break; + } + + value *= value; + } + return result; + } + + public static long ShiftDecimalPlaces(long value, int places) + { + if (places == 0) + { + return value; + } + + var factor = ToPower(10, Math.Abs(places)); + return places < 0 ? value / factor : value * factor; + } + + public static decimal ShiftDecimalPlaces(decimal value, int places) + { + if (places == 0) + { + return value; + } + + var factor = ToPower(10, Math.Abs(places)); + return places < 0 ? value / factor : value * factor; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/NameValueCollectionExtensions.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/NameValueCollectionExtensions.cs new file mode 100644 index 00000000..2347b3eb --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/NameValueCollectionExtensions.cs @@ -0,0 +1,19 @@ +using System.Collections.Specialized; + +namespace ClickHouse.Client.Utility +{ + public static class NameValueCollectionExtensions + { + public static void SetOrRemove(this NameValueCollection collection, string name, string value) + { + if (!string.IsNullOrEmpty(value)) + { + collection.Set(name, value); + } + else + { + collection.Remove(name); + } + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/SchemaDescriber.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/SchemaDescriber.cs new file mode 100644 index 00000000..3aaf8ec0 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/SchemaDescriber.cs @@ -0,0 +1,152 @@ +using System; +using System.Data; +using System.Linq; +using System.Text; +using ClickHouse.Client.ADO; +using ClickHouse.Client.ADO.Adapters; +using ClickHouse.Client.ADO.Readers; +using ClickHouse.Client.Types; + +namespace ClickHouse.Client.Utility +{ + internal static class SchemaDescriber + { + private static readonly string[] Columns = + { + "ColumnName", + "ColumnOrdinal", + "ColumnSize", + "NumericPrecision", + "NumericScale", + "DataType", + "ProviderType", + "IsLong", + "AllowDBNull", + "IsReadOnly", + "IsRowVersion", + "IsUnique", + "IsKey", + "IsAutoIncrement", + "BaseCatalogName", + "BaseSchemaName", + "BaseTableName", + "BaseColumnName", + "AutoIncrementSeed", + "AutoIncrementStep", + "DefaultValue", + "Expression", + "ColumnMapping", + "BaseTableNamespace", + "BaseColumnNamespace", + }; + + public static DataTable DescribeSchema(this ClickHouseDataReader reader) + { + var result = new DataTable(); + foreach (var columnName in Columns) + result.Columns.Add(columnName); + for (int i = 0; i < reader.FieldCount; i++) + result.Rows.Add(reader.DescribeColumn(i)); + + return result; + } + + private static object[] DescribeColumn(this ClickHouseDataReader reader, int ordinal) + { + var chType = reader.GetClickHouseType(ordinal); + + var result = new object[Columns.Length]; + result[0] = reader.GetName(ordinal); // ColumnName + result[1] = ordinal; // ColumnOrdinal + result[2] = null; // ColumnSize + result[3] = null; // NumericPrecision + result[4] = null; // NumericScale + result[5] = chType.FrameworkType; // DataType + result[6] = chType.ToString(); // ProviderType + result[7] = chType.TypeCode == ClickHouseTypeCode.String; // IsLong + result[8] = chType.TypeCode == ClickHouseTypeCode.Nullable; // AllowDBNull + result[9] = true; // IsReadOnly + result[10] = false; // IsRowVersion + result[11] = false; // IsUnique + result[12] = false; // IsKey + result[13] = false; // IsAutoIncrement + result[14] = null; // BaseCatalogName + result[15] = null; // BaseSchemaName + result[16] = null; // BaseTableName + result[17] = reader.GetName(ordinal); // BaseColumnName + result[18] = null; // AutoIncrementSeed + result[19] = null; // AutoIncrementStep + result[20] = null; // DefaultValue + result[21] = null; // Expression + result[22] = MappingType.Element; // ColumnMapping + result[23] = null; // BaseTableNamespace + result[24] = null; // BaseColumnNamespace + + if (chType is DecimalType dt) + { + result[2] = dt.Size; + result[3] = dt.Precision; + result[4] = dt.Scale; + } + + return result; + } + + public static DataTable DescribeSchema(this ClickHouseConnection connection, string type, string[] restrictions) => type switch + { + "Columns" => DescribeColumns(connection, restrictions), + "Tables"=> DescribeTables(connection,restrictions), + _ => throw new NotSupportedException(), + }; + + private static DataTable DescribeTables(ClickHouseConnection connection, string[] restrictions) + { + var command = connection.CreateCommand(); + var query = new StringBuilder("show tables in"); + var database = restrictions != null && restrictions.Length > 0 ? restrictions[0] : null; + query.Append($" {database}"); + command.CommandText = query.ToString(); + using var adapter = new ClickHouseDataAdapter(); + adapter.SelectCommand = command; + DataTable result = new DataTable(); + adapter.Fill(result); + return result; + } + + private static DataTable DescribeColumns(ClickHouseConnection connection, string[] restrictions) + { + var command = connection.CreateCommand(); + var query = new StringBuilder("SELECT database as Database, table as Table, name as Name, type as ProviderType, type as DataType FROM system.columns"); + var database = restrictions != null && restrictions.Length > 0 ? restrictions[0] : null; + var table = restrictions != null && restrictions.Length > 1 ? restrictions[1] : null; + + if (database != null) + { + query.Append(" WHERE database={database:String}"); + command.AddParameter("database", "String", database); + } + + if (table != null) + { + query.Append(" AND table={table:String}"); + command.AddParameter("table", "String", table); + } + + command.CommandText = query.ToString(); + using var adapter = new ClickHouseDataAdapter(); + adapter.SelectCommand = command; + var result = new DataTable(); + adapter.Fill(result); + + foreach (var row in result.Rows.Cast()) + { + var clickHouseType = TypeConverter.ParseClickHouseType((string)row["ProviderType"]); + row["ProviderType"] = clickHouseType.ToString(); + // TODO: this should return actual framework type like other implementations do + row["DataType"] = clickHouseType.FrameworkType.ToString().Replace("System.", string.Empty); + } + + return result; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/StringExtensions.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/StringExtensions.cs new file mode 100644 index 00000000..97cc2327 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/ClickHouse.Client/Utility/StringExtensions.cs @@ -0,0 +1,29 @@ +using System.Text; + +namespace ClickHouse.Client.Utility +{ + public static class StringExtensions + { + public static string Escape(this string str) => "'" + str.Replace("\\", "\\\\").Replace("\'", "\\\'") + "'"; + + /// + /// Encloses column name in backticks (`). Escapes ` symbol if met inside name + /// Does nothing if column is already enclosed + /// + /// Column name + /// Backticked column name + public static string EncloseColumnName(this string str) + { + if (string.IsNullOrEmpty(str)) + return str; + if (str[0] == '`' && str[str.Length - 1] == '`') + return str; // Early return if already enclosed + + var builder = new StringBuilder(); + builder.Append('`'); + builder.Append(str.Replace("`", "\\`")); + builder.Append('`'); + return builder.ToString(); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseAssemblyInitializer.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseAssemblyInitializer.cs new file mode 100644 index 00000000..3b8ef575 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseAssemblyInitializer.cs @@ -0,0 +1,17 @@ +using FastReport.Utils; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FastReport.ClickHouse +{ + public class ClickHouseAssemblyInitializer : AssemblyInitializerBase + { + public ClickHouseAssemblyInitializer() + { + RegisteredObjects.AddConnection(typeof(ClickHouseDataConnection),"ClickHouse"); + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnection.DesignExt.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnection.DesignExt.cs new file mode 100644 index 00000000..46e0472d --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnection.DesignExt.cs @@ -0,0 +1,37 @@ +using ClickHouse.Client.ADO; +using ClickHouse.Client.Types; +using FastReport.Data; +using FastReport.Data.ConnectionEditors; +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FastReport.ClickHouse +{ + public partial class ClickHouseDataConnection + { + public override Type GetParameterType() + { + return typeof(ClickHouseTypeCode); + } + public override ConnectionEditorBase GetEditor() + { + return new ClickHouseConnectionEditor(); + } + public override string GetConnectionId() + { + ClickHouseConnectionStringBuilder builder = new ClickHouseConnectionStringBuilder(ConnectionString); + string info = ""; + try + { + info = builder.Database; + } + catch + { + } + return "ClickHouse: " + info; + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.Designer.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.Designer.cs new file mode 100644 index 00000000..c1c49977 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.Designer.cs @@ -0,0 +1,196 @@ +namespace FastReport.Data +{ + partial class ClickHouseConnectionEditor + { + /// + /// Required designer variable. + /// + private System.ComponentModel.IContainer components = null; + + /// + /// Clean up any resources being used. + /// + /// true if managed resources should be disposed; otherwise, false. + protected override void Dispose(bool disposing) + { + if (disposing && (components != null)) + { + components.Dispose(); + } + base.Dispose(disposing); + } + + #region Component Designer generated code + + /// + /// Required method for Designer support - do not modify + /// the contents of this method with the code editor. + /// + private void InitializeComponent() + { + this.label1 = new FastReport.Controls.LabelLine(); + this.gbServer = new System.Windows.Forms.GroupBox(); + this.tbPort = new System.Windows.Forms.TextBox(); + this.port = new System.Windows.Forms.Label(); + this.lblServer = new System.Windows.Forms.Label(); + this.tbServer = new System.Windows.Forms.TextBox(); + this.tbUserName = new System.Windows.Forms.TextBox(); + this.tbPassword = new System.Windows.Forms.TextBox(); + this.lblUserName = new System.Windows.Forms.Label(); + this.lblPassword = new System.Windows.Forms.Label(); + this.gbDatabase = new System.Windows.Forms.GroupBox(); + this.lblDatabase = new System.Windows.Forms.Label(); + this.tbDatabase = new System.Windows.Forms.TextBox(); + this.gbServer.SuspendLayout(); + this.gbDatabase.SuspendLayout(); + this.SuspendLayout(); + // + // label1 + // + this.label1.Location = new System.Drawing.Point(8, 278); + this.label1.Name = "label1"; + this.label1.Size = new System.Drawing.Size(320, 17); + this.label1.TabIndex = 2; + // + // gbServer + // + this.gbServer.Controls.Add(this.tbPort); + this.gbServer.Controls.Add(this.port); + this.gbServer.Controls.Add(this.lblServer); + this.gbServer.Controls.Add(this.tbServer); + this.gbServer.Controls.Add(this.tbUserName); + this.gbServer.Controls.Add(this.tbPassword); + this.gbServer.Controls.Add(this.lblUserName); + this.gbServer.Controls.Add(this.lblPassword); + this.gbServer.Location = new System.Drawing.Point(8, 16); + this.gbServer.Name = "gbServer"; + this.gbServer.Size = new System.Drawing.Size(320, 156); + this.gbServer.TabIndex = 4; + this.gbServer.TabStop = false; + this.gbServer.Text = "Server"; + // + // tbPort + // + this.tbPort.Location = new System.Drawing.Point(120, 74); + this.tbPort.Name = "tbPort"; + this.tbPort.Size = new System.Drawing.Size(188, 20); + this.tbPort.TabIndex = 6; + // + // port + // + this.port.AutoSize = true; + this.port.Location = new System.Drawing.Point(12, 78); + this.port.Name = "port"; + this.port.Size = new System.Drawing.Size(66, 13); + this.port.TabIndex = 5; + this.port.Text = "Server port:"; + // + // lblServer + // + this.lblServer.AutoSize = true; + this.lblServer.Location = new System.Drawing.Point(12, 20); + this.lblServer.Name = "lblServer"; + this.lblServer.Size = new System.Drawing.Size(72, 13); + this.lblServer.TabIndex = 4; + this.lblServer.Text = "Server name:"; + // + // tbServer + // + this.tbServer.Location = new System.Drawing.Point(12, 40); + this.tbServer.Name = "tbServer"; + this.tbServer.Size = new System.Drawing.Size(296, 20); + this.tbServer.TabIndex = 0; + // + // tbUserName + // + this.tbUserName.Location = new System.Drawing.Point(120, 101); + this.tbUserName.Name = "tbUserName"; + this.tbUserName.Size = new System.Drawing.Size(188, 20); + this.tbUserName.TabIndex = 1; + // + // tbPassword + // + this.tbPassword.Location = new System.Drawing.Point(120, 125); + this.tbPassword.Name = "tbPassword"; + this.tbPassword.Size = new System.Drawing.Size(188, 20); + this.tbPassword.TabIndex = 2; + this.tbPassword.UseSystemPasswordChar = true; + // + // lblUserName + // + this.lblUserName.AutoSize = true; + this.lblUserName.Location = new System.Drawing.Point(12, 105); + this.lblUserName.Name = "lblUserName"; + this.lblUserName.Size = new System.Drawing.Size(62, 13); + this.lblUserName.TabIndex = 0; + this.lblUserName.Text = "User name:"; + // + // lblPassword + // + this.lblPassword.AutoSize = true; + this.lblPassword.Location = new System.Drawing.Point(12, 129); + this.lblPassword.Name = "lblPassword"; + this.lblPassword.Size = new System.Drawing.Size(57, 13); + this.lblPassword.TabIndex = 1; + this.lblPassword.Text = "Password:"; + // + // gbDatabase + // + this.gbDatabase.Controls.Add(this.lblDatabase); + this.gbDatabase.Controls.Add(this.tbDatabase); + this.gbDatabase.Location = new System.Drawing.Point(8, 187); + this.gbDatabase.Name = "gbDatabase"; + this.gbDatabase.Size = new System.Drawing.Size(320, 76); + this.gbDatabase.TabIndex = 5; + this.gbDatabase.TabStop = false; + this.gbDatabase.Text = "Database"; + // + // lblDatabase + // + this.lblDatabase.AutoSize = true; + this.lblDatabase.Location = new System.Drawing.Point(12, 20); + this.lblDatabase.Name = "lblDatabase"; + this.lblDatabase.Size = new System.Drawing.Size(57, 13); + this.lblDatabase.TabIndex = 3; + this.lblDatabase.Text = "Database:"; + // + // tbDatabase + // + this.tbDatabase.Location = new System.Drawing.Point(12, 40); + this.tbDatabase.Name = "tbDatabase"; + this.tbDatabase.Size = new System.Drawing.Size(296, 20); + this.tbDatabase.TabIndex = 0; + // + // ClickHouseConnectionEditor + // + this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.Controls.Add(this.gbDatabase); + this.Controls.Add(this.gbServer); + this.Controls.Add(this.label1); + this.Name = "ClickHouseConnectionEditor"; + this.Size = new System.Drawing.Size(336, 358); + this.gbServer.ResumeLayout(false); + this.gbServer.PerformLayout(); + this.gbDatabase.ResumeLayout(false); + this.gbDatabase.PerformLayout(); + this.ResumeLayout(false); + + } + + #endregion + private FastReport.Controls.LabelLine label1; + private System.Windows.Forms.GroupBox gbServer; + private System.Windows.Forms.Label lblServer; + private System.Windows.Forms.TextBox tbServer; + private System.Windows.Forms.TextBox tbUserName; + private System.Windows.Forms.TextBox tbPassword; + private System.Windows.Forms.Label lblUserName; + private System.Windows.Forms.Label lblPassword; + private System.Windows.Forms.GroupBox gbDatabase; + private System.Windows.Forms.Label lblDatabase; + private System.Windows.Forms.TextBox tbDatabase; + private System.Windows.Forms.TextBox tbPort; + private System.Windows.Forms.Label port; + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.cs new file mode 100644 index 00000000..db934f9d --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.cs @@ -0,0 +1,55 @@ +using System; +using System.Collections.Generic; +using System.ComponentModel; +using System.Drawing; +using System.Data; +using System.Text; +using System.Windows.Forms; +using FastReport.Data.ConnectionEditors; +using FastReport.Forms; +using FastReport.Utils; +using ClickHouse.Client.ADO; + +namespace FastReport.Data +{ + public partial class ClickHouseConnectionEditor : ConnectionEditorBase + { + private string FConnectionString; + private void Localize() + { + //for now is empty. + } + + protected override string GetConnectionString() + { + ClickHouseConnectionStringBuilder builder = new ClickHouseConnectionStringBuilder(FConnectionString); + + builder.Username = tbUserName.Text; + builder.Host = tbServer.Text; + builder.Port = ushort.Parse(tbPort.Text); + builder.Password = tbPassword.Text; + builder.Database = tbDatabase.Text; + + return builder.ToString(); + } + + protected override void SetConnectionString(string value) + { + FConnectionString = value; + ClickHouseConnectionStringBuilder builder = new ClickHouseConnectionStringBuilder(FConnectionString); + tbServer.Text = builder.Host; + tbUserName.Text = builder.Username; + tbPassword.Text = builder.Password; + tbDatabase.Text = builder.Database; + tbPort.Text = builder.Port.ToString(); + } + + public ClickHouseConnectionEditor() + { + InitializeComponent(); + Localize(); + } + + + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.resx b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.resx new file mode 100644 index 00000000..d58980a3 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseConnectionEditor.resx @@ -0,0 +1,120 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + text/microsoft-resx + + + 2.0 + + + System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + + System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089 + + \ No newline at end of file diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseDataConnection.cs new file mode 100644 index 00000000..14c7cee5 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/ClickHouseDataConnection.cs @@ -0,0 +1,120 @@ +using ClickHouse.Client.ADO; +using ClickHouse.Client.ADO.Adapters; +using ClickHouse.Client.ADO.Parameters; +using ClickHouse.Client.ADO.Readers; +using ClickHouse.Client.Types; +using ClickHouse.Client.Utility; +using FastReport.Data; +using System; +using System.Collections.Generic; +using System.Data; +using System.Data.Common; +using System.Linq; +using System.Text; +using System.Threading.Tasks; + +namespace FastReport.ClickHouse +{ + public partial class ClickHouseDataConnection : DataConnectionBase + { + private void GetDBObjectNames(string name, List list) + { + DataTable schema = null; + DbConnection connection = GetConnection(); + try + { + OpenConnection(connection); + schema = connection.GetSchema(name, new string[] { connection.Database }); + } + finally + { + DisposeConnection(connection); + } + foreach (DataRow row in schema.Rows) + { + list.Add(row["name"].ToString()); + } + } + public override string QuoteIdentifier(string value, DbConnection connection) + { + return "\"" + value + "\""; + } + public override Type GetConnectionType() + { + return typeof(ClickHouseConnection); + } + public override string[] GetTableNames() + { + List list = new List(); + GetDBObjectNames("Tables", list); + return list.ToArray(); + } + + public override DbDataAdapter GetAdapter(string selectCommand, DbConnection connection, CommandParameterCollection parameters) + { + ClickHouseDataAdapter clickHouseDataAdapter = new ClickHouseDataAdapter(); + var command = connection.CreateCommand() as ClickHouseCommand; + + foreach (CommandParameter p in parameters) + { + selectCommand = selectCommand.Replace($"@{p.Name}", $"{{{p.Name}:{(ClickHouseTypeCode)p.DataType}}}"); + command.AddParameter(p.Name, ((ClickHouseTypeCode)p.DataType).ToString(), p.Value); + } + command.CommandText = selectCommand; + clickHouseDataAdapter.SelectCommand = command; + return clickHouseDataAdapter; + } + private string PrepareSelectCommand(string selectCommand, string tableName, DbConnection connection) + { + if (String.IsNullOrEmpty(selectCommand)) + { + selectCommand = "select * from " + QuoteIdentifier(tableName, connection); + } + return selectCommand; + } + private IEnumerable GetColumns(ClickHouseDataReader reader) + { + for (int i = 0; i < reader.FieldCount; i++) + { + var columnType = reader.GetFieldType(i); + + yield return new DataColumn(reader.GetName(i), columnType); + } + } + + public override void FillTableSchema(DataTable table, string selectCommand, CommandParameterCollection parameters) + { + ClickHouseConnection clickHouseConnection = GetConnection() as ClickHouseConnection; + + try + { + OpenConnection(clickHouseConnection); + + selectCommand = PrepareSelectCommand(selectCommand, table.TableName, clickHouseConnection); + /*To reduce size of traffic and size of answer from ClickHouse server. + Because FillSchema doesn't work in this ADO.NET library. + LIMIT 0 gets an empy set, but we still have list of desired columns + Prorably can be a better way. + */ + selectCommand += " LIMIT 0"; + ClickHouseCommand clickHouseCommand = clickHouseConnection.CreateCommand(); + + foreach (CommandParameter p in parameters) + { + selectCommand = selectCommand.Replace($"@{p.Name}", $"{{{p.Name}:{(ClickHouseTypeCode)p.DataType}}}"); + clickHouseCommand.AddParameter(p.Name, ((ClickHouseTypeCode)p.DataType).ToString(), p.Value); + } + clickHouseCommand.CommandText = selectCommand; + using (ClickHouseDataReader reader = clickHouseCommand.ExecuteReader() as ClickHouseDataReader) + { + var clms = GetColumns(reader); + table.Columns.AddRange(clms.ToArray()); + } + } + finally + { + DisposeConnection(clickHouseConnection); + } + } + } +} diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/Directory.Build.targets b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/Directory.Build.targets new file mode 100644 index 00000000..18cedec9 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/Directory.Build.targets @@ -0,0 +1,11 @@ + + + + + + + + + + + \ No newline at end of file diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.ClickHouse.csproj b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.ClickHouse.csproj new file mode 100644 index 00000000..e46160af --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.ClickHouse.csproj @@ -0,0 +1,51 @@ + + + + net472 + true + Fast Reports Inc. + Fast Reports Inc. + https://www.fast-report.com/en/product/fast-report-net/license + https://www.fast-report.com/en/product/fast-report-net + Fast Reports Inc. + FastReport.Data.ClickHouse + Represents a connection to ClickHouse database for FastReport.Net. + FastReport.Data.ClickHouse + https://www.fast-report.com/download/images/frlogo-big.png + reporting, ClickHouse, connection, reports + 1.0.0 + Debug;Release; + FastReport.Data.ClickHouse + FastReport.Data + + + + + + + + + + all + + + + + + + + + + UserControl + + + ClickHouseConnectionEditor.cs + + + ClickHouseConnectionEditor.cs + Designer + + + diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.Core.Data.ClickHouse.csproj b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.Core.Data.ClickHouse.csproj new file mode 100644 index 00000000..5a8b3a3a --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.Core.Data.ClickHouse.csproj @@ -0,0 +1,43 @@ + + + + net472;netstandard2.1;netcoreapp2.2 + true + Fast Reports Inc. + Fast Reports Inc. + https://www.fast-report.com/en/product/fast-report-net/license + https://www.fast-report.com/en/product/fast-report-net + Fast Reports Inc. + FastReport.Data.ClickHouse + Represents a connection to ClickHouse database for FastReport .NET + FastReport.Core.Data.ClickHouse + https://www.fast-report.com/download/images/frlogo-big.png + reporting, ClickHouse, connection, reports + 1.0.0 + Debug;Release; + FastReport.Data.ClickHouse + FastReport.Data + + + + + + + + + + + + + + + + + + + + + + diff --git a/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.OpenSource.Data.ClickHouse.csproj b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.OpenSource.Data.ClickHouse.csproj new file mode 100644 index 00000000..4bbf40b1 --- /dev/null +++ b/Extras/Core/FastReport.Data/FastReport.ClickHouse/FastReport.ClickHouse/FastReport.OpenSource.Data.ClickHouse.csproj @@ -0,0 +1,43 @@ + + + + net472;netstandard2.1;netcoreapp2.2 + true + Fast Reports Inc. + Fast Reports Inc. + https://www.fast-report.com/en/product/fast-report-net/license + https://www.fast-report.com/en/product/fast-report-net + Fast Reports Inc. + FastReport.Data.ClickHouse + Represents a connection to ClickHouse database for FastReport .NET + FastReport.Core.Data.ClickHouse + https://www.fast-report.com/download/images/frlogo-big.png + reporting, ClickHouse, connection, reports + 1.0.0 + Debug;Release; + FastReport.Data.ClickHouse + FastReport.Data + + + + + + + + + + + + + + + + + + + + + + diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Couchbase/CouchbaseConnectionEditor.Designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.Couchbase/CouchbaseConnectionEditor.Designer.cs index dd5578bf..4caca978 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Couchbase/CouchbaseConnectionEditor.Designer.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.Couchbase/CouchbaseConnectionEditor.Designer.cs @@ -144,7 +144,7 @@ private void InitializeComponent() // CouchbaseConnectionEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.Controls.Add(this.gbDatabase); this.Controls.Add(this.gbServer); this.Controls.Add(this.labelLine1); diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Json/JsonConnectionEditor.cs b/Extras/Core/FastReport.Data/FastReport.Data.Json/JsonConnectionEditor.cs index 8eeaea0d..066247eb 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Json/JsonConnectionEditor.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.Json/JsonConnectionEditor.cs @@ -15,7 +15,7 @@ private void Localize() { gbSelect.Text = Res.Get("ConnectionEditors,Common,Database"); lblJsonPath.Text = Res.Get("ConnectionEditors,Json,Path"); - tbJsonPath.Image = Res.GetImage(1); + tbJsonPath.ImageIndex = 1; } private void tbFile_ButtonClick(object sender, EventArgs e) diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Json/JsonDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.Data.Json/JsonDataConnection.cs index 426a05fd..cf8dd0c1 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Json/JsonDataConnection.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.Json/JsonDataConnection.cs @@ -33,6 +33,7 @@ public string JsonData { get { + System.Net.ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00); if (string.IsNullOrEmpty(jsonData)) using (WebClient webClient = new WebClient()) { diff --git a/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/FastReport.Data.MongoDB.csproj b/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/FastReport.Data.MongoDB.csproj index 8e1e8597..cabe1e48 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/FastReport.Data.MongoDB.csproj +++ b/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/FastReport.Data.MongoDB.csproj @@ -34,9 +34,7 @@ - - UserControl - + MongoDBConnectionEditor.cs diff --git a/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/MongoDBConnectionEditor.Designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/MongoDBConnectionEditor.Designer.cs index bfe4bd79..db1108bf 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/MongoDBConnectionEditor.Designer.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/MongoDBConnectionEditor.Designer.cs @@ -214,7 +214,7 @@ private void InitializeComponent() // MongoDBConnectionEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.Controls.Add(this.gbDatabase); this.Controls.Add(this.btnAdvanced); this.Controls.Add(this.gbServer); diff --git a/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/MongoDBDataConnection.cs b/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/MongoDBDataConnection.cs index 069d7098..0c648c17 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/MongoDBDataConnection.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.MongoDB/MongoDBDataConnection.cs @@ -64,9 +64,12 @@ protected DataTable CreateDataTable(DataTable table, bool allRows) var collection = db.GetCollection(table.TableName); if (!allRows) { - var documents = collection.Find(new BsonDocument()).First(); - DataRow dr = table.NewRow(); - ExecuteFillDataTable(documents, table, dr, string.Empty); + var documents = collection.Find(new BsonDocument()).FirstOrDefault(); + if (documents != null) + { + DataRow dr = table.NewRow(); + ExecuteFillDataTable(documents, table, dr, string.Empty); + } } else { diff --git a/Extras/Core/FastReport.Data/FastReport.Data.MySql/MySqlConnectionEditor.Designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.MySql/MySqlConnectionEditor.Designer.cs index 157f198e..6bfc6739 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.MySql/MySqlConnectionEditor.Designer.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.MySql/MySqlConnectionEditor.Designer.cs @@ -157,7 +157,7 @@ private void InitializeComponent() // PostgresConnectionEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.Controls.Add(this.gbServer); this.Controls.Add(this.label1); this.Controls.Add(this.gbDatabase); diff --git a/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/OracleConnectionEditor.Designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/OracleConnectionEditor.Designer.cs index 3e58f834..ae389a32 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/OracleConnectionEditor.Designer.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.OracleODPCore/OracleConnectionEditor.Designer.cs @@ -129,7 +129,7 @@ private void InitializeComponent() // OracleConnectionEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.Controls.Add(this.gbServer); this.Controls.Add(this.label1); this.Controls.Add(this.btnAdvanced); diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/FastReport.Data.Postgres.csproj b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/FastReport.Data.Postgres.csproj index 1c8fdba6..b0e1e36a 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/FastReport.Data.Postgres.csproj +++ b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/FastReport.Data.Postgres.csproj @@ -48,8 +48,6 @@ - - UserControl - + \ No newline at end of file diff --git a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresConnectionEditor.Designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresConnectionEditor.Designer.cs index d3ef8a76..8790b96e 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresConnectionEditor.Designer.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.Postgres/PostgresConnectionEditor.Designer.cs @@ -170,7 +170,7 @@ private void InitializeComponent() // PostgresConnectionEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.Controls.Add(this.enableSystemSchemasCb); this.Controls.Add(this.gbServer); this.Controls.Add(this.label1); diff --git a/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/FastReport.Data.RavenDB.csproj b/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/FastReport.Data.RavenDB.csproj index a71dc767..510305b8 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/FastReport.Data.RavenDB.csproj +++ b/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/FastReport.Data.RavenDB.csproj @@ -19,7 +19,7 @@ FastReport.Data - + all @@ -41,9 +41,7 @@ - - UserControl - + RavenDBConnectionEditor.cs diff --git a/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/RavenDBConnectionEditor.Designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/RavenDBConnectionEditor.Designer.cs index 7a9a49d2..1cbd6c9b 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/RavenDBConnectionEditor.Designer.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/RavenDBConnectionEditor.Designer.cs @@ -148,7 +148,7 @@ private void InitializeComponent() // RavenDBConnectionEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(9F, 19F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.Controls.Add(this.gbDatabase); this.Controls.Add(this.gbServer); this.Controls.Add(this.labelLine1); diff --git a/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/RavenDBConnectionEditor.cs b/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/RavenDBConnectionEditor.cs index 82537d10..5cae3056 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/RavenDBConnectionEditor.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.RavenDB/RavenDBConnectionEditor.cs @@ -39,7 +39,7 @@ private void Localize() lblPassword.Text = res.Get("Password"); gbDatabase.Text = res.Get("Database"); lblDatabase.Text = res.Get("DatabaseName"); - tbCertificatePath.Image = Res.GetImage(1); + tbCertificatePath.ImageIndex = 1; res = new MyRes("Export,Email"); } diff --git a/Extras/Core/FastReport.Data/FastReport.Data.SQLite/SQLiteConnectionEditor.Designer.cs b/Extras/Core/FastReport.Data/FastReport.Data.SQLite/SQLiteConnectionEditor.Designer.cs index 71d7767f..21656408 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.SQLite/SQLiteConnectionEditor.Designer.cs +++ b/Extras/Core/FastReport.Data/FastReport.Data.SQLite/SQLiteConnectionEditor.Designer.cs @@ -97,7 +97,7 @@ private void InitializeComponent() // SQLiteConnectionEditor // this.AutoScaleDimensions = new System.Drawing.SizeF(6F, 13F); - this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.Font; + this.AutoScaleMode = System.Windows.Forms.AutoScaleMode.None; this.Controls.Add(this.label1); this.Controls.Add(this.gbDatabase); this.Controls.Add(this.btnAdvanced); diff --git a/Extras/Core/FastReport.Data/FastReport.Data.sln b/Extras/Core/FastReport.Data/FastReport.Data.sln index f9e406ea..38637b90 100644 --- a/Extras/Core/FastReport.Data/FastReport.Data.sln +++ b/Extras/Core/FastReport.Data/FastReport.Data.sln @@ -21,6 +21,8 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.Data.Couchbase", EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.Data.RavenDB", "FastReport.Data.RavenDB\FastReport.Data.RavenDB.csproj", "{BB143926-05EB-4569-A474-EA21225EB6CC}" EndProject +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.ClickHouse", "FastReport.ClickHouse\FastReport.ClickHouse\FastReport.ClickHouse.csproj", "{95AA6CC1-39AA-41A6-90E5-D8931DD814DF}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|Any CPU = Debug|Any CPU @@ -63,6 +65,10 @@ Global {BB143926-05EB-4569-A474-EA21225EB6CC}.Debug|Any CPU.Build.0 = Debug|Any CPU {BB143926-05EB-4569-A474-EA21225EB6CC}.Release|Any CPU.ActiveCfg = Release|Any CPU {BB143926-05EB-4569-A474-EA21225EB6CC}.Release|Any CPU.Build.0 = Release|Any CPU + {95AA6CC1-39AA-41A6-90E5-D8931DD814DF}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {95AA6CC1-39AA-41A6-90E5-D8931DD814DF}.Debug|Any CPU.Build.0 = Debug|Any CPU + {95AA6CC1-39AA-41A6-90E5-D8931DD814DF}.Release|Any CPU.ActiveCfg = Release|Any CPU + {95AA6CC1-39AA-41A6-90E5-D8931DD814DF}.Release|Any CPU.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE diff --git a/FastReport.Base/AssemblyInitializer.cs b/FastReport.Base/AssemblyInitializer.cs index a7715aa3..9dee0435 100644 --- a/FastReport.Base/AssemblyInitializer.cs +++ b/FastReport.Base/AssemblyInitializer.cs @@ -114,7 +114,7 @@ public AssemblyInitializer() RegisteredObjects.AddCategory("ReportPage,Barcodes", 123, 9, "Objects,BarcodeObject"); for (int i = 0; i <= Barcodes.Items.Length - 1; i++) - RegisteredObjects.Add(typeof(BarcodeObject), "ReportPage,Barcodes", -1, "ComponentMenu,Barcode,Barcodes,Barcode" + i, i); + RegisteredObjects.Add(typeof(BarcodeObject), "ReportPage,Barcodes", -1, Barcodes.Items[i].barcodeName, i); RegisteredObjects.Add(typeof(CheckBoxObject), "ReportPage", 124, 10); diff --git a/FastReport.Base/Base.cs b/FastReport.Base/Base.cs index 7605b616..07e13b8f 100644 --- a/FastReport.Base/Base.cs +++ b/FastReport.Base/Base.cs @@ -757,6 +757,7 @@ public virtual void Clear() ObjectCollection list = ChildObjects; foreach (Base c in list) { + c.Clear(); c.Dispose(); } } diff --git a/FastReport.Base/Data/CsvDataConnection.cs b/FastReport.Base/Data/CsvDataConnection.cs index 993917a5..93b2cdef 100644 --- a/FastReport.Base/Data/CsvDataConnection.cs +++ b/FastReport.Base/Data/CsvDataConnection.cs @@ -6,6 +6,7 @@ using System.Data.Common; using System.IO; using System.Net; +using FastReport.Utils; namespace FastReport.Data { @@ -297,6 +298,8 @@ protected override DataSet CreateDataSet() { string allText = ""; + ServicePointManager.Expect100Continue = true; + ServicePointManager.SecurityProtocol = (SecurityProtocolType)3072; WebRequest request; WebResponse response = null; try @@ -305,6 +308,8 @@ protected override DataSet CreateDataSet() if (uri.IsFile) { + if (Config.ForbidLocalData) + throw new Exception(Res.Get("ConnectionEditors,Common,OnlyUrlException")); request = (FileWebRequest)WebRequest.Create(uri); request.Timeout = 5000; response = (FileWebResponse)request.GetResponse(); diff --git a/FastReport.Base/Data/DataConnectionBase.cs b/FastReport.Base/Data/DataConnectionBase.cs index b4f9ab31..2783d679 100644 --- a/FastReport.Base/Data/DataConnectionBase.cs +++ b/FastReport.Base/Data/DataConnectionBase.cs @@ -4,6 +4,7 @@ using System.Data; using System.Data.Common; using System.Drawing.Design; +using FastReport.Data.JsonConnection; using FastReport.Utils; namespace FastReport.Data @@ -747,6 +748,48 @@ public virtual void DeleteTable(TableDataSource source) } } + /// + /// Clone table. + /// For internal use only. + /// + public virtual void Clone() + { + XmlItem item = new XmlItem(); + using(FRWriter writer = new FRWriter(item)) + { + writer.SerializeTo = SerializeTo.Clipboard; + writer.BlobStore = new BlobStore(false); + writer.Write(this); + } + using (FRReader reader = new FRReader(Report, item)) + { + reader.DeserializeFrom = SerializeTo.Clipboard; + reader.BlobStore = new BlobStore(false); + var connection = Activator.CreateInstance(this.GetType()) as DataConnectionBase; + connection.Parent = this.Parent; + connection.SetReport(Report); + reader.Read(connection); + connection.CreateUniqueName(); + foreach (TableDataSource table in connection.Tables) + table.CreateUniqueName(); + Report.Dictionary.AddChild(connection); + } + } + + protected void CreateUniqueNames(DataConnectionBase copyTo) + { + int i = 1; + string s; + do + { + s = this.Alias + i.ToString(); + i++; + } + while (Report.Dictionary.FindByAlias(s) != null); + copyTo.Alias = s; + copyTo.Name = s; + } + /// public override void Serialize(FRWriter writer) { diff --git a/FastReport.Base/Data/JsonConnection/JsonDataSourceConnection.cs b/FastReport.Base/Data/JsonConnection/JsonDataSourceConnection.cs index 21374a96..faba8532 100644 --- a/FastReport.Base/Data/JsonConnection/JsonDataSourceConnection.cs +++ b/FastReport.Base/Data/JsonConnection/JsonDataSourceConnection.cs @@ -201,6 +201,8 @@ private void InitConnection(bool rebuildSchema) // jsonText = client.DownloadString(jsonText); //} + ServicePointManager.Expect100Continue = true; + ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00); HttpWebRequest req = (HttpWebRequest)WebRequest.Create(jsonText); foreach (var header in builder.Headers) diff --git a/FastReport.Base/Data/Total.cs b/FastReport.Base/Data/Total.cs index 1b535034..e919ac81 100644 --- a/FastReport.Base/Data/Total.cs +++ b/FastReport.Base/Data/Total.cs @@ -1,4 +1,5 @@ using System; +using System.Collections; using System.Collections.Generic; using System.ComponentModel; using System.Drawing.Design; @@ -6,390 +7,406 @@ namespace FastReport.Data { - /// - /// Specifies the total type. - /// - public enum TotalType - { /// - /// The total returns sum of values. + /// Specifies the total type. /// - Sum, + public enum TotalType + { + /// + /// The total returns sum of values. + /// + Sum, + + /// + /// The total returns minimal value. + /// + Min, + + /// + /// The total returns maximal value. + /// + Max, + + /// + /// The total returns average value. + /// + Avg, + + /// + /// The total returns number of values. + /// + Count, + + + /// + /// The total returns number of distinct values. + /// + CountDistinct + } /// - /// The total returns minimal value. + /// Represents a total that is used to calculate aggregates such as Sum, Min, Max, Avg, Count. /// - Min, + public partial class Total : Base + { + #region Fields + private TotalType totalType; + private string expression; + private DataBand evaluator; + private BandBase printOn; + private string evaluateCondition; + private bool includeInvisibleRows; + private bool resetAfterPrint; + private bool resetOnReprint; + // engine + private object value; + private int count; + private bool keeping; + private Total keepTotal; + private TotalCollection subTotals; + private TotalCollection parentTotal; + private const string subPrefix = "_sub"; + private Hashtable distinctValues; + #endregion + + #region Properties + /// + /// Gets or sets the total type. + /// + [DefaultValue(TotalType.Sum)] + [Category("Data")] + public TotalType TotalType + { + get { return totalType; } + set { totalType = value; } + } - /// - /// The total returns maximal value. - /// - Max, + /// + /// Gets or sets the expression used to calculate the total. + /// + [Category("Data")] + [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] + public string Expression + { + get { return expression; } + set { expression = value; } + } - /// - /// The total returns average value. - /// - Avg, + /// + /// Gets or sets the evaluator databand. + /// + /// + /// The total will be calculated for each row of this band. + /// + [Category("Data")] + public DataBand Evaluator + { + get { return evaluator; } + set { evaluator = value; } + } - /// - /// The total returns number of values. - /// - Count - } - - /// - /// Represents a total that is used to calculate aggregates such as Sum, Min, Max, Avg, Count. - /// - public partial class Total : Base - { - #region Fields - private TotalType totalType; - private string expression; - private DataBand evaluator; - private BandBase printOn; - private string evaluateCondition; - private bool includeInvisibleRows; - private bool resetAfterPrint; - private bool resetOnReprint; - // engine - private object value; - private int count; - private bool keeping; - private Total keepTotal; - private TotalCollection subTotals; - private TotalCollection parentTotal; - private const string subPrefix = "_sub"; - #endregion - - #region Properties - /// - /// Gets or sets the total type. - /// - [DefaultValue(TotalType.Sum)] - [Category("Data")] - public TotalType TotalType - { - get { return totalType; } - set { totalType = value; } - } + /// + /// This property is kept for compatibility only. + /// + [Category("Data")] + [Browsable(false)] + public BandBase Resetter + { + get { return printOn; } + set { printOn = value; } + } - /// - /// Gets or sets the expression used to calculate the total. - /// - [Category("Data")] - [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] - public string Expression - { - get { return expression; } - set { expression = value; } - } - - /// - /// Gets or sets the evaluator databand. - /// - /// - /// The total will be calculated for each row of this band. - /// - [Category("Data")] - public DataBand Evaluator - { - get { return evaluator; } - set { evaluator = value; } - } - - /// - /// This property is kept for compatibility only. - /// - [Category("Data")] - [Browsable(false)] - public BandBase Resetter - { - get { return printOn; } - set { printOn = value; } - } + /// + /// Gets or sets the band to print the total on. + /// + /// + /// The total will be resetted after the specified band has been printed. + /// + [Category("Data")] + public BandBase PrintOn + { + get { return printOn; } + set { printOn = value; } + } - /// - /// Gets or sets the band to print the total on. - /// - /// - /// The total will be resetted after the specified band has been printed. - /// - [Category("Data")] - public BandBase PrintOn - { - get { return printOn; } - set { printOn = value; } - } + /// + /// Gets or sets a value that determines whether the total should be resetted after print. + /// + [DefaultValue(true)] + [Category("Behavior")] + public bool ResetAfterPrint + { + get { return resetAfterPrint; } + set { resetAfterPrint = value; } + } - /// - /// Gets or sets a value that determines whether the total should be resetted after print. - /// - [DefaultValue(true)] - [Category("Behavior")] - public bool ResetAfterPrint - { - get { return resetAfterPrint; } - set { resetAfterPrint = value; } - } + /// + /// Gets or sets a value that determines whether the total should be resetted if printed + /// on repeated band (i.e. band with "RepeatOnEveryPage" flag). + /// + [DefaultValue(true)] + [Category("Behavior")] + public bool ResetOnReprint + { + get { return resetOnReprint; } + set { resetOnReprint = value; } + } - /// - /// Gets or sets a value that determines whether the total should be resetted if printed - /// on repeated band (i.e. band with "RepeatOnEveryPage" flag). - /// - [DefaultValue(true)] - [Category("Behavior")] - public bool ResetOnReprint - { - get { return resetOnReprint; } - set { resetOnReprint = value; } - } + /// + /// Gets or sets the condition which tells the total to evaluate. + /// + [Category("Data")] + [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] + public string EvaluateCondition + { + get { return evaluateCondition; } + set { evaluateCondition = value; } + } - /// - /// Gets or sets the condition which tells the total to evaluate. - /// - [Category("Data")] - [Editor("FastReport.TypeEditors.ExpressionEditor, FastReport", typeof(UITypeEditor))] - public string EvaluateCondition - { - get { return evaluateCondition; } - set { evaluateCondition = value; } - } - - /// - /// Gets or sets a value that determines if invisible rows of the Evaluator should - /// be included into the total's value. - /// - [DefaultValue(false)] - [Category("Behavior")] - public bool IncludeInvisibleRows - { - get { return includeInvisibleRows; } - set { includeInvisibleRows = value; } - } + /// + /// Gets or sets a value that determines if invisible rows of the Evaluator should + /// be included into the total's value. + /// + [DefaultValue(false)] + [Category("Behavior")] + public bool IncludeInvisibleRows + { + get { return includeInvisibleRows; } + set { includeInvisibleRows = value; } + } - /// - /// This property is not relevant to this class. - /// - [Browsable(false)] - public new Restrictions Restrictions - { - get { return base.Restrictions; } - set { base.Restrictions = value; } - } + /// + /// This property is not relevant to this class. + /// + [Browsable(false)] + public new Restrictions Restrictions + { + get { return base.Restrictions; } + set { base.Restrictions = value; } + } - /// - /// Gets the value of total. - /// - [Browsable(false)] - public object Value - { - get { return GetValue(); } - } + /// + /// Gets the value of total. + /// + [Browsable(false)] + public object Value + { + get { return GetValue(); } + } - private bool IsPageFooter - { - get - { - return PrintOn is PageFooterBand || PrintOn is ColumnFooterBand || - ((PrintOn is HeaderFooterBandBase) && (PrintOn as HeaderFooterBandBase).RepeatOnEveryPage); - } - } + private bool IsPageFooter + { + get + { + return PrintOn is PageFooterBand || PrintOn is ColumnFooterBand || + ((PrintOn is HeaderFooterBandBase) && (PrintOn as HeaderFooterBandBase).RepeatOnEveryPage); + } + } - private bool IsInsideHierarchy - { - get - { - return Report.Engine.HierarchyLevel > 1 && - !Name.StartsWith(subPrefix) && - PrintOn != null && PrintOn.ParentDataBand != null && PrintOn.ParentDataBand.IsHierarchical; - } - } - #endregion + private bool IsInsideHierarchy + { + get + { + return Report.Engine.HierarchyLevel > 1 && + !Name.StartsWith(subPrefix) && + PrintOn != null && PrintOn.ParentDataBand != null && PrintOn.ParentDataBand.IsHierarchical; + } + } + #endregion - #region Private Methods - private object GetValue() - { - if (IsInsideHierarchy) - { - Total subTotal = FindSubTotal(subPrefix + Report.Engine.HierarchyLevel.ToString()); - return subTotal.Value; - } - - if (TotalType == TotalType.Avg) - { - if (value == null || count == 0) - return null; - return new Variant(value) / count; - } - else if (TotalType == TotalType.Count) - return count; - return value; - } + #region Private Methods + private object GetValue() + { + if (IsInsideHierarchy) + { + Total subTotal = FindSubTotal(subPrefix + Report.Engine.HierarchyLevel.ToString()); + return subTotal.Value; + } - private void AddValue(object value) - { - if (value == null || value is DBNull) - return; - - if (this.value == null) - this.value = value; - else - { - switch (TotalType) + if (TotalType == TotalType.Avg) + { + if (value == null || count == 0) + return null; + return new Variant(value) / count; + } + else if (TotalType == TotalType.Count) + return count; + else if (TotalType == TotalType.CountDistinct) + return distinctValues.Keys.Count; + return value; + } + + private void AddValue(object value) { - case TotalType.Sum: - case TotalType.Avg: + if (value == null || value is DBNull) + return; + + if (TotalType == TotalType.CountDistinct) + { + distinctValues[value] = 1; + return; + } + + if (this.value == null) + this.value = value; + else + { + switch (TotalType) + { + case TotalType.Sum: + case TotalType.Avg: this.value = (new Variant(this.value) + new Variant(value)).Value; - break; + break; - case TotalType.Min: + case TotalType.Min: IComparable val1 = this.value as IComparable; IComparable val2 = value as IComparable; - if (val1 != null && val2 != null && val1.CompareTo(val2) > 0) + if (val1 != null && val2 != null && val1.CompareTo(val2) > 0) this.value = value; - break; + break; - case TotalType.Max: - val1 = this.value as IComparable; - val2 = value as IComparable; - if (val1 != null && val2 != null && val1.CompareTo(val2) < 0) + case TotalType.Max: + val1 = this.value as IComparable; + val2 = value as IComparable; + if (val1 != null && val2 != null && val1.CompareTo(val2) < 0) this.value = value; - break; + break; + } + } } - } - } - private Total FindSubTotal(string name) - { - Total result = subTotals.FindByName(name); - if (result == null) - { - result = this.Clone(); - result.Name = name; - subTotals.Add(result); - } - - return result; - } - #endregion - - #region Public Methods - /// - public override void Assign(Base source) - { - BaseAssign(source); - } + private Total FindSubTotal(string name) + { + Total result = subTotals.FindByName(name); + if (result == null) + { + result = this.Clone(); + result.Name = name; + subTotals.Add(result); + } - /// - public override void Serialize(FRWriter writer) - { - Total c = writer.DiffObject as Total; - base.Serialize(writer); - - if (TotalType != c.TotalType) - writer.WriteValue("TotalType", TotalType); - if (Expression != c.Expression) - writer.WriteStr("Expression", Expression); - if (Evaluator != c.Evaluator) - writer.WriteRef("Evaluator", Evaluator); - if (PrintOn != c.PrintOn) - writer.WriteRef("PrintOn", PrintOn); - if (ResetAfterPrint != c.ResetAfterPrint) - writer.WriteBool("ResetAfterPrint", ResetAfterPrint); - if (ResetOnReprint != c.ResetOnReprint) - writer.WriteBool("ResetOnReprint", ResetOnReprint); - if (EvaluateCondition != c.EvaluateCondition) - writer.WriteStr("EvaluateCondition", EvaluateCondition); - if (IncludeInvisibleRows != c.IncludeInvisibleRows) - writer.WriteBool("IncludeInvisibleRows", IncludeInvisibleRows); - } + return result; + } + #endregion - internal Total Clone() - { - Total total = new Total(); - total.SetReport(Report); - total.TotalType = TotalType; - total.Expression = Expression; - total.Evaluator = Evaluator; - total.PrintOn = PrintOn; - total.ResetAfterPrint = ResetAfterPrint; - total.ResetOnReprint = ResetOnReprint; - total.EvaluateCondition = EvaluateCondition; - total.IncludeInvisibleRows = IncludeInvisibleRows; - return total; - } - #endregion - - #region Report Engine - /// - public override string[] GetExpressions() - { - List expressions = new List(); - if (!String.IsNullOrEmpty(Expression)) - expressions.Add(Expression); - if (!String.IsNullOrEmpty(EvaluateCondition)) - expressions.Add(EvaluateCondition); - return expressions.ToArray(); - } + #region Public Methods + /// + public override void Assign(Base source) + { + BaseAssign(source); + } - /// - public override void Clear() - { - base.Clear(); - value = null; - count = 0; - } - - internal void AddValue() - { - if (IsInsideHierarchy) - { - Total subTotal = FindSubTotal(subPrefix + Report.Engine.HierarchyLevel.ToString()); - subTotal.AddValue(); - return; - } - - if (!Evaluator.Visible && !IncludeInvisibleRows) - return; - if (!String.IsNullOrEmpty(EvaluateCondition) && (bool)Report.Calc(EvaluateCondition) == false) - return; - if (TotalType != TotalType.Count && String.IsNullOrEmpty(Expression)) - return; - - if (keeping) - { - keepTotal.AddValue(); - return; - } - - // if Total refers to another total - Total total = IsRefersToTotal(); - if (total != null) + /// + public override void Serialize(FRWriter writer) + { + Total c = writer.DiffObject as Total; + base.Serialize(writer); + + if (TotalType != c.TotalType) + writer.WriteValue("TotalType", TotalType); + if (Expression != c.Expression) + writer.WriteStr("Expression", Expression); + if (Evaluator != c.Evaluator) + writer.WriteRef("Evaluator", Evaluator); + if (PrintOn != c.PrintOn) + writer.WriteRef("PrintOn", PrintOn); + if (ResetAfterPrint != c.ResetAfterPrint) + writer.WriteBool("ResetAfterPrint", ResetAfterPrint); + if (ResetOnReprint != c.ResetOnReprint) + writer.WriteBool("ResetOnReprint", ResetOnReprint); + if (EvaluateCondition != c.EvaluateCondition) + writer.WriteStr("EvaluateCondition", EvaluateCondition); + if (IncludeInvisibleRows != c.IncludeInvisibleRows) + writer.WriteBool("IncludeInvisibleRows", IncludeInvisibleRows); + } + + internal Total Clone() + { + Total total = new Total(); + total.SetReport(Report); + total.TotalType = TotalType; + total.Expression = Expression; + total.Evaluator = Evaluator; + total.PrintOn = PrintOn; + total.ResetAfterPrint = ResetAfterPrint; + total.ResetOnReprint = ResetOnReprint; + total.EvaluateCondition = EvaluateCondition; + total.IncludeInvisibleRows = IncludeInvisibleRows; + return total; + } + #endregion + + #region Report Engine + /// + public override string[] GetExpressions() { - if (!total.parentTotal.Contains(this)) + List expressions = new List(); + if (!String.IsNullOrEmpty(Expression)) + expressions.Add(Expression); + if (!String.IsNullOrEmpty(EvaluateCondition)) + expressions.Add(EvaluateCondition); + return expressions.ToArray(); + } + + /// + public override void Clear() + { + base.Clear(); + value = null; + count = 0; + distinctValues.Clear(); + } + + internal void AddValue() + { + if (IsInsideHierarchy) + { + Total subTotal = FindSubTotal(subPrefix + Report.Engine.HierarchyLevel.ToString()); + subTotal.AddValue(); + return; + } + + if (!Evaluator.Visible && !IncludeInvisibleRows) + return; + if (!String.IsNullOrEmpty(EvaluateCondition) && (bool)Report.Calc(EvaluateCondition) == false) + return; + if (TotalType != TotalType.Count && String.IsNullOrEmpty(Expression)) + return; + + if (keeping) + { + keepTotal.AddValue(); + return; + } + + // if Total refers to another total + Total total = IsRefersToTotal(); + if (total != null) { - total.parentTotal.Add(this); + if (!total.parentTotal.Contains(this)) + { + total.parentTotal.Add(this); + } + return; } - return; + + object value = TotalType == TotalType.Count ? null : Report.Calc(Expression); + AddValue(value); + if (TotalType != TotalType.Avg || (value != null && !(value is DBNull))) + count++; } - object value = TotalType == TotalType.Count ? null : Report.Calc(Expression); - AddValue(value); - if (TotalType != TotalType.Avg || (value != null && !(value is DBNull))) - count++; - } + internal void ResetValue() + { + if (IsInsideHierarchy) + { + Total subTotal = FindSubTotal(subPrefix + Report.Engine.HierarchyLevel.ToString()); + subTotal.ResetValue(); + return; + } - internal void ResetValue() - { - if (IsInsideHierarchy) - { - Total subTotal = FindSubTotal(subPrefix + Report.Engine.HierarchyLevel.ToString()); - subTotal.ResetValue(); - return; - } - - Clear(); - } + Clear(); + } internal void ExecuteTotal(object val) { @@ -413,37 +430,48 @@ private Total IsRefersToTotal() return Report.Dictionary.Totals.FindByName(expr); } - internal void StartKeep() - { - if (!IsPageFooter || keeping) - return; - keeping = true; + internal void StartKeep() + { + if (!IsPageFooter || keeping) + return; + keeping = true; - keepTotal = Clone(); - } + keepTotal = Clone(); + } - internal void EndKeep() - { - if (!IsPageFooter || !keeping) - return; - keeping = false; + internal void EndKeep() + { + if (!IsPageFooter || !keeping) + return; + keeping = false; - AddValue(keepTotal.value); - count += keepTotal.count; - } - #endregion + if (TotalType == TotalType.CountDistinct) + { + foreach (object key in keepTotal.distinctValues) + { + distinctValues[key] = 1; + } + } + else + { + AddValue(keepTotal.value); + count += keepTotal.count; + } + } + #endregion - /// - /// Initializes a new instance of the class with default settings. - /// - public Total() - { - expression = ""; - evaluateCondition = ""; - resetAfterPrint = true; - subTotals = new TotalCollection(null); + /// + /// Initializes a new instance of the class with default settings. + /// + public Total() + { + expression = ""; + evaluateCondition = ""; + resetAfterPrint = true; + subTotals = new TotalCollection(null); parentTotal = new TotalCollection(null); - SetFlags(Flags.CanCopy, false); + distinctValues = new Hashtable(); + SetFlags(Flags.CanCopy, false); + } } - } } diff --git a/FastReport.Base/Data/XmlDataConnection.cs b/FastReport.Base/Data/XmlDataConnection.cs index 8bb63fd6..9da9827f 100644 --- a/FastReport.Base/Data/XmlDataConnection.cs +++ b/FastReport.Base/Data/XmlDataConnection.cs @@ -5,6 +5,7 @@ using System.ComponentModel; using FastReport.Utils; using System.Data.Common; +using System.Net; namespace FastReport.Data { @@ -60,16 +61,15 @@ public string XmlFile ConnectionString = builder.ToString(); } } - #endregion + #endregion #region Protected Methods /// protected override DataSet CreateDataSet() { DataSet dataset = base.CreateDataSet(); - if (!String.IsNullOrEmpty(XsdFile)) - dataset.ReadXmlSchema(XsdFile); - dataset.ReadXml(XmlFile); + ReadXmlSchema(dataset); + ReadXml(dataset); return dataset; } @@ -117,12 +117,94 @@ public override string QuoteIdentifier(string value, DbConnection connection) { return value; } - #endregion + #endregion - /// - /// Initializes a new instance of the class with default settings. - /// - public XmlDataConnection() + #region private methods + private void ReadXml(DataSet dataset) + { + try + { + Uri uri = new Uri(XmlFile); + + if (uri.IsFile) + { + if (Config.ForbidLocalData) + throw new Exception(Res.Get("ConnectionEditors,Common,OnlyUrlException")); + dataset.ReadXml(XmlFile); + } + else if (uri.OriginalString.StartsWith("http") || uri.OriginalString.StartsWith("ftp")) + { + LoadXmlFromUrl(dataset); + } + } + catch (Exception e) + { + throw e; + } + } + + private void LoadXmlFromUrl(DataSet dataset) + { + ServicePointManager.Expect100Continue = true; + ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00); + HttpWebRequest req = (HttpWebRequest)WebRequest.Create(XmlFile); + using (var response = req.GetResponse() as HttpWebResponse) + { + var encoding = response.CharacterSet.Equals(String.Empty) ? Encoding.UTF8 : Encoding.GetEncoding(response.CharacterSet); + + using (var responseStream = response.GetResponseStream()) + using (var reader = new System.IO.StreamReader(responseStream, encoding)) + dataset.ReadXml(reader, XmlReadMode.Auto); + } + } + + private void ReadXmlSchema(DataSet dataset) + { + if (String.IsNullOrEmpty(XsdFile)) + return; + + try + { + Uri uri = new Uri(XsdFile); + + if (uri.IsFile) + { + if (Config.ForbidLocalData) + throw new Exception(Res.Get("ConnectionEditors,Common,OnlyUrlException")); + dataset.ReadXmlSchema(XsdFile); + } + else if (uri.OriginalString.StartsWith("http") || uri.OriginalString.StartsWith("ftp")) + { + LoadXmlSchemaFromUrl(dataset); + } + } + catch (Exception e) + { + throw e; + } + } + + private void LoadXmlSchemaFromUrl(DataSet dataset) + { + ServicePointManager.Expect100Continue = true; + ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00); + HttpWebRequest req = (HttpWebRequest)WebRequest.Create(XsdFile); + using (var response = req.GetResponse() as HttpWebResponse) + { + var encoding = response.CharacterSet.Equals(String.Empty) ? Encoding.UTF8 : Encoding.GetEncoding(response.CharacterSet); + + using (var responseStream = response.GetResponseStream()) + using (var reader = new System.IO.StreamReader(responseStream, encoding)) + dataset.ReadXmlSchema(reader); + } + } + + #endregion + + /// + /// Initializes a new instance of the class with default settings. + /// + public XmlDataConnection() { IsSqlBased = false; } diff --git a/FastReport.Base/Engine/ReportEngine.Bands.cs b/FastReport.Base/Engine/ReportEngine.Bands.cs index efc0ffcb..c06c52b8 100644 --- a/FastReport.Base/Engine/ReportEngine.Bands.cs +++ b/FastReport.Base/Engine/ReportEngine.Bands.cs @@ -375,7 +375,7 @@ internal void AddToPreparedPages(BandBase band) { bandHeight = 0; } - while (FreeSpace - bandHeight - band.Child.Height > 0) + while (FreeSpace - bandHeight - band.Child.Height >= 0) { float saveCurY = CurY; ShowBand(band.Child); diff --git a/FastReport.Base/Engine/ReportEngine.cs b/FastReport.Base/Engine/ReportEngine.cs index 9da041ab..813ed196 100644 --- a/FastReport.Base/Engine/ReportEngine.cs +++ b/FastReport.Base/Engine/ReportEngine.cs @@ -344,16 +344,6 @@ private void InitializeSecondPassData() } } - private void InitializePages() - { - for (int i = 0; i < Report.Pages.Count; i++) - { - ReportPage page = Report.Pages[i] as ReportPage; - if (page != null) - PreparedPages.AddSourcePage(page); - } - } - private void PrepareToFirstPass(bool append) { finalPass = !Report.DoublePass; diff --git a/FastReport.Base/Export/ExportBase.cs b/FastReport.Base/Export/ExportBase.cs index dadf991e..15b8e28a 100644 --- a/FastReport.Base/Export/ExportBase.cs +++ b/FastReport.Base/Export/ExportBase.cs @@ -465,37 +465,41 @@ public void Export(Report report, Stream stream) internal void ExportPageNew(int pageNo) { PreparedPage ppage = Report.PreparedPages.GetPreparedPage(pageNo); - ReportPage page = null; - try { - page = ppage.StartGetPage(pageNo); - page.Width = ppage.PageSize.Width; - page.Height = ppage.PageSize.Height; - ExportPageBegin(page); - float topShift = 0; - foreach (Base obj in ppage.GetPageItems(page, false)) + ReportPage page = null; + try { - if (shiftNonExportable && topShift != 0 && obj is BandBase && - !(obj is PageFooterBand) && !(obj as BandBase).PrintOnBottom) - { - (obj as BandBase).Top -= topShift; - } - if ((obj as BandBase).Exportable - || webPreview) - ExportBand(obj); - else if (obj != null) + page = ppage.StartGetPage(pageNo); + page.Width = ppage.PageSize.Width; + page.Height = ppage.PageSize.Height; + ExportPageBegin(page); + float topShift = 0; + foreach (Base obj in ppage.GetPageItems(page, false)) { - if (shiftNonExportable) - topShift += (obj as BandBase).Height; - obj.Dispose(); - } + if (shiftNonExportable && topShift != 0 && obj is BandBase && + !(obj is PageFooterBand) && !(obj as BandBase).PrintOnBottom) + { + (obj as BandBase).Top -= topShift; + } + if ((obj as BandBase).Exportable + || webPreview) + ExportBand(obj); + else if (obj != null) + { + if (shiftNonExportable) + topShift += (obj as BandBase).Height; + obj.Dispose(); + } + } + ExportPageEnd(page); } - ExportPageEnd(page); - } - finally - { - ppage.EndGetPage(page); + finally + { + ppage.EndGetPage(page); + } + if(page != null) + page.Dispose(); } } diff --git a/FastReport.Base/Export/ExportUtils.cs b/FastReport.Base/Export/ExportUtils.cs index 5ba7365c..df420ffc 100644 --- a/FastReport.Base/Export/ExportUtils.cs +++ b/FastReport.Base/Export/ExportUtils.cs @@ -539,16 +539,6 @@ internal static string GetID() return SystemFake.Guid.NewGuid().ToString(); } - internal static void CopyStream(Stream source, Stream target) - { - source.Position = 0; - int bufflength = 2048; - byte[] buff = new byte[bufflength]; - int i; - while ((i = source.Read(buff, 0, bufflength)) > 0) - target.Write(buff, 0, i); - } - internal static byte[] StringToByteArray(string source) { byte[] result = new byte[source.Length]; diff --git a/FastReport.Base/Export/Image/ImageExport.cs b/FastReport.Base/Export/Image/ImageExport.cs index 1831c9f4..cb9d8b78 100644 --- a/FastReport.Base/Export/Image/ImageExport.cs +++ b/FastReport.Base/Export/Image/ImageExport.cs @@ -473,7 +473,7 @@ protected override void Start() bigImage = CreateImage((int)(w * ResolutionX / 96f), (int)(h * ResolutionY / 96f), ""); bigGraphics = Graphics.FromImage(bigImage); - bigGraphics.Clear(Color.White); + bigGraphics.Clear(Color.Transparent); } pageNumber = 0; } @@ -503,8 +503,14 @@ protected override void ExportPageBegin(ReportPage page) else g = Graphics.FromImage(image); - state = g.Save(); - g.FillRegion(Brushes.White, new Region(new RectangleF(0, curOriginY, width, height))); + state = g.Save(); + + g.FillRegion(Brushes.Transparent, new Region(new RectangleF(0, curOriginY, width, height))); + if (bigImage != null && curOriginY + height * 2 > bigImage.Height) + page.Fill.Draw(new FRPaintEventArgs(g, 1, 1, Report.GraphicCache), new RectangleF(0, curOriginY, widthK, bigImage.Height - curOriginY)); + else + page.Fill.Draw(new FRPaintEventArgs(g, 1, 1, Report.GraphicCache), new RectangleF(0, curOriginY, widthK, height + paddingNonSeparatePages * 2)); + if (image == bigImage) { diff --git a/FastReport.Base/Fills.cs b/FastReport.Base/Fills.cs index 0a6a0c35..7e4677aa 100644 --- a/FastReport.Base/Fills.cs +++ b/FastReport.Base/Fills.cs @@ -1119,7 +1119,8 @@ public TextureFill() Stream dummy = ResourceLoader.GetStream("FastReport", "icon16.ico"); using (MemoryStream ms = new MemoryStream()) { - Res.CopyTo(dummy, ms); + const int BUFFER_SIZE = 4 * 1024; + dummy.CopyTo(ms, BUFFER_SIZE); SetImageData(ms.ToArray()); } WrapMode = WrapMode.Tile; diff --git a/FastReport.Base/Functions/NumToWordsPl.cs b/FastReport.Base/Functions/NumToWordsPl.cs new file mode 100644 index 00000000..ba120dda --- /dev/null +++ b/FastReport.Base/Functions/NumToWordsPl.cs @@ -0,0 +1,128 @@ +using System; +using System.Text; +using System.Collections; +using System.Collections.Generic; + +namespace FastReport.Functions +{ + internal class NumToWordsPl : NumToWordsBase + { + private static Dictionary currencyList; + + private static string[] fixedWords = + { + "", "jeden", "dwa", "trzy", "cztery", "pięć", "sześć", + "siedem", "osiem", "dziewięć", "dziesięć", "jedenaście", + "dwanaście", "trzynaście", "czternaście", "piętnaście", + "szesnaście", "siedemnaście", "osiemnaście", "dziewiętnaście" + }; + + private static string[] tens = + { + "", "dziesięć", "dwadzieścia", "trzydzieści", "czterdzieści", "pięćdziesiąt", + "sześćdziesiąt", "siedemdziesiąt", "osiemdziesiąt", "dziewięćdziesiąt" + }; + + private static string[] hunds = + { + "", "sto", "dwieście", "trzysta", "czterysta", + "czterysta", "sześćset", "siedemset", "osiemset", "dziewięćset" + }; + + private static WordInfo thousands = new WordInfo(false, "tysiąc", "tysiące", "tysięcy"); + private static WordInfo millions = new WordInfo(true, "milion", "miliony", "milionów"); + private static WordInfo milliards = new WordInfo(true, "miliard", "miliardy", "miliardów"); + private static WordInfo trillions = new WordInfo(true, "bilion", "biliony", "bilionów"); + + protected override string GetFixedWords(bool male, long value) + { + string result = fixedWords[value]; + if (!male) + { + if (value == 1) + return "jedna"; + if (value == 2) + return "dwie"; + } + return result; + } + + protected override string GetTen(bool male, long value) + { + return tens[value]; + } + + protected override string GetHund(bool male, long value) + { + return hunds[value / 100]; + } + + protected override WordInfo GetThousands() + { + return thousands; + } + + protected override WordInfo GetMillions() + { + return millions; + } + + protected override WordInfo GetMilliards() + { + return milliards; + } + + protected override WordInfo GetTrillions() + { + return trillions; + } + + protected override CurrencyInfo GetCurrency(string currencyName) + { + currencyName = currencyName.ToUpper(); + if (currencyName == "RUR") + currencyName = "RUB"; + return currencyList[currencyName]; + } + + protected override string GetZero() + { + return "zero"; + } + + protected override string GetMinus() + { + return "minus"; + } + + protected override string Case(long value, WordInfo info) + { + value = value % 100; + if (value > GetFixedWordsCount()) + value = value % 10; + + switch (value) + { + case 1: + return info.one; + + case 2: + case 3: + case 4: + return info.two; + + default: + return info.many; + } + } + + static NumToWordsPl() + { + currencyList = new Dictionary(1); + currencyList.Add("PLN", new CurrencyInfo( + new WordInfo(true, "złoty", "zlote", "złotych"), + new WordInfo(false, "grosz", "grosze", "groszy"))); + + } + } +} \ No newline at end of file diff --git a/FastReport.Base/Functions/StdFunctions.cs b/FastReport.Base/Functions/StdFunctions.cs index 45bf2235..b6461e8b 100644 --- a/FastReport.Base/Functions/StdFunctions.cs +++ b/FastReport.Base/Functions/StdFunctions.cs @@ -1052,6 +1052,41 @@ public static string ToWordsPersian(object value, string one, string many) return new NumToWordsPersian().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); } + /// + /// Converts a numeric value to a polish string representation of that value. + /// + /// The numeric value to convert. + /// The string representation of the specified value. + public static string ToWordsPl(object value) + { + return ToWordsPl(value, "PLN"); + } + + /// + /// Converts a numeric value to a polish representation of that value. + /// + /// he numeric value to convert. + /// The 3-digit ISO name of the currency, for example "EUR". + /// + public static string ToWordsPl(object value, string currencyName) + { + return new NumToWordsPl().ConvertCurrency(Convert.ToDecimal(value), currencyName); + } + + /// + /// Converts a numeric value to a polish string representation of that value. + /// + /// The numeric value to convert. + /// True if the name is of male gender. + /// The name in singular form, for example "silla". + /// The name in plural form, for example "Sillas". + /// The name in plural form, for example "Sillas". + /// The string representation of the specified value. + public static string ToWordsPl(object value, string one, string many) + { + return new NumToWordsPl().ConvertNumber(Convert.ToDecimal(value), true, one, many, many); + } + /// /// Converts a value to an english (US) alphabet string representation of that value. /// @@ -1319,6 +1354,9 @@ internal static void Register() RegisteredObjects.AddFunction(myConv.GetMethod("ToLetters", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToLetters"); RegisteredObjects.AddFunction(myConv.GetMethod("ToLettersRu", new Type[] { typeof(object) }), "Conversion,ToLettersRu"); RegisteredObjects.AddFunction(myConv.GetMethod("ToLettersRu", new Type[] { typeof(object), typeof(bool) }), "Conversion,ToLettersRu"); + RegisteredObjects.AddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(string) }), "Conversion,ToWordsPl"); + RegisteredObjects.AddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object) }), "Conversion,ToWordsPl"); + RegisteredObjects.AddFunction(myConv.GetMethod("ToWordsPl", new Type[] { typeof(object), typeof(string), typeof(string) }), "Conversion,ToWordsPl"); #endregion #region Program Flow diff --git a/FastReport.Base/Matrix/MatrixCellDescriptor.cs b/FastReport.Base/Matrix/MatrixCellDescriptor.cs index 9e71ef7a..491508dc 100644 --- a/FastReport.Base/Matrix/MatrixCellDescriptor.cs +++ b/FastReport.Base/Matrix/MatrixCellDescriptor.cs @@ -41,11 +41,16 @@ public enum MatrixAggregateFunction /// Count, - /// - /// Specifies the custom function. - /// - Custom - } + /// + /// Specifies the count of distinct values. + /// + CountDistinct, + + /// + /// Specifies the custom function. + /// + Custom + } /// /// Determines how matrix percents are calculated. diff --git a/FastReport.Base/Matrix/MatrixHelper.cs b/FastReport.Base/Matrix/MatrixHelper.cs index 9cf9f6c8..206192bb 100644 --- a/FastReport.Base/Matrix/MatrixHelper.cs +++ b/FastReport.Base/Matrix/MatrixHelper.cs @@ -911,6 +911,16 @@ private object GetAggregatedValue(ArrayList list, int cellIndex) if (function == MatrixAggregateFunction.Count) return list.Count; + if (function == MatrixAggregateFunction.CountDistinct) + { + Hashtable distinctValues = new Hashtable(); + foreach (object value in list) + { + distinctValues[value] = 1; + } + return distinctValues.Keys.Count; + } + // aggregated value Variant aggrValue = new Variant(); diff --git a/FastReport.Base/Matrix/MatrixObject.cs b/FastReport.Base/Matrix/MatrixObject.cs index 4d7e61da..412d4dca 100644 --- a/FastReport.Base/Matrix/MatrixObject.cs +++ b/FastReport.Base/Matrix/MatrixObject.cs @@ -649,6 +649,7 @@ public override void Assign(Base source) MatrixEvenStylePriority = src.MatrixEvenStylePriority; SplitRows = src.SplitRows; PrintIfEmpty = src.PrintIfEmpty; + data = src.Data; } /// diff --git a/FastReport.Base/PictureObject.cs b/FastReport.Base/PictureObject.cs index d45c0d0d..736a508a 100644 --- a/FastReport.Base/PictureObject.cs +++ b/FastReport.Base/PictureObject.cs @@ -376,6 +376,8 @@ public override void DrawImage(FRPaintEventArgs e) GraphicsState state = g.Save(); try { + if (Config.IsRunningOnMono) // strange behavior of mono - we need to reset clip before we set new one + g.ResetClip(); g.SetClip(drawRect); Report report = Report; if (report != null && report.SmoothGraphics) @@ -419,7 +421,7 @@ public override void DrawImage(FRPaintEventArgs e) protected override void DrawImageInternal2(Graphics graphics, PointF upperLeft, PointF upperRight, PointF lowerLeft) { - Image image = transparentImage != null ? transparentImage : Image; + Image image = transparentImage != null ? transparentImage.Clone() as Image : Image.Clone() as Image; if (image == null) return; if (Grayscale) @@ -435,7 +437,24 @@ protected override void DrawImageInternal2(Graphics graphics, PointF upperLeft, image = grayscaleBitmap; } - graphics.DrawImage(image, new PointF[] { upperLeft, upperRight, lowerLeft }); + //graphics.DrawImage(image, new PointF[] { upperLeft, upperRight, lowerLeft }); + DrawImage3Points(graphics, image, upperLeft, upperRight, lowerLeft); + image = null; + } + + // This is analogue of graphics.DrawImage(image, PointF[] points) method. + // The original gdi+ method does not work properly in mono on linux/macos. + private void DrawImage3Points(Graphics g, Image image, PointF p0, PointF p1, PointF p2) + { + if (image == null || image.Width == 0 || image.Height == 0) + return; + RectangleF rect = new RectangleF(0, 0, image.Width, image.Height); + float m11 = (p1.X - p0.X) / rect.Width; + float m12 = (p1.Y - p0.Y) / rect.Width; + float m21 = (p2.X - p0.X) / rect.Height; + float m22 = (p2.Y - p0.Y) / rect.Height; + g.MultiplyTransform(new System.Drawing.Drawing2D.Matrix(m11, m12, m21, m22, p0.X, p0.Y), MatrixOrder.Prepend); + g.DrawImage(image, rect); } /// diff --git a/FastReport.Base/PictureObjectBase.cs b/FastReport.Base/PictureObjectBase.cs index 7ac3257e..15c89296 100644 --- a/FastReport.Base/PictureObjectBase.cs +++ b/FastReport.Base/PictureObjectBase.cs @@ -787,7 +787,6 @@ public override void Serialize(FRWriter writer) /// /// /// -#if MONO internal virtual void DrawImageInternal(FRPaintEventArgs e, RectangleF drawRect) { bool rotate = Angle == 90 || Angle == 270; @@ -799,105 +798,8 @@ internal virtual void DrawImageInternal(FRPaintEventArgs e, RectangleF drawRect) PointF lowerLeft; System.Drawing.Drawing2D.Matrix matrix = e.Graphics.Transform; GetImageAngleTransform(drawRect, imageWidth, imageHeight, e.ScaleX, e.ScaleY, matrix.OffsetX, matrix.OffsetY, out upperLeft, out upperRight, out lowerLeft); - - // TODO translate tranform matrix, WTF mono or coreCompat or both - // cant work with negative transforms so need to fix it - - bool NeedTransform = Config.IsRunningOnMono; - System.Drawing.Drawing2D.Matrix matrixBack = null; -#if NETSTANDARD2_0 || NETSTANDARD2_1 - NeedTransform = true; -#else - NeedTransform = Config.IsRunningOnMono; -#endif - if (NeedTransform) - { - matrixBack = e.Graphics.Transform; - System.Drawing.Drawing2D.Matrix matrixTemp = new System.Drawing.Drawing2D.Matrix( - matrixBack.Elements[0], - matrixBack.Elements[1], - matrixBack.Elements[2], - matrixBack.Elements[3], - 0, - 0 - ); - - upperLeft.X += matrixBack.OffsetX; - upperLeft.Y += matrixBack.OffsetY; - - upperRight.X += matrixBack.OffsetX; - upperRight.Y += matrixBack.OffsetY; - - lowerLeft.X += matrixBack.OffsetX; - lowerLeft.Y += matrixBack.OffsetY; - - e.Graphics.Transform = matrixTemp; - } - - DrawImageInternal2(e.Graphics, upperLeft, upperRight, lowerLeft); - - if (NeedTransform) - e.Graphics.Transform = matrixBack; - } - -#else - internal virtual void DrawImageInternal(FRPaintEventArgs e, RectangleF drawRect) - { - bool rotate = Angle == 90 || Angle == 270; - float imageWidth = ImageWidth;//rotate ? Image.Height : Image.Width; - float imageHeight = ImageHeight;//rotate ? Image.Width : Image.Height; - - PointF upperLeft; - PointF upperRight; - PointF lowerLeft; - System.Drawing.Drawing2D.Matrix matrix = e.Graphics.Transform; - GetImageAngleTransform(drawRect, imageWidth, imageHeight, e.ScaleX, e.ScaleY, matrix.OffsetX, matrix.OffsetY, out upperLeft, out upperRight, out lowerLeft); - - // TODO translate tranform matrix, WTF mono or coreCompat or both - // cant work with negative transforms so need to fix it - -#if NETSTANDARD2_0 || NETSTANDARD2_1 - - System.Drawing.Drawing2D.Matrix matrixBack = e.Graphics.Transform; - /* - Временное решение (т.к. не удалось найти универсального), которое устраняет проблемы с экспортом картинок в Excel2007 на Windows, но на Linux не работает. - */ - if (!Config.IsWindows) - { - System.Drawing.Drawing2D.Matrix matrixTemp = new System.Drawing.Drawing2D.Matrix( - matrixBack.Elements[0], - matrixBack.Elements[1], - matrixBack.Elements[2], - matrixBack.Elements[3], - 0, - 0 - ); - - upperLeft.X += matrixBack.OffsetX; - upperLeft.Y += matrixBack.OffsetY; - - upperRight.X += matrixBack.OffsetX; - upperRight.Y += matrixBack.OffsetY; - - lowerLeft.X += matrixBack.OffsetX; - lowerLeft.Y += matrixBack.OffsetY; - - e.Graphics.Transform = matrixTemp; - } -#endif - DrawImageInternal2(e.Graphics, upperLeft, upperRight, lowerLeft); - -#if NETSTANDARD2_0 || NETSTANDARD2_1 - if (!Config.IsWindows) - { - e.Graphics.Transform = matrixBack; - } -#endif - } -#endif - #endregion Internal Methods #region Protected Methods diff --git a/FastReport.Base/Preview/PreparedPage.cs b/FastReport.Base/Preview/PreparedPage.cs index 1fc5e132..96a6e639 100644 --- a/FastReport.Base/Preview/PreparedPage.cs +++ b/FastReport.Base/Preview/PreparedPage.cs @@ -99,7 +99,6 @@ private bool DoAdd(Base c, XmlItem item) DoAdd(obj, item); } } - return true; } @@ -279,52 +278,54 @@ internal void ReCalcSizes() reader.DeserializeFrom = SerializeTo.Preview; reader.BlobStore = preparedPages.BlobStore; reader.ReadChildren = false; - ReportPage page; - Base obj; - BandBase band; - page = ReadPage(Report, item, false, reader); - float maxWidth = 0.0f; - float maxHeight = 0.0f; - for (int i = 0; i < item.Count; i++) + using (ReportPage page = ReadPage(Report, item, false, reader)) { - obj = ReadObject(page, item[i], true, reader); - if (obj is BandBase) + if (page.UnlimitedHeight | page.UnlimitedWidth) { - band = obj as BandBase; - float bandsHeight = band.Top + band.Height; - if (maxHeight < bandsHeight) - maxHeight = bandsHeight; - float bandWidth = 0.0f; - foreach (ComponentBase comp in band.Objects) + + float maxWidth = 0.0f; + float maxHeight = 0.0f; + for (int i = 0; i < item.Count; i++) { - if ((comp.Anchor & AnchorStyles.Right) == 0 && comp.Dock == DockStyle.None) + using (Base obj = ReadObject(page, item[i], true, reader)) { - bandWidth = Math.Max(bandWidth, comp.Left + comp.Width); + if (obj is BandBase) + { + BandBase band = obj as BandBase; + float bandsHeight = band.Top + band.Height; + if (maxHeight < bandsHeight) + maxHeight = bandsHeight; + float bandWidth = 0.0f; + foreach (ComponentBase comp in band.Objects) + { + if ((comp.Anchor & AnchorStyles.Right) == 0 && comp.Dock == DockStyle.None) + { + bandWidth = Math.Max(bandWidth, comp.Left + comp.Width); + } + } + if (maxWidth < bandWidth) + maxWidth = bandWidth; + } } } - if (maxWidth < bandWidth) - maxWidth = bandWidth; - } - obj.Dispose(); - } - if (page.UnlimitedHeight) - page.UnlimitedHeightValue = maxHeight + (page.TopMargin + page.BottomMargin) * Units.Millimeters; - if (page.UnlimitedWidth) - page.UnlimitedWidthValue = maxWidth + (page.LeftMargin + page.RightMargin) * Units.Millimeters; + if (page.UnlimitedHeight) + page.UnlimitedHeightValue = maxHeight + (page.TopMargin + page.BottomMargin) * Units.Millimeters; + if (page.UnlimitedWidth) + page.UnlimitedWidthValue = maxWidth + (page.LeftMargin + page.RightMargin) * Units.Millimeters; - pageSize = new SizeF(page.WidthInPixels, page.HeightInPixels); + } + pageSize = new SizeF(page.WidthInPixels, page.HeightInPixels); - using (FRWriter writer = new FRWriter(item)) - { - writer.SerializeTo = SerializeTo.Preview; - writer.SaveChildren = false; - writer.BlobStore = preparedPages.BlobStore; - writer.Write(page); + using (FRWriter writer = new FRWriter(item)) + { + writer.SerializeTo = SerializeTo.Preview; + writer.SaveChildren = false; + writer.BlobStore = preparedPages.BlobStore; + writer.Write(page); + } } - - page.Dispose(); } } diff --git a/FastReport.Base/Preview/PreparedPages.cs b/FastReport.Base/Preview/PreparedPages.cs index 05b8f260..94ca8817 100644 --- a/FastReport.Base/Preview/PreparedPages.cs +++ b/FastReport.Base/Preview/PreparedPages.cs @@ -525,17 +525,18 @@ public void Load(Stream stream) { Clear(); + if (stream.Length == 0) + return; + if (!stream.CanSeek) { MemoryStream tempStream = new MemoryStream(); - FileUtils.CopyStream(stream, tempStream); + const int BUFFER_SIZE = 32768; + stream.CopyTo(tempStream, BUFFER_SIZE); tempStream.Position = 0; stream = tempStream; } - if (stream.Length == 0) - return; - bool compressed = Compressor.IsStreamCompressed(stream); if (compressed) stream = Compressor.Decompress(stream, false); diff --git a/FastReport.Base/Preview/SourcePages.cs b/FastReport.Base/Preview/SourcePages.cs index 0aa83c5d..a3c40532 100644 --- a/FastReport.Base/Preview/SourcePages.cs +++ b/FastReport.Base/Preview/SourcePages.cs @@ -5,11 +5,11 @@ namespace FastReport.Preview { - internal class SourcePages : IDisposable + internal partial class SourcePages : IDisposable { #region Fields - private List pages; - private PreparedPages preparedPages; + private readonly List pages; + private readonly PreparedPages preparedPages; #endregion #region Properties @@ -25,6 +25,7 @@ public ReportPage this[int index] #endregion #region Private Methods + private Base CloneObjects(Base source, Base parent) { if (source is ReportComponentBase && !(source as ReportComponentBase).FlagPreviewVisible) @@ -36,11 +37,7 @@ private Base CloneObjects(Base source, Base parent) Base clone = Activator.CreateInstance(source.GetType()) as Base; using (XmlItem xml = new XmlItem()) using (FRWriter writer = new FRWriter(xml)) -#if MONO - using (FRReader reader = new FRReader(source.Report, xml)) -#else using (FRReader reader = new FRReader(null, xml)) -#endif { reader.DeserializeFrom = SerializeTo.SourcePages; writer.SaveChildren = false; @@ -71,12 +68,9 @@ private Base CloneObjects(Base source, Base parent) ObjectCollection childObjects = source.ChildObjects; foreach (Base c in childObjects) { - CloneObjects(c, clone); + CloneObjects(c, clone); } clone.Parent = parent; -#if MONO - clone.SetReport(source.Report); -#endif return clone; } #endregion diff --git a/FastReport.Base/TextObject.cs b/FastReport.Base/TextObject.cs index 6a285952..c6f98bfd 100644 --- a/FastReport.Base/TextObject.cs +++ b/FastReport.Base/TextObject.cs @@ -525,7 +525,7 @@ public float LineHeight /// [DefaultValue(0f)] [Category("Appearance")] - [TypeConverter("FastReport.TypeConverters.UnitsConverter, FastReport")] + //[TypeConverter("FastReport.TypeConverters.UnitsConverter, FastReport")] public float FirstTabOffset { get { return firstTabOffset; } @@ -1078,17 +1078,17 @@ internal HtmlTextRenderer GetHtmlTextRenderer(Graphics g, RectangleF textRect, f internal HtmlTextRenderer GetHtmlTextRenderer(Graphics g, float scale, float fontScale, RectangleF textRect, StringFormat format) { - return GetHtmlTextRenderer(g, fontScale, scale, fontScale, textRect, format); + return GetHtmlTextRenderer(g, fontScale, scale, fontScale, textRect, format, false); } - internal HtmlTextRenderer GetHtmlTextRenderer(Graphics g, float formatScale, float scale, float fontScale, RectangleF textRect, StringFormat format) + internal HtmlTextRenderer GetHtmlTextRenderer(Graphics g, float formatScale, float scale, float fontScale, RectangleF textRect, StringFormat format, bool isPrinting) { return new HtmlTextRenderer(Text, g, font.Name, font.Size, font.Style, TextColor, textOutline.Color, textRect, Underlines, format, horzAlign, vertAlign, ParagraphFormat.MultipleScale(formatScale), ForceJustify, - scale * 96f / DrawUtils.ScreenDpi, fontScale * 96f / DrawUtils.ScreenDpi, InlineImageCache - ); + scale * 96f / DrawUtils.ScreenDpi, fontScale * 96f / DrawUtils.ScreenDpi, InlineImageCache, + isPrinting); } /// @@ -1137,7 +1137,7 @@ public void DrawText(FRPaintEventArgs e) try { using (HtmlTextRenderer htmlRenderer = GetHtmlTextRenderer(e.Graphics, e.ScaleX, - IsPrinting ? 1 : e.ScaleX, IsPrinting ? 1 : e.ScaleX, textRect, format)) + IsPrinting ? 1 : e.ScaleX, IsPrinting ? 1 : e.ScaleX, textRect, format, IsPrinting)) { htmlRenderer.Draw(); } @@ -1156,7 +1156,7 @@ public void DrawText(FRPaintEventArgs e) outlinePen, textRect, format, HorzAlign, VertAlign, LineHeight * e.ScaleY, Angle, FontWidthRatio, ForceJustify, Wysiwyg, HasHtmlTags, false, e.ScaleX * 96f / DrawUtils.ScreenDpi, - IsPrinting ? 1 : e.ScaleX * 96f / DrawUtils.ScreenDpi, InlineImageCache); + IsPrinting ? 1 : e.ScaleX * 96f / DrawUtils.ScreenDpi, InlineImageCache, IsPrinting); advancedRenderer.Draw(); } else diff --git a/FastReport.Base/Utils/Compressor.cs b/FastReport.Base/Utils/Compressor.cs index ee068d1f..de55a2ae 100644 --- a/FastReport.Base/Utils/Compressor.cs +++ b/FastReport.Base/Utils/Compressor.cs @@ -11,11 +11,7 @@ internal static class Compressor { public static Stream Decompress(Stream source, bool bidiStream) { - int byte1 = source.ReadByte(); - int byte2 = source.ReadByte(); - source.Position -= 2; - bool result = byte1 == 0x1F && byte2 == 0x8B; - if (result) + if (IsStreamCompressed(source)) { if (bidiStream) { @@ -23,13 +19,8 @@ public static Stream Decompress(Stream source, bool bidiStream) Stream stream = new MemoryStream(); using (GZipStream gzip = new GZipStream(source, CompressionMode.Decompress)) { - byte[] buffer = new byte[4096]; - while (true) - { - int bytesRead = gzip.Read(buffer, 0, 4096); - if (bytesRead == 0) break; - stream.Write(buffer, 0, bytesRead); - } + const int BUFFER_SIZE = 4096; + gzip.CopyTo(stream, BUFFER_SIZE); } stream.Position = 0; return stream; diff --git a/FastReport.Base/Utils/Config.cs b/FastReport.Base/Utils/Config.cs index fac12630..6419fe5b 100644 --- a/FastReport.Base/Utils/Config.cs +++ b/FastReport.Base/Utils/Config.cs @@ -25,9 +25,10 @@ public static partial class Config #endif #region Private Fields - private static CultureInfo engCultureInfo = new CultureInfo("en-US"); - private static XmlDocument FDoc = new XmlDocument(); + private static readonly CultureInfo engCultureInfo = new CultureInfo("en-US"); + private static readonly XmlDocument FDoc = new XmlDocument(); + private static readonly string version = typeof(Report).Assembly.GetName().Version.ToString(3); private static string FFolder = null; private static string FFontListFolder = null; private static string FLogs = ""; @@ -36,13 +37,16 @@ public static partial class Config private static bool FRightToLeft = false; private static string FTempFolder = null; private static string systemTempFolder = null; - private static bool FStringOptimization = false; + private static bool FStringOptimization = true; private static bool FWebMode; private static bool preparedCompressed = true; private static bool disableHotkeys = false; private static bool disableBacklight = false; private static bool enableScriptSecurity = false; private static ScriptSecurityProperties scriptSecurityProps = null; + private static bool forbidLocalData = false; + private static bool userSetsScriptSecurity = false; + #if NETSTANDARD2_0 || NETSTANDARD2_1 private static readonly bool isWindows = RuntimeInformation.IsOSPlatform(OSPlatform.Windows); @@ -67,6 +71,17 @@ public static bool IsWindows #endif + + /// + /// Gets or sets a value indicating is it impossible to specify a local data path in Xml and Csv. + /// + public static bool ForbidLocalData + { + get { return forbidLocalData; } + set { forbidLocalData = value; } + } + + /// /// Gets or sets the optimization of strings. Is experimental feature. /// @@ -188,7 +203,7 @@ public static string SystemTempFolder /// public static string Version { - get { return typeof(Report).Assembly.GetName().Version.ToString(3); } + get { return version; } } /// @@ -218,6 +233,13 @@ public static bool EnableScriptSecurity if (OnEnableScriptSecurityChanged != null) OnEnableScriptSecurityChanged.Invoke(null, null); enableScriptSecurity = value; + // + userSetsScriptSecurity = true; + if (value) + { + if(scriptSecurityProps == null) + scriptSecurityProps = new ScriptSecurityProperties(); + } } } @@ -233,7 +255,6 @@ public static ScriptSecurityProperties ScriptSecurityProps { get { return scriptSecurityProps; } } - #endregion Public Properties #region Internal Methods @@ -254,12 +275,14 @@ internal static void Init() { FIsRunningOnMono = Type.GetType("Mono.Runtime") != null; - InitWebMode(); + CheckWebMode(); + #if !NETSTANDARD if (!WebMode) LoadConfig(); #endif - if (WebMode) + + if (!userSetsScriptSecurity && WebMode) { enableScriptSecurity = true; // don't throw event scriptSecurityProps = new ScriptSecurityProperties(); @@ -281,23 +304,28 @@ internal static void Init() } } - private static void InitWebMode() + private static void CheckWebMode() { - string processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; - -#if NETSTANDARD - ConfigSetsWebMode(String.Compare(processName, "dotnet") == 0); + // If we/user sets 'WebMode = true' before this check - Config shouln't change it (because check may be incorrect) + if (!WebMode) + { +#if NETSTANDARD || NETCOREAPP + var loadedAssemblies = AppDomain.CurrentDomain.GetAssemblies(); + foreach (var loadedAsmbly in loadedAssemblies) + { + bool isAspNetCore = loadedAsmbly.GetName().Name.StartsWith("Microsoft.AspNetCore"); + if (isAspNetCore) + { + WebMode = true; + break; + } + } #else - ConfigSetsWebMode(String.Compare(processName, "iisexpress") == 0 || - String.Compare(processName, "w3wp") == 0); + string processName = System.Diagnostics.Process.GetCurrentProcess().ProcessName; + WebMode = String.Compare(processName, "iisexpress") == 0 || + String.Compare(processName, "w3wp") == 0; #endif - } - - // If we/user sets 'WebMode = true' before checks in Config.cs - Config shouln't change it (because check may be incorrect) - private static void ConfigSetsWebMode(bool value) - { - if (!FWebMode) - WebMode = value; + } } internal static void WriteLogString(string s) @@ -581,8 +609,6 @@ private static void RestoreUIOptions() } } - - #endregion Private Methods } } \ No newline at end of file diff --git a/FastReport.Base/Utils/DrawUtils.cs b/FastReport.Base/Utils/DrawUtils.cs index 88452b7c..6bfe419e 100644 --- a/FastReport.Base/Utils/DrawUtils.cs +++ b/FastReport.Base/Utils/DrawUtils.cs @@ -1,9 +1,15 @@ using System; using System.Drawing; -using System.Windows.Forms; namespace FastReport.Utils { + internal enum MonoRendering + { + Undefined, + Pango, + Cairo + } + public static partial class DrawUtils { private static Font FDefaultFont; @@ -13,6 +19,7 @@ public static partial class DrawUtils private static Font FFixedFont; private static int FScreenDpi; private static float FDpiFX; + private static MonoRendering FMonoRendering = MonoRendering.Undefined; public static int ScreenDpi { @@ -197,6 +204,22 @@ public static void FloodFill(Bitmap bmp, int x, int y, Color color, Color replac FloodFill(bmp, x, y + 1, color, replacementColor); } + internal static MonoRendering GetMonoRendering(Graphics printerGraphics) + { + if (FMonoRendering == MonoRendering.Undefined) + { + GraphicsUnit savedUnit = printerGraphics.PageUnit; + printerGraphics.PageUnit = GraphicsUnit.Point; + + string s = "test string test string test string test string"; + float f1 = printerGraphics.MeasureString(s, DefaultReportFont).Width; + FMonoRendering = f1 > 200 ? MonoRendering.Pango : MonoRendering.Cairo; + + printerGraphics.PageUnit = savedUnit; + } + return FMonoRendering; + } + } } \ No newline at end of file diff --git a/FastReport.Base/Utils/FastNameCreator.cs b/FastReport.Base/Utils/FastNameCreator.cs index 3dcbe5b0..fb0b89a2 100644 --- a/FastReport.Base/Utils/FastNameCreator.cs +++ b/FastReport.Base/Utils/FastNameCreator.cs @@ -77,7 +77,7 @@ public FastNameCreator(ObjectCollection objects) } baseNames[baseName] = num; } - else + else if (!baseNames.ContainsKey(objName)) { baseNames[objName] = 0; } diff --git a/FastReport.Base/Utils/FileUtils.cs b/FastReport.Base/Utils/FileUtils.cs index 4b7115bc..917133f6 100644 --- a/FastReport.Base/Utils/FileUtils.cs +++ b/FastReport.Base/Utils/FileUtils.cs @@ -35,13 +35,5 @@ public static string GetRelativePath(string absPath, string baseDirectoryPath) result += String.Join(Path.DirectorySeparatorChar.ToString(), aPath, indx, aPath.Length - indx); return result; } - - public static void CopyStream(Stream input, Stream output) - { - byte[] buffer = new byte[32768]; - int read; - while ((read = input.Read(buffer, 0, buffer.Length)) > 0) - output.Write(buffer, 0, read); - } } } diff --git a/FastReport.Base/Utils/HtmlTextRenderer.cs b/FastReport.Base/Utils/HtmlTextRenderer.cs index 39b9685b..48db751b 100644 --- a/FastReport.Base/Utils/HtmlTextRenderer.cs +++ b/FastReport.Base/Utils/HtmlTextRenderer.cs @@ -40,7 +40,7 @@ internal class HtmlTextRenderer : IDisposable private StyleDescriptor initalStyle; private float fontScale; private FastString cacheString = new FastString(100); - + private bool isPrinting; #endregion Private Fields #region Public Properties @@ -110,7 +110,7 @@ public bool WordWrap public HtmlTextRenderer(string text, Graphics g, string font, float size, FontStyle style, Color color, Color underlineColor, RectangleF rect, bool underlines, StringFormat format, HorzAlign horzAlign, VertAlign vertAlign, - ParagraphFormat paragraphFormat, bool forceJustify, float scale, float fontScale, InlineImageCache cache) + ParagraphFormat paragraphFormat, bool forceJustify, float scale, float fontScale, InlineImageCache cache, bool isPrinting = false) { this.cache = cache; this.scale = scale; @@ -142,6 +142,7 @@ public HtmlTextRenderer(string text, Graphics g, string font, float size, this.paragraphFormat = paragraphFormat; this.font = font; this.size = size; + this.isPrinting = isPrinting; everUnderlines = underlines; backgrounds = new List(); diff --git a/FastReport.Base/Utils/ImageHelper.cs b/FastReport.Base/Utils/ImageHelper.cs index 248cad93..2f65e577 100644 --- a/FastReport.Base/Utils/ImageHelper.cs +++ b/FastReport.Base/Utils/ImageHelper.cs @@ -164,8 +164,7 @@ public static byte[] LoadURL(string url) { if (!String.IsNullOrEmpty(url)) { - //Enable TLS1.2, 1.1. and 1.0; - System.Net.ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00); // System.Net.SecurityProtocolType.Tls | System.Net.SecurityProtocolType.Tls11 | System.Net.SecurityProtocolType.Tls12; + System.Net.ServicePointManager.SecurityProtocol = (SecurityProtocolType)(0xc0 | 0x300 | 0xc00); using (WebClient web = new WebClient()) { return web.DownloadData(url); diff --git a/FastReport.Base/Utils/Res.cs b/FastReport.Base/Utils/Res.cs index b327676d..c058c623 100644 --- a/FastReport.Base/Utils/Res.cs +++ b/FastReport.Base/Utils/Res.cs @@ -88,17 +88,6 @@ private static void LoadBuiltinLocale() FLocaleLoaded = true; } - public static void CopyTo(Stream input, Stream output) - { - byte[] buffer = new byte[4 * 1024]; - int bytesRead; - - while ((bytesRead = input.Read(buffer, 0, buffer.Length)) > 0) - { - output.Write(buffer, 0, bytesRead); - } - } - /// /// Loads the locale from a file. /// diff --git a/FastReport.Base/Utils/ResourceLoader.cs b/FastReport.Base/Utils/ResourceLoader.cs index 26df9756..90906190 100644 --- a/FastReport.Base/Utils/ResourceLoader.cs +++ b/FastReport.Base/Utils/ResourceLoader.cs @@ -58,15 +58,10 @@ public static Stream UnpackStream(string assembly, string resource) using (Stream gzipStream = new GZipStream(packedStream, CompressionMode.Decompress, true)) { MemoryStream result = new MemoryStream(); - - byte[] buffer = new byte[4096]; - while (true) - { - int bytesRead = gzipStream.Read(buffer, 0, 4096); - if (bytesRead == 0) break; - result.Write(buffer, 0, bytesRead); - } - + + const int BUFFER_SIZE = 4096; + gzipStream.CopyTo(result, BUFFER_SIZE); + result.Position = 0; return result; } diff --git a/FastReport.Base/Utils/TextRenderer.cs b/FastReport.Base/Utils/TextRenderer.cs index 30a64d84..f6d7f392 100644 --- a/FastReport.Base/Utils/TextRenderer.cs +++ b/FastReport.Base/Utils/TextRenderer.cs @@ -9,2643 +9,2637 @@ namespace FastReport.Utils { - /// - /// Advanced text renderer is used to perform the following tasks: - /// - draw justified text, text with custom line height, text containing html tags; - /// - calculate text height, get part of text that does not fit in the display rectangle; - /// - get paragraphs, lines, words and char sequence to perform accurate export to such - /// formats as PDF, TXT, RTF - /// - /// Here is how one may operate the renderer items: - /// - /// foreach (AdvancedTextRenderer.Paragraph paragraph in renderer.Paragraphs) - /// { - /// foreach (AdvancedTextRenderer.Line line in paragraph.Lines) - /// { - /// foreach (AdvancedTextRenderer.Word word in line.Words) - /// { - /// if (renderer.HtmlTags) - /// { - /// foreach (AdvancedTextRenderer.Run run in word.Runs) - /// { - /// using (Font f = run.GetFont()) - /// using (Brush b = run.GetBrush()) - /// { - /// g.DrawString(run.Text, f, b, run.Left, run.Top, renderer.Format); - /// } - /// } - /// } - /// else - /// { - /// g.DrawString(word.Text, renderer.Font, renderer.Brush, word.Left, word.Top, renderer.Format); - /// } - /// } - /// } - /// } - /// - /// - public class AdvancedTextRenderer - { - #region Fields - private List paragraphs; - private string text; - private Graphics graphics; - private Font font; - private Brush brush; - private Pen outlinePen; - private RectangleF displayRect; - private StringFormat format; - private HorzAlign horzAlign; - private VertAlign vertAlign; - private float lineHeight; - private float fontLineHeight; - private int angle; - private float widthRatio; - private bool forceJustify; - private bool wysiwyg; - private bool htmlTags; - private bool pDFMode; - private float spaceWidth; - private float scale; - private InlineImageCache cache; - private float fontScale; - #endregion - - #region Properties - public List Paragraphs + /// + /// Advanced text renderer is used to perform the following tasks: + /// - draw justified text, text with custom line height, text containing html tags; + /// - calculate text height, get part of text that does not fit in the display rectangle; + /// - get paragraphs, lines, words and char sequence to perform accurate export to such + /// formats as PDF, TXT, RTF + /// + /// Here is how one may operate the renderer items: + /// + /// foreach (AdvancedTextRenderer.Paragraph paragraph in renderer.Paragraphs) + /// { + /// foreach (AdvancedTextRenderer.Line line in paragraph.Lines) + /// { + /// foreach (AdvancedTextRenderer.Word word in line.Words) + /// { + /// if (renderer.HtmlTags) + /// { + /// foreach (AdvancedTextRenderer.Run run in word.Runs) + /// { + /// using (Font f = run.GetFont()) + /// using (Brush b = run.GetBrush()) + /// { + /// g.DrawString(run.Text, f, b, run.Left, run.Top, renderer.Format); + /// } + /// } + /// } + /// else + /// { + /// g.DrawString(word.Text, renderer.Font, renderer.Brush, word.Left, word.Top, renderer.Format); + /// } + /// } + /// } + /// } + /// + /// + public class AdvancedTextRenderer { - get { return paragraphs; } - } + #region Fields + private List paragraphs; + private string text; + private Graphics graphics; + private Font font; + private Brush brush; + private Pen outlinePen; + private RectangleF displayRect; + private StringFormat format; + private HorzAlign horzAlign; + private VertAlign vertAlign; + private float lineHeight; + private float fontLineHeight; + private int angle; + private float widthRatio; + private bool forceJustify; + private bool wysiwyg; + private bool htmlTags; + private bool pDFMode; + private float spaceWidth; + private float scale; + private InlineImageCache cache; + private float fontScale; + #endregion + + #region Properties + public List Paragraphs + { + get { return paragraphs; } + } - public Graphics Graphics - { - get { return graphics; } - } + public Graphics Graphics + { + get { return graphics; } + } - public Font Font - { - get { return font; } - } + public Font Font + { + get { return font; } + } - public Brush Brush - { - get { return brush; } - } + public Brush Brush + { + get { return brush; } + } - public Pen OutlinePen - { - get { return outlinePen; } - } + public Pen OutlinePen + { + get { return outlinePen; } + } - public Color BrushColor - { - get { return brush is SolidBrush ? (brush as SolidBrush).Color : Color.Black; } - } + public Color BrushColor + { + get { return brush is SolidBrush ? (brush as SolidBrush).Color : Color.Black; } + } - public RectangleF DisplayRect - { - get { return displayRect; } - } + public RectangleF DisplayRect + { + get { return displayRect; } + } - public StringFormat Format - { - get { return format; } - } + public StringFormat Format + { + get { return format; } + } - public HorzAlign HorzAlign - { - get { return horzAlign; } - } + public HorzAlign HorzAlign + { + get { return horzAlign; } + } - public VertAlign VertAlign - { - get { return vertAlign; } - } + public VertAlign VertAlign + { + get { return vertAlign; } + } - public float LineHeight - { - get { return lineHeight; } - } + public float LineHeight + { + get { return lineHeight; } + } - public float FontLineHeight - { - get { return fontLineHeight; } - } + public float FontLineHeight + { + get { return fontLineHeight; } + } - public int Angle - { - get { return angle; } - } + public int Angle + { + get { return angle; } + } - public float WidthRatio - { - get { return widthRatio; } - } + public float WidthRatio + { + get { return widthRatio; } + } - public bool ForceJustify - { - get { return forceJustify; } - } + public bool ForceJustify + { + get { return forceJustify; } + } - public bool Wysiwyg - { - get { return wysiwyg; } - } + public bool Wysiwyg + { + get { return wysiwyg; } + } - public bool HtmlTags - { - get { return htmlTags; } - } + public bool HtmlTags + { + get { return htmlTags; } + } - public float TabSize - { - get - { - // re fix tab offset #2823 sorry linux users, on linux firstTab is firstTab not tabSizes[0] - float firstTab = 0; - float[] tabSizes = Format.GetTabStops(out firstTab); - if (tabSizes.Length > 1) - return tabSizes[1]; - return 0; - } - } + public float TabSize + { + get + { + // re fix tab offset #2823 sorry linux users, on linux firstTab is firstTab not tabSizes[0] + float firstTab = 0; + float[] tabSizes = Format.GetTabStops(out firstTab); + if (tabSizes.Length > 1) + return tabSizes[1]; + return 0; + } + } - public float TabOffset - { - get - { - // re fix tab offset #2823 sorry linux users, on linux firstTab is firstTab not tabSizes[0] - float firstTab = 0; - float[] tabSizes = Format.GetTabStops(out firstTab); - if (tabSizes.Length > 0) - return tabSizes[0]; - return 0; - } - } + public float TabOffset + { + get + { + // re fix tab offset #2823 sorry linux users, on linux firstTab is firstTab not tabSizes[0] + float firstTab = 0; + float[] tabSizes = Format.GetTabStops(out firstTab); + if (tabSizes.Length > 0) + return tabSizes[0]; + return 0; + } + } - public bool WordWrap - { - get { return (Format.FormatFlags & StringFormatFlags.NoWrap) == 0; } - } + public bool WordWrap + { + get { return (Format.FormatFlags & StringFormatFlags.NoWrap) == 0; } + } - public bool RightToLeft - { - get { return (Format.FormatFlags & StringFormatFlags.DirectionRightToLeft) != 0; } - } - - public bool PDFMode - { - get { return pDFMode; } - } - - internal float SpaceWidth - { - get { return spaceWidth; } - } + public bool RightToLeft + { + get { return (Format.FormatFlags & StringFormatFlags.DirectionRightToLeft) != 0; } + } - /// - /// The scale for font tag - /// - public float FontScale { get { return fontScale; } set { fontScale = value; } } - public float Scale { get { return scale; } set { scale = value; } } + public bool PDFMode + { + get { return pDFMode; } + } - public InlineImageCache Cache - { - get - { - if (cache == null) - cache = new InlineImageCache(); - return cache; - } - } - #endregion + internal float SpaceWidth + { + get { return spaceWidth; } + } - #region Private Methods + /// + /// The scale for font tag + /// + public float FontScale { get { return fontScale; } set { fontScale = value; } } + public float Scale { get { return scale; } set { scale = value; } } - const string ab = "abcdefabcdef"; - const string a40b = "abcdef abcdef"; + public InlineImageCache Cache + { + get + { + if (cache == null) + cache = new InlineImageCache(); + return cache; + } + } + #endregion - internal static float CalculateSpaceSize(Graphics g, Font f) - { - float w_ab = g.MeasureString(ab, f).Width; - float w_a40b = g.MeasureString(a40b, f).Width; - return (w_a40b - w_ab) / 40; - } - private void SplitToParagraphs(string text) - { - StyleDescriptor style = new StyleDescriptor(Font.Style, BrushColor, BaseLine.Normal); - if (HtmlTags) - text = text.Replace("
", "\r\n").Replace("
", "\r\n").Replace("
", "\r\n"); - string[] lines = text.Split(new char[] { '\n' }); - int originalCharIndex = 0; - - foreach (string line in lines) - { - string s = line; - if (s.Length > 0 && s[s.Length - 1] == '\r') - s = s.Remove(s.Length - 1); - - Paragraph paragraph = new Paragraph(s, this, originalCharIndex); - paragraphs.Add(paragraph); - if (HtmlTags) - style = paragraph.WrapHtmlLines(style); - else - paragraph.WrapLines(); - - originalCharIndex += line.Length + 1; - } - - // skip empty paragraphs at the end - for (int i = paragraphs.Count - 1; i >= 0; i--) - { - if (paragraphs[i].IsEmpty && paragraphs.Count != 1) - paragraphs.RemoveAt(i); - else - break; - } - } + #region Private Methods + + const string ab = "abcdefabcdef"; + const string a40b = "abcdef abcdef"; + + internal static float CalculateSpaceSize(Graphics g, Font f) + { + float w_ab = g.MeasureString(ab, f).Width; + float w_a40b = g.MeasureString(a40b, f).Width; + return (w_a40b - w_ab) / 40; + } + + private void SplitToParagraphs(string text) + { + StyleDescriptor style = new StyleDescriptor(Font.Style, BrushColor, BaseLine.Normal); + if (HtmlTags) + text = text.Replace("
", "\r\n").Replace("
", "\r\n").Replace("
", "\r\n"); + string[] lines = text.Split(new char[] { '\n' }); + int originalCharIndex = 0; + + foreach (string line in lines) + { + string s = line; + if (s.Length > 0 && s[s.Length - 1] == '\r') + s = s.Remove(s.Length - 1); + + Paragraph paragraph = new Paragraph(s, this, originalCharIndex); + paragraphs.Add(paragraph); + if (HtmlTags) + style = paragraph.WrapHtmlLines(style); + else + paragraph.WrapLines(); + + originalCharIndex += line.Length + 1; + } + + // skip empty paragraphs at the end + for (int i = paragraphs.Count - 1; i >= 0; i--) + { + if (paragraphs[i].IsEmpty && paragraphs.Count != 1) + paragraphs.RemoveAt(i); + else + break; + } + } + + private void AdjustParagraphLines() + { + // calculate text height + float height = 0; + height = CalcHeight(); + /*foreach (Paragraph paragraph in Paragraphs) + { + height += paragraph.Lines.Count * LineHeight; + }*/ + + // calculate Y offset + float offsetY = DisplayRect.Top; + if (VertAlign == VertAlign.Center) + offsetY += (DisplayRect.Height - height) / 2; + else if (VertAlign == VertAlign.Bottom) + offsetY += (DisplayRect.Height - height) - 1; + + for (int i = 0; i < Paragraphs.Count; i++) + { + Paragraph paragraph = Paragraphs[i]; + paragraph.AlignLines(i == Paragraphs.Count - 1 && ForceJustify); + + // adjust line tops + foreach (Line line in paragraph.Lines) + { + line.Top = offsetY; + line.MakeUnderlines(); + offsetY += line.CalcHeight(); + } + } + } + #endregion + + #region Public Methods + public void Draw() + { + // set clipping + GraphicsState state = Graphics.Save(); + Graphics.SetClip(DisplayRect, CombineMode.Intersect); + + // reset alignment + StringAlignment saveAlign = Format.Alignment; + StringAlignment saveLineAlign = Format.LineAlignment; + Format.Alignment = StringAlignment.Near; + Format.LineAlignment = StringAlignment.Near; + + if (Angle != 0) + { + Graphics.TranslateTransform(DisplayRect.Left + DisplayRect.Width / 2, + DisplayRect.Top + DisplayRect.Height / 2); + Graphics.RotateTransform(Angle); + } + + Graphics.ScaleTransform(WidthRatio, 1); + + foreach (Paragraph paragraph in Paragraphs) + { + paragraph.Draw(); + } + + // restore alignment and clipping + Format.Alignment = saveAlign; + Format.LineAlignment = saveLineAlign; + Graphics.Restore(state); + } + + public float CalcHeight() + { + int charsFit = 0; + StyleDescriptor style = null; + return CalcHeight(out charsFit, out style); + } + + public float CalcHeight(out int charsFit, out StyleDescriptor style) + { + charsFit = 0; + style = null; + float height = 0; + float displayHeight = DisplayRect.Height; + if (LineHeight > displayHeight) + return 0; + + foreach (Paragraph paragraph in Paragraphs) + { + foreach (Line line in paragraph.Lines) + { + height += line.CalcHeight(); + if (charsFit == 0 && height > displayHeight) + { + charsFit = line.OriginalCharIndex; + if (HtmlTags) + style = line.Style; + } + } + } + + if (charsFit == 0) + charsFit = text.Length; + return height; + } + + public float CalcWidth() + { + float width = 0; + + foreach (Paragraph paragraph in Paragraphs) + { + foreach (Line line in paragraph.Lines) + { + if (width < line.Width) + width = line.Width; + } + } + return width + spaceWidth; + } + + internal float GetTabPosition(float pos) + { + float tabOffset = TabOffset; + float tabSize = TabSize; + int tabPosition = (int)((pos - tabOffset) / tabSize); + if (pos < tabOffset) + return tabOffset; + return (tabPosition + 1) * tabSize + tabOffset; + } + #endregion + + public AdvancedTextRenderer(string text, Graphics g, Font font, Brush brush, Pen outlinePen, + RectangleF rect, StringFormat format, HorzAlign horzAlign, VertAlign vertAlign, + float lineHeight, int angle, float widthRatio, + bool forceJustify, bool wysiwyg, bool htmlTags, bool pdfMode, + float scale, float fontScale, InlineImageCache cache, bool isPrinting = false) + { + this.cache = cache; + this.scale = scale; + this.fontScale = fontScale; + paragraphs = new List(); + this.text = text; + graphics = g; + this.font = font; + this.brush = brush; + this.outlinePen = outlinePen; + displayRect = rect; + this.format = format; + this.horzAlign = horzAlign; + this.vertAlign = vertAlign; + this.lineHeight = lineHeight; + fontLineHeight = font.GetHeight(g); + if (this.lineHeight == 0) + { + this.lineHeight = fontLineHeight; + if (isPrinting && Config.IsRunningOnMono && DrawUtils.GetMonoRendering(g) == MonoRendering.Pango) + { + // we need this in order to fix inconsistent line spacing when print using Pango rendering + this.lineHeight = fontLineHeight * 1.33f; + } + } + this.angle = angle % 360; + this.widthRatio = widthRatio; + this.forceJustify = forceJustify; + this.wysiwyg = wysiwyg; + this.htmlTags = htmlTags; + pDFMode = pdfMode; + spaceWidth = CalculateSpaceSize(g, font);// g.MeasureString(" ", font).Width; + + StringFormatFlags saveFlags = Format.FormatFlags; + StringTrimming saveTrimming = Format.Trimming; + + // match DrawString behavior: + // if height is less than 1.25 of font height, turn off word wrap + // commented out due to bug with band.break + //if (rect.Height < FFontLineHeight * 1.25f) + //FFormat.FormatFlags |= StringFormatFlags.NoWrap; + + // if word wrap is set, ignore trimming + if (WordWrap) + Format.Trimming = StringTrimming.Word; + + Format.FormatFlags = Format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces; + + if (Angle != 0) + { + // shift displayrect + displayRect.X = -DisplayRect.Width / 2; + displayRect.Y = -DisplayRect.Height / 2; + + // rotate displayrect if angle is 90 or 270 + if ((Angle >= 90 && Angle < 180) || (Angle >= 270 && Angle < 360)) + displayRect = new RectangleF(DisplayRect.Y, DisplayRect.X, DisplayRect.Height, DisplayRect.Width); + } + + displayRect.X /= WidthRatio; + displayRect.Width /= WidthRatio; + + SplitToParagraphs(text); + AdjustParagraphLines(); + + // restore original values + displayRect = rect; + Format.FormatFlags = saveFlags; + Format.Trimming = saveTrimming; + } + + + /// + /// Paragraph represents single paragraph. It consists of one or several . + /// + public class Paragraph + { + #region Fields + private List lines; + private AdvancedTextRenderer renderer; + private string text; + private int originalCharIndex; + #endregion + + #region Properties + public List Lines + { + get { return lines; } + } + + public AdvancedTextRenderer Renderer + { + get { return renderer; } + } + + public bool Last + { + get { return renderer.Paragraphs[renderer.Paragraphs.Count - 1] == this; } + } + + public bool IsEmpty + { + get { return String.IsNullOrEmpty(text); } + } + + public string Text + { + get { return text; } + } + #endregion + + #region Private Methods + private int MeasureString(string text) + { + if (text.Length > 0) + { + // BEGIN: The fix for linux and core app a264aae5-193b-4e5c-955c-0818de3ca01b + float left = 0; + int tabFit = 0; + while (text.Length > 0 && text[0] == '\t') + { + left = Renderer.GetTabPosition(left); + text = text.Substring(1); + if (Renderer.DisplayRect.Width < left) + return tabFit; + tabFit++; + } + if (tabFit > 0 && Renderer.DisplayRect.Width < left) + return tabFit; + int charsFit = 0; + int linesFit = 0; + // END: The fix for linux and core app a264aae5-193b-4e5c-955c-0818de3ca01b + Renderer.Graphics.MeasureString(text, Renderer.Font, + new SizeF(Renderer.DisplayRect.Width - left, Renderer.FontLineHeight + 1), + Renderer.Format, out charsFit, out linesFit); + return charsFit + tabFit; + } + return 0; + } + #endregion + + #region Public Methods + public void WrapLines() + { + string text = this.text; + int charsFit = 0; + + if (String.IsNullOrEmpty(text)) + { + lines.Add(new Line("", this, originalCharIndex)); + return; + } + + if (Renderer.WordWrap) + { + int originalCharIndex = this.originalCharIndex; + while (text.Length > 0) + { + charsFit = MeasureString(text); + + // avoid infinite loop when width of object less than width of one character + if (charsFit == 0) + { + break; + } + + string textFit = text.Substring(0, charsFit).TrimEnd(new char[] { ' ' }); + lines.Add(new Line(textFit, this, originalCharIndex)); + text = text.Substring(charsFit) + // Fix for linux system + .TrimStart(' '); + originalCharIndex += charsFit; + } + } + else + { + string ellipsis = "\u2026"; + StringTrimming trimming = Renderer.Format.Trimming; + if (trimming == StringTrimming.EllipsisPath) + Renderer.Format.Trimming = StringTrimming.Character; + charsFit = MeasureString(text); + + switch (trimming) + { + case StringTrimming.Character: + case StringTrimming.Word: + text = text.Substring(0, charsFit); + break; + + case StringTrimming.EllipsisCharacter: + case StringTrimming.EllipsisWord: + if (charsFit < text.Length) + { + text = text.Substring(0, charsFit); + if (text.EndsWith(" ")) + text = text.Substring(0, text.Length - 1); + text += ellipsis; + } + break; + + case StringTrimming.EllipsisPath: + if (charsFit < text.Length) + { + while (text.Length > 3) + { + int mid = text.Length / 2; + string newText = text.Substring(0, mid) + ellipsis + text.Substring(mid + 1); + if (MeasureString(newText) == newText.Length) + { + text = newText; + break; + } + else + { + text = text.Remove(mid, 1); + } + } + } + break; + } + + lines.Add(new Line(text, this, originalCharIndex)); + } + } + + public StyleDescriptor WrapHtmlLines(StyleDescriptor style) + { + Line line = new Line("", this, this.originalCharIndex); + lines.Add(line); + Word word = new Word("", line); + line.Words.Add(word); + // for img + //RunImage img = null; + //end img + string text = this.text; + StringBuilder currentWord = new StringBuilder(100); + float width = 0; + bool skipSpace = true; + int originalCharIndex = this.originalCharIndex; + + for (int i = 0; i < text.Length; i++) + { + char lastChar = text[i]; + if (lastChar == '&') + { + if (Converter.FromHtmlEntities(text, ref i, currentWord)) + { + if (i >= text.Length - 1) + { + word.Runs.Add(new Run(currentWord.ToString(), style, word)); + // check width + width += word.Width + Renderer.SpaceWidth; + if (width > Renderer.DisplayRect.Width) + { + // line is too long, make a new line + if (line.Words.Count > 1) + { + // if line has several words, delete the last word from the current line + line.Words.RemoveAt(line.Words.Count - 1); + // make new line + line = new Line("", this, originalCharIndex); + // and add word to it + line.Words.Add(word); + word.SetLine(line); + lines.Add(line); + } + } +#if DOTNET_4 + currentWord.Clear(); // .NET 2.0 doesn't have Clear() +#else + currentWord.Length = 0; +#endif + lastChar = ' '; + } + else + { + if (currentWord[currentWord.Length - 1] == '\t') + { + currentWord.Length--; + lastChar = '\t'; + + } + else + { + continue; + } + } + } + } + if (lastChar == '<') + { + // probably html tag + StyleDescriptor newStyle = new StyleDescriptor(style.FontStyle, style.Color, style.BaseLine); + newStyle.Font = style.Font; + newStyle.Size = style.Size; + string tag = ""; + bool match = false; + + // , , + if (i + 3 <= text.Length) + { + match = true; + tag = text.Substring(i, 3).ToLower(); + if (tag == "") + newStyle.FontStyle |= FontStyle.Bold; + else if (tag == "") + newStyle.FontStyle |= FontStyle.Italic; + else if (tag == "") + newStyle.FontStyle |= FontStyle.Underline; + else + match = false; + + if (match) + i += 3; + } + + // , , + if (!match && i + 4 <= text.Length && text[i + 1] == '/') + { + match = true; + tag = text.Substring(i, 4).ToLower(); + if (tag == "") + newStyle.FontStyle &= ~FontStyle.Bold; + else if (tag == "") + newStyle.FontStyle &= ~FontStyle.Italic; + else if (tag == "") + newStyle.FontStyle &= ~FontStyle.Underline; + else + match = false; + + if (match) + i += 4; + } + + // , // ") + newStyle.BaseLine = BaseLine.Subscript; + else if (tag == "") + newStyle.BaseLine = BaseLine.Superscript; + else if (tag == "', i + 5); + if (right <= 0) match = false; + else + { + //found img and parse them + string src = null; + string alt = " "; + //currentWord = ""; + int src_ind = text.IndexOf("src=\"", i + 5); + if (src_ind < right && src_ind >= 0) + { + src_ind += 5; + int src_end = text.IndexOf("\"", src_ind); + if (src_end < right && src_end >= 0) + { + src = text.Substring(src_ind, src_end - src_ind); + } + } + int alt_ind = text.IndexOf("alt=\"", i + 5); + if (alt_ind < right && alt_ind >= 0) + { + alt_ind += 5; + int alt_end = text.IndexOf("\"", alt_ind); + if (alt_end < right && alt_end >= 0) + { + alt = text.Substring(alt_ind, alt_end - alt_ind); + } + } + //begin + if (currentWord.Length != 0) + { + // finish the word + word.Runs.Add(new Run(currentWord.ToString(), style, word)); + } +#if DOTNET_4 + currentWord.Clear(); // .NET 2.0 doesn't have Clear() +#else + currentWord.Length = 0; +#endif + //end + word.Runs.Add(new RunImage(src, alt, style, word)); + skipSpace = false; + i = right - 4; + } + } + else if (tag == "', i + 5); + if (right <= 0) match = false; + else + { + //found font and parse them + string color = null; + string face = null; + string size = null; + int color_ind = text.IndexOf("color=\"", i + 5); + if (color_ind < right && color_ind >= 0) + { + color_ind += 7; + int color_end = text.IndexOf("\"", color_ind); + if (color_end < right && color_end >= 0) + { + color = text.Substring(color_ind, color_end - color_ind); + } + } + + int face_ind = text.IndexOf("face=\"", i + 5); + if (face_ind < right && face_ind >= 0) + { + face_ind += 6; + int face_end = text.IndexOf("\"", face_ind); + if (face_end < right && face_end >= 0) + { + face = text.Substring(face_ind, face_end - face_ind); + } + } + + int size_ind = text.IndexOf("size=\"", i + 5); + if (size_ind < right && size_ind >= 0) + { + size_ind += 6; + int size_end = text.IndexOf("\"", size_ind); + if (size_end < right && size_end >= 0) + { + size = text.Substring(size_ind, size_end - size_ind); + } + } + + if (color != null) + { + if (color.StartsWith("\"") && color.EndsWith("\"")) + color = color.Substring(1, color.Length - 2); + if (color.StartsWith("#")) + { + newStyle.Color = Color.FromArgb((int)(0xFF000000 + uint.Parse(color.Substring(1), NumberStyles.HexNumber))); + } + else + { + newStyle.Color = Color.FromName(color); + } + } + newStyle.Font = face; + if (size != null) + { + + try + { + size = size.Trim(' '); + + switch (size[0]) + { + case '-': + size = size.Substring(1); + if (style.Size == 0) + newStyle.Size = Renderer.Font.Size - (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; + else + newStyle.Size = style.Size - (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; + break; + case '+': + size = size.Substring(1); + if (style.Size == 0) + newStyle.Size = Renderer.Font.Size + (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; + else + newStyle.Size = style.Size + (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; + break; + default: newStyle.Size = (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; break; + } + if (newStyle.Size < 0) newStyle.Size = 0; + } + catch { } + } + i = right - 4; + } + } + else + match = false; + + if (match) + i += 5; + } + + // , + if (!match && i + 6 <= text.Length && text[i + 1] == '/') + { + match = true; + tag = text.Substring(i, 6).ToLower(); + if (tag == "") + newStyle.BaseLine = BaseLine.Normal; + else if (tag == "") + newStyle.BaseLine = BaseLine.Normal; + else + match = false; + + if (match) + i += 6; + } + + // + if (!match && i + 8 <= text.Length && text.Substring(i, 8).ToLower() == "") + { + newStyle.FontStyle |= FontStyle.Strikeout; + match = true; + i += 8; + } + + // + if (!match && i + 9 <= text.Length && text.Substring(i, 9).ToLower() == "") + { + newStyle.FontStyle &= ~FontStyle.Strikeout; + match = true; + i += 9; + } + /* + // + if (!match && i + 7 <= text.Length && text.Substring(i, 7).ToLower() == "") + { + newStyle.Color = Renderer.BrushColor; + newStyle.Size = 0; + newStyle.Font = null; + match = true; + i += 7; + } + + if (match) + { + if (currentWord.Length != 0) + { + // finish the word + word.Runs.Add(new Run(currentWord.ToString(), style, word)); + } + +#if DOTNET_4 + currentWord.Clear(); // .NET 2.0 doesn't have Clear() +#else + currentWord.Length = 0; +#endif + style = newStyle; + i--; + + if (i >= text.Length - 1) + { + // check width + width += word.Width + Renderer.SpaceWidth; + if (width > Renderer.DisplayRect.Width) + { + // line is too long, make a new line + if (line.Words.Count > 1) + { + // if line has several words, delete the last word from the current line + line.Words.RemoveAt(line.Words.Count - 1); + // make new line + line = new Line("", this, originalCharIndex); + // and add word to it + line.Words.Add(word); + word.SetLine(line); + lines.Add(line); + } + } + } + continue; + } + } + if (lastChar == ' ' || lastChar == '\t' || i == text.Length - 1) + { + // finish the last word + bool isLastWord = i == text.Length - 1; + if (isLastWord) + { + currentWord.Append(lastChar); + skipSpace = false; + } + + if (lastChar == '\t') + skipSpace = false; + + // space + if (skipSpace) + { + currentWord.Append(lastChar); + } + else + { + // finish the word + if (currentWord.Length != 0) + word.Runs.Add(new Run(currentWord.ToString(), style, word)); + + // check width + width += word.Width + word.SpaceWidth; + if (width > Renderer.DisplayRect.Width) + { + // line is too long, make a new line + width = 0; + if (line.Words.Count > 1) + { + // if line has several words, delete the last word from the current line + line.Words.RemoveAt(line.Words.Count - 1); + // make new line + line = new Line("", this, originalCharIndex); + // and add word to it + line.Words.Add(word); + word.SetLine(line); + width += word.Width + word.SpaceWidth; + } + else + { + line = new Line("", this, i + 1); + } + lines.Add(line); + } + + // TAB symbol + if (lastChar == '\t') + { + if (currentWord.Length == 0 && line.Words.Count > 0 && line.Words[line.Words.Count - 1].Width == 0) + line.Words.RemoveAt(line.Words.Count - 1); + word = new Word("\t", line); + line.Words.Add(word); + // adjust width + width = Renderer.GetTabPosition(width); + } + + if (!isLastWord) + { + word = new Word("", line); + line.Words.Add(word); +#if DOTNET_4 + currentWord.Clear(); // .NET 2.0 doesn't have Clear() +#else + currentWord.Length = 0; +#endif + originalCharIndex = this.originalCharIndex + i + 1; + skipSpace = true; + } + } + } + else + { + // symbol + currentWord.Append(lastChar); + skipSpace = false; + } + } + + return style; + } + + public void AlignLines(bool forceJustify) + { + for (int i = 0; i < Lines.Count; i++) + { + HorzAlign align = Renderer.HorzAlign; + if (align == HorzAlign.Justify && i == Lines.Count - 1 && !forceJustify) + align = HorzAlign.Left; + Lines[i].AlignWords(align); + } + } + + public void Draw() + { + foreach (Line line in Lines) + { + line.Draw(); + } + } + #endregion + + public Paragraph(string text, AdvancedTextRenderer renderer, int originalCharIndex) + { + lines = new List(); + this.text = text; + this.renderer = renderer; + this.originalCharIndex = originalCharIndex; + } + } + + + /// + /// Line represents single text line. It consists of one or several . + /// Simple line (that does not contain tabs, html tags, and is not justified) has + /// single which contains all the text. + /// + public class Line + { + #region Fields + private List words; + private string text; + private bool hasTabs; + private Paragraph paragraph; + private float top; + private float width; + private int originalCharIndex; + private List underlines; + private List strikeouts; + #endregion + + #region Properties + public List Words + { + get { return words; } + } + + public string Text + { + get { return text; } + } + + public bool HasTabs + { + get { return hasTabs; } + } + + public float Left + { + get { return Words.Count > 0 ? Words[0].Left : 0; } + } + + public float Top + { + get { return top; } + set { top = value; } + } + + public float Width + { + get { return width; } + } + + public int OriginalCharIndex + { + get { return originalCharIndex; } + } + + public AdvancedTextRenderer Renderer + { + get { return paragraph.Renderer; } + } + + public StyleDescriptor Style + { + get + { + if (Words.Count > 0) + if (Words[0].Runs.Count > 0) + return Words[0].Runs[0].Style; + return null; + } + } + + public bool Last + { + get { return paragraph.Lines[paragraph.Lines.Count - 1] == this; } + } + + public List Underlines + { + get { return underlines; } + } + + public List Strikeouts + { + get { return strikeouts; } + } + #endregion + + #region Private Methods + private void PrepareUnderlines(List list, FontStyle style) + { + list.Clear(); + if (Words.Count == 0) + return; + + if (Renderer.HtmlTags) + { + float left = 0; + float right = 0; + bool styleOn = false; + + foreach (Word word in Words) + { + foreach (Run run in word.Runs) + { + using (Font fnt = run.GetFont()) + { + if ((fnt.Style & style) > 0) + { + if (!styleOn) + { + styleOn = true; + left = run.Left; + } + right = run.Left + run.Width; + } + if ((fnt.Style & style) == 0 && styleOn) + { + styleOn = false; + list.Add(new RectangleF(left, Top, right - left, 1)); + } + } + } + } + // close the style + if (styleOn) + list.Add(new RectangleF(left, Top, right - left, 1)); + } + else if ((Renderer.Font.Style & style) > 0) + { + float lineWidth = Width; + if (Renderer.HorzAlign == HorzAlign.Justify && (!Last || (paragraph.Last && Renderer.ForceJustify))) + lineWidth = Renderer.DisplayRect.Width - Renderer.SpaceWidth; + + list.Add(new RectangleF(Left, Top, lineWidth, 1)); + } + } + #endregion + + #region Public Methods + public void AlignWords(HorzAlign align) + { + width = 0; + + // handle each word + if (align == HorzAlign.Justify || HasTabs || Renderer.Wysiwyg || Renderer.HtmlTags) + { + float left = 0; + Word word = null; + for (int i = 0; i < Words.Count; i++) + { + word = Words[i]; + word.Left = left; + + if (word.Text == "\t") + { + left = Renderer.GetTabPosition(left); + // remove tab + Words.RemoveAt(i); + i--; + } + else + left += word.Width + word.SpaceWidth; + } + if (word != null) + width = left - word.SpaceWidth; + else + width = left - Renderer.SpaceWidth; + } + else + { + // join all words into one + Words.Clear(); + Words.Add(new Word(text, this)); + width = Words[0].Width; + } + + float rectWidth = Renderer.DisplayRect.Width; + if (align == HorzAlign.Justify) + { + float delta = (rectWidth - width - Renderer.SpaceWidth) / (Words.Count - 1); + float curDelta = delta; + for (int i = 1; i < Words.Count; i++) + { + words[i].Left += curDelta; + curDelta += delta; + } + } + else + { + float delta = 0; + if (align == HorzAlign.Center) + delta = (rectWidth - width) / 2; + else if (align == HorzAlign.Right) + delta = rectWidth - width - Renderer.SpaceWidth; + for (int i = 0; i < Words.Count; i++) + { + words[i].Left += delta; + } + } + + // adjust X offset + foreach (Word word in Words) + { + if (Renderer.RightToLeft) + word.Left = Renderer.DisplayRect.Right - word.Left; + else + word.Left += Renderer.DisplayRect.Left; + word.AdjustRuns(); + if (Renderer.RightToLeft && Renderer.PDFMode) + word.Left -= word.Width; + } + } + + public void MakeUnderlines() + { + PrepareUnderlines(underlines, FontStyle.Underline); + PrepareUnderlines(strikeouts, FontStyle.Strikeout); + } + + public void Draw() + { + foreach (Word word in Words) + { + word.Draw(); + } + + if (Underlines.Count > 0 || Strikeouts.Count > 0) + { + using (Pen pen = new Pen(Renderer.Brush, Renderer.Font.Size * 0.1f)) + { + float h = Renderer.FontLineHeight; + float w = h * 0.1f; // to match .net char X offset + // invert offset in case of rtl + if (Renderer.RightToLeft) + w = -w; + + // emulate underline & strikeout + foreach (RectangleF rect in Underlines) + { + Renderer.Graphics.DrawLine(pen, rect.Left + w, rect.Top + h - w, rect.Right + w, rect.Top + h - w); + } + + h /= 2; + foreach (RectangleF rect in Strikeouts) + { + Renderer.Graphics.DrawLine(pen, rect.Left + w, rect.Top + h, rect.Right + w, rect.Top + h); + } + } + } + } + + public float CalcHeight() + { + float height = -1; + foreach (Word word in Words) + { + height = Math.Max(height, word.CalcHeight()); + } + if (height < 0) + height = Renderer.LineHeight; + return height; + } + #endregion + + public Line(string text, Paragraph paragraph, int originalCharIndex) + { + this.words = new List(); + this.text = text; + this.paragraph = paragraph; + this.originalCharIndex = originalCharIndex; + underlines = new List(); + strikeouts = new List(); + hasTabs = text.Contains("\t"); + + // split text by spaces + string[] words = text.Split(new char[] { ' ' }); + string textWithSpaces = ""; + + foreach (string word in words) + { + if (word == "") + textWithSpaces += " "; + else + { + // split text by tabs + textWithSpaces += word; + string[] tabWords = textWithSpaces.Split(new char[] { '\t' }); + + foreach (string word1 in tabWords) + { + if (word1 == "") + this.words.Add(new Word("\t", this)); + else + { + this.words.Add(new Word(word1, this)); + this.words.Add(new Word("\t", this)); + } + } + + // remove last tab + this.words.RemoveAt(this.words.Count - 1); + + textWithSpaces = ""; + } + } + } - private void AdjustParagraphLines() - { - // calculate text height - float height = 0; - height = CalcHeight(); - /*foreach (Paragraph paragraph in Paragraphs) - { - height += paragraph.Lines.Count * LineHeight; - }*/ - - // calculate Y offset - float offsetY = DisplayRect.Top; - if (VertAlign == VertAlign.Center) - offsetY += (DisplayRect.Height - height) / 2; - else if (VertAlign == VertAlign.Bottom) - offsetY += (DisplayRect.Height - height) - 1; - - for (int i = 0; i < Paragraphs.Count; i++) - { - Paragraph paragraph = Paragraphs[i]; - paragraph.AlignLines(i == Paragraphs.Count - 1 && ForceJustify); - - // adjust line tops - foreach (Line line in paragraph.Lines) - { - line.Top = offsetY; - line.MakeUnderlines(); - offsetY += line.CalcHeight(); - } - } - } - #endregion + internal float CalcBaseLine() + { - #region Public Methods - public void Draw() - { - // set clipping - GraphicsState state = Graphics.Save(); - Graphics.SetClip(DisplayRect, CombineMode.Intersect); - - // reset alignment - StringAlignment saveAlign = Format.Alignment; - StringAlignment saveLineAlign = Format.LineAlignment; - Format.Alignment = StringAlignment.Near; - Format.LineAlignment = StringAlignment.Near; - - if (Angle != 0) - { - Graphics.TranslateTransform(DisplayRect.Left + DisplayRect.Width / 2, - DisplayRect.Top + DisplayRect.Height / 2); - Graphics.RotateTransform(Angle); - } - - Graphics.ScaleTransform(WidthRatio, 1); - - foreach (Paragraph paragraph in Paragraphs) - { - paragraph.Draw(); - } - - // restore alignment and clipping - Format.Alignment = saveAlign; - Format.LineAlignment = saveLineAlign; - Graphics.Restore(state); - } + float baseline = 0; + foreach (Word word in Words) + { + baseline = Math.Max(baseline, word.CalcBaseLine()); + } + return baseline; + } - public float CalcHeight() - { - int charsFit = 0; - StyleDescriptor style = null; - return CalcHeight(out charsFit, out style); - } + internal float CalcUnderBaseLine() + { - public float CalcHeight(out int charsFit, out StyleDescriptor style) - { - charsFit = 0; - style = null; - float height = 0; - float displayHeight = DisplayRect.Height; - if (LineHeight > displayHeight) - return 0; - - foreach (Paragraph paragraph in Paragraphs) - { - foreach (Line line in paragraph.Lines) - { - height += line.CalcHeight(); - if (charsFit == 0 && height > displayHeight) - { - charsFit = line.OriginalCharIndex; - if (HtmlTags) - style = line.Style; - } + float underbaseline = 0; + foreach (Word word in Words) + { + underbaseline = Math.Max(underbaseline, word.CalcUnderBaseLine()); + } + return underbaseline; + } } - } - - if (charsFit == 0) - charsFit = text.Length; - return height; - } - public float CalcWidth() - { - float width = 0; - foreach (Paragraph paragraph in Paragraphs) - { - foreach (Line line in paragraph.Lines) + /// + /// Word represents single word. It may consist of one or several , in case + /// when HtmlTags are enabled in the main class. + /// + public class Word { - if (width < line.Width) - width = line.Width; - } - } - return width + spaceWidth; - } - - internal float GetTabPosition(float pos) - { - float tabOffset = TabOffset; - float tabSize = TabSize; - int tabPosition = (int)((pos - tabOffset) / tabSize); - if (pos < tabOffset) - return tabOffset; - return (tabPosition + 1) * tabSize + tabOffset; - } - #endregion - - //public AdvancedTextRenderer(string text, Graphics g, Font font, Brush brush, Pen outlinePen, - // RectangleF rect, StringFormat format, HorzAlign horzAlign, VertAlign vertAlign, - // float lineHeight, int angle, float widthRatio, - // bool forceJustify, bool wysiwyg, bool htmlTags, bool pdfMode, - // float scale) - // : this (text, g, font, brush, outlinePen, - // rect, format, horzAlign, vertAlign, - // lineHeight, angle, widthRatio, - // forceJustify, wysiwyg, htmlTags, pdfMode, - // scale, null) - //{ - - //} - - public AdvancedTextRenderer(string text, Graphics g, Font font, Brush brush, Pen outlinePen, - RectangleF rect, StringFormat format, HorzAlign horzAlign, VertAlign vertAlign, - float lineHeight, int angle, float widthRatio, - bool forceJustify, bool wysiwyg, bool htmlTags, bool pdfMode, - float scale, float fontScale, InlineImageCache cache) - { - this.cache = cache; - this.scale = scale; - this.fontScale = fontScale; - paragraphs = new List(); - this.text = text; - graphics = g; - this.font = font; - this.brush = brush; - this.outlinePen = outlinePen; - displayRect = rect; - this.format = format; - this.horzAlign = horzAlign; - this.vertAlign = vertAlign; - this.lineHeight = lineHeight; - fontLineHeight = font.GetHeight(g); - if (this.lineHeight == 0) - this.lineHeight = fontLineHeight; - this.angle = angle % 360; - this.widthRatio = widthRatio; - this.forceJustify = forceJustify; - this.wysiwyg = wysiwyg; - this.htmlTags = htmlTags; - pDFMode = pdfMode; - spaceWidth = CalculateSpaceSize(g, font);// g.MeasureString(" ", font).Width; - - StringFormatFlags saveFlags = Format.FormatFlags; - StringTrimming saveTrimming = Format.Trimming; - - // match DrawString behavior: - // if height is less than 1.25 of font height, turn off word wrap - // commented out due to bug with band.break - //if (rect.Height < FFontLineHeight * 1.25f) - //FFormat.FormatFlags |= StringFormatFlags.NoWrap; - - // if word wrap is set, ignore trimming - if (WordWrap) - Format.Trimming = StringTrimming.Word; - - Format.FormatFlags = Format.FormatFlags | StringFormatFlags.MeasureTrailingSpaces; - - if (Angle != 0) - { - // shift displayrect - displayRect.X = -DisplayRect.Width / 2; - displayRect.Y = -DisplayRect.Height / 2; - - // rotate displayrect if angle is 90 or 270 - if ((Angle >= 90 && Angle < 180) || (Angle >= 270 && Angle < 360)) - displayRect = new RectangleF(DisplayRect.Y, DisplayRect.X, DisplayRect.Height, DisplayRect.Width); - } - - displayRect.X /= WidthRatio; - displayRect.Width /= WidthRatio; - - SplitToParagraphs(text); - AdjustParagraphLines(); - - // restore original values - displayRect = rect; - Format.FormatFlags = saveFlags; - Format.Trimming = saveTrimming; - } + #region Fields + private List runs; + protected string text; + private float left; + private float width; + internal Line line; + #endregion + + #region Properties + public string Text + { + get { return text; } + } + public float Left + { + get { return left; } + set { left = value; } + } - /// - /// Paragraph represents single paragraph. It consists of one or several . - /// - public class Paragraph - { - #region Fields - private List lines; - private AdvancedTextRenderer renderer; - private string text; - private int originalCharIndex; - #endregion - - #region Properties - public List Lines - { - get { return lines; } - } - - public AdvancedTextRenderer Renderer - { - get { return renderer; } - } - - public bool Last - { - get { return renderer.Paragraphs[renderer.Paragraphs.Count - 1] == this; } - } - - public bool IsEmpty - { - get { return String.IsNullOrEmpty(text); } - } - - public string Text - { - get { return text; } - } - #endregion - - #region Private Methods - private int MeasureString(string text) - { - if (text.Length > 0) + public float Width + { + get { - // BEGIN: The fix for linux and core app a264aae5-193b-4e5c-955c-0818de3ca01b - float left = 0; - int tabFit = 0; - while (text.Length > 0 && text[0] == '\t') + if (width == -1) { - left = Renderer.GetTabPosition(left); - text = text.Substring(1); - if (Renderer.DisplayRect.Width < left) - return tabFit; - tabFit++; + if (Renderer.HtmlTags) + { + width = 0; + foreach (Run run in Runs) + { + width += run.Width; + } + } + else + { + width = Renderer.Graphics.MeasureString(text, Renderer.Font, 10000, StringFormat.GenericTypographic).Width; + } } - if (tabFit > 0 && Renderer.DisplayRect.Width < left) - return tabFit; - int charsFit = 0; - int linesFit = 0; - // END: The fix for linux and core app a264aae5-193b-4e5c-955c-0818de3ca01b - Renderer.Graphics.MeasureString(text, Renderer.Font, - new SizeF(Renderer.DisplayRect.Width - left, Renderer.FontLineHeight + 1), - Renderer.Format, out charsFit, out linesFit); - return charsFit + tabFit; + return width; } - return 0; - } - #endregion - - #region Public Methods - public void WrapLines() - { - string text = this.text; - int charsFit = 0; + } - if (String.IsNullOrEmpty(text)) - { - lines.Add(new Line("", this, originalCharIndex)); - return; - } + public float Top + { + get { return line.Top; } + } - if (Renderer.WordWrap) - { - int originalCharIndex = this.originalCharIndex; - while (text.Length > 0) - { - charsFit = MeasureString(text); + public AdvancedTextRenderer Renderer + { + get { return line.Renderer; } + } - // avoid infinite loop when width of object less than width of one character - if (charsFit == 0) + public List Runs { - break; + get { return runs; } } - string textFit = text.Substring(0, charsFit).TrimEnd(new char[] { ' ' }); - lines.Add(new Line(textFit, this, originalCharIndex)); - text = text.Substring(charsFit) - // Fix for linux system - .TrimStart(' '); - originalCharIndex += charsFit; - } - } - else - { - string ellipsis = "\u2026"; - StringTrimming trimming = Renderer.Format.Trimming; - if (trimming == StringTrimming.EllipsisPath) - Renderer.Format.Trimming = StringTrimming.Character; - charsFit = MeasureString(text); - - switch (trimming) - { - case StringTrimming.Character: - case StringTrimming.Word: - text = text.Substring(0, charsFit); - break; - - case StringTrimming.EllipsisCharacter: - case StringTrimming.EllipsisWord: - if (charsFit < text.Length) - { - text = text.Substring(0, charsFit); - if (text.EndsWith(" ")) - text = text.Substring(0, text.Length - 1); - text += ellipsis; - } - break; - - case StringTrimming.EllipsisPath: - if (charsFit < text.Length) - { - while (text.Length > 3) + public float SpaceWidth + { + get { - int mid = text.Length / 2; - string newText = text.Substring(0, mid) + ellipsis + text.Substring(mid + 1); - if (MeasureString(newText) == newText.Length) - { - text = newText; - break; - } - else - { - text = text.Remove(mid, 1); - } + if (Runs == null || Runs.Count == 0) + return Renderer.SpaceWidth; + return Runs[Runs.Count - 1].SpaceWidth; } - } - break; - } + } + #endregion - lines.Add(new Line(text, this, originalCharIndex)); - } - } - - public StyleDescriptor WrapHtmlLines(StyleDescriptor style) - { - Line line = new Line("", this, this.originalCharIndex); - lines.Add(line); - Word word = new Word("", line); - line.Words.Add(word); - // for img - //RunImage img = null; - //end img - string text = this.text; - StringBuilder currentWord = new StringBuilder(100); - float width = 0; - bool skipSpace = true; - int originalCharIndex = this.originalCharIndex; - - for (int i = 0; i < text.Length; i++) - { - char lastChar = text[i]; - if (lastChar == '&') - { - if (Converter.FromHtmlEntities(text, ref i, currentWord)) - { - if (i >= text.Length - 1) - { - word.Runs.Add(new Run(currentWord.ToString(), style, word)); - // check width - width += word.Width + Renderer.SpaceWidth; - if (width > Renderer.DisplayRect.Width) - { - // line is too long, make a new line - if (line.Words.Count > 1) - { - // if line has several words, delete the last word from the current line - line.Words.RemoveAt(line.Words.Count - 1); - // make new line - line = new Line("", this, originalCharIndex); - // and add word to it - line.Words.Add(word); - word.SetLine(line); - lines.Add(line); - } - } -#if DOTNET_4 - currentWord.Clear(); // .NET 2.0 doesn't have Clear() -#else - currentWord.Length = 0; -#endif - lastChar = ' '; - } - else - { - if (currentWord[currentWord.Length - 1] == '\t') + #region Public Methods + public void AdjustRuns() + { + float left = Left; + foreach (Run run in Runs) { - currentWord.Length--; - lastChar = '\t'; + run.Left = left; - } - else - { - continue; - } - } - } - } - if (lastChar == '<') - { - // probably html tag - StyleDescriptor newStyle = new StyleDescriptor(style.FontStyle, style.Color, style.BaseLine); - newStyle.Font = style.Font; - newStyle.Size = style.Size; - string tag = ""; - bool match = false; - - // , , - if (i + 3 <= text.Length) - { - match = true; - tag = text.Substring(i, 3).ToLower(); - if (tag == "") - newStyle.FontStyle |= FontStyle.Bold; - else if (tag == "") - newStyle.FontStyle |= FontStyle.Italic; - else if (tag == "") - newStyle.FontStyle |= FontStyle.Underline; - else - match = false; - - if (match) - i += 3; - } - - // , , - if (!match && i + 4 <= text.Length && text[i + 1] == '/') - { - match = true; - tag = text.Substring(i, 4).ToLower(); - if (tag == "") - newStyle.FontStyle &= ~FontStyle.Bold; - else if (tag == "") - newStyle.FontStyle &= ~FontStyle.Italic; - else if (tag == "") - newStyle.FontStyle &= ~FontStyle.Underline; - else - match = false; - - if (match) - i += 4; - } - - // , // ") - newStyle.BaseLine = BaseLine.Subscript; - else if (tag == "") - newStyle.BaseLine = BaseLine.Superscript; - else if (tag == "', i + 5); - if (right <= 0) match = false; - else - { - //found img and parse them - string src = null; - string alt = " "; - //currentWord = ""; - int src_ind = text.IndexOf("src=\"", i + 5); - if (src_ind < right && src_ind >= 0) - { - src_ind += 5; - int src_end = text.IndexOf("\"", src_ind); - if (src_end < right && src_end >= 0) + if (Renderer.RightToLeft) { - src = text.Substring(src_ind, src_end - src_ind); + left -= run.Width; + if (Renderer.PDFMode) + run.Left -= run.Width; } - } - int alt_ind = text.IndexOf("alt=\"", i + 5); - if (alt_ind < right && alt_ind >= 0) - { - alt_ind += 5; - int alt_end = text.IndexOf("\"", alt_ind); - if (alt_end < right && alt_end >= 0) + else + left += run.Width; + } + } + + public void SetLine(Line line) + { + this.line = line; + } + + public void Draw() + { + if (Renderer.HtmlTags) + { + foreach (Run run in Runs) { - alt = text.Substring(alt_ind, alt_end - alt_ind); + run.Draw(); } - } - //begin - if (currentWord.Length != 0) - { - // finish the word - word.Runs.Add(new Run(currentWord.ToString(), style, word)); - } -#if DOTNET_4 - currentWord.Clear(); // .NET 2.0 doesn't have Clear() -#else - currentWord.Length = 0; -#endif - //end - word.Runs.Add(new RunImage(src, alt, style, word)); - skipSpace = false; - i = right - 4; } - } - else if (tag == "', i + 5); - if (right <= 0) match = false; else { - //found font and parse them - string color = null; - string face = null; - string size = null; - int color_ind = text.IndexOf("color=\"", i + 5); - if (color_ind < right && color_ind >= 0) - { - color_ind += 7; - int color_end = text.IndexOf("\"", color_ind); - if (color_end < right && color_end >= 0) + // don't draw underlines & strikeouts because they are drawn in the Line.Draw method + Font font = Renderer.Font; + bool disposeFont = false; + if ((Renderer.Font.Style & FontStyle.Underline) > 0 || (Renderer.Font.Style & FontStyle.Strikeout) > 0) { - color = text.Substring(color_ind, color_end - color_ind); + font = new Font(Renderer.Font, Renderer.Font.Style & ~FontStyle.Underline & ~FontStyle.Strikeout); + disposeFont = true; } - } - - int face_ind = text.IndexOf("face=\"", i + 5); - if (face_ind < right && face_ind >= 0) - { - face_ind += 6; - int face_end = text.IndexOf("\"", face_ind); - if (face_end < right && face_end >= 0) + + if (Renderer.OutlinePen == null) { - face = text.Substring(face_ind, face_end - face_ind); + Renderer.Graphics.DrawString(Text, font, Renderer.Brush, Left, Top, Renderer.Format); } - } - - int size_ind = text.IndexOf("size=\"", i + 5); - if (size_ind < right && size_ind >= 0) - { - size_ind += 6; - int size_end = text.IndexOf("\"", size_ind); - if (size_end < right && size_end >= 0) + else { - size = text.Substring(size_ind, size_end - size_ind); + GraphicsPath path = new GraphicsPath(); + path.AddString(Text, font.FontFamily, Convert.ToInt32(font.Style), Renderer.Graphics.DpiY * font.Size / 72, new PointF(Left - 1, Top - 1), Renderer.Format); + Renderer.Graphics.FillPath(Renderer.Brush, path); + Renderer.Graphics.DrawPath(Renderer.OutlinePen, path); } - } - if (color != null) - { - if (color.StartsWith("\"") && color.EndsWith("\"")) - color = color.Substring(1, color.Length - 2); - if (color.StartsWith("#")) + if (disposeFont) { - newStyle.Color = Color.FromArgb((int)(0xFF000000 + uint.Parse(color.Substring(1), NumberStyles.HexNumber))); + font.Dispose(); + font = null; } - else + } + } + + internal float CalcHeight() + { + if (Renderer.HtmlTags) + { + float height = -1; + foreach (Run run in Runs) { - newStyle.Color = Color.FromName(color); + height = Math.Max(height, run.Height); } - } - newStyle.Font = face; - if (size != null) - { + if (height < 0) + height = Renderer.LineHeight; + return height; + } + else + { + return Renderer.LineHeight; + } + } - try + internal float CalcBaseLine() + { + float baseLine = 0; + if (Renderer.HtmlTags) + { + foreach (Run run in Runs) { - size = size.Trim(' '); - - switch(size[0]) - { - case '-': - size = size.Substring(1); - if (style.Size == 0) - newStyle.Size = Renderer.Font.Size - (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; - else - newStyle.Size = style.Size - (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; - break; - case '+': - size = size.Substring(1); - if (style.Size == 0) - newStyle.Size = Renderer.Font.Size + (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; - else - newStyle.Size = style.Size + (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; - break; - default: newStyle.Size = (float)Converter.FromString(typeof(float), size) * Renderer.FontScale; break; - } - if (newStyle.Size < 0) newStyle.Size = 0; + baseLine = Math.Max(baseLine, run.CurrentBaseLine); } - catch { } - } - i = right - 4; + return baseLine; + } + else + { + return 0; } - } - else - match = false; + } - if (match) - i += 5; + internal float CalcUnderBaseLine() + { + float underbaseLine = 0; + if (Renderer.HtmlTags) + { + foreach (Run run in Runs) + { + underbaseLine = Math.Max(underbaseLine, run.CurrentUnderBaseLine); + } + return underbaseLine; + } + else + { + return 0; + } } + #endregion - // , - if (!match && i + 6 <= text.Length && text[i + 1] == '/') + public Word(string text, Line line) { - match = true; - tag = text.Substring(i, 6).ToLower(); - if (tag == "") - newStyle.BaseLine = BaseLine.Normal; - else if (tag == "") - newStyle.BaseLine = BaseLine.Normal; - else - match = false; + this.text = text; + runs = new List(); + this.line = line; + width = -1; + } + } + - if (match) - i += 6; + + + /// + /// Represents character placement. + /// + public enum BaseLine + { + Normal, + Subscript, + Superscript + } + + + /// + /// Represents a style used in HtmlTags mode. + /// + public class StyleDescriptor + { + #region Fields + private FontStyle fontStyle; + private Color color; + private BaseLine baseLine; + private string font; + private float size; + #endregion + + #region Properties + public FontStyle FontStyle + { + get { return fontStyle; } + set { fontStyle = value; } } - // - if (!match && i + 8 <= text.Length && text.Substring(i, 8).ToLower() == "") + public string Font { - newStyle.FontStyle |= FontStyle.Strikeout; - match = true; - i += 8; + get { return font; } + set { font = value; } } - // - if (!match && i + 9 <= text.Length && text.Substring(i, 9).ToLower() == "") + public float Size { - newStyle.FontStyle &= ~FontStyle.Strikeout; - match = true; - i += 9; + get { return size; } + set { size = value; } } - /* - // - if (!match && i + 7 <= text.Length && text.Substring(i, 7).ToLower() == "") + #endregion + + #region Public Methods + public override string ToString() { - newStyle.Color = Renderer.BrushColor; - newStyle.Size = 0; - newStyle.Font = null; - match = true; - i += 7; + string result = ""; + + if ((FontStyle & FontStyle.Bold) != 0) + result += ""; + if ((FontStyle & FontStyle.Italic) != 0) + result += ""; + if ((FontStyle & FontStyle.Underline) != 0) + result += ""; + if ((FontStyle & FontStyle.Strikeout) != 0) + result += ""; + if (BaseLine == BaseLine.Subscript) + result += ""; + if (BaseLine == BaseLine.Superscript) + result += ""; + + result += "= text.Length - 1) - { - // check width - width += word.Width + Renderer.SpaceWidth; - if (width > Renderer.DisplayRect.Width) - { - // line is too long, make a new line - if (line.Words.Count > 1) - { - // if line has several words, delete the last word from the current line - line.Words.RemoveAt(line.Words.Count - 1); - // make new line - line = new Line("", this, originalCharIndex); - // and add word to it - line.Words.Add(word); - word.SetLine(line); - lines.Add(line); - } - } - } - continue; + + /// + /// Represents sequence of characters that have the same . + /// + public class Run + { + #region Fields + protected string text; + private StyleDescriptor style; + protected Word word; + private float left; + protected float width; + protected float lineHeight; + protected float fontLineHeight; + private float baseLine; + protected float underBaseLine; + protected float spaceWidth; + #endregion + + #region Properties + public string Text + { + get { return text; } } - } - if (lastChar == ' ' || lastChar == '\t' || i == text.Length - 1) - { - // finish the last word - bool isLastWord = i == text.Length - 1; - if (isLastWord) + + public StyleDescriptor Style { - currentWord.Append(lastChar); - skipSpace = false; + get { return style; } } - if (lastChar == '\t') - skipSpace = false; + public AdvancedTextRenderer Renderer + { + get { return word.Renderer; } + } - // space - if (skipSpace) + public float Left { - currentWord.Append(lastChar); + get { return left; } + set { left = value; } } - else + + public float LineHeight { - // finish the word - if (currentWord.Length != 0) - word.Runs.Add(new Run(currentWord.ToString(), style, word)); + get + { + if (lineHeight == 0) + { + if (style.Font == null && style.Size <= 0) + lineHeight = Renderer.LineHeight; + else + lineHeight = GetFont().GetHeight(Renderer.Graphics); + } + return lineHeight; + } + } - // check width - width += word.Width + word.SpaceWidth; - if (width > Renderer.DisplayRect.Width) - { - // line is too long, make a new line - width = 0; - if (line.Words.Count > 1) + virtual public float CurrentBaseLine + { + get { - // if line has several words, delete the last word from the current line - line.Words.RemoveAt(line.Words.Count - 1); - // make new line - line = new Line("", this, originalCharIndex); - // and add word to it - line.Words.Add(word); - word.SetLine(line); - width += word.Width + word.SpaceWidth; + if (baseLine < 0) + { + Font ff = GetFont(); + float lineSpace = ff.FontFamily.GetLineSpacing(Style.FontStyle); + float ascent = ff.FontFamily.GetCellAscent(Style.FontStyle); + baseLine = FontLineHeight * ascent / lineSpace; + underBaseLine = FontLineHeight - baseLine; + } + return baseLine; } - else + } + + virtual public float CurrentUnderBaseLine + { + get { - line = new Line("", this, i + 1); + if (underBaseLine < 0) + { + Font ff = GetFont(); + float lineSpace = ff.FontFamily.GetLineSpacing(Style.FontStyle); + float ascent = ff.FontFamily.GetCellAscent(Style.FontStyle); + baseLine = FontLineHeight * ascent / lineSpace; + underBaseLine = FontLineHeight - baseLine; + } + return baseLine; } - lines.Add(line); - } - - // TAB symbol - if (lastChar == '\t') - { - if (currentWord.Length == 0 && line.Words.Count > 0 && line.Words[line.Words.Count-1].Width == 0) - line.Words.RemoveAt(line.Words.Count - 1); - word = new Word("\t", line); - line.Words.Add(word); - // adjust width - width = Renderer.GetTabPosition(width); - } + } - if (!isLastWord) - { - word = new Word("", line); - line.Words.Add(word); -#if DOTNET_4 - currentWord.Clear(); // .NET 2.0 doesn't have Clear() -#else - currentWord.Length = 0; -#endif - originalCharIndex = this.originalCharIndex + i + 1; - skipSpace = true; - } - } - } - else - { - // symbol - currentWord.Append(lastChar); - skipSpace = false; - } - } + public float FontLineHeight + { + get + { + if (fontLineHeight == 0) + { + if (style.Font == null && style.Size <= 0) + fontLineHeight = Renderer.FontLineHeight; + else + fontLineHeight = GetFont().GetHeight(Renderer.Graphics); + } + return fontLineHeight; + } + } - return style; - } + public virtual float Top + { + get + { + float baseLine = 0; + if (Style.BaseLine == BaseLine.Subscript) + baseLine += FontLineHeight * 0.45f; + else if (Style.BaseLine == BaseLine.Superscript) + baseLine -= FontLineHeight * 0.15f; + return word.Top + word.line.CalcBaseLine() - CurrentBaseLine + baseLine; + } + } - public void AlignLines(bool forceJustify) - { - for (int i = 0; i < Lines.Count; i++) - { - HorzAlign align = Renderer.HorzAlign; - if (align == HorzAlign.Justify && i == Lines.Count - 1 && !forceJustify) - align = HorzAlign.Left; - Lines[i].AlignWords(align); - } - } + virtual public float Width + { + get { return width; } + } - public void Draw() - { - foreach (Line line in Lines) - { - line.Draw(); - } - } -#endregion - - public Paragraph(string text, AdvancedTextRenderer renderer, int originalCharIndex) - { - lines = new List(); - this.text = text; - this.renderer = renderer; - this.originalCharIndex = originalCharIndex; - } - } + virtual public float Height + { + get + { + return LineHeight; + } + } - /// - /// Line represents single text line. It consists of one or several . - /// Simple line (that does not contain tabs, html tags, and is not justified) has - /// single which contains all the text. - /// - public class Line - { -#region Fields - private List words; - private string text; - private bool hasTabs; - private Paragraph paragraph; - private float top; - private float width; - private int originalCharIndex; - private List underlines; - private List strikeouts; -#endregion - -#region Properties - public List Words - { - get { return words; } - } - - public string Text - { - get { return text; } - } - - public bool HasTabs - { - get { return hasTabs; } - } - - public float Left - { - get { return Words.Count > 0 ? Words[0].Left : 0; } - } - - public float Top - { - get { return top; } - set { top = value; } - } - - public float Width - { - get { return width; } - } - - public int OriginalCharIndex - { - get { return originalCharIndex; } - } - - public AdvancedTextRenderer Renderer - { - get { return paragraph.Renderer; } - } - - public StyleDescriptor Style - { - get - { - if (Words.Count > 0) - if (Words[0].Runs.Count > 0) - return Words[0].Runs[0].Style; - return null; - } - } - - public bool Last - { - get { return paragraph.Lines[paragraph.Lines.Count - 1] == this; } - } - - public List Underlines - { - get { return underlines; } - } - - public List Strikeouts - { - get { return strikeouts; } - } -#endregion - -#region Private Methods - private void PrepareUnderlines(List list, FontStyle style) - { - list.Clear(); - if (Words.Count == 0) - return; - - if (Renderer.HtmlTags) - { - float left = 0; - float right = 0; - bool styleOn = false; - foreach (Word word in Words) - { - foreach (Run run in word.Runs) + public float SpaceWidth { - using (Font fnt = run.GetFont()) - { - if ((fnt.Style & style) > 0) - { - if (!styleOn) - { - styleOn = true; - left = run.Left; - } - right = run.Left + run.Width; - } - if ((fnt.Style & style) == 0 && styleOn) + get { - styleOn = false; - list.Add(new RectangleF(left, Top, right - left, 1)); + if (spaceWidth < 0) + { + spaceWidth = CalculateSpaceSize(Renderer.Graphics, GetFont());// Renderer.Graphics.MeasureString(" ", GetFont()).Width; + } + return spaceWidth; } - } } - } - // close the style - if (styleOn) - list.Add(new RectangleF(left, Top, right - left, 1)); - } - else if ((Renderer.Font.Style & style) > 0) - { - float lineWidth = Width; - if (Renderer.HorzAlign == HorzAlign.Justify && (!Last || (paragraph.Last && Renderer.ForceJustify))) - lineWidth = Renderer.DisplayRect.Width - Renderer.SpaceWidth; + #endregion - list.Add(new RectangleF(Left, Top, lineWidth, 1)); - } - } -#endregion + #region Private Methods + private Font GetFont(bool disableUnderlinesStrikeouts) + { + float fontSize = Renderer.Font.Size; + if (Style.Size != 0) + fontSize = Style.Size; + if (Style.BaseLine != BaseLine.Normal) + fontSize *= 0.6f; + + FontStyle fontStyle = Style.FontStyle; + if (disableUnderlinesStrikeouts) + fontStyle = fontStyle & ~FontStyle.Underline & ~FontStyle.Strikeout; + if (Style.Font != null) + return new Font(Style.Font, fontSize, fontStyle); + return new Font(Renderer.Font.Name, fontSize, fontStyle); + } + #endregion -#region Public Methods - public void AlignWords(HorzAlign align) - { - width = 0; + #region Public Methods + public Font GetFont() + { + return GetFont(false); + } - // handle each word - if (align == HorzAlign.Justify || HasTabs || Renderer.Wysiwyg || Renderer.HtmlTags) - { - float left = 0; - Word word = null; - for (int i = 0; i < Words.Count; i++) - { - word = Words[i]; - word.Left = left; + public Brush GetBrush() + { + return new SolidBrush(Style.Color); + } - if (word.Text == "\t") + public virtual void Draw() { - left = Renderer.GetTabPosition(left); - // remove tab - Words.RemoveAt(i); - i--; + using (Font font = GetFont(true)) + using (Brush brush = GetBrush()) + { + Renderer.Graphics.DrawString(text, font, brush, Left, Top, Renderer.Format); + } } - else - left += word.Width + word.SpaceWidth; - } - if (word != null) - width = left - word.SpaceWidth; - else - width = left - Renderer.SpaceWidth; - } - else - { - // join all words into one - Words.Clear(); - Words.Add(new Word(text, this)); - width = Words[0].Width; - } + #endregion - float rectWidth = Renderer.DisplayRect.Width; - if (align == HorzAlign.Justify) - { - float delta = (rectWidth - width - Renderer.SpaceWidth) / (Words.Count - 1); - float curDelta = delta; - for (int i = 1; i < Words.Count; i++) - { - words[i].Left += curDelta; - curDelta += delta; - } - } - else - { - float delta = 0; - if (align == HorzAlign.Center) - delta = (rectWidth - width) / 2; - else if (align == HorzAlign.Right) - delta = rectWidth - width - Renderer.SpaceWidth; - for (int i = 0; i < Words.Count; i++) - { - words[i].Left += delta; - } + public Run(string text, StyleDescriptor style, Word word) + { + baseLine = float.MinValue; + underBaseLine = float.MinValue; + this.text = text; + this.style = new StyleDescriptor(style.FontStyle, style.Color, style.BaseLine); + this.style.Font = style.Font; + this.style.Size = style.Size; + this.word = word; + spaceWidth = -1; + + using (Font font = GetFont()) + { + width = Renderer.Graphics.MeasureString(text, font, 10000, StringFormat.GenericTypographic).Width; + } + } } - // adjust X offset - foreach (Word word in Words) + /// + /// Represents inline Image. + /// + internal class RunImage : Run { - if (Renderer.RightToLeft) - word.Left = Renderer.DisplayRect.Right - word.Left; - else - word.Left += Renderer.DisplayRect.Left; - word.AdjustRuns(); - if (Renderer.RightToLeft && Renderer.PDFMode) - word.Left -= word.Width; - } - } + public Image Image { get { return image; } } + override public float Width + { + get + { + if (Image == null) return base.Width; + return Image.Width; + } + } + + override public float Top + { + get + { + float baseLine = 0; + if (Style.BaseLine == BaseLine.Subscript) + baseLine += FontLineHeight * 0.45f; + else if (Style.BaseLine == BaseLine.Superscript) + baseLine -= FontLineHeight * 0.15f; + return word.Top + word.line.CalcBaseLine() - CurrentBaseLine + baseLine; + } + } - public void MakeUnderlines() - { - PrepareUnderlines(underlines, FontStyle.Underline); - PrepareUnderlines(strikeouts, FontStyle.Strikeout); - } + override public float CurrentBaseLine + { + get + { + if (Image == null) return base.CurrentBaseLine; + return Image.Height; + } + } - public void Draw() - { - foreach (Word word in Words) - { - word.Draw(); - } + override public float Height + { + get + { + if (Image == null) return base.Height; + return Image.Height + word.line.CalcUnderBaseLine(); + } + } - if (Underlines.Count > 0 || Strikeouts.Count > 0) - { - using (Pen pen = new Pen(Renderer.Brush, Renderer.Font.Size * 0.1f)) - { - float h = Renderer.FontLineHeight; - float w = h * 0.1f; // to match .net char X offset - // invert offset in case of rtl - if (Renderer.RightToLeft) - w = -w; + private Image image; - // emulate underline & strikeout - foreach (RectangleF rect in Underlines) + override public void Draw() { - Renderer.Graphics.DrawLine(pen, rect.Left + w, rect.Top + h - w, rect.Right + w, rect.Top + h - w); + if (Image == null) + { + base.Draw(); + return; + } + Renderer.Graphics.DrawImage(Image, Left, Top);// (FText, font, brush, Left, Top, Renderer.Format); } - h /= 2; - foreach (RectangleF rect in Strikeouts) + public static Bitmap ResizeImage(Image image, float scale) { - Renderer.Graphics.DrawLine(pen, rect.Left + w, rect.Top + h, rect.Right + w, rect.Top + h); - } - } - } - } + int width = (int)(image.Width * scale); + int height = (int)(image.Height * scale); + if (width == 0) width = 1; + if (height == 0) height = 1; + Rectangle destRect = new Rectangle(0, 0, width, height); + Bitmap destImage = new Bitmap(width, height); - public float CalcHeight() - { - float height = -1; - foreach (Word word in Words) - { - height = Math.Max(height, word.CalcHeight()); - } - if (height < 0) - height = Renderer.LineHeight; - return height; - } -#endregion - - public Line(string text, Paragraph paragraph, int originalCharIndex) - { - this.words = new List(); - this.text = text; - this.paragraph = paragraph; - this.originalCharIndex = originalCharIndex; - underlines = new List(); - strikeouts = new List(); - hasTabs = text.Contains("\t"); - - // split text by spaces - string[] words = text.Split(new char[] { ' ' }); - string textWithSpaces = ""; - - foreach (string word in words) - { - if (word == "") - textWithSpaces += " "; - else - { - // split text by tabs - textWithSpaces += word; - string[] tabWords = textWithSpaces.Split(new char[] { '\t' }); - - foreach (string word1 in tabWords) - { - if (word1 == "") - this.words.Add(new Word("\t", this)); - else - { - this.words.Add(new Word(word1, this)); - this.words.Add(new Word("\t", this)); - } - } - - // remove last tab - this.words.RemoveAt(this.words.Count - 1); - - textWithSpaces = ""; - } - } - } + destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); - internal float CalcBaseLine() - { + using (Graphics graphics = Graphics.FromImage(destImage)) + { + graphics.CompositingMode = CompositingMode.SourceCopy; + graphics.CompositingQuality = CompositingQuality.HighQuality; + graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; + graphics.SmoothingMode = SmoothingMode.HighQuality; + graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - float baseline = 0; - foreach (Word word in Words) - { - baseline = Math.Max(baseline, word.CalcBaseLine()); - } - return baseline; - } + using (System.Drawing.Imaging.ImageAttributes wrapMode = new System.Drawing.Imaging.ImageAttributes()) + { + wrapMode.SetWrapMode(WrapMode.TileFlipXY); + graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode); + } + } - internal float CalcUnderBaseLine() - { + return destImage; + } - float underbaseline = 0; - foreach (Word word in Words) - { - underbaseline = Math.Max(underbaseline, word.CalcUnderBaseLine()); + public RunImage(string src, string text, StyleDescriptor style, Word word) : base(text, style, word) + { + underBaseLine = 0; + image = ResizeImage(InlineImageCache.Load(Renderer.Cache, src), Renderer.Scale); + } } - return underbaseline; - } } /// - /// Word represents single word. It may consist of one or several , in case - /// when HtmlTags are enabled in the main class. + /// Standard text renderer uses standard DrawString method to draw text. It also supports: + /// - text rotation; + /// - fonts with non-standard width ratio. + /// In case your text is justified, or contains html tags, use the + /// class instead. /// - public class Word + internal class StandardTextRenderer { -#region Fields - private List runs; - protected string text; - private float left; - private float width; - internal Line line; -#endregion - -#region Properties - public string Text - { - get { return text; } - } - - public float Left - { - get { return left; } - set { left = value; } - } - - public float Width - { - get + public static void Draw(string text, Graphics g, Font font, Brush brush, Pen outlinePen, + RectangleF rect, StringFormat format, int angle, float widthRatio) { - if (width == -1) - { - if (Renderer.HtmlTags) + GraphicsState state = g.Save(); + g.SetClip(rect, CombineMode.Intersect); + g.TranslateTransform(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2); + g.RotateTransform(angle); + rect.X = -rect.Width / 2; + rect.Y = -rect.Height / 2; + + if ((angle >= 90 && angle < 180) || (angle >= 270 && angle < 360)) + rect = new RectangleF(rect.Y, rect.X, rect.Height, rect.Width); + + g.ScaleTransform(widthRatio, 1); + rect.X /= widthRatio; + rect.Width /= widthRatio; + + if (outlinePen == null) { - width = 0; - foreach (Run run in Runs) - { - width += run.Width; - } + g.DrawString(text, font, brush, rect, format); } else { - width = Renderer.Graphics.MeasureString(text, Renderer.Font, 10000, StringFormat.GenericTypographic).Width; + GraphicsPath path = new GraphicsPath(); + path.AddString(text, font.FontFamily, Convert.ToInt32(font.Style), g.DpiY * font.Size / 72, rect, format); + g.FillPath(brush, path); + g.DrawPath(outlinePen, path); } - } - return width; - } - } - - public float Top - { - get { return line.Top; } - } - - public AdvancedTextRenderer Renderer - { - get { return line.Renderer; } - } - - public List Runs - { - get { return runs; } - } - - public float SpaceWidth - { - get - { - if (Runs == null || Runs.Count == 0) - return Renderer.SpaceWidth; - return Runs[Runs.Count - 1].SpaceWidth; - } - } -#endregion - -#region Public Methods - public void AdjustRuns() - { - float left = Left; - foreach (Run run in Runs) - { - run.Left = left; - - if (Renderer.RightToLeft) - { - left -= run.Width; - if (Renderer.PDFMode) - run.Left -= run.Width; - } - else - left += run.Width; - } - } - public void SetLine(Line line) - { - this.line = line; - } - - public void Draw() - { - if (Renderer.HtmlTags) - { - foreach (Run run in Runs) - { - run.Draw(); - } - } - else - { - // don't draw underlines & strikeouts because they are drawn in the Line.Draw method - Font font = Renderer.Font; - bool disposeFont = false; - if ((Renderer.Font.Style & FontStyle.Underline) > 0 || (Renderer.Font.Style & FontStyle.Strikeout) > 0) - { - font = new Font(Renderer.Font, Renderer.Font.Style & ~FontStyle.Underline & ~FontStyle.Strikeout); - disposeFont = true; - } - - if (Renderer.OutlinePen == null) - { - Renderer.Graphics.DrawString(Text, font, Renderer.Brush, Left, Top, Renderer.Format); - } - else - { - GraphicsPath path = new GraphicsPath(); - path.AddString(Text, font.FontFamily, Convert.ToInt32(font.Style), Renderer.Graphics.DpiY * font.Size / 72, new PointF(Left - 1, Top - 1), Renderer.Format); - Renderer.Graphics.FillPath(Renderer.Brush, path); - Renderer.Graphics.DrawPath(Renderer.OutlinePen, path); - } - - if (disposeFont) - { - font.Dispose(); - font = null; - } + g.Restore(state); } - } + } - internal float CalcHeight() - { - if (Renderer.HtmlTags) - { - float height = -1; - foreach (Run run in Runs) - { - height = Math.Max(height, run.Height); - } - if (height < 0) - height = Renderer.LineHeight; - return height; - } - else - { - return Renderer.LineHeight; - } - } + /// + /// Cache for rendering img tags in textobject. + /// You can use only HTTP[s] protocol with absolute urls. + /// + public class InlineImageCache : IDisposable + { + #region Private Fields - internal float CalcBaseLine() - { - float baseLine = 0; - if (Renderer.HtmlTags) - { - foreach (Run run in Runs) - { - baseLine = Math.Max(baseLine, run.CurrentBaseLine); - } - return baseLine; - } - else - { - return 0; - } - } + private WebClient client; - internal float CalcUnderBaseLine() - { - float underbaseLine = 0; - if (Renderer.HtmlTags) - { - foreach (Run run in Runs) - { - underbaseLine = Math.Max(underbaseLine, run.CurrentUnderBaseLine); - } - return underbaseLine; - } - else - { - return 0; - } - } -#endregion - - public Word(string text, Line line) - { - this.text = text; - runs = new List(); - this.line = line; - width = -1; - } - } + private Dictionary items; + private bool serialized; + private object locker; + #endregion Private Fields - /// - /// Represents character placement. - /// - public enum BaseLine - { - Normal, - Subscript, - Superscript - } + #region Public Properties + /// + /// Is serialized + /// + public bool Serialized { get { return serialized; } set { serialized = value; } } - /// - /// Represents a style used in HtmlTags mode. - /// - public class StyleDescriptor - { -#region Fields - private FontStyle fontStyle; - private Color color; - private BaseLine baseLine; - private string font; - private float size; -#endregion - -#region Properties - public FontStyle FontStyle - { - get { return fontStyle; } - set { fontStyle = value; } - } - - public string Font - { - get { return font; } - set { font = value; } - } - - public float Size - { - get { return size; } - set { size = value; } - } - public Color Color - { - get { return color; } - set { color = value; } - } - - public BaseLine BaseLine - { - get { return baseLine; } - set { baseLine = value; } - } -#endregion - -#region Public Methods - public override string ToString() - { - string result = ""; - - if ((FontStyle & FontStyle.Bold) != 0) - result += ""; - if ((FontStyle & FontStyle.Italic) != 0) - result += ""; - if ((FontStyle & FontStyle.Underline) != 0) - result += ""; - if ((FontStyle & FontStyle.Strikeout) != 0) - result += ""; - if (BaseLine == BaseLine.Subscript) - result += ""; - if (BaseLine == BaseLine.Superscript) - result += ""; - - result += " - /// Represents sequence of characters that have the same . - ///
- public class Run - { -#region Fields - protected string text; - private StyleDescriptor style; - protected Word word; - private float left; - protected float width; - protected float lineHeight; - protected float fontLineHeight; - private float baseLine; - protected float underBaseLine; - protected float spaceWidth; -#endregion - -#region Properties - public string Text - { - get { return text; } - } - - public StyleDescriptor Style - { - get { return style; } - } - - public AdvancedTextRenderer Renderer - { - get { return word.Renderer; } - } - - public float Left - { - get { return left; } - set { left = value; } - } - - public float LineHeight - { - get + /// + /// Get or set WebClient for downloading imgs by url + /// + private WebClient Client { - if(lineHeight == 0) - { - if (style.Font == null && style.Size <=0) - lineHeight = Renderer.LineHeight; - else - lineHeight = GetFont().GetHeight(Renderer.Graphics); - } - return lineHeight; + get + { + if (client == null) + { + client = new WebClient(); + } + return client; + } + set + { + client = value; + } } - } - virtual public float CurrentBaseLine - { - get - { - if(baseLine < 0) - { - Font ff = GetFont(); - float lineSpace = ff.FontFamily.GetLineSpacing(Style.FontStyle); - float ascent = ff.FontFamily.GetCellAscent(Style.FontStyle); - baseLine = FontLineHeight * ascent / lineSpace; - underBaseLine = FontLineHeight - baseLine; - } - return baseLine; - } - } + #endregion Private Properties - virtual public float CurrentUnderBaseLine - { - get - { - if (underBaseLine < 0) - { - Font ff = GetFont(); - float lineSpace = ff.FontFamily.GetLineSpacing(Style.FontStyle); - float ascent = ff.FontFamily.GetCellAscent(Style.FontStyle); - baseLine = FontLineHeight * ascent / lineSpace; - underBaseLine = FontLineHeight - baseLine; - } - return baseLine; - } - } + #region Public Events - public float FontLineHeight - { - get - { - if(fontLineHeight == 0) - { - if (style.Font == null && style.Size <=0) - fontLineHeight = Renderer.FontLineHeight; - else - fontLineHeight = GetFont().GetHeight(Renderer.Graphics); - } - return fontLineHeight; - } - } + /// + /// Occurs before image load + /// + public static event EventHandler AfterLoad; + /// + /// Occurs after image load + /// + public static event EventHandler BeforeLoad; - public virtual float Top - { - get - { - float baseLine = 0; - if (Style.BaseLine == BaseLine.Subscript) - baseLine += FontLineHeight * 0.45f; - else if (Style.BaseLine == BaseLine.Superscript) - baseLine -= FontLineHeight * 0.15f; - return word.Top + word.line.CalcBaseLine() - CurrentBaseLine + baseLine; - } - } + #endregion Public Events - virtual public float Width - { - get { return width; } - } + #region Public Methods - virtual public float Height - { - get + /// + /// Enumerates all values + /// + /// + public IEnumerable AllItems() { - return LineHeight; + List list = new List(); + lock (locker) + { + if (items != null) + { + foreach (KeyValuePair item in items) + { + item.Value.Src = item.Key; + list.Add(item.Value); + } + } + } + return list; } - } - - - public float SpaceWidth - { - get - { - if(spaceWidth < 0) - { - spaceWidth = CalculateSpaceSize(Renderer.Graphics, GetFont());// Renderer.Graphics.MeasureString(" ", GetFont()).Width; - } - return spaceWidth; - } - } -#endregion - -#region Private Methods - private Font GetFont(bool disableUnderlinesStrikeouts) - { - float fontSize = Renderer.Font.Size; - if (Style.Size != 0) - fontSize = Style.Size; - if (Style.BaseLine != BaseLine.Normal) - fontSize *= 0.6f; - - FontStyle fontStyle = Style.FontStyle; - if (disableUnderlinesStrikeouts) - fontStyle = fontStyle & ~FontStyle.Underline & ~FontStyle.Strikeout; - if(Style.Font!=null) - return new Font(Style.Font, fontSize, fontStyle); - return new Font(Renderer.Font.Name, fontSize, fontStyle); - } -#endregion - -#region Public Methods - public Font GetFont() - { - return GetFont(false); - } - - public Brush GetBrush() - { - return new SolidBrush(Style.Color); - } - - public virtual void Draw() - { - using (Font font = GetFont(true)) - using (Brush brush = GetBrush()) + /// + /// Return CacheItem by src + /// + /// Src attribute from img tag + /// + public CacheItem Get(string src) { - Renderer.Graphics.DrawString(text, font, brush, Left, Top, Renderer.Format); + CacheItem item = null; + if (!Validate(src)) + item = new CacheItem(); + if (String.IsNullOrEmpty(src)) + return item; + lock (locker) + { + if (items == null) + { + items = new Dictionary(); + if (item == null) + item = new CacheItem(); + items[src] = item; + Serialized = false; + } + if (items.ContainsKey(src)) + return items[src]; + } + return item; } - } -#endregion - - public Run(string text, StyleDescriptor style, Word word) - { - baseLine = float.MinValue; - underBaseLine = float.MinValue; - this.text = text; - this.style = new StyleDescriptor(style.FontStyle, style.Color, style.BaseLine); - this.style.Font = style.Font; - this.style.Size = style.Size; - this.word = word; - spaceWidth = -1; - - using (Font font = GetFont()) - { - width = Renderer.Graphics.MeasureString(text, font, 10000, StringFormat.GenericTypographic).Width; - } - } - } - /// - /// Represents inline Image. - /// - internal class RunImage : Run - { - public Image Image { get { return image; } } - override public float Width - { - get + /// + /// + /// + /// + /// + public Image Load(string src) { - if (Image == null) return base.Width; - return Image.Width; + CacheItem item = null; + if (String.IsNullOrEmpty(src)) + item = new CacheItem(); + else + lock (locker) + { + if (items == null) + items = new Dictionary(); + else + if (items.ContainsKey(src)) + return items[src].Image; + item = new CacheItem(); + if (Validate(src)) + { + try + { + if (src.StartsWith("data:")) + { + item.Set(src.Substring(src.IndexOf("base64,") + "base64,".Length)); + } + else + item.Set(Client.DownloadData(src)); + } + catch + { + item.Set(""); + } + } + items[src] = item; + Serialized = false; + } + item.Src = src; + return item.Image; } - } - override public float Top - { - get + /// + /// Set CacheItem by src + /// + /// Src attribute from img tag + /// CacheItem + /// + public CacheItem Set(string src, CacheItem item) { - float baseLine = 0; - if (Style.BaseLine == BaseLine.Subscript) - baseLine += FontLineHeight * 0.45f; - else if (Style.BaseLine == BaseLine.Superscript) - baseLine -= FontLineHeight * 0.15f; - return word.Top + word.line.CalcBaseLine() - CurrentBaseLine + baseLine; + if (String.IsNullOrEmpty(src)) + return new CacheItem(); + lock (locker) + { + if (items == null) + items = new Dictionary(); + if (!Validate(src)) + item = new CacheItem(); + items[src] = item; + Serialized = false; + } + item.Src = src; + return item; } - } - override public float CurrentBaseLine - { - get + /// + /// Validate src attribute from image + /// + /// Src attribute from img tag + /// return true if src is valid + public bool Validate(string src) { - if (Image == null) return base.CurrentBaseLine; - return Image.Height; + if (String.IsNullOrEmpty(src)) + return false; + src = src.ToLower(); + if (src.StartsWith("http://")) + return true; + if (src.StartsWith("https://")) + return true; + if (src.StartsWith("data:") && src.IndexOf("base64,") > 0) + return true; + return false; } - } - override public float Height - { - get - { - if (Image == null) return base.Height; - return Image.Height + word.line.CalcUnderBaseLine(); - } - } + #endregion Public Methods - private Image image; + #region Internal Methods - override public void Draw() - { - if (Image == null) + static internal Image Load(InlineImageCache cache, string src) { - base.Draw(); - return; + LoadEventArgs args = new LoadEventArgs(cache, src); + if (BeforeLoad != null) BeforeLoad(null, args); + Image result = null; + if (!args.Handled) result = cache.Load(src); + args.Handled = false; + if (AfterLoad != null) AfterLoad(null, args); + if (args.Handled) + return cache.Get(src).Image; + return result; } - Renderer.Graphics.DrawImage(Image, Left, Top);// (FText, font, brush, Left, Top, Renderer.Format); - } - public static Bitmap ResizeImage(Image image, float scale) - { - int width = (int)(image.Width * scale); - int height = (int)(image.Height * scale); - if (width == 0) width = 1; - if (height == 0) height = 1; - Rectangle destRect = new Rectangle(0, 0, width, height); - Bitmap destImage = new Bitmap(width, height); + #endregion Internal Methods - destImage.SetResolution(image.HorizontalResolution, image.VerticalResolution); + #region Public Constructors - using (Graphics graphics = Graphics.FromImage(destImage)) + /// + public InlineImageCache() { - graphics.CompositingMode = CompositingMode.SourceCopy; - graphics.CompositingQuality = CompositingQuality.HighQuality; - graphics.InterpolationMode = InterpolationMode.HighQualityBicubic; - graphics.SmoothingMode = SmoothingMode.HighQuality; - graphics.PixelOffsetMode = PixelOffsetMode.HighQuality; - - using (System.Drawing.Imaging.ImageAttributes wrapMode = new System.Drawing.Imaging.ImageAttributes()) - { - wrapMode.SetWrapMode(WrapMode.TileFlipXY); - graphics.DrawImage(image, destRect, 0, 0, image.Width, image.Height, GraphicsUnit.Pixel, wrapMode); - } + locker = new object(); + client = null; } - return destImage; - } + /// + /// + /// + ~InlineImageCache() + { + Dispose(false); + } - public RunImage(string src, string text, StyleDescriptor style, Word word) : base(text, style, word) - { - underBaseLine = 0; - image = ResizeImage(InlineImageCache.Load(Renderer.Cache,src), Renderer.Scale); - } - } - } - - - /// - /// Standard text renderer uses standard DrawString method to draw text. It also supports: - /// - text rotation; - /// - fonts with non-standard width ratio. - /// In case your text is justified, or contains html tags, use the - /// class instead. - /// - internal class StandardTextRenderer - { - public static void Draw(string text, Graphics g, Font font, Brush brush, Pen outlinePen, - RectangleF rect, StringFormat format, int angle, float widthRatio) - { - GraphicsState state = g.Save(); - g.SetClip(rect, CombineMode.Intersect); - g.TranslateTransform(rect.Left + rect.Width / 2, rect.Top + rect.Height / 2); - g.RotateTransform(angle); - rect.X = -rect.Width / 2; - rect.Y = -rect.Height / 2; - - if ((angle >= 90 && angle < 180) || (angle >= 270 && angle < 360)) - rect = new RectangleF(rect.Y, rect.X, rect.Height, rect.Width); - - g.ScaleTransform(widthRatio, 1); - rect.X /= widthRatio; - rect.Width /= widthRatio; - - if (outlinePen == null) - { - g.DrawString(text, font, brush, rect, format); - } - else - { - GraphicsPath path = new GraphicsPath(); - path.AddString(text, font.FontFamily, Convert.ToInt32(font.Style), g.DpiY * font.Size / 72, rect, format); - g.FillPath(brush, path); - g.DrawPath(outlinePen, path); - } - - g.Restore(state); - } - } + #endregion Public Constructors - /// - /// Cache for rendering img tags in textobject. - /// You can use only HTTP[s] protocol with absolute urls. - /// - public class InlineImageCache : IDisposable - { -#region Private Fields + #region Public Classes - private WebClient client; + /// + /// Item of image cache Dictionary + /// + public class CacheItem : IDisposable + { + #region Private Fields - private Dictionary items; + private string base64; - private bool serialized; + private bool error; - private object locker; + private Image image; -#endregion Private Fields + //private int FId; + private string src; -#region Public Properties + private byte[] stream; - /// - /// Is serialized - /// - public bool Serialized { get { return serialized; } set { serialized = value; } } + #endregion Private Fields -#endregion Public Properties + #region Public Properties -#region Private Properties + /// + /// Get Base64 string + /// + public string Base64 + { + get + { + try + {//For strange img tag + if (base64 != null) + return base64; + if (stream != null) + { + base64 = Convert.ToBase64String(stream); + return base64; + } + if (image != null) + { + using (MemoryStream ms = new MemoryStream()) + { + image.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + ms.Flush(); + stream = ms.ToArray(); + } + base64 = Convert.ToBase64String(stream); + return base64; + } + } + catch { } + GetErrorImage(); + return ""; + } + } - /// - /// Get or set WebClient for downloading imgs by url - /// - private WebClient Client - { - get - { - if (client == null) - { - client = new WebClient(); - } - return client; - } - set - { - client = value; - } - } + /// + /// Return true if has some error with Image + /// + public bool Error + { + get { return error; } + } -#endregion Private Properties + /// + /// Get Image + /// + public Image Image + { + get + { + try + {//for strange img tag + if (image != null) + return image; + if (stream != null) + { + MemoryStream ms = new MemoryStream(stream); + image = Bitmap.FromStream(ms); + return image; + } + if (base64 != null) + { + this.stream = Convert.FromBase64String(base64); + MemoryStream ms = new MemoryStream(stream); + image = Bitmap.FromStream(ms); + return image; + } + } + catch { } + return GetErrorImage(); + } + } -#region Public Events + /// + /// Get byte array + /// + public byte[] Stream + { + get + { + if (stream != null) return stream; + if (base64 != null) + { + stream = Convert.FromBase64String(base64); + return stream; + } + if (image != null) + { + using (MemoryStream ms = new MemoryStream()) + { + image.Save(ms, System.Drawing.Imaging.ImageFormat.Png); + ms.Flush(); + stream = ms.ToArray(); + } + return stream; + } + return new byte[0]; + } + } - /// - /// Occurs before image load - /// - public static event EventHandler AfterLoad; - /// - /// Occurs after image load - /// - public static event EventHandler BeforeLoad; + #endregion Public Properties -#endregion Public Events + #region Internal Properties -#region Public Methods + internal string Src + { + get { return src; } + set { src = value; } + } - /// - /// Enumerates all values - /// - /// - public IEnumerable AllItems() - { - List list = new List(); - lock (locker) - { - if (items != null) - { - foreach (KeyValuePair item in items) - { - item.Value.Src = item.Key; - list.Add(item.Value); - } - } - } - return list; - } + #endregion Internal Properties - /// - /// Return CacheItem by src - /// - /// Src attribute from img tag - /// - public CacheItem Get(string src) - { - CacheItem item = null; - if (!Validate(src)) - item = new CacheItem(); - if (String.IsNullOrEmpty(src)) - return item; - lock (locker) - { - if (items == null) - { - items = new Dictionary(); - if (item == null) - item = new CacheItem(); - items[src] = item; - Serialized = false; - } - if (items.ContainsKey(src)) - return items[src]; - } - return item; - } + #region Public Methods - /// - /// - /// - /// - /// - public Image Load(string src) - { - CacheItem item = null; - if (String.IsNullOrEmpty(src)) - item = new CacheItem(); - else - lock (locker) - { - if (items == null) - items = new Dictionary(); - else - if (items.ContainsKey(src)) - return items[src].Image; - item = new CacheItem(); - if (Validate(src)) - { - try - { - if (src.StartsWith("data:")) - { - item.Set(src.Substring(src.IndexOf("base64,") + "base64,".Length)); - } - else - item.Set(Client.DownloadData(src)); - } - catch - { - item.Set(""); - } - } - items[src] = item; - Serialized = false; - } - item.Src = src; - return item.Image; - } + /// + /// Return error image and set true to error property + /// + /// + public Image GetErrorImage() + { + error = true; + base64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAFdJREFUOE9jbGlq+c9AIqipq2GEawEZQAo4dvgYqoXD0QAGhv9ATyKCBY1PXBjANKEbBjSWOANA9mPRDBImzgCKXECVMMCTsojzwtAzAOQvUjCJmRe/cgDt6ZAkZx23LwAAAABJRU5ErkJggg=="; + src = "data:image/png;base64," + base64; + stream = Convert.FromBase64String(base64); + using (MemoryStream ms = new MemoryStream(stream)) + image = Bitmap.FromStream(ms); + return image; + } - /// - /// Set CacheItem by src - /// - /// Src attribute from img tag - /// CacheItem - /// - public CacheItem Set(string src, CacheItem item) - { - if (String.IsNullOrEmpty(src)) - return new CacheItem(); - lock (locker) - { - if (items == null) - items = new Dictionary(); - if (!Validate(src)) - item = new CacheItem(); - items[src] = item; - Serialized = false; - } - item.Src = src; - return item; - } + /// + /// Set value for cache item + /// + /// Image encoded base64 string + public void Set(string base64) + { + this.base64 = base64; + image = null; + stream = null; + } - /// - /// Validate src attribute from image - /// - /// Src attribute from img tag - /// return true if src is valid - public bool Validate(string src) - { - if (String.IsNullOrEmpty(src)) - return false; - src = src.ToLower(); - if (src.StartsWith("http://")) - return true; - if (src.StartsWith("https://")) - return true; - if (src.StartsWith("data:") && src.IndexOf("base64,") > 0) - return true; - return false; - } + /// + /// Set value for cache item + /// + /// Image + public void Set(Image img) + { + base64 = null; + image = img; + stream = null; + } -#endregion Public Methods + /// + /// Set value for cache item + /// + /// Image + public void Set(byte[] arr) + { + base64 = null; + image = null; + stream = arr; + } -#region Internal Methods + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls - static internal Image Load(InlineImageCache cache, string src) - { - LoadEventArgs args = new LoadEventArgs(cache, src); - if (BeforeLoad != null) BeforeLoad(null, args); - Image result = null; - if (!args.Handled) result = cache.Load(src); - args.Handled = false; - if (AfterLoad != null) AfterLoad(null, args); - if (args.Handled) - return cache.Get(src).Image; - return result; - } + /// + /// + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + if (image != null) + { + image.Dispose(); + image = null; + } + // TODO: dispose managed state (managed objects). + } -#endregion Internal Methods + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. -#region Public Constructors + disposedValue = true; + } + } - /// - public InlineImageCache() - { - locker = new object(); - client = null; - } + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~CacheItem() { + // // Do not change this code. Put cleanup code in Dispose(bool disposing) above. + // Dispose(false); + // } - /// - /// - /// - ~InlineImageCache() - { - 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 -#endregion Public Constructors + #endregion Public Methods + } -#region Public Classes + /// + /// WebClientEventArgs + /// + public class LoadEventArgs : EventArgs + { + #region Private Fields - /// - /// Item of image cache Dictionary - /// - public class CacheItem : IDisposable - { -#region Private Fields + private InlineImageCache cache; + private bool handled; + private string source; - private string base64; + #endregion Private Fields - private bool error; + #region Public Properties - private Image image; + /// + /// Gets a cache + /// + public InlineImageCache Cache { get { return cache; } } - //private int FId; - private string src; + /// + /// Gets or sets a value indicating whether the event was handled. + /// + public bool Handled { get { return handled; } set { handled = value; } } - private byte[] stream; + /// + /// Gets or sets a url from src attribue of img tag + /// + public string Source { get { return source; } set { source = value; } } -#endregion Private Fields + #endregion Public Properties -#region Public Properties + #region Internal Constructors - /// - /// Get Base64 string - /// - public string Base64 - { - get - { - try - {//For strange img tag - if (base64 != null) - return base64; - if (stream != null) - { - base64 = Convert.ToBase64String(stream); - return base64; - } - if (image != null) - { - using (MemoryStream ms = new MemoryStream()) - { - image.Save(ms, System.Drawing.Imaging.ImageFormat.Png); - ms.Flush(); - stream = ms.ToArray(); - } - base64 = Convert.ToBase64String(stream); - return base64; - } - } - catch { } - GetErrorImage(); - return ""; - } - } - - /// - /// Return true if has some error with Image - /// - public bool Error - { - get { return error; } - } - - /// - /// Get Image - /// - public Image Image - { - get - { - try - {//for strange img tag - if (image != null) - return image; - if (stream != null) - { - MemoryStream ms = new MemoryStream(stream); - image = Bitmap.FromStream(ms); - return image; - } - if (base64 != null) - { - this.stream = Convert.FromBase64String(base64); - MemoryStream ms = new MemoryStream(stream); - image = Bitmap.FromStream(ms); - return image; - } - } - catch { } - return GetErrorImage(); - } - } - - /// - /// Get byte array - /// - public byte[] Stream - { - get - { - if (stream != null) return stream; - if (base64 != null) - { - stream = Convert.FromBase64String(base64); - return stream; - } - if (image != null) - { - using (MemoryStream ms = new MemoryStream()) - { - image.Save(ms, System.Drawing.Imaging.ImageFormat.Png); - ms.Flush(); - stream = ms.ToArray(); - } - return stream; - } - return new byte[0]; - } - } - -#endregion Public Properties - -#region Internal Properties - - internal string Src - { - get { return src; } - set { src = value; } - } - -#endregion Internal Properties - -#region Public Methods - - /// - /// Return error image and set true to error property - /// - /// - public Image GetErrorImage() - { - error = true; - base64 = "iVBORw0KGgoAAAANSUhEUgAAABAAAAAQCAYAAAAf8/9hAAAAAXNSR0IArs4c6QAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAFdJREFUOE9jbGlq+c9AIqipq2GEawEZQAo4dvgYqoXD0QAGhv9ATyKCBY1PXBjANKEbBjSWOANA9mPRDBImzgCKXECVMMCTsojzwtAzAOQvUjCJmRe/cgDt6ZAkZx23LwAAAABJRU5ErkJggg=="; - src = "data:image/png;base64," + base64; - stream = Convert.FromBase64String(base64); - using (MemoryStream ms = new MemoryStream(stream)) - image = Bitmap.FromStream(ms); - return image; - } - - /// - /// Set value for cache item - /// - /// Image encoded base64 string - public void Set(string base64) - { - this.base64 = base64; - image = null; - stream = null; - } - - /// - /// Set value for cache item - /// - /// Image - public void Set(Image img) - { - base64 = null; - image = img; - stream = null; - } - - /// - /// Set value for cache item - /// - /// Image - public void Set(byte[] arr) - { - base64 = null; - image = null; - stream = arr; - } - -#region IDisposable Support - private bool disposedValue = false; // To detect redundant calls - - /// - /// - /// - /// - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - if (image != null) + internal LoadEventArgs(InlineImageCache c, string src) { - image.Dispose(); - image = null; + cache = c; + source = src; + handled = false; } - // 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; + #endregion Internal Constructors } - } - - // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. - // ~CacheItem() { - // // 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 - -#endregion Public Methods - } - - /// - /// WebClientEventArgs - /// - public class LoadEventArgs : EventArgs - { -#region Private Fields - - private InlineImageCache cache; - private bool handled; - private string source; - -#endregion Private Fields -#region Public Properties + #region IDisposable Support + private bool disposedValue = false; // To detect redundant calls - /// - /// Gets a cache - /// - public InlineImageCache Cache { get { return cache; } } - - /// - /// Gets or sets a value indicating whether the event was handled. - /// - public bool Handled { get { return handled; } set { handled = value; } } - - /// - /// Gets or sets a url from src attribue of img tag - /// - public string Source { get { return source; } set { source = value; } } - -#endregion Public Properties - -#region Internal Constructors - - internal LoadEventArgs(InlineImageCache c, string src) - { - cache = c; - source = src; - handled = false; - } + /// + /// + /// + /// + protected virtual void Dispose(bool disposing) + { + if (!disposedValue) + { + if (disposing) + { + // TODO: dispose managed state (managed objects). + } -#endregion Internal Constructors - } + if (this.items != null) + { + Dictionary items = this.items; + this.items = null; + foreach (CacheItem item in items.Values) + item.Dispose(); + } -#region IDisposable Support - private bool disposedValue = false; // To detect redundant calls + // TODO: free unmanaged resources (unmanaged objects) and override a finalizer below. + // TODO: set large fields to null. - /// - /// - /// - /// - protected virtual void Dispose(bool disposing) - { - if (!disposedValue) - { - if (disposing) - { - // TODO: dispose managed state (managed objects). + disposedValue = true; + } } - if (this.items != null) + // TODO: override a finalizer only if Dispose(bool disposing) above has code to free unmanaged resources. + // ~InlineImageCache() { + // // 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() { - Dictionary items = this.items; - this.items = null; - foreach (CacheItem item in items.Values) - item.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 - // 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. - // ~InlineImageCache() { - // // 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 Public Classes } -#endregion - -#endregion Public Classes - } } \ No newline at end of file diff --git a/FastReport.Base/Utils/Zip.cs b/FastReport.Base/Utils/Zip.cs index 39fa944e..74ca4ec6 100644 --- a/FastReport.Base/Utils/Zip.cs +++ b/FastReport.Base/Utils/Zip.cs @@ -152,7 +152,7 @@ public void SaveToStream(Stream Stream) { if (fileList[i].Disk) { - ZipFile.LocalFileHeader.FileName = fileList[i].Name.Replace(rootFolder + "\\", ""); + ZipFile.LocalFileHeader.FileName = fileList[i].Name.Replace(rootFolder + Path.DirectorySeparatorChar, ""); using (FileStream file = new FileStream(fileList[i].Name, FileMode.Open)) AddStreamToZip(file, ZipFile); } diff --git a/FastReport.Core.Web/Application/Dialog/WebComboBox.cs b/FastReport.Core.Web/Application/Dialog/WebComboBox.cs index dcc01131..da29c50e 100644 --- a/FastReport.Core.Web/Application/Dialog/WebComboBox.cs +++ b/FastReport.Core.Web/Application/Dialog/WebComboBox.cs @@ -22,6 +22,8 @@ private string GetComboBoxHtml(ComboBoxControl control) } else { + if (control.SelectedIndex == -1) + control.SelectedIndex = 0; control.SelectedItem = control.Items[control.SelectedIndex]; control.Text = control.SelectedItem.ToString(); } diff --git a/FastReport.Core.Web/Application/WebReportDesigner.cs b/FastReport.Core.Web/Application/WebReportDesigner.cs index 00c08399..6542a6d7 100644 --- a/FastReport.Core.Web/Application/WebReportDesigner.cs +++ b/FastReport.Core.Web/Application/WebReportDesigner.cs @@ -45,6 +45,16 @@ partial class WebReport /// Callback method for saving an edited report by Online Designer /// Params: reportID, report file name, report, out - message ///
+ /// + /// webReport.DesignerSaveMethod = (string reportID, string filename, string report) => + /// { + /// string webRootPath = _hostingEnvironment.WebRootPath; + /// string pathToSave = Path.Combine(webRootPath, filename); + /// System.IO.File.WriteAllTextAsync(pathToSave, report); + /// + /// return "OK"; + /// }; + /// public Func DesignerSaveMethod { get; set; } /// @@ -119,18 +129,19 @@ internal IActionResult DesignerSaveReport(HttpContext context) // paste restricted back in report before save Report.LoadFromString(PasteRestricted(reportString)); - SaveDesignedReportEventArgs e = new SaveDesignedReportEventArgs(); - e.Stream = new MemoryStream(); - Report.Save(e.Stream); - e.Stream.Position = 0; - OnSaveDesignedReport(e); + if (SaveDesignedReport != null) + { + SaveDesignedReportEventArgs e = new SaveDesignedReportEventArgs(); + e.Stream = new MemoryStream(); + Report.Save(e.Stream); + e.Stream.Position = 0; + OnSaveDesignedReport(e); + } if (!DesignerSaveCallBack.IsNullOrWhiteSpace()) { string report = Report.SaveToString(); - var reportName = (!String.IsNullOrEmpty(Report.ReportInfo.Name) ? - Report.ReportInfo.Name : Path.GetFileNameWithoutExtension(Report.FileName)); - string reportFileName = $"{reportName}.frx"; + string reportFileName = ReportFileName; UriBuilder uri = new UriBuilder { @@ -586,7 +597,7 @@ string PasteRestricted(string xmlString) // context.Response.Write(sb.ToString()); //} - string GetPOSTReport(HttpContext context) + internal string GetPOSTReport(HttpContext context) { string requestString = ""; using (TextReader textReader = new StreamReader(context.Request.Body)) diff --git a/FastReport.Core.Web/Controllers/DesignerController.cs b/FastReport.Core.Web/Controllers/DesignerController.cs index f701fc39..3d1e230b 100644 --- a/FastReport.Core.Web/Controllers/DesignerController.cs +++ b/FastReport.Core.Web/Controllers/DesignerController.cs @@ -40,7 +40,7 @@ public DesignerController() : base() { // save by using a Func - string report = webReport.Report.SaveToString(); + string report = webReport.GetPOSTReport(Context); string msg = string.Empty; int code = 200; try diff --git a/FastReport.Core.Web/FastReport.OpenSource.Web.csproj b/FastReport.Core.Web/FastReport.OpenSource.Web.csproj index 531e3bd5..f16598c0 100644 --- a/FastReport.Core.Web/FastReport.OpenSource.Web.csproj +++ b/FastReport.Core.Web/FastReport.OpenSource.Web.csproj @@ -7,28 +7,22 @@ Fast Reports Inc. Fast Reports Inc. FastReport.Web - FastReport.Net is a full-featured reporting solution for .Net Core 2.0. -Various report objects will allow your report to look exactly how you want it to: 13 types of bands, 25 types of barcodes, table object, diagram, maps, shapes, line, PolyLine, Polygon and many more. -FastReport.Net supports export to various popular formats, such as PDF/A, Excel, Word, Open Office, HTML, CSV, Json, XAML, ZPL, etc. - Fast Reports Inc. https://www.fast-report.com/en/product/fast-report-net - https://www.fast-report.com/download/images/frlogo-big.png + reporting, reports, pdf, html, mvc, core true Debug;Release;Demo - - The full version of the package is available in FastReport.Net Professional at https://www.fast-report.com/en/fast-report-net-editions-compare/ - https://github.com/FastReports/FastReport/blob/master/LICENSE.md OPENSOURCE; FastReport.OpenSource.Web ../FastReport.OpenSource.snk - FastReport Open Source is an open source reporting solution for .Net Core 2.x and .Net Framework 4.x. + FastReport Open Source is an open source reporting solution for .Net Core and .Net Framework 4.x. Various report objects will allow your report to look exactly how you want it to: 13 types of bands, 25 types of barcodes, table object, shapes, line, PolyLine, Polygon and many more. https://github.com/FastReports/FastReport + frlogo-big.png @@ -54,6 +48,10 @@ Various report objects will allow your report to look exactly how you want it to + + True + + @@ -77,6 +75,6 @@ Various report objects will allow your report to look exactly how you want it to - + diff --git a/FastReport.Core.Web/FastReport.Web.csproj b/FastReport.Core.Web/FastReport.Web.csproj index 258998c9..33da3800 100644 --- a/FastReport.Core.Web/FastReport.Web.csproj +++ b/FastReport.Core.Web/FastReport.Web.csproj @@ -14,7 +14,8 @@ FastReport.Net supports export to various popular formats, such as PDF/A, Excel, Fast Reports Inc. https://www.fast-report.com/en/product/fast-report-net - https://www.fast-report.com/download/images/frlogo-big.png + + frlogo-big.png reporting, reports, pdf, html, mvc, core true Debug;Release;Demo @@ -25,9 +26,9 @@ FastReport.Net supports export to various popular formats, such as PDF/A, Excel, FastReport.Web ../FastReport.Net.snk - FastReport.Net is a full-featured reporting solution for .Net Core. + FastReport.Core is a full-featured reporting solution for .Net Core. Various report objects will allow your report to look exactly how you want it to: 13 types of bands, 25 types of barcodes, table object, diagram, maps, shapes, line, PolyLine, Polygon and many more. -FastReport.Net supports export to various popular formats, such as PDF/A, Excel, Word, Open Office, HTML, CSV, Json, XAML, ZPL, etc. +FastReport.Core supports export to various popular formats, such as PDF/A, Excel, Word, Open Office, HTML, CSV, Json, XAML, ZPL, etc. $(DemoDescription) @@ -55,6 +56,10 @@ $(DemoDescription) + + True + + diff --git a/FastReport.Core.Web/Templates/style.cs b/FastReport.Core.Web/Templates/style.cs index d8066131..ff49b4fe 100644 --- a/FastReport.Core.Web/Templates/style.cs +++ b/FastReport.Core.Web/Templates/style.cs @@ -16,6 +16,11 @@ string template_style() => $@" position: relative; }} +.{template_FR}-container * {{ + box-sizing: content-box; + -moz-box-sizing: content-box; +}} + .{template_FR}-body {{ display: flex; overflow: hidden; diff --git a/FastReport.OpenSource.sln b/FastReport.OpenSource.sln index e4082537..e9fe0c67 100644 --- a/FastReport.OpenSource.sln +++ b/FastReport.OpenSource.sln @@ -41,11 +41,7 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.OpenSource.Expor EndProject Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.OpenSource.Export.PdfSimple.Tests", "Extras\OpenSource\FastReport.OpenSource.Export.PdfSimple\FastReport.OpenSource.Export.PdfSimple.Tests\FastReport.OpenSource.Export.PdfSimple.Tests.csproj", "{01DA6786-77D4-4457-BF33-E204F0C9E155}" EndProject -Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "ReportBuilder", "ReportBuilder", "{669B319D-9D1F-49B3-A719-03C9E9ABD692}" -EndProject -Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.ReportBuilder", "Extras\ReportBuilder\FastReport.ReportBuilder\FastReport.ReportBuilder.csproj", "{ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}" -EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "FastReport.ReportBuilder.UnitTest", "Extras\ReportBuilder\FastReport.ReportBuilder.UnitTest\FastReport.ReportBuilder.UnitTest.csproj", "{17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "FastReport.OpenSource.Data.ClickHouse", "Extras\Core\FastReport.Data\FastReport.ClickHouse\FastReport.ClickHouse\FastReport.OpenSource.Data.ClickHouse.csproj", "{BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -216,26 +212,16 @@ Global {01DA6786-77D4-4457-BF33-E204F0C9E155}.Release|Any CPU.Build.0 = Release|Any CPU {01DA6786-77D4-4457-BF33-E204F0C9E155}.WinForms|Any CPU.ActiveCfg = Debug|Any CPU {01DA6786-77D4-4457-BF33-E204F0C9E155}.WinForms|Any CPU.Build.0 = Debug|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.Academic|Any CPU.ActiveCfg = Debug|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.Academic|Any CPU.Build.0 = Debug|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.Debug|Any CPU.Build.0 = Debug|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.Demo|Any CPU.ActiveCfg = Debug|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.Demo|Any CPU.Build.0 = Debug|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.Release|Any CPU.ActiveCfg = Release|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.Release|Any CPU.Build.0 = Release|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.WinForms|Any CPU.ActiveCfg = Debug|Any CPU - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A}.WinForms|Any CPU.Build.0 = Debug|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.Academic|Any CPU.ActiveCfg = Debug|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.Academic|Any CPU.Build.0 = Debug|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.Debug|Any CPU.ActiveCfg = Debug|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.Debug|Any CPU.Build.0 = Debug|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.Demo|Any CPU.ActiveCfg = Debug|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.Demo|Any CPU.Build.0 = Debug|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.Release|Any CPU.ActiveCfg = Release|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.Release|Any CPU.Build.0 = Release|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.WinForms|Any CPU.ActiveCfg = Debug|Any CPU - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE}.WinForms|Any CPU.Build.0 = Debug|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.Academic|Any CPU.ActiveCfg = Debug|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.Academic|Any CPU.Build.0 = Debug|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.Debug|Any CPU.Build.0 = Debug|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.Demo|Any CPU.ActiveCfg = Debug|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.Demo|Any CPU.Build.0 = Debug|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.Release|Any CPU.ActiveCfg = Release|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.Release|Any CPU.Build.0 = Release|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.WinForms|Any CPU.ActiveCfg = Debug|Any CPU + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35}.WinForms|Any CPU.Build.0 = Debug|Any CPU EndGlobalSection GlobalSection(SolutionProperties) = preSolution HideSolutionNode = FALSE @@ -255,9 +241,7 @@ Global {FD840207-C565-4E98-8118-D7EF7F183D7C} = {8E9E75EB-2ABC-4CC1-8AA0-C11DEE54A152} {09E87462-E2B8-4B8F-AE41-55A8584471F9} = {CCF32DAC-85D9-43D4-A4A7-72626A13D806} {01DA6786-77D4-4457-BF33-E204F0C9E155} = {CCF32DAC-85D9-43D4-A4A7-72626A13D806} - {669B319D-9D1F-49B3-A719-03C9E9ABD692} = {CCF32DAC-85D9-43D4-A4A7-72626A13D806} - {ABFBDAC5-AC9E-44B4-9201-FE8BFBD5DB0A} = {669B319D-9D1F-49B3-A719-03C9E9ABD692} - {17CE85B1-B182-4BEA-90A8-F8D110B1CFCE} = {669B319D-9D1F-49B3-A719-03C9E9ABD692} + {BDB7D5CB-8DD2-48DB-BA81-FDDC88135B35} = {898AF8DC-11C3-4640-B60C-5D8CBF2F29CF} EndGlobalSection GlobalSection(ExtensibilityGlobals) = postSolution SolutionGuid = {DFBB1F5B-F03E-4BCB-AFEE-A45A2C4D5BB8} diff --git a/FastReport.OpenSource/Code/AssemblyDescriptor.Core.cs b/FastReport.OpenSource/Code/AssemblyDescriptor.Core.cs index fc56aa9b..504715d8 100644 --- a/FastReport.OpenSource/Code/AssemblyDescriptor.Core.cs +++ b/FastReport.OpenSource/Code/AssemblyDescriptor.Core.cs @@ -9,24 +9,12 @@ namespace FastReport.Code { partial class AssemblyDescriptor { - private void ErrorMsg(CompilerError ce, int number) - { - - } + partial void ErrorMsg(CompilerError ce, int number); - private void ErrorMsg(string str, CompilerError ce) - { + partial void ErrorMsg(string str, CompilerError ce); - } + partial void ErrorMsg(string str); - private void ErrorMsg(string str) - { - - } - - private void ReviewReferencedAssemblies(StringCollection referencedAssemblies) - { - - } + partial void ReviewReferencedAssemblies(StringCollection referencedAssemblies); } } diff --git a/FastReport.OpenSource/Code/ExpressionDescriptor.Core.cs b/FastReport.OpenSource/Code/ExpressionDescriptor.Core.cs index 7d27f156..b34c15d8 100644 --- a/FastReport.OpenSource/Code/ExpressionDescriptor.Core.cs +++ b/FastReport.OpenSource/Code/ExpressionDescriptor.Core.cs @@ -6,7 +6,7 @@ internal class ExpressionDescriptor { private string methodName; private MethodInfo methodInfo; - private AssemblyDescriptor assembly; + private readonly AssemblyDescriptor assembly; public string MethodName { diff --git a/FastReport.OpenSource/CrossVeiw/CrossViewHelper.Core.cs b/FastReport.OpenSource/CrossVeiw/CrossViewHelper.Core.cs index f06ac539..100fb30b 100644 --- a/FastReport.OpenSource/CrossVeiw/CrossViewHelper.Core.cs +++ b/FastReport.OpenSource/CrossVeiw/CrossViewHelper.Core.cs @@ -5,9 +5,6 @@ partial class CrossViewHelper /// /// Does nothing /// - private void OnProgressInternal() - { - - } + partial void OnProgressInternal(); } } diff --git a/FastReport.OpenSource/CrossVeiw/CrossViewObject.Core.cs b/FastReport.OpenSource/CrossVeiw/CrossViewObject.Core.cs index 93544b0e..d3825680 100644 --- a/FastReport.OpenSource/CrossVeiw/CrossViewObject.Core.cs +++ b/FastReport.OpenSource/CrossVeiw/CrossViewObject.Core.cs @@ -6,9 +6,6 @@ partial class CrossViewObject /// Does nothing /// /// - private void RefreshTemplate(bool flag) - { - - } + partial void RefreshTemplate(bool flag); } } diff --git a/FastReport.OpenSource/Data/DataConnectionBase.Core.cs b/FastReport.OpenSource/Data/DataConnectionBase.Core.cs index 207197af..a7697562 100644 --- a/FastReport.OpenSource/Data/DataConnectionBase.Core.cs +++ b/FastReport.OpenSource/Data/DataConnectionBase.Core.cs @@ -11,9 +11,7 @@ partial class DataConnectionBase /// Does nothing /// /// - private void FilterTables(List tableNames) - { - } + partial void FilterTables(List tableNames); /// /// Does nothing @@ -36,9 +34,7 @@ private bool ShouldNotDispose(DbConnection connection) /// /// Does nothing /// - private void ShowLoginForm(string lastConnectionString) - { - } + partial void ShowLoginForm(string lastConnectionString); #endregion Private Methods } diff --git a/FastReport.OpenSource/Data/TableDataSource.Core.cs b/FastReport.OpenSource/Data/TableDataSource.Core.cs index 4b59cd88..51d16a6e 100644 --- a/FastReport.OpenSource/Data/TableDataSource.Core.cs +++ b/FastReport.OpenSource/Data/TableDataSource.Core.cs @@ -7,9 +7,7 @@ partial class TableDataSource /// /// Does nothing /// - private void TryToLoadData() - { - } + partial void TryToLoadData(); #endregion Private Methods } diff --git a/FastReport.OpenSource/Dialog/DialogPage.Core.cs b/FastReport.OpenSource/Dialog/DialogPage.Core.cs index 86c33de1..6955fb69 100644 --- a/FastReport.OpenSource/Dialog/DialogPage.Core.cs +++ b/FastReport.OpenSource/Dialog/DialogPage.Core.cs @@ -7,9 +7,7 @@ partial class DialogPage /// /// Does nothing /// - private void ResetFormBitmap() - { - } + partial void ResetFormBitmap(); #endregion Private Methods } diff --git a/FastReport.OpenSource/Engine/ReportEngine.Core.cs b/FastReport.OpenSource/Engine/ReportEngine.Core.cs index 3e3b6094..e5a4139e 100644 --- a/FastReport.OpenSource/Engine/ReportEngine.Core.cs +++ b/FastReport.OpenSource/Engine/ReportEngine.Core.cs @@ -2,8 +2,6 @@ { partial class ReportEngine { - private void ShowProgress() - { - } + partial void ShowProgress(); } } \ No newline at end of file diff --git a/FastReport.OpenSource/Engine/ReportEngine.Rich.cs b/FastReport.OpenSource/Engine/ReportEngine.Rich.cs new file mode 100644 index 00000000..38e731b1 --- /dev/null +++ b/FastReport.OpenSource/Engine/ReportEngine.Rich.cs @@ -0,0 +1,15 @@ +namespace FastReport.Engine +{ + partial class ReportEngine + { + private void InitializePages() + { + for (int i = 0; i < Report.Pages.Count; i++) + { + ReportPage page = Report.Pages[i] as ReportPage; + if (page != null) + PreparedPages.AddSourcePage(page); + } + } + } +} \ No newline at end of file diff --git a/FastReport.OpenSource/Export/ExportBase.Core.cs b/FastReport.OpenSource/Export/ExportBase.Core.cs index 1f0292bc..993cc71d 100644 --- a/FastReport.OpenSource/Export/ExportBase.Core.cs +++ b/FastReport.OpenSource/Export/ExportBase.Core.cs @@ -10,10 +10,7 @@ partial class ExportBase /// Does nothing /// /// - private void ShowPerformance(int int0) - { - - } + partial void ShowPerformance(int int0); protected ReportPage GetOverlayPage(ReportPage page) { diff --git a/FastReport.OpenSource/Export/Html/HTMLExport.Core.cs b/FastReport.OpenSource/Export/Html/HTMLExport.Core.cs index ea57ec80..6efff202 100644 --- a/FastReport.OpenSource/Export/Html/HTMLExport.Core.cs +++ b/FastReport.OpenSource/Export/Html/HTMLExport.Core.cs @@ -27,10 +27,7 @@ private bool HasExtendedExport(ReportComponentBase obj) return false; } - private void ExtendExport(FastString Page, ReportComponentBase obj, FastString text) - { - - } + partial void ExtendExport(FastString Page, ReportComponentBase obj, FastString text); private class ExportIEMStyle { diff --git a/FastReport.OpenSource/FastReport.OpenSource.csproj b/FastReport.OpenSource/FastReport.OpenSource.csproj index dba9a72d..013d4a2b 100644 --- a/FastReport.OpenSource/FastReport.OpenSource.csproj +++ b/FastReport.OpenSource/FastReport.OpenSource.csproj @@ -14,6 +14,7 @@ true + frlogo-big.png ../FastReport.OpenSource.snk true Fast Reports Inc. @@ -23,13 +24,13 @@ Fast Reports Inc. FastReport FastReport.OpenSource - https://www.fast-report.com/download/images/frlogo-big.png + reporting, reports, pdf, html, mvc, core 1.0.0 Debug;Release FastReport FastReport - FastReport Open Source is an open source reporting solution for .Net Core 2.x and .Net Framework 4.x. + FastReport Open Source is an open source reporting solution for .Net Core and .Net Framework 4.x. Various report objects will allow your report to look exactly how you want it to: 13 types of bands, 25 types of barcodes, table object, shapes, line, PolyLine, Polygon and many more. https://github.com/FastReports/FastReport @@ -74,4 +75,12 @@ Various report objects will allow your report to look exactly how you want it to
+ + + + True + + + + \ No newline at end of file diff --git a/FastReport.OpenSource/HtmlObject.Core.cs b/FastReport.OpenSource/HtmlObject.Core.cs index cfaa9776..92ff7aa3 100644 --- a/FastReport.OpenSource/HtmlObject.Core.cs +++ b/FastReport.OpenSource/HtmlObject.Core.cs @@ -8,9 +8,6 @@ partial class HtmlObject /// Does nothing /// /// - private void DrawDesign(FRPaintEventArgs e) - { - - } + partial void DrawDesign(FRPaintEventArgs e); } } diff --git a/FastReport.OpenSource/Matrix/MatrixObject.Core.cs b/FastReport.OpenSource/Matrix/MatrixObject.Core.cs index a22bec13..42018c8d 100644 --- a/FastReport.OpenSource/Matrix/MatrixObject.Core.cs +++ b/FastReport.OpenSource/Matrix/MatrixObject.Core.cs @@ -7,17 +7,13 @@ partial class MatrixObject /// /// Does nothing /// - private void InitDesign() - { - } + partial void InitDesign(); /// /// Does nothing /// /// - private void RefreshTemplate(bool flag) - { - } + partial void RefreshTemplate(bool flag); #endregion Private Methods } diff --git a/FastReport.OpenSource/PictureObjectBase.Core.cs b/FastReport.OpenSource/PictureObjectBase.Core.cs index 8bbf337b..acf18220 100644 --- a/FastReport.OpenSource/PictureObjectBase.Core.cs +++ b/FastReport.OpenSource/PictureObjectBase.Core.cs @@ -19,9 +19,6 @@ protected void DrawErrorImage(Graphics g, FRPaintEventArgs e) /// Does nothing /// /// - private void DrawDesign(FRPaintEventArgs e) - { - - } + partial void DrawDesign(FRPaintEventArgs e); } } diff --git a/FastReport.OpenSource/PolyLineObject.Core.cs b/FastReport.OpenSource/PolyLineObject.Core.cs index e0897091..a120465d 100644 --- a/FastReport.OpenSource/PolyLineObject.Core.cs +++ b/FastReport.OpenSource/PolyLineObject.Core.cs @@ -10,24 +10,18 @@ partial class PolyLineObject /// Does nothing /// /// - private void DrawDesign0(FRPaintEventArgs e) - { - } + partial void DrawDesign0(FRPaintEventArgs e); /// /// Does nothing /// /// - private void DrawDesign1(FRPaintEventArgs e) - { - } + partial void DrawDesign1(FRPaintEventArgs e); /// /// Does nothing /// - private void InitDesign() - { - } + partial void InitDesign(); #endregion Private Methods } diff --git a/FastReport.OpenSource/Preview/SourcePages.OpenSource.cs b/FastReport.OpenSource/Preview/SourcePages.OpenSource.cs new file mode 100644 index 00000000..7f778141 --- /dev/null +++ b/FastReport.OpenSource/Preview/SourcePages.OpenSource.cs @@ -0,0 +1,15 @@ +using System; +using System.Collections.Generic; +using System.Text; +using FastReport.Utils; + +namespace FastReport.Preview +{ + internal partial class SourcePages + { + private Base RichObjectTranslation(Base source, Base parent) + { + return null; + } + } +} diff --git a/FastReport.OpenSource/Report.Core.cs b/FastReport.OpenSource/Report.Core.cs index 039e11ba..60b78886 100644 --- a/FastReport.OpenSource/Report.Core.cs +++ b/FastReport.OpenSource/Report.Core.cs @@ -27,34 +27,22 @@ private string ShowPaswordForm(string password) /// /// /// - private void SerializeDesign(FRWriter writer, Report report) - { - - } + partial void SerializeDesign(FRWriter writer, Report report); /// /// Does nothing /// - private void InitDesign() - { - - } + partial void InitDesign(); /// /// Does nothing /// - private void ClearDesign() - { - - } + partial void ClearDesign(); /// /// Does nothing /// - private void DisposeDesign() - { - - } + partial void DisposeDesign(); #endregion Private Methods } diff --git a/FastReport.OpenSource/ReportPage.Core.cs b/FastReport.OpenSource/ReportPage.Core.cs index 2e362b7b..36c775e7 100644 --- a/FastReport.OpenSource/ReportPage.Core.cs +++ b/FastReport.OpenSource/ReportPage.Core.cs @@ -10,25 +10,19 @@ partial class ReportPage /// Does nothing /// /// - private void AssignPreview(ReportPage reportPage) - { - } + partial void AssignPreview(ReportPage reportPage); /// /// Does nothing /// - private void InitPreview() - { - } + partial void InitPreview(); /// /// Does nothing /// /// /// - private void WritePreview(FRWriter writer, ReportPage reportPage) - { - } + partial void WritePreview(FRWriter writer, ReportPage reportPage); #endregion Private Methods } diff --git a/FastReport.OpenSource/ReportSettings.Core.cs b/FastReport.OpenSource/ReportSettings.Core.cs index 40e3c8eb..4b6c04ea 100644 --- a/FastReport.OpenSource/ReportSettings.Core.cs +++ b/FastReport.OpenSource/ReportSettings.Core.cs @@ -30,7 +30,7 @@ internal void OnProgress(Report report, string str) /// internal void OnProgress(Report report, string str, int int1, int int2) { - + } /// diff --git a/FastReport.OpenSource/Table/TableBase.Core.cs b/FastReport.OpenSource/Table/TableBase.Core.cs index 4d0e9f12..49c71032 100644 --- a/FastReport.OpenSource/Table/TableBase.Core.cs +++ b/FastReport.OpenSource/Table/TableBase.Core.cs @@ -10,41 +10,31 @@ partial class TableBase /// Does nothing /// /// - private void DrawDesign(FRPaintEventArgs e) - { - } + partial void DrawDesign(FRPaintEventArgs e); /// /// Does nothing /// /// - private void DrawDesign_Borders(FRPaintEventArgs e) - { - } + partial void DrawDesign_Borders(FRPaintEventArgs e); /// /// Does nothing /// /// - private void DrawDesign_BordersRtl(FRPaintEventArgs e) - { - } + partial void DrawDesign_BordersRtl(FRPaintEventArgs e); /// /// Does nothing /// /// - private void DrawDesign_SelectedCells(FRPaintEventArgs e) - { - } + partial void DrawDesign_SelectedCells(FRPaintEventArgs e); /// /// Does nothing /// /// - private void DrawDesign_SelectedCellsRtl(FRPaintEventArgs e) - { - } + partial void DrawDesign_SelectedCellsRtl(FRPaintEventArgs e); #endregion Private Methods } diff --git a/FastReport.OpenSource/TextObject.Core.cs b/FastReport.OpenSource/TextObject.Core.cs index d9ef5159..0b9eda76 100644 --- a/FastReport.OpenSource/TextObject.Core.cs +++ b/FastReport.OpenSource/TextObject.Core.cs @@ -8,9 +8,6 @@ partial class TextObject /// Does nothing /// /// - private void DrawDesign(FRPaintEventArgs e) - { - - } + partial void DrawDesign(FRPaintEventArgs e); } } diff --git a/FastReport.OpenSource/Utils/Config.Core.cs b/FastReport.OpenSource/Utils/Config.Core.cs index 6cfd354b..59d42931 100644 --- a/FastReport.OpenSource/Utils/Config.Core.cs +++ b/FastReport.OpenSource/Utils/Config.Core.cs @@ -41,28 +41,18 @@ public static bool WebMode /// /// Does nothing /// - private static void RestoreUIStyle() - { - } + static partial void RestoreUIStyle(); /// /// Does nothing /// - private static void SaveUIStyle() - { - } + static partial void SaveUIStyle(); - private static void RestorePreviewSettings() - { - } + static partial void RestorePreviewSettings(); - private static void SavePreviewSettings() - { - } + static partial void SavePreviewSettings(); - private static void SaveExportOptions() - { - } + static partial void SaveExportOptions(); private static void RestoreExportOptions() { diff --git a/FastReport.OpenSource/Utils/ExportsOptions.Core.cs b/FastReport.OpenSource/Utils/ExportsOptions.Core.cs index fde26b2e..410259f8 100644 --- a/FastReport.OpenSource/Utils/ExportsOptions.Core.cs +++ b/FastReport.OpenSource/Utils/ExportsOptions.Core.cs @@ -8,8 +8,8 @@ namespace FastReport.Utils { partial class ExportsOptions { - private void SaveOptions() { } + partial void SaveOptions(); - private void RestoreOptions() { } + partial void RestoreOptions(); } } diff --git a/FastReport.OpenSource/Utils/RegisteredObjects.Core.cs b/FastReport.OpenSource/Utils/RegisteredObjects.Core.cs index e5048902..fab52ed7 100644 --- a/FastReport.OpenSource/Utils/RegisteredObjects.Core.cs +++ b/FastReport.OpenSource/Utils/RegisteredObjects.Core.cs @@ -15,9 +15,7 @@ partial class ObjectInfo /// /// /// - private void UpdateDesign(object obj, Bitmap image, int imageIndex, string text, int flags, bool multiInsert) - { - } + partial void UpdateDesign(object obj, Bitmap image, int imageIndex, string text, int flags, bool multiInsert); /// /// Does nothing. @@ -29,9 +27,7 @@ private void UpdateDesign(object obj, Bitmap image, int imageIndex, string text, /// /// /// - private void UpdateDesign(object obj, Bitmap image, int imageIndex, int ButtonIndex, string text, int flags, bool multiInsert) - { - } + partial void UpdateDesign(object obj, Bitmap image, int imageIndex, int ButtonIndex, string text, int flags, bool multiInsert); #endregion Private Methods } diff --git a/FastReport/Resources/en.xml b/FastReport/Resources/en.xml index d4fc4002..540cf6f8 100644 --- a/FastReport/Resources/en.xml +++ b/FastReport/Resources/en.xml @@ -60,6 +60,8 @@ + + @@ -459,6 +461,7 @@ + @@ -1838,6 +1841,7 @@ + @@ -1850,6 +1854,7 @@ + @@ -1960,9 +1965,7 @@ - + @@ -1985,12 +1988,10 @@ - + + (for example, for Rosreestr payments)"/> @@ -2031,6 +2032,7 @@ + @@ -2048,8 +2050,8 @@ - - + + @@ -2381,6 +2383,9 @@ + + + diff --git a/Localization/Arabic.frl b/Localization/Arabic.frl new file mode 100644 index 00000000..435b29f4 --- /dev/null +++ b/Localization/Arabic.frl @@ -0,0 +1,2258 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
+ + + + + + + + + + + + + + + + + + + + + + +