Эта страница может содержать автоматически переведенный текст.

Краткое руководство по созданию PDF-файлов в C#

Узнайте, как создавать файлы PDF, такие как отчеты, счета-фактуры и квитанции, в ваших проектах .NET. Используя C# или VB.NET, легко создавайте PDF-документы, комбинируя структурные элементы. К элементам относятся верхние и нижние колонтитулы, контейнеры, таблицы, абзацы, изображения и т.п. API автоматически разбивает контент на страницы.

Некоторые другие методы создания PDF-файлов описаны в статье Создание PDF-документов в C# и VB.NET.

Представляем библиотеку создания PDF-файлов

Мы предлагаем API разметки в качестве бесплатного дополнения к библиотеке Docotic.Pdf. И библиотека, и дополнение Layout доступны на NuGet и на нашем сайте. Чтобы опробовать библиотеку без ограничений ознакомительного режима, получите бесплатный ограниченный по времени лицензионный ключ на странице библиотеки.

Библиотека Docotic.Pdf 9.4.17469-dev Дополнение Layout 9.4.17469-dev
Регрессионные тесты 14,760 прошло Всего загрузок NuGet 4,447,259

Установка

Установите пакет 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.