реализовал 1 компонент

This commit is contained in:
bekodeg 2024-10-25 20:29:41 +04:00
parent 2f1ed5603e
commit dd36522051
31 changed files with 1760 additions and 3 deletions

View File

@ -5,7 +5,9 @@ VisualStudioVersion = 17.10.35122.118
MinimumVisualStudioVersion = 10.0.40219.1
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Cop.Borovkov.Var3", "Cop.Borovkov.Var3\Cop.Borovkov.Var3.csproj", "{A8604186-0CDE-4504-805B-46104141269A}"
EndProject
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TestCustomComponents", "TestCustomComponents\TestCustomComponents.csproj", "{E2C46D08-ACCE-4547-922B-E92AD76D99C8}"
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TestCustomComponents", "TestCustomComponents\TestCustomComponents.csproj", "{E2C46D08-ACCE-4547-922B-E92AD76D99C8}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "PIHelperSh.PdfCreator", "PIHelperSh.PdfCreater\PIHelperSh.PdfCreator.csproj", "{572BD835-A500-43C9-A66F-648540F4A1C8}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
@ -21,6 +23,10 @@ Global
{E2C46D08-ACCE-4547-922B-E92AD76D99C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{E2C46D08-ACCE-4547-922B-E92AD76D99C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{E2C46D08-ACCE-4547-922B-E92AD76D99C8}.Release|Any CPU.Build.0 = Release|Any CPU
{572BD835-A500-43C9-A66F-648540F4A1C8}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{572BD835-A500-43C9-A66F-648540F4A1C8}.Debug|Any CPU.Build.0 = Debug|Any CPU
{572BD835-A500-43C9-A66F-648540F4A1C8}.Release|Any CPU.ActiveCfg = Release|Any CPU
{572BD835-A500-43C9-A66F-648540F4A1C8}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE

View File

@ -0,0 +1,36 @@
namespace Cop.Borovkov.Var3.Components
{
partial class CustomPdfTable
{
/// <summary>
/// Обязательная переменная конструктора.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Освободить все используемые ресурсы.
/// </summary>
/// <param name="disposing">истинно, если управляемый ресурс должен быть удален; иначе ложно.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Код, автоматически созданный конструктором компонентов
/// <summary>
/// Требуемый метод для поддержки конструктора — не изменяйте
/// содержимое этого метода с помощью редактора кода.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
}
#endregion
}
}

View File

@ -0,0 +1,92 @@
using Cop.Borovkov.Var3.Models;
using PIHelperSh.PdfCreator;
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
using PIHelperSh.PdfCreator.Models.TableModels;
using System.ComponentModel;
namespace Cop.Borovkov.Var3.Components
{
/// <summary>
/// Компонент для сохранения таблицы в пдф
/// </summary>
public partial class CustomPdfTable : Component
{
/// <summary>
/// </summary>
public CustomPdfTable()
{
InitializeComponent();
}
/// <summary>
/// </summary>
/// <param name="container"></param>
public CustomPdfTable(IContainer container)
{
container.Add(this);
InitializeComponent();
}
/// <summary>
/// Сохранить набор таблиц в пдф
/// </summary>
/// <param name="fileName"></param>
/// <param name="title"></param>
/// <param name="tables"></param>
public void SaveToPdf(PdfTableInfo tableInfo)
{
if (!tableInfo.Tables.Any())
{
return;
}
PdfCreator creator = new PdfCreator();
creator.AddParagraph(new()
{
Style = PdfStyleType.Title,
Text = tableInfo.Title,
MarginAfter = PdfMargin.Smal,
});
foreach (string[,] table in tableInfo.Tables)
{
List<PDFSimpleTableRow> rows = new();
for (int i = 0; i < table.GetLength(0); ++i)
{
PDFSimpleTableRow row = new();
for (int j = 0; j < table.GetLength(1); ++j)
{
row.Items.Add(table[i, j]);
}
rows.Add(row);
}
creator.AddSimpleTable(new()
{
Header = rows.First().Items.Select(item => new PdfTableColumn()
{
Title = item,
Size = 3,
} as IPdfColumnItem).ToList(),
Rows = rows.Skip(1).ToList(),
HeaderStyle = PdfStyleType.Basic,
HeaderHorizontalAlignment = PdfAlignmentType.Left,
RowHorizontalAlignment = PdfAlignmentType.Left,
});
creator.AddParagraph(new()
{
MarginAfter = PdfMargin.Smal,
});
}
creator.SavePdf(tableInfo.FilePath);
}
}
}

View File

@ -7,4 +7,8 @@
<ImplicitUsings>enable</ImplicitUsings>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\PIHelperSh.PdfCreater\PIHelperSh.PdfCreator.csproj" />
</ItemGroup>
</Project>

View File

@ -0,0 +1,23 @@
namespace Cop.Borovkov.Var3.Models
{
/// <summary>
/// Параметры для создания таблиц в пдф
/// </summary>
public record PdfTableInfo
{
/// <summary>
/// имя файла (включая путь до файла)
/// </summary>
public string FilePath { get; init; } = @"C:\pdfTable.pdf";
/// <summary>
/// название документа(заголовок в документе)
/// </summary>
public string Title { get; init; } = "Таблица";
/// <summary>
/// Список таблиц
/// </summary>
public IEnumerable<string[,]> Tables { get; init; } = [];
}
}

View File

@ -0,0 +1,32 @@
using MigraDoc.DocumentObjectModel;
using PIHelperSh.Core.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Enums
{
/// <summary>
/// Тип выравнивания элементов
/// </summary>
public enum PdfAlignmentType
{
/// <summary>
/// По центру
/// </summary>
[TypeValue<ParagraphAlignment>(ParagraphAlignment.Center)]
Center,
/// <summary>
/// По левому краю
/// </summary>
[TypeValue<ParagraphAlignment>(ParagraphAlignment.Left)]
Left,
/// <summary>
/// По правому краю
/// </summary>
[TypeValue<ParagraphAlignment>(ParagraphAlignment.Right)]
Rigth
}
}

View File

@ -0,0 +1,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Enums
{
/// <summary>
/// Варианты расположения легенды
/// </summary>
public enum PdfLegendPosition
{
/// <summary>
/// Снизу(по умолчанию)
/// </summary>
Bottom,
/// <summary>
/// Справа
/// </summary>
Right,
/// <summary>
/// Слева
/// </summary>
Left,
/// <summary>
/// Сверху
/// </summary>
Top
}
}

View File

@ -0,0 +1,36 @@
using PIHelperSh.Core.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Enums
{
/// <summary>
/// Типы отступов после элементов (вертикальный)
/// </summary>
public enum PdfMargin
{
/// <summary>
/// Отступа нет
/// </summary>
[TypeValue<string>("ocm")]
None,
/// <summary>
/// Отступ небольшой
/// </summary>
[TypeValue<string>("0.3cm")]
Smal,
/// <summary>
/// Отступ средний
/// </summary>
[TypeValue<string>("1cm")]
Medium,
/// <summary>
/// Отступ большой
/// </summary>
[TypeValue<string>("1.6cm")]
Big
}
}

View File

@ -0,0 +1,54 @@
using PIHelperSh.Core.Attributes;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Enums
{
/// <summary>
/// Типы стилей в документе
/// </summary>
public enum PdfStyleType
{
/// <summary>
/// Основная часть документа
/// </summary>
[TypeValue<string>("Normal")]
Basic,
/// <summary>
/// Название документа
/// </summary>
[TypeValue<string>("NormalTitle")]
Title,
/// <summary>
/// Уровень в списке 1
/// </summary>
[TypeValue<string>("BulletList")]
[TypeValue<int>(1)]
ListLevel1,
/// <summary>
/// Уровень в списке 2
/// </summary>
[TypeValue<string>("BulletList2")]
[TypeValue<int>(2)]
ListLevel2,
/// <summary>
/// Уровень в списке 3
/// </summary>
[TypeValue<string>("BulletList3")]
[TypeValue<int>(3)]
ListLevel3,
/// <summary>
/// Жирный шрифт
/// </summary>
[TypeValue<string>("NormalBold")]
Bold,
/// <summary>
/// Мелкий шрифт
/// </summary>
[TypeValue<string>("NormalSmall")]
Small
}
}

View File

@ -0,0 +1,15 @@
namespace PIHelperSh.PdfCreator.Interfaces
{
/// <summary>
/// </summary>
public interface IPdfColumnItem
{
/// <summary>
/// </summary>
public float? Size { get; }
/// <summary>
/// </summary>
public string? Title { get; set; }
}
}

View File

@ -0,0 +1,64 @@
using PIHelperSh.PdfCreator.Models.ImageModels;
using PIHelperSh.PdfCreator.Models.PieChartModel;
using PIHelperSh.PdfCreator.Models.TableModels;
using PIHelperSh.PdfCreator.Models.TextModels;
namespace PIHelperSh.PdfCreator.Interfaces
{
/// <summary>
/// Создатель pdf документа
/// </summary>
public interface IPdfCreator
{
/// <summary>
/// Создаём параграф
/// </summary>
/// <param name="paragraph">Модель параграфа, который вставляем</param>
public void AddParagraph(PdfParagraph paragraph);
/// <summary>
/// Создаём маркированный список
/// </summary>
/// <param name="List">Модель списка(может быть многоуровневым). Max - 3 уровня</param>
public void AddList(PdfList List);
/// <summary>
/// Создаёт таблицу, с шапкой из 2-х строк(с группировками)
/// </summary>
/// <typeparam name="T">Тип DTO, из которой берутся данные в таблицу</typeparam>
/// <param name="header">Модель самой таблицы</param>
/// <param name="rowHeaded"></param>
public void AddTable<T>(PdfTable<T> header, bool rowHeaded = false);
/// <summary>
/// Создаёт табличку, наподобие той, что с T, но проще
/// </summary>
/// <param name="tableData"></param>
public void AddSimpleTable(PDFSimpleTable tableData);
/// <summary>
/// Создаёт круговую диаграмму.
/// </summary>
/// <param name="pieChart">Модель для круговой диаграммы</param>
public void AddChart(PdfPieChartModel pieChart);
/// <summary>
/// Добавляем на лист изображение. Можно по пути, можно по потоку, можно по Base64 строке
/// </summary>
/// <param name="image">Модель одного изображения</param>
public void AddImage(PdfImage image);
/// <summary>
/// Метод сохранения созданного PDF документа
/// </summary>
/// <returns>Поток MemoryStream с документом</returns>
public MemoryStream SavePdf();
/// <summary>
/// Метод сохранения созданного PDF документа в файл
/// </summary>
/// <param name="filename">Имя файла и путь до него. Проверки на расширение нет</param>
public void SavePdf(string filename);
}
}

View File

@ -0,0 +1,20 @@
using PIHelperSh.PdfCreator.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Interfaces
{
/// <summary>
/// Аналогично word этот интерфейс нужен для работы списков.
/// </summary>
public interface IPdfElement
{
/// <summary>
/// //Отступ после(может быть и у списков и у параграфов)
/// </summary>
PdfMargin MarginAfter { get; }
}
}

View File

@ -0,0 +1,75 @@
namespace PIHelperSh.PdfCreator.Models.ImageModels
{
/// <summary>
/// Удобный метод для хранения изображений (для создания Pdf документа можно использовать либо путь до фото, либо поток с данными о нём)
/// </summary>
public class MiraDocImage
{
/// <summary>
/// Путь до фото
/// </summary>
public string? Path { get; set; } = null;
/// <summary>
/// Поток фото
/// </summary>
public Stream? Source
{
get => _source;
set
{
_source = value;
_b64Source = null;
}
}
private Stream? _source;
private string? _b64Source = null;
/// <summary>
/// Создание изображения из пути
/// </summary>
/// <param name="path">Путь</param>
/// <returns></returns>
public static MiraDocImage CreateFromPath(string path) => new() { Path = path };
/// <summary>
/// Создания изображения из строки base64
/// </summary>
/// <param name="base64">изображение в формате base64</param>
/// <returns></returns>
public static MiraDocImage CreateFromBase64(string base64) => new() { _b64Source = $"base64:{base64}" };
/// <summary>
/// Создание изображения из потока
/// </summary>
/// <param name="stream">Поток</param>
/// <returns></returns>
public static MiraDocImage CreateFromStream(Stream stream) => new() { Source = stream };
/// <summary>
/// </summary>
/// <returns></returns>
/// <exception cref="ArgumentNullException"></exception>
public string GetImageForMiraDoc()
{
if (!string.IsNullOrEmpty(Path)) return Path;
if (_b64Source != null) return _b64Source;
if (_source == null) throw new ArgumentNullException("Для изображения не задан ни путь, ни поток. Необходимо заполнить как минимум одно из этого, для работы");
_b64Source = GetImageAsStringBase64(_source);
return _b64Source;
}
private string GetImageAsStringBase64(Stream stream)
{
//Вот это вот, в общем-то, ещё один жуткий костыль. Дело в том, что MigraDoc не поддерживает создание изображений из потоков. Только из путей. Но всё же, способ есть
//Вот он собственно. И да, фактически это жуткая система, записывающая картинку в строку. Увы, по другому не получится
stream.Position = 0;
int count = (int)stream.Length;
byte[] data = new byte[count];
stream.Read(data, 0, count);
return $"base64:{Convert.ToBase64String(data)}";
}
}
}

View File

@ -0,0 +1,41 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.ImageModels
{
/// <summary>
/// Информация о изображении
/// </summary>
public class PdfImage : IPdfElement
{
/// <summary>
/// Путь до изображения
/// </summary>
public MiraDocImage Image { get; set; } = null!;
/// <summary>
/// Ширина изображения
/// </summary>
public int? Width { get; set; } = null;
/// <summary>
/// Высота изображения
/// </summary>
public int? Height { get; set; } = null;
/// <summary>
/// Выравнивание текста внутри параграфа (по умолчанию - по левой строне)
/// </summary>
public PdfAlignmentType ImageAlignment { get; set; } = PdfAlignmentType.Left;
/// <summary>
/// Отступ после параграфа (по умолчанию средний)
/// </summary>
public PdfMargin MarginAfter { get; set; } = PdfMargin.Medium;
}
}

View File

@ -0,0 +1,39 @@
using System.Drawing;
namespace PIHelperSh.PdfCreator.Models.PieChartModel
{
/// <summary>
/// Элемент данных. Одно значение для диаграммы.
/// </summary>
public class PdfPieChartData
{
/// <summary>
/// Название варианта
/// </summary>
public string DisplayName { get; set; } = null!;
/// <summary>
/// Значение варианта(по ним строят диаграмму)
/// </summary>
public double Value { get; set; }
/// <summary>
/// Цвет области на диаграме. При null будет использоватсся выдача цветов по умолчанию)
/// </summary>
public Color? Color { get; set; } = null;
/// <summary>
/// </summary>
public PdfPieChartData() { }
/// <summary>
/// </summary>
/// <param name="displayName"></param>
/// <param name="value"></param>
public PdfPieChartData(string displayName, double value)
{
DisplayName = displayName;
Value = value;
}
}
}

View File

@ -0,0 +1,50 @@
using PIHelperSh.PdfCreator.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.PieChartModel
{
/// <summary>
/// Модель круговой диаграммы
/// </summary>
public class PdfPieChartModel
{
/// <summary>
/// Ширина диаграммы в см по умолчанию на всю ширину
/// </summary>
public double Width { get; set; } = 16;
/// <summary>
/// Высота диаграммы в см
/// </summary>
public double Height { get; set; } = 12;
/// <summary>
/// Заголовок диаграммы. Будет отображён над ней
/// </summary>
public string ChartName { get; set; } = null!;
/// <summary>
/// Стиль заголовка
/// </summary>
public PdfStyleType HeaderStyle { get; set; } = PdfStyleType.Bold;
/// <summary>
/// Выравнивание заголовка по горизонтали
/// </summary>
public PdfAlignmentType HeaderAlignment { get; set; } = PdfAlignmentType.Center;
/// <summary>
/// Вариант расположения легенды
/// </summary>
public PdfLegendPosition LegendPosition { get; set; } = PdfLegendPosition.Bottom;
/// <summary>
/// Набор данных для создания диаграммы
/// </summary>
public List<PdfPieChartData> DataSet { get; set; } = new();
}
}

View File

@ -0,0 +1,51 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Простая табличка в PDF
/// </summary>
public class PDFSimpleTable
{
/// <summary>
/// Заголовок
/// </summary>
public List<IPdfColumnItem>? Header { get; set; } = null;
/// <summary>
/// Стиль заголовка (по умолчанию - жирный)
/// </summary>
public PdfStyleType HeaderStyle { get; set; } = PdfStyleType.Bold;
/// <summary>
/// Выравнивание текста внутри заголовка (по умолчанию - по центру)
/// </summary>
public PdfAlignmentType HeaderHorizontalAlignment { get; set; } = PdfAlignmentType.Center;
/// <summary>
/// Набор строк
/// </summary>
public List<PDFSimpleTableRow> Rows { get; set; } = new();
/// <summary>
/// Базовый стиль строк
/// </summary>
public PdfStyleType RowStyle = PdfStyleType.Basic;
/// <summary>
/// Базовое выравнивание элементов сторок
/// </summary>
public PdfAlignmentType RowHorizontalAlignment = PdfAlignmentType.Rigth;
/// <summary>
/// Отступ после таблицы
/// </summary>
public PdfMargin MarginAfter = PdfMargin.None;
/// <summary>
/// Меняем ли ориентацию страницы на альбомную
/// </summary>
public bool ChangePageOrientation = false;
}
}

View File

@ -0,0 +1,30 @@
using PIHelperSh.PdfCreator.Enums;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Строка простой таблицы
/// </summary>
public class PDFSimpleTableRow
{
/// <summary>
/// Элемменты данной стоки
/// </summary>
public List<string> Items = new List<string>();
/// <summary>
/// Стиль элементов строки, если он отличается от стиля строк, определённого в таблице
/// </summary>
public PdfStyleType? Style = null;
/// <summary>
/// Выравнивание элементов строки, если оно отличается от выравнивания, определённого в таблице
/// </summary>
public PdfAlignmentType? Alignment = null;
}
}

View File

@ -0,0 +1,42 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Таблица для вставки на лист
/// </summary>
/// <typeparam name="T"></typeparam>
public class PdfTable<T>
{
/// <summary>
/// Шапка таблицы(2 строки/2 столбца)
/// </summary>
public List<IPdfColumnItem>? Header { get; set; } = null;
/// <summary>
/// Стиль заголовка (по умолчанию - жирный)
/// </summary>
public PdfStyleType HeaderStyle { get; set; } = PdfStyleType.Bold;
/// <summary>
/// Выравнивание текста внутри заголовка (по умолчанию - по центру)
/// </summary>
public PdfAlignmentType HeaderHorizontalAlignment { get; set; } = PdfAlignmentType.Center;
/// <summary>
/// Список объектов, информация о которых будет в таблице.
/// </summary>
public List<T> Records { get; set; } = new();
/// <summary>
/// Стиль объектов в таблице (по умолчанию - базовый)
/// </summary>
public PdfStyleType RecordStyle { get; set; } = PdfStyleType.Basic;
/// <summary>
/// Выравнивание текста объектов в таблице (по умолчанию - по левой строне)
/// </summary>
public PdfAlignmentType RecordHorizontalAlignment { get; set; } = PdfAlignmentType.Left;
}
}

View File

@ -0,0 +1,25 @@
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Столбец(строка) таблицы, определяющий дальнейшее содержимое
/// </summary>
public class PdfTableColumn : IPdfColumnItem
{
/// <summary>
/// Заголовок
/// </summary>
public string? Title { get; set; }
/// <summary>
/// Размер (Ширина столбца/Высота строки)
/// </summary>
public float? Size { get; set; }
/// <summary>
/// Название свойства, которое будет заполнять столбец
/// </summary>
public string PropertyName { get; set; } = null!;
}
}

View File

@ -0,0 +1,25 @@
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator.Models.TableModels
{
/// <summary>
/// Группа столбцов(строк) таблицы, определяющий дальнейшее содержимое
/// </summary>
public class PdfTableColumnGroup : IPdfColumnItem
{
/// <summary>
/// Заголовок
/// </summary>
public string? Title { get; set; }
/// <summary>
/// Количество элементов в группе
/// </summary>
public float? Size => InnerColumns.Sum(x => x.Size);
/// <summary>
/// Столбцы/строки в группе
/// </summary>
public List<PdfTableColumn> InnerColumns { get; set; } = new();
}
}

View File

@ -0,0 +1,26 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.TextModels
{
/// <summary>
/// Список элементов в PDF
/// </summary>
public class PdfList : IPdfElement
{
/// <summary>
/// Элементы списка (параграфы или иные спсики)
/// </summary>
public List<IPdfElement> Content { get; set; } = new();
/// <summary>
/// Отступ после списка (по умолчанию - средний)
/// </summary>
public PdfMargin MarginAfter { get; set; } = PdfMargin.Medium;
}
}

View File

@ -0,0 +1,36 @@
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Interfaces;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace PIHelperSh.PdfCreator.Models.TextModels
{
/// <summary>
/// Параграф в PDF
/// </summary>
public class PdfParagraph : IPdfElement
{
/// <summary>
/// Текст параграфа
/// </summary>
public string Text { get; set; } = string.Empty;
/// <summary>
/// Стиль параграфа (по умолчанию - базовый)
/// </summary>
public PdfStyleType Style { get; set; } = PdfStyleType.Basic;
/// <summary>
/// Выравнивание текста внутри параграфа (по умолчанию - по левой строне)
/// </summary>
public PdfAlignmentType ParagraphAlignment { get; set; } = PdfAlignmentType.Left;
/// <summary>
/// Отступ после параграфа (по умолчанию средний)
/// </summary>
public PdfMargin MarginAfter { get; set; } = PdfMargin.Medium;
}
}

View File

@ -0,0 +1,38 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>net8.0</TargetFramework>
<ImplicitUsings>enable</ImplicitUsings>
<Nullable>enable</Nullable>
<GeneratePackageOnBuild>True</GeneratePackageOnBuild>
<Title>Библиотека PDF</Title>
<Authors>MaximK</Authors>
<Description>Небольшая надстройка для более удобной работы с PDF</Description>
<PackageReadmeFile>README.md</PackageReadmeFile>
<RepositoryUrl>https://github.com/KuzarinM/PIHelperSh/tree/master/PIHelperSh.PdfCreater</RepositoryUrl>
<GenerateDocumentationFile>True</GenerateDocumentationFile>
<PackageLicenseFile>LICENSE</PackageLicenseFile>
<Version>1.1.1</Version>
<PackageRequireLicenseAcceptance>True</PackageRequireLicenseAcceptance>
</PropertyGroup>
<ItemGroup>
<None Include="..\..\LICENSE">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
<ItemGroup>
<PackageReference Include="PdfSharp.MigraDoc.Standard" Version="1.51.15" />
<PackageReference Include="PIHelperSh.Core" Version="1.0.1" />
</ItemGroup>
<ItemGroup>
<None Update="README.md">
<Pack>True</Pack>
<PackagePath>\</PackagePath>
</None>
</ItemGroup>
</Project>

View File

@ -0,0 +1,550 @@
using MigraDoc.DocumentObjectModel;
using System.Text;
using MigraDoc.Rendering;
using MigraDoc.DocumentObjectModel.Tables;
using System.Reflection;
using MigraDoc.DocumentObjectModel.Shapes.Charts;
using PIHelperSh.Core.Extensions;
using PIHelperSh.PdfCreator.Enums;
using PIHelperSh.PdfCreator.Models.TableModels;
using PIHelperSh.PdfCreator.Models.TextModels;
using PIHelperSh.PdfCreator.Models.ImageModels;
using PIHelperSh.PdfCreator.Models.PieChartModel;
using PIHelperSh.PdfCreator.Interfaces;
namespace PIHelperSh.PdfCreator
{
/// <summary>
/// </summary>
public class PdfCreator : IPdfCreator
{
private Document? _document;
private Section? _section;
private readonly Unit _borderWidth = 0.5;
/// <summary>
/// </summary>
public PdfCreator()
{
_document = new Document();
DefineStyles(_document);
Encoding.RegisterProvider(CodePagesEncodingProvider.Instance);
_section = _document.AddSection();
}
#region Внутренние методы
private static string GetListLavel(int level)
{
try
{
return level.CreateEnumFromValue<PdfStyleType>().GetValue<string>();
}
catch (Exception)
{
return PdfStyleType.Basic.GetValue<string>();
}
}
private void ConfigurateChartLegend(Chart chart, PdfLegendPosition position)
{
switch (position)
{
case PdfLegendPosition.Bottom:
chart.FooterArea.AddLegend();
break;
case PdfLegendPosition.Right:
chart.RightArea.AddLegend();
break;
case PdfLegendPosition.Left:
chart.LeftArea.AddLegend();
break;
case PdfLegendPosition.Top:
chart.TopArea.AddLegend();
break;
}
}
private static void DefineStyles(Document document)
{
#region Базовый стиль
var style = document.Styles["Normal"];
style.Font.Name = "Times New Roman";
style.Font.Size = 14;
#endregion
#region Стиль заголовка
style = document.Styles.AddStyle("NormalTitle", "Normal");
style.Font.Bold = true;
style.Font.Size = 18;
#endregion
#region Стиль жирный
style = document.Styles.AddStyle("NormalBold", "Normal");
style.Font.Bold = true;
#endregion
#region Маркированный список уровень 1
style = document.AddStyle("BulletList", "Normal");
style.ParagraphFormat.LeftIndent = "1.5cm";
style.ParagraphFormat.ListInfo = new ListInfo
{
ContinuePreviousList = true,//Продолжать список, который уже был ранее
ListType = ListType.BulletList1//Маркер
};
style.ParagraphFormat.TabStops.ClearAll();
style.ParagraphFormat.TabStops.AddTabStop(Unit.FromCentimeter(1.5), TabAlignment.Left);
style.ParagraphFormat.FirstLineIndent = "-0.5cm";
#endregion
#region Маркированный список уровень 2
style = document.AddStyle("BulletList2", "BulletList");
style.ParagraphFormat.LeftIndent = "3.0cm";
style.ParagraphFormat.ListInfo.ListType = ListType.BulletList2;
style.ParagraphFormat.TabStops.ClearAll();
style.ParagraphFormat.TabStops.AddTabStop(Unit.FromCentimeter(3.0), TabAlignment.Left);
#endregion
#region Маркированный список уровень 3
style = document.AddStyle("BulletList3", "BulletList");
style.ParagraphFormat.LeftIndent = "4.5cm";
style.ParagraphFormat.ListInfo.ListType = ListType.BulletList3;
style.ParagraphFormat.TabStops.ClearAll();
style.ParagraphFormat.TabStops.AddTabStop(Unit.FromCentimeter(4.5), TabAlignment.Left);
#endregion
#region Мелкий шрифт
style = document.Styles.AddStyle("NormalSmall", "Normal");
style.Font.Size = 11;
#endregion
}
private Paragraph? MakeParagraph(PdfParagraph pdfParagraph)
{
if (_section == null)
return null;
var paragraph = _section.AddParagraph(pdfParagraph.Text);
paragraph.Format.Alignment = pdfParagraph.ParagraphAlignment.GetValue<ParagraphAlignment>();
//Т.к стили могут назначаться по иному, тут будет вот так вот
return paragraph;
}
private Paragraph? MakeList(PdfList pdfList, int level)
{
Paragraph? last = null;
foreach (IPdfElement element in pdfList.Content)
{
if (element is PdfParagraph par)
{
var paragraph = MakeParagraph(par);
if (paragraph == null)
continue;
paragraph.Format.SpaceAfter = "0.3cm";
paragraph.Style = GetListLavel(level);
last = paragraph;
}
else if (element is PdfList ls)
{
last = MakeList(ls, level + 1);
}
}
return last;
}
private void ConfigurateParagraph(Paragraph paragraph, PdfParagraph properties)
{
paragraph.Format.Alignment = properties.ParagraphAlignment.GetValue<ParagraphAlignment>(); ;
if (properties.MarginAfter != PdfMargin.None) paragraph.Format.SpaceAfter = properties.MarginAfter.GetValue<string>();
paragraph.Style = properties.Style.GetValue<string>();
}
private void ConfigurateCell(Cell cell, string text, PdfAlignmentType alignment, PdfStyleType style, int? rightMerge = null, int? downMerge = null, bool dcw = false)
{
if (rightMerge.HasValue) cell.MergeRight = rightMerge.Value;
if (downMerge.HasValue)
{
cell.MergeDown = downMerge.Value;
cell.VerticalAlignment = VerticalAlignment.Center;
}
Paragraph paragraph = cell.AddParagraph(text);
ConfigurateParagraph(paragraph, new()
{
ParagraphAlignment = alignment,
Style = style,
MarginAfter = PdfMargin.None
});
cell.Borders.Left.Width = _borderWidth;
cell.Borders.Right.Width = _borderWidth;
cell.Borders.Top.Width = _borderWidth;
cell.Borders.Bottom.Width = _borderWidth;
if (dcw)
{
float columnNeeds = text.Length / 3.8f;
if (cell.Column.Width.Centimeter < columnNeeds)
cell.Column.Width = Unit.FromCentimeter(columnNeeds);
}
}
private Func<object?, object?> GetGetter(FieldInfo[] fields, PropertyInfo[] props, string Name)
{
var a = fields.FirstOrDefault(x => x.Name == Name);
if (a != null) return a.GetValue;
var b = props.FirstOrDefault(x => x.Name == Name);
if (b != null) return b.GetValue;
throw new KeyNotFoundException($"У объекта не найдено поле/свойство {Name}");
}
private List<Func<object?, object?>> MakeTableHeader<T>(Table table, PdfTable<T> header)
{
foreach (var item in header.Header!)
{
if (item is PdfTableColumnGroup group)
{
group.InnerColumns.ForEach(x => table.AddColumn(Unit.FromCentimeter(x.Size!.Value)));
}
else
{
table.AddColumn(Unit.FromCentimeter(item.Size!.Value));
}
}
Row upRow = table.AddRow();
Row downRow = table.AddRow();
upRow.HeadingFormat = downRow.HeadingFormat = true;
upRow.Format.Font.Bold = downRow.Format.Font.Bold = true;
int upColumn = 0;
int downColumn = 0;
Type eType = typeof(T);
var fields = eType.GetFields();
var prop = eType.GetProperties();
List<Func<object?, object?>> objectFields = new();
foreach (var item in header.Header)
{
if (item is PdfTableColumnGroup group)
{
ConfigurateCell(upRow.Cells[upColumn], group.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, rightMerge: group.InnerColumns.Count - 1);
upColumn += group.InnerColumns.Count;
foreach (var ic in group.InnerColumns)
{
ConfigurateCell(downRow.Cells[downColumn], ic.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle);
objectFields.Add(GetGetter(fields, prop, ic.PropertyName));
downColumn++;
}
}
else
{
ConfigurateCell(upRow.Cells[upColumn], item.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, downMerge: 1);
ConfigurateCell(downRow.Cells[downColumn], item.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle);
objectFields.Add(GetGetter(fields, prop, ((PdfTableColumn)item).PropertyName));
upColumn++;
downColumn++;
}
}
return objectFields;
}
private void MakeSimpleTableHeader(Table table, PDFSimpleTable header)
{
foreach (var item in header.Header!)
{
if (item is PdfTableColumnGroup group)
{
group.InnerColumns.ForEach(x => table.AddColumn(Unit.FromCentimeter(x.Size!.Value)));
}
else
{
table.AddColumn(Unit.FromCentimeter(item.Size!.Value));
}
}
Row upRow = table.AddRow();
Row downRow = table.AddRow();
upRow.HeadingFormat = downRow.HeadingFormat = true;
upRow.Format.Font.Bold = downRow.Format.Font.Bold = true;
int upColumn = 0;
int downColumn = 0;
foreach (var item in header.Header)
{
if (item is PdfTableColumnGroup group)
{
ConfigurateCell(upRow.Cells[upColumn], group.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, rightMerge: group.InnerColumns.Count - 1);
upColumn += group.InnerColumns.Count;
foreach (var ic in group.InnerColumns)
{
ConfigurateCell(downRow.Cells[downColumn], ic.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle);
downColumn++;
}
}
else
{
ConfigurateCell(upRow.Cells[upColumn], item.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, downMerge: 1);
ConfigurateCell(downRow.Cells[downColumn], item.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle);
upColumn++;
downColumn++;
}
}
}
private void MakeTableWithHederInRow<T>(Table table, PdfTable<T> header)
{
for (int i = 0; i < header.Records.Count + 2; i++)
{
_ = table.AddColumn("0.5cm");
}
Type tp = typeof(T);
var fields = tp.GetFields();
var props = tp.GetProperties();
foreach (var item in header.Header!)
{
Row? row = table.AddRow();
if (item is PdfTableColumnGroup group)
{
ConfigurateCell(row.Cells[0], group.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, downMerge: group.InnerColumns.Count - 1, dcw: true);
foreach (var innerParam in group.InnerColumns)
{
row ??= table.AddRow();//Первая(а точнее вторая) часть костыля с первой строкой
row.HeightRule = RowHeightRule.Exactly;
row.Height = Unit.FromCentimeter(innerParam.Size!.Value);//Устанавливаем высоту строки. Так просили...
ConfigurateCell(row.Cells[1], innerParam.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, dcw: true);
Func<object?, object?> getter = GetGetter(fields, props, innerParam.PropertyName);
for (int i = 0; i < header.Records.Count; i++)
{
ConfigurateCell(row.Cells[i + 2], getter(header.Records[i])!.ToString()!, header.RecordHorizontalAlignment, header.RecordStyle, dcw: true);
}
row = null; //Это небольшой костыль, который позволяет не создавать новую строчку в самый первый раз(т.к. она же есть)
}
}
else if (item is PdfTableColumn column)
{
row.HeightRule = RowHeightRule.Exactly;
row.Height = Unit.FromCentimeter(column.Size!.Value);
ConfigurateCell(row.Cells[0], column.Title!, header.HeaderHorizontalAlignment, header.HeaderStyle, rightMerge: 1, dcw: true);
Func<object?, object?> getter = GetGetter(fields, props, column.PropertyName);
for (int i = 0; i < header.Records.Count; i++)
{
ConfigurateCell(row.Cells[i + 2], getter(header.Records[i])!.ToString()!, header.RecordHorizontalAlignment, header.RecordStyle, dcw: true);
}
}
}
}
private void ConfiguratePieChart(Chart chart, PdfPieChartModel pieChart)
{
chart.Width = Unit.FromCentimeter(pieChart.Width);
chart.Height = Unit.FromCentimeter(pieChart.Height);
Paragraph p = chart.HeaderArea.AddParagraph(pieChart.ChartName);
ConfigurateParagraph(p, new()
{
Style = pieChart.HeaderStyle,
ParagraphAlignment = pieChart.HeaderAlignment,
MarginAfter = PdfMargin.None
});
chart.LineFormat.Visible = true;
ConfigurateChartLegend(chart, pieChart.LegendPosition);
chart.DataLabel.Position = DataLabelPosition.OutsideEnd;
}
#endregion
/// <summary>
/// Создаём параграф
/// </summary>
/// <param name="pdfParagraph">Модель параграфа, который вставляем</param>
public void AddParagraph(PdfParagraph pdfParagraph)
{
var paragraph = MakeParagraph(pdfParagraph);
if (paragraph == null)
return;
ConfigurateParagraph(paragraph, pdfParagraph);
}
/// <summary>
/// Создаём маркированный список
/// </summary>
/// <param name="pdfList">Модель списка(может быть многоуровневым). Max - 3 уровня</param>
public void AddList(PdfList pdfList)
{
if (_section == null)
{
return;
}
var lastP = MakeList(pdfList, 1);
if (lastP != null) lastP.Format.SpaceAfter = pdfList.MarginAfter.GetValue<string>();
}
/// <summary>
/// Создаёт таблицу, с шапкой из 2-х строк(с группировками)
/// </summary>
/// <typeparam name="T">Тип DTO, из которой берутся данные в таблицу</typeparam>
/// <param name="header">Модель самой таблицы</param>
/// <param name="rowHeaded"></param>
public void AddTable<T>(PdfTable<T> header, bool rowHeaded = false)
{
if (_document == null)
{
return;
}
if (rowHeaded)
{
_section!.PageSetup.Orientation = Orientation.Landscape;
_section.PageSetup.LeftMargin = 10;
MakeTableWithHederInRow(_document.LastSection.AddTable(), header);
return;
}
var table = _document.LastSection.AddTable();
if (rowHeaded)
{
MakeTableWithHederInRow(table, header);
return;
}
var maper = MakeTableHeader(table, header);
foreach (var item in header.Records)
{
var row = table.AddRow();
for (int i = 0; i < maper.Count; i++)
{
ConfigurateCell(row.Cells[i], maper[i](item)?.ToString()!, header.RecordHorizontalAlignment, header.RecordStyle);
}
}
}
/// <summary>
/// Создаёт табличку, наподобие той, что с T, но проще
/// </summary>
/// <param name="tableData"></param>
public void AddSimpleTable(PDFSimpleTable tableData)
{
if (_document == null)
{
return;
}
if (tableData.ChangePageOrientation)
{
_section!.PageSetup.Orientation = Orientation.Landscape;
_section.PageSetup.LeftMargin = 10;
_section.PageSetup.BottomMargin = 5;
}
var table = _document.LastSection.AddTable();
MakeSimpleTableHeader(table, tableData);
foreach (var item in tableData.Rows)
{
var row = table.AddRow();
for (int i = 0; i < item.Items.Count; i++)
{
ConfigurateCell(row.Cells[i], item.Items[i], item.Alignment ?? tableData.RowHorizontalAlignment, item.Style ?? tableData.RowStyle);
}
}
if (tableData.MarginAfter != PdfMargin.None) table.Format.SpaceAfter = tableData.MarginAfter.GetValue<string>();
}
/// <summary>
/// Создаёт круговую диаграмму.
/// </summary>
/// <param name="pieChart">Модель для круговой диаграммы</param>
public void AddChart(PdfPieChartModel pieChart)
{
if (_document == null)
{
return;
}
Chart chart = new Chart(ChartType.Pie2D);
ConfiguratePieChart(chart, pieChart);
Series series = chart.SeriesCollection.AddSeries();
XSeries xseries = chart.XValues.AddXSeries();
foreach (var item in pieChart.DataSet)
{
var p = series.Add(item.Value);
xseries.Add(item.DisplayName);
if (item.Color.HasValue)
{
p.FillFormat.Color = new Color((uint)item.Color.Value.ToArgb());
}
}
_section?.Add(chart);
}
/// <summary>
/// Добавляем на лист изображение. Можно по пути, можно по потоку, можно по Base64 строке
/// </summary>
/// <param name="img">Модель одного изображения</param>
public void AddImage(PdfImage img)
{
if (_section == null) return;
var paragraph = _section.AddParagraph();//Это некоторый костыли. у изображения настроить выравнивание - это очень непросто. А вот так вот - можно
var image = paragraph.AddImage(img.Image.GetImageForMiraDoc());
if (img.Width.HasValue) image.Width = img.Width.Value;
if (img.Height.HasValue) image.Width = img.Height.Value;
paragraph.Format.Alignment = img.ImageAlignment.GetValue<ParagraphAlignment>();
paragraph.Format.SpaceAfter = img.MarginAfter.GetValue<string>();
}
/// <summary>
/// Метод сохранения созданного PDF документа
/// </summary>
/// <returns>Поток MemoryStream с документом</returns>
public MemoryStream SavePdf()
{
var renderer = new PdfDocumentRenderer(true)
{
Document = _document
};
renderer.RenderDocument();
MemoryStream file = new MemoryStream();
renderer.PdfDocument.Save(file, false);
file.Seek(0, SeekOrigin.Begin);
return file;
}
/// <summary>
/// Метод сохранения созданного PDF документа в файл
/// </summary>
/// <param name="filename">Имя файла и путь до него. Проверки на расширение нет</param>
public void SavePdf(string filename)
{
using Stream streamToWriteTo = File.Open(filename, FileMode.Create);
MemoryStream ms = SavePdf();
ms.Position = 0;
ms.CopyTo(streamToWriteTo);
}
}
}

View File

@ -0,0 +1,87 @@
# Библиотека PDF
Данная библиотека является надстройкой над MigraDoc и упрощает работу с PDF в контексте ряда операций.
Основным интерфейсом системы является **IPdfCreator**. На данный момент имеется единственная реализация - **PdfCreator**
## Основные методы IPdfCreator
- **AddParagraph(PdfParagraph paragraph)** данный метод предназначен для добавления на лист PDF параграфа текста. Принимает стандартную модель параграфа (о ней позже)
- **AddList(PdfList List)** данный метод предназначен для добавления на лист PDF маркированного списка. Принимает стандартныую модель списка ( о ней позже). `Поддерживается не более 3-х уровней!`
- **AddChart(PdfPieChartModel pieChart)** данный метод предназначен для добавления на лист PDF круговой диаграммы. Принимает модель диаграммы (о ней позже)
- **AddImage(PdfImage image)** данный метод предназначен для добавления на лист изображения. Принимает стандартную модель (о ней позже).
- **MemoryStream SavePdf()** данный метод сохраняет PDF документ и возвращает поток данных файла.
- **SavePdf(string filename)** данный метод сохраняет PDF документ на диске по пути из filename
- **AddTable\<T>(PdfTable\<T> header, bool rowHeaded = false)** данный метод позволяет добавлять на лист таблицу с шапкой и значениями. **rowHeaded** позволяет описать будет ли шапка в строках или в столбцах.
- **AddSimpleTable(PDFSimpleTable tableData)** данный метод позволяет добавть на лист простую табличку, строки в которой задаются пользователем самостоятельно.
## Основные модели
### PdfParagraph
Модель имеет следующие поля:
- **PdfMargin MarginAfter** - Отступ после параграфа. По умолчанию значение *Medium*
- **PdfAlignmentType ParagraphAlignment** - Выравнивание параграфа. По умолчанию значение *Left*
- **PdfStyleType Style** - Стиль параграфа. По сути объединение шрифта, жирности и отступов. По умолчанию - Basic. На данный момент имеются следующие (В дальнейшем планируется добавить возможность добавлять свои стили):
- *Basic*: Times New Roman 14
- *Title*: Times New Roman 18 жирный
- *Bold*: Times New Roman 14 жирный
- *ListLevel1*: Times New Roman 14 отступ 1.5cm
- *ListLevel2*: Times New Roman 14 отступ 3.0cm
- *ListLevel3*: Times New Roman 14 отступ 4.5cm
- *Small*: Times New Roman 11
- **string Text** - Текст параграфа
### PdfList
Данная модель позволяет создать многоуровневый список.
- **PdfMargin MarginAfter** - подробно описан ранее
- **List\<IPdfElement> Content** - элементы списка. Это могут быть как параграфы, так и другие списки
### PdfPieChartModel
Данная модель позволяет добавлять диаграмму на лист
- **double Width** - Ширина диаграммы на листе. По умолчанию 16.
- **double Height** - Высота диаграммы на листе. По умолчанию 12.
- **string ChartName** - Надпись над диаграммой. По сути её имя.
- **PdfStyleType HeaderStyle** - Стиль заголовка диаграммы.
- **PdfAlignmentType HeaderAlignment** - Выравнивание заголовка диаграммы.
- **PdfLegendPosition LegendPosition** - Местоположение легенды диаграммы.
- **List\<PdfPieChartData> DataSet** - Набор данных для диаграммы
### PdfPieChartData:
- **string DisplayName** - Имя для значения
- **double Value** - Значение
- **Color? Color** - Если нужно, цвет для значения
### PdfImage
Моделька для вывода изображения на лист. Имеет поля:
- **MigradocImage Image** - само изображение (обёртка)
- **int? Width** - Ширина изображения (если null - по размеру изображения)
- **int? Height** - Высота сообщения (если null - по соотношению сторон к длине)
- **PdfAlignmentType ImageAlignment** - Выравнивание изображения (по умолчанию - Left)
- **PdfMargin MarginAfter** - Отступ после. По умолчанию Medium
### MigradocImage
Обёртка, предназначенная для вывода изображений в MigraDock
Объект создаётся только с помощью статических методов:
- **CreateFromPath(string path)** - создаём из картинки, расположенной на компьютере по пути
- **MigradocImage CreateFromBase64(string base64)** - создаём из base64 строки, в которой зашифрована картинка
- **MigradocImage CreateFromStream(Stream stream)** - создаём из потока картинки
### PdfTable
Сейчас будет немного сложно. Данная модель предназначена для создания таблицы из коллекции объектов определённого типа, при этом шапка таблицы поддерживает 2 уровня(то есть, ряд столбцов могут быть сгруппированы общим заголовком). Поля:
- **List\<IPdfColumnItem>? Header** - модельки заголовков таблицы
- **PdfStyleType HeaderStyle** - Стиль заголовка таблицы. По умолчанию - Bold
- **PdfAlignmentType HeaderHorisontalAlignment** - Выравнивание текста внутри заголовков. По умолчанию - по центру
- **PdfStyleType RecordStyle** - Стиль текста записей строк таблицы. По умолчанию - Basic
- **PdfAlignmentType RecordHorisontalAlignment** - Выравнивае записей строк таблицы внутри ячейки. По умолчанию - Left
- **List\<T> Records** - список записей, из которых формируем таблицу
### PdfTableColumn
Столбец таблицы:
- **string? Title** - заголовок
- **float? Size** - ширина/высота ячейки
- **string PropertyName** - Имя свойства, которое будет заполнять данный столбец/строку
### PdfTableColumnGroup
Сгруппированные столбцы/строки заголовка
Поля:
- **string? Title** - заголовок этой группы
- **List\<PdfTableColumn> InnerColumns** - внутренние столбцы/строки
### PDFSimpleTable
- **List<IPdfColumnItem>? Header** - Заголовок таблицы. Уже был ранее описан
- **PdfStyleType HeaderStyle** - Стиль заголовка таблицы. По умолчанию - Bold
- **PdfAlignmentType HeaderHorisontalAlignment** - Выравнивание текста внутри заголовков. По умолчанию - по центру
- **List<PDFSimpleTableRow> Rows** - непосредственно строки таблицы
- **PdfStyleType RowStyle** - ситль текста таблицы по умолчанию. Может быть переопределён в каждой строке
- **PdfAlignmentType RowHorisontalAlignment** - выравнивание текста строки таблицы по умолчанию. Может быть переопределён
- **PdfMargin MarginAfter** - Отступ после. По умолчанию None
- **bool ChangePageOrientation** - флаг, меняем ли мы ориентацию листа на альбомную
### PDFSimpleTableRow
- **List<string>** Items - элементы строки таблицы
- **PdfStyleType? Style** - возможность переопределить стиль данной строки. Если Null - берётся стиль по умолчанию
- **PdfAlignmentType? Alignment** - возможность переопределить выравниваение данной строки. Если Null - берётся стиль по умолчанию

View File

@ -0,0 +1,62 @@
namespace TestCustomComponents.Forms
{
partial class Form2
{
/// <summary>
/// Required designer variable.
/// </summary>
private System.ComponentModel.IContainer components = null;
/// <summary>
/// Clean up any resources being used.
/// </summary>
/// <param name="disposing">true if managed resources should be disposed; otherwise, false.</param>
protected override void Dispose(bool disposing)
{
if (disposing && (components != null))
{
components.Dispose();
}
base.Dispose(disposing);
}
#region Windows Form Designer generated code
/// <summary>
/// Required method for Designer support - do not modify
/// the contents of this method with the code editor.
/// </summary>
private void InitializeComponent()
{
components = new System.ComponentModel.Container();
customPdfTable1 = new Cop.Borovkov.Var3.Components.CustomPdfTable(components);
button1 = new Button();
SuspendLayout();
//
// button1
//
button1.Location = new Point(12, 12);
button1.Name = "button1";
button1.Size = new Size(75, 23);
button1.TabIndex = 0;
button1.Text = "button1";
button1.UseVisualStyleBackColor = true;
button1.Click += button1_Click;
//
// Form2
//
AutoScaleDimensions = new SizeF(7F, 15F);
AutoScaleMode = AutoScaleMode.Font;
ClientSize = new Size(800, 450);
Controls.Add(button1);
Name = "Form2";
Text = "Form2";
ResumeLayout(false);
}
#endregion
private Cop.Borovkov.Var3.Components.CustomPdfTable customPdfTable1;
private Button button1;
}
}

View File

@ -0,0 +1,42 @@
namespace TestCustomComponents.Forms
{
public partial class Form2 : Form
{
public Form2()
{
InitializeComponent();
}
private void button1_Click(object sender, EventArgs e)
{
customPdfTable1.SaveToPdf(new()
{
FilePath = @"F:\test\test.pdf",
Title = "Текст заголовка",
Tables = [new[,]
{
{ "00", "01", "02" },
{ "10", "11", "12" },
{ "20", "21", "22" },
}],
});
customPdfTable1.SaveToPdf(new()
{
FilePath = @"F:\test\test.pdf",
Title = "Текст заголовка",
Tables = [new[,]
{
{ "000", "001", "002" },
{ "010", "011", "012" },
{ "020", "021", "022" },
}, new[,]
{
{ "100", "101", "102" },
{ "110", "111", "112" },
{ "120", "121", "122" },
}],
});
}
}
}

View File

@ -0,0 +1,123 @@
<?xml version="1.0" encoding="utf-8"?>
<root>
<!--
Microsoft ResX Schema
Version 2.0
The primary goals of this format is to allow a simple XML format
that is mostly human readable. The generation and parsing of the
various data types are done through the TypeConverter classes
associated with the data types.
Example:
... ado.net/XML headers & schema ...
<resheader name="resmimetype">text/microsoft-resx</resheader>
<resheader name="version">2.0</resheader>
<resheader name="reader">System.Resources.ResXResourceReader, System.Windows.Forms, ...</resheader>
<resheader name="writer">System.Resources.ResXResourceWriter, System.Windows.Forms, ...</resheader>
<data name="Name1"><value>this is my long string</value><comment>this is a comment</comment></data>
<data name="Color1" type="System.Drawing.Color, System.Drawing">Blue</data>
<data name="Bitmap1" mimetype="application/x-microsoft.net.object.binary.base64">
<value>[base64 mime encoded serialized .NET Framework object]</value>
</data>
<data name="Icon1" type="System.Drawing.Icon, System.Drawing" mimetype="application/x-microsoft.net.object.bytearray.base64">
<value>[base64 mime encoded string representing a byte array form of the .NET Framework object]</value>
<comment>This is a comment</comment>
</data>
There are any number of "resheader" rows that contain simple
name/value pairs.
Each data row contains a name, and value. The row also contains a
type or mimetype. Type corresponds to a .NET class that support
text/value conversion through the TypeConverter architecture.
Classes that don't support this are serialized and stored with the
mimetype set.
The mimetype is used for serialized objects, and tells the
ResXResourceReader how to depersist the object. This is currently not
extensible. For a given mimetype the value must be set accordingly:
Note - application/x-microsoft.net.object.binary.base64 is the format
that the ResXResourceWriter will generate, however the reader can
read any of the formats listed below.
mimetype: application/x-microsoft.net.object.binary.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Binary.BinaryFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.soap.base64
value : The object must be serialized with
: System.Runtime.Serialization.Formatters.Soap.SoapFormatter
: and then encoded with base64 encoding.
mimetype: application/x-microsoft.net.object.bytearray.base64
value : The object must be serialized into a byte array
: using a System.ComponentModel.TypeConverter
: and then encoded with base64 encoding.
-->
<xsd:schema id="root" xmlns="" xmlns:xsd="http://www.w3.org/2001/XMLSchema" xmlns:msdata="urn:schemas-microsoft-com:xml-msdata">
<xsd:import namespace="http://www.w3.org/XML/1998/namespace" />
<xsd:element name="root" msdata:IsDataSet="true">
<xsd:complexType>
<xsd:choice maxOccurs="unbounded">
<xsd:element name="metadata">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" />
</xsd:sequence>
<xsd:attribute name="name" use="required" type="xsd:string" />
<xsd:attribute name="type" type="xsd:string" />
<xsd:attribute name="mimetype" type="xsd:string" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="assembly">
<xsd:complexType>
<xsd:attribute name="alias" type="xsd:string" />
<xsd:attribute name="name" type="xsd:string" />
</xsd:complexType>
</xsd:element>
<xsd:element name="data">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
<xsd:element name="comment" type="xsd:string" minOccurs="0" msdata:Ordinal="2" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" msdata:Ordinal="1" />
<xsd:attribute name="type" type="xsd:string" msdata:Ordinal="3" />
<xsd:attribute name="mimetype" type="xsd:string" msdata:Ordinal="4" />
<xsd:attribute ref="xml:space" />
</xsd:complexType>
</xsd:element>
<xsd:element name="resheader">
<xsd:complexType>
<xsd:sequence>
<xsd:element name="value" type="xsd:string" minOccurs="0" msdata:Ordinal="1" />
</xsd:sequence>
<xsd:attribute name="name" type="xsd:string" use="required" />
</xsd:complexType>
</xsd:element>
</xsd:choice>
</xsd:complexType>
</xsd:element>
</xsd:schema>
<resheader name="resmimetype">
<value>text/microsoft-resx</value>
</resheader>
<resheader name="version">
<value>2.0</value>
</resheader>
<resheader name="reader">
<value>System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<resheader name="writer">
<value>System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089</value>
</resheader>
<metadata name="customPdfTable1.TrayLocation" type="System.Drawing.Point, System.Drawing, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a">
<value>17, 17</value>
</metadata>
</root>

View File

@ -4,7 +4,7 @@
{
public int Id { get; set; }
public string Name { get; set; }
public string Name { get; set; } = string.Empty;
public int Age { get; set; }
}

View File

@ -1,3 +1,5 @@
using TestCustomComponents.Forms;
namespace TestCustomComponents
{
internal static class Program
@ -11,7 +13,7 @@ namespace TestCustomComponents
// To customize application configuration such as set high DPI settings or default font,
// see https://aka.ms/applicationconfiguration.
ApplicationConfiguration.Initialize();
Application.Run(new Form1());
Application.Run(new Form2());
}
}
}