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

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

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

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

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

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

Библиотека Docotic.Pdf 9.4.17342 Дополнение Layout 9.4.17342
Регрессионные тесты 14,727 прошло Всего загрузок NuGet 4,260,602

Установка

Установите пакет 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 выполняет важную работу в трех своих строках. Он добавляет изображение с отступами сверху и снизу. И он также центрирует изображение на странице.

Списки

Что такое список? Давайте представим его как группу пронумерованных элементов, написанных один под другим. Такой подход подсказывает способ реализации списка.

Пример кода для этого шага находится в классе 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-документе с таблицей. В документе таблица начинается на второй странице и продолжается на третьей.

Я использую простой цикл для составления строк. Код в цикле начинается с получения информации о каждом месяце. Затем он добавляет три ячейки, составляющие строку информации.

Примеры кода

Выше я представил некоторые из наиболее популярных функций для генерации PDF-файлов на C#. Я предлагаю вам продолжить знакомство с API, прочитав следующую статью. Также попробуйте примеры кода из репозитория Docotic.Pdf.Samples на GitHub. Примеры кода для Layout API находятся в папке Layout репозитория.

Вы можете использовать примеры кода в качестве площадки для разработки кода. Таким образом, вы сможете опробовать некоторые идеи, не начиная каждый раз с нуля.

Те же примеры кода находятся в папке Samples ZIP-пакета. Там два файла решения: SamplesCSharp для примеров на языке C# и SamplesVB.NET для примеров на VB.NET.