Эта страница может содержать автоматически переведенный текст.
Краткое руководство по созданию PDF-файлов в C#
Узнайте, как создавать файлы PDF, такие как отчеты, счета-фактуры и квитанции, в ваших проектах .NET. Используя C# или VB.NET, легко создавайте PDF-документы, комбинируя структурные элементы. К элементам относятся верхние и нижние колонтитулы, контейнеры, таблицы, абзацы, изображения и т.п. API автоматически разбивает контент на страницы.
Некоторые другие методы создания PDF-файлов описаны в статье Создание PDF-документов в C# и VB.NET.
Мы предлагаем API разметки в качестве бесплатного дополнения к библиотеке Docotic.Pdf. И библиотека, и дополнение Layout доступны на NuGet и на нашем сайте. Получите библиотеку, дополнение и бесплатный лицензионный ключ с ограниченным сроком действия на странице Скачать PDF библиотеку C# .NET.
9.5.17615-dev 9.5.17615-dev14,813 прошло Всего загрузок NuGet 4,924,084
Установка
Установите пакет BitMiracle.Docotic.Pdf.Layout из NuGet. Это самый простой и удобный способ установки дополнения Layout.
В качестве альтернативы вы можете скачать ZIP-архив с бинарными файлами библиотеки на нашем сайте. Чтобы использовать Layout API, добавьте ссылки на следующие DLL из ZIP-пакета:
BitMiracle.Docotic.Pdf.dll
Layout add-on/BitMiracle.Docotic.Pdf.Layout.dll
Подход
Точкой входа является класс
PdfDocumentBuilder. Чтобы начать создание
PDF-файла, вы вызываете метод
Generate этого класса. Методу
требуется делегат типа Action
<Document> в качестве
одного из параметров. Библиотека ожидает, что вы разметите содержимое документа в делегате.
PdfDocumentBuilder.Create().Generate("output.pdf", (Document doc) =>
{
// создайте здесь содержимое документа
});
Получив объект типа Document
, делегат должен определить макет. Полный макет состоит из более
мелких строительных блоков. Страницы, верхние и нижние колонтитулы, контейнеры, текстовые блоки
являются примерами таких блоков.
Многие вызовы методов объединены в цепочку. Порядок вызовов в цепочке важен. Вот пример, показывающий, как настроить содержимое страниц при помощи нескольких связанных вызовов.
PdfDocumentBuilder.Create()
.Generate("output.pdf", doc => doc.Pages(pages =>
{
pages.Content().Padding(24).AlignCenter().Text("Some text");
}));
Intellisense поможет вам исследовать API. Попробуйте API самостоятельно. Я надеюсь, что вы найдете его простым в использовании.
Привет, мир!
Давайте разработаем простое приложение, которое покажет общие строительные блоки в действии. Приложение будет использовать упомянутый выше подход.
Я поместил полный код приложения в наш репозиторий примеров кода Docotic.Pdf.
Код для этого шага находится в классе HelloWorld
.
public void CreatePdf()
{
PdfDocumentBuilder.Create().Generate("hello.pdf", doc => doc.Pages(page =>
{
page.Content().Text("Hello, world!");
}));
}
Кода не так много, но что здесь происходит?
Длинное объяснение короткого кода
Код создает экземпляр конструктора документов и просит его сгенерировать hello.pdf
. Конструктор
делегирует работу по компоновке содержимого документа моему коду.
В коде делегата я прошу документ создать несколько страниц. Теперь документ делегирует работу по размещению содержимого страниц моему коду.
В делегате для страниц я получаю доступ к контейнеру макета для основного содержимого страниц. Я делаю это, вызывая метод Content. Последующий вызов Text добавляет знаменитый фрагмент текста к содержимому страниц.
Теперь пришло время конструктору сгенерировать документ с несколькими страницами в соответствии с предоставленным макетом. Конструктор создает точное количество страниц, необходимое для размещения данных, которые я добавил в контейнер макета для основного контента. Одной страницы достаточно в этом случае? Хорошо.
Приготовления к будущим дополнениям
Я собираюсь добавить в код еще несколько функций. Чтобы было удобнее развивать приложение дальше, я изменил код так:
public void CreatePdf()
{
PdfDocumentBuilder.Create().Generate("hello.pdf", BuildDocument);
}
static void BuildDocument(Document doc)
{
doc.Pages(BuildPages);
}
static void BuildPages(PageLayout pages)
{
pages.Content().Text("Hello, world!");
}
Это может показаться ненужным, но разделение кода на методы, по крайней мере, делает его более читабельным.
Пример кода для этого шага находится в классе HelloWorld2
тестового
приложения.
Связанные ресурсы
Пожалуйста, ознакомьтесь со следующими статьями для получения дополнительной информации о документах, страницах и контейнерах.
Шрифты и цвета
Шрифт по умолчанию хорош, но я покажу, как использовать другой. Основная идея — создать стиль текста с помощью шрифта. Затем, если необходимо, примените к стилю некоторые дополнительные свойства и, наконец, используйте стиль в текстовом фрагменте.
Вы можете использовать шрифт из коллекции шрифтов, установленных в операционной системе, или загрузить шрифт из файла или потока.
Я рекомендую переопределить предопределенные стили, настроив типографику для документа. Но это не обязательно, и вы можете использовать стили текста напрямую.
Пример кода для этого шага находится в классе HelloWorld3
тестового
приложения. Ниже приведены части, которые изменились по сравнению с предыдущим
шагом.
static void BuildDocument(Document doc)
{
doc.Typography(t =>
{
t.Document = doc.TextStyleWithFont(SystemFont.Family("Calibri"));
});
doc.Pages(BuildPages);
}
static void BuildPages(PageLayout pages)
{
pages.Size(PdfPaperSize.A6);
pages.Margin(10);
BuildTextContent(pages.Content());
}
static void BuildTextContent(LayoutContainer content)
{
var colorAccented = new PdfRgbColor(56, 194, 10);
var styleAccented = TextStyle.Parent.FontColor(colorAccented);
content.Text(t =>
{
t.Span("Hello, ").Style(styleAccented);
t.Span("world!").Style(styleAccented.Underline());
});
}
Как видите, я обновил код BuildDocument
и BuildPages
. Я также добавил новый метод с именем
BuildTextContent
.
В BuildDocument
я создаю текстовый стиль из системного шрифта с именем "Calibri". Затем я
установливаю этот стиль текста в качестве стиля текста по умолчанию для документа.
Метод BuildPages
теперь содержит код для установки размера и полей для всех страниц. Кроме того,
метод вызывает BuildTextContent
, передавая в качестве параметра контейнер для основного
содержимого страниц.
Способ, которым я создаю основной контент, теперь другой. Я использую два текстовых фрагмента и применяю к каждому разные стили текста. В результате, в обоих фрагментах используется акцентный цвет, но второй фрагмент также имеет подчеркивание.
Код создает PDF с пользовательским шрифтом и цветом текста. Если ваш результат содержит предупреждение о пробной версии, получите бесплатную ограниченную по времени лицензию на странице библиотеки Docotic.Pdf.
Верхний и нижний колонтитулы
Многие реальные PDF-документы, такие как отчеты или счета-фактуры, содержат верхний и нижний колонтитулы. Позвольте мне показать, как добавить верхний и нижний колонтитулы в PDF-файл.
Пример кода для этого шага находится в классе HelloWorld4
тестового
приложения. Ниже приведены части, которые изменились по сравнению с предыдущим
шагом.
static void BuildPages(PageLayout pages)
{
pages.Size(PdfPaperSize.A6);
pages.Margin(10);
BuildPagesHeader(pages.Header());
BuildPagesFooter(pages.Footer());
BuildTextContent(pages.Content());
}
static void BuildPagesHeader(LayoutContainer header)
{
header.TextStyle(TextStyle.Parent.FontSize(8))
.AlignRight()
.Text(t =>
{
t.Line($"Created by: {Environment.UserName}");
t.Line($"Date: {DateTime.Now}");
});
}
static void BuildPagesFooter(LayoutContainer footer)
{
footer.Height(20)
.Background(new PdfGrayColor(95))
.AlignCenter()
.Text(t => t.CurrentPageNumber());
}
Я изменил метод BuildPages
, чтобы он вызывал BuildPagesHeader
с контейнером заголовка в
качестве параметра. Этот метод также вызывает BuildPagesFooter
, передавая ему контейнер нижнего
колонтитула.
Полученный в результате PDF с верхним и нижним колонтитулом определенно больше похож на профессиональный PDF-документ.
Метод BuildPagesHeader
устанавливает меньший размер шрифта для текста. Для содержимого заголовка
я использую две строки: одну с именем текущего пользователя и вторую с текущей датой. Текст
выравнивается по правому краю.
Обратите внимание, что я не указываю явный размер заголовка. Он будет занимать всю ширину страницы за вычетом левого и правого полей. Высота будет зависеть от высоты текстовых строк.
Нижний колонтитул аналогичен, за исключением того, что его высота указана явно. И к нему применен цвет фона. Интересно то, что для размещения текущего номера страницы в нижнем колонтитуле PDF я могу просто использовать метод CurrentPageNumber. Все расчеты происходят внутри библиотеки.
Изображения
Как говорится, можно сэкономить много слов, используя одну картинку. В коммерческом предложении или квитанции вы, вероятно, будете использовать логотип вашей компании или какое-либо другое важное изображение. В этом примере кода я буду использовать просто красивую картинку.
Файл находится на нашем сайте, и вы можете увидеть, как выглядит получившийся PDF с красивым изображением.
Чтобы использовать изображение, вам необходимо сначала добавить его в документ. Библиотека может
читать изображение из файла или потока. Я изменил метод BuildDocument
, чтобы показать, как
добавить изображение в документ.
Пример кода для этого шага находится в классе HelloWorld5
тестового
приложения. Ниже приведены части, которые изменились по сравнению с предыдущим
шагом.
static void BuildDocument(Document doc)
{
doc.Typography(t =>
{
t.Document = doc.TextStyleWithFont(SystemFont.Family("Calibri"));
});
var imageFile = new FileInfo("red-flowers-at-butterfly-world.jpg");
var image = doc.Image(imageFile);
doc.Pages(pages => {
BuildPages(pages);
pages.Content().Column(c =>
{
BuildTextContent(c.Item());
BuildImageContent(c.Item(), image);
});
});
}
static void BuildPages(PageLayout pages)
{
pages.Size(PdfPaperSize.A6);
pages.Margin(10);
BuildPagesHeader(pages.Header());
BuildPagesFooter(pages.Footer());
}
static void BuildImageContent(LayoutContainer content, Image image)
{
content.AlignCenter()
.PaddingVertical(20)
.Image(image);
}
Как видите, в методе BuildDocument
новые изменения. Раньше в коде использовался метод
Text, чтобы установить текст в качестве
основного содержимого страниц. Метод BuildTextContent
по-прежнему это делает. Но теперь мне ещё
нужно изображение в содержимом.
Чтобы в основном содержимом страниц присутствовали и текст, и изображение, мне нужен контейнер. Я
использую контейнер Column для добавления текста и
изображения вертикально друг за другом. Метод Item
контейнера столбца предоставляет вложенный
контейнер. Я использую один вызов метода Item
, чтобы получить контейнер для текста, и другой
вызов, чтобы получить контейнер для изображения.
Как видите, я ни капельки не изменил BuildTextContent
. Но, конечно, мне пришлось убрать вызов
BuildTextContent
из метода BuildPages
.
BuildImageContent
выполняет важную работу в трех своих строках. Он добавляет изображение с
отступами сверху и снизу. И он также центрирует изображение на странице.
Более подробную информацию о контейнере Column и изображениях вы найдете в следующих статьях.
Списки
Что такое список? Давайте представим его как группу пронумерованных элементов, написанных один под другим. Такой подход подсказывает способ реализации списка.
Пример кода для этого шага находится в классе HelloWorld6
тестового
приложения. Ниже приведены части, которые изменились по сравнению с предыдущим
шагом.
static void BuildDocument(Document doc)
{
....
doc.Pages(pages => {
BuildPages(pages);
pages.Content().Column(c =>
{
BuildTextContent(c.Item());
BuildImageContent(c.Item(), image);
BuildListContent(c.Item());
});
});
}
static void BuildListContent(LayoutContainer content)
{
var dayNames = DateTimeFormatInfo.InvariantInfo.DayNames;
var dayNamesSpain = DateTimeFormatInfo.GetInstance(new CultureInfo("es-ES")).DayNames;
content.Column(column =>
{
for (int i = 0; i < dayNames.Length; i++)
{
column.Item().Row(row =>
{
row.Spacing(5);
row.AutoItem().Text($"{i + 1}.");
row.RelativeItem().Text(t => {
t.Line(dayNames[i]);
t.Line($"In Spain they call it {dayNamesSpain[i]}");
});
});
}
});
}
Единственное изменение в BuildDocument
— это вызов нового метода. Я добавил вызов
BuildListContent
после вызова BuildImageContent
.
BuildListContent
создает список в виде столбца строк. В каждой строке по два элемента. Первый
(слева) предназначен для номера позиции. Другой (справа) предназначен для текста элемента. Код явно
устанавливает расстояние между элементами в строке.
Чтобы упорядочить элементы, контейнеру Row необходимо знать заранее или рассчитать размер каждого элемента. В этом примере я использую методы AutoItem и RelativeItem. В результате контейнер строки рассчитает ширину, необходимую для размещения первого элемента. Затем контейнер будет использовать оставшуюся доступную ширину для второго элемента.
После добавления списка содержимое получившегося PDF-файла больше не помещается на одну страницу. Layout API автоматически добавляет вторую страницу и помещает список на нее. Вы можете видеть, что API повторил верхний и нижний колонтитулы на новой странице в PDF-документе с несколькими страницами.
Дополнительную информацию о списках можно найти в статье Составные контейнеры.
Таблицы
Многие PDF-документы содержат таблицы. В этом нет ничего удивительного, поскольку таблицы повышают ясность и организацию данных. Позвольте мне показать, как создать таблицу в PDF с помощью Layout API.
Пример кода для этого шага находится в классе HelloWorld7
тестового
приложения. Ниже приведены части, которые изменились по сравнению с предыдущим
шагом.
static void BuildDocument(Document doc)
{
....
doc.Pages(pages => {
BuildPages(pages);
pages.Content().Column(c =>
{
BuildTextContent(c.Item());
BuildImageContent(c.Item(), image);
BuildListContent(c.Item());
BuildTableContent(c.Item());
});
});
}
static void BuildTableContent(LayoutContainer content)
{
var color = new PdfGrayColor(75);
content.PaddingTop(20).Table(t =>
{
t.Columns(c =>
{
c.RelativeColumn(4);
c.RelativeColumn(1);
c.RelativeColumn(4);
});
t.Header(h =>
{
h.Cell().Background(color).Text("Month in 2024");
h.Cell().Background(color).Text("Days");
h.Cell().Background(color).Text("First Day");
});
for (int i = 0; i < 12; i++)
{
var stats = GetMonthStats(2024, i);
t.Cell().Text(stats.Item1);
t.Cell().Text(stats.Item2);
t.Cell().Text(stats.Item3);
}
});
}
static (string, string, string) GetMonthStats(int year, int monthIndex)
{
return (
DateTimeFormatInfo.InvariantInfo.MonthNames[monthIndex],
DateTime.DaysInMonth(year, monthIndex + 1).ToString(),
new DateTime(year, monthIndex + 1, 1).DayOfWeek.ToString()
);
}
Вызов BuildTableContent
— единственное изменение в BuildDocument
.
В BuildTableContent
я создаю простую таблицу. В таблице отображается тривиальная информация о
месяцах 2024 года. Для начала я определяю три столбца с относительной шириной. Левый и самый правый
столбцы будут в 4 раза шире, чем средний столбец.
Код также определяет заголовок таблицы. Для этого добавляются ячейки заголовков и задаются цвет текста и фона для каждой ячейки. Когда таблица не умещается на одной странице, заголовок повторяется на каждой странице, занятой таблицей. Вы можете увидеть это в полученном PDF-документе с таблицей. В документе таблица начинается на второй странице и продолжается на третьей.
Я использую простой цикл для составления строк. Код в цикле начинается с получения информации о каждом месяце. Затем он добавляет три ячейки, составляющие строку информации.
В статье Контейнер Table более подробно описаны все функции контейнера Table
.
Примеры кода
Выше я представил некоторые из наиболее популярных функций для генерации PDF-файлов на C#. Я предлагаю вам продолжить знакомство с API, прочитав следующую статью. Также попробуйте примеры кода из репозитория Docotic.Pdf.Samples на GitHub. Примеры кода для Layout API находятся в папке Layout репозитория.
Вы можете использовать примеры кода в качестве площадки для разработки кода. Таким образом, вы сможете опробовать некоторые идеи, не начиная каждый раз с нуля.
Те же примеры кода находятся в папке Samples
ZIP-пакета. Там два файла решения: SamplesCSharp
для примеров на языке C# и SamplesVB.NET
для примеров на VB.NET.