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

Составные контейнеры

Сложные контейнеры играют решающую роль в структурировании и организации контента. Используя подходящий контейнер, вы можете легко представить текст и изображения в удобной для пользователя форме.

Определенно возможно создать документ, используя только простые текстовые и графические контейнеры. Однако существуют требования, которые сложно или невозможно реализовать только с помощью простых контейнеров. В таких случаях помогают составные контейнеры. Кроме того, сложные контейнеры помогают достичь ваших целей с меньшим количеством кода.

Составные контейнеры

Используйте методы класса LayoutContainer для добавления сложных контейнеров, таких как Row и Column, на страницы документа. Используя эти контейнеры, вы можете реализовать другие контейнеры, такие как сетки и списки. Есть также методы Inlined и Layers для менее распространенных, но все же важных случаев.

Эта статья - часть серии статей про Layout API для генерации PDF-файлов. Если вы новичок в работе с API, то сначала прочитайте часть Начало работы с Layout API.

Библиотека Docotic.Pdf 9.5.17615-dev Дополнение Layout 9.5.17615-dev
Регрессионные тесты 14,813 прошло Всего загрузок NuGet 4,924,084

Row

Контейнеры Row предоставляют место для элементов, расположенных горизонтально в одну строку. Каждый элемент в строке является контейнером. Это означает, что вы можете размещать контент разных типов в одной строке. Вы можете указать расстояние между элементами, используя метод Spacing.

Все элементы в строке имеют одинаковую высоту. Библиотека использует высоту самого высокого элемента в качестве высоты строки. Есть три способа указать ширину элемента. Вы должны выбрать один при создании элемента. Строка может содержать элементы, созданные разными способами.

Метод Row.AutoItem создает элемент без явно указанной ширины. Для таких элементов библиотека вычисляет внутренний размер их содержимого. Рассчитанная ширина контента — это ширина элемента. Обратите внимание, что элементы, созданные с помощью AutoItem, не переносят длинные строки.

Используйте метод ConstantItem, чтобы создать элемент с шириной, равной точному количеству точек.

RelativeItem удобен, когда вы не знаете точную ширину элементов в строке и не хотите использовать внутренние размеры. Вместо этого вы можете указать относительную ширину элементов в строке. Метод принимает количество частей, которые должен занимать элемент. Общее количество частей — это сумма всех чисел во всех вызовах RelativeItem в этой строке.

Например, если есть один вызов RelativeItem, то номер не важен. Элемент займет всю доступную ширину. Для двух и более предметов цифры определяют пропорцию.

row.RelativeItem(2)
row.RelativeItem(3)
row.RelativeItem(1)

Все элементы, созданные с помощью приведенного выше кода, состоят из 6 частей (2 + 3 + 1 = 6). Отдельные предметы занимают 2 из 6, 3 из 6 и 1 из 6 частей соответственно.

Layout API использует следующую формулу для расчета ширины одной части:

PartWidth = (RowWidth - AutoWidth - ConstantWidth) / TotalParts

где:
RowWidth = ширина контейнера строки
AutoWidth = ширина всех элементов, созданных с помощью метода AutoItem
ConstantWidth = ширина всех элементов, созданных с помощью метода ConstantItem

Вот пример, в котором создается строка с элементами всех трех типов.

var monthNames = DateTimeFormatInfo.InvariantInfo.MonthNames;
var groups = new[]
{
    string.Join(", ", monthNames.Take(4)),
    string.Join(", ", monthNames.Skip(4).Take(4)),
    string.Join(", ", monthNames.Skip(8).Take(4)),
};

PdfDocumentBuilder.Create().Generate("compounds-row.pdf", doc => doc.Pages(page =>
{
    page.Content()
        .Padding(20)
        .MinimalBox()
        .Row(row =>
        {
            row.ConstantItem(100)
                .Background(new PdfRgbColor(187, 237, 237))
                .Text("100 points wide");

            for (int i = 0; i < groups.Length; i++)
            {
                row.AutoItem().LineVertical(0.1);

                var numberOfParts = groups.Length - i + 1;
                row.RelativeItem(numberOfParts)
                    .PaddingHorizontal(5)
                    .Text(t =>
                    {
                        t.Line($"{numberOfParts} parts wide");
                        t.Line();
                        t.Line(groups[i]);
                    });
            }
        });
}));

Вы можете увидеть результат выполнения кода в compounds-row.pdf.

Column

Чтобы расположить элементы вертикально друг за другом, используйте контейнер Column. Каждый элемент в столбце является контейнером. Благодаря этому вы можете поместить контент разных типов в один столбец.

Ширина каждого элемента равна ширине столбца. Высота каждого элемента зависит от его содержимого и свойств. Контейнеры Column поддерживают разбиение по страницам, поэтому надстройка Layout может отображать элементы столбца на нескольких страницах.

По умолчанию контейнер Column не имеет содержимого верхнего или нижнего колонтитула. Используйте методы Header и Footer для доступа и настройки соответствующих контейнеров. Когда элементы столбца занимают более одной страницы, библиотека повторяет как верхние, так и нижние колонтитулы на каждой странице.

Используйте метод Spacing, чтобы добавить вертикальное пространство между элементами столбца. Обратите внимание, что библиотека не применяет интервал между заголовком и первым элементом. Библиотека также не добавляет пространство перед нижним колонтитулом.

PdfDocumentBuilder.Create().Generate("compounds-column.pdf", doc => doc.Pages(page =>
{
    page.Size(PdfPaperSize.A6);

    page.Content()
        .Padding(20)
        .Column(column =>
        {
            column.Header()
                .Background(new PdfRgbColor(187, 237, 237))
                .Padding(5)
                .Text("Month names");

            for (int i = 0; i < 12; i++)
            {
                column.Item().Background(
                    new PdfGrayColor(i % 2 == 0 ? 90 : 100))
                    .Padding(5)
                    .Text(DateTimeFormatInfo.InvariantInfo.MonthNames[i]);
            }

            column.Footer().LineHorizontal(1);
        });
}));

Вы можете увидеть результат выполнения кода в compounds-column.pdf.

Сетка

Сетчатые разметки организуют элементы в столбцы и строки. В этом аспекте сетки похожи на таблицы. Layout API не предоставляет специальный тип контейнера для сеток. Вы можете реализовать сетчатую разметку, используя контейнеры Column и Row.

Полезно представить сетку как столбец, где каждый элемент представляет собой строку. Контейнеры Column и Row предоставляют возможность устанавливать расстояние между элементами. Если хотите, вы можете использовать верхний и нижний колонтитулы.

Каждая строка может иметь независимую разметку. В каждой строке может быть разное количество элементов. Элементы могут иметь разную ширину и высоту. Можно добавить дополнительное пространство до, после или между элементами в строке. Используйте для этого элементы без содержания и оформления.

var blue = new PdfRgbColor(187, 237, 237);
var darkerBlue = blue.Darken(50);
PdfDocumentBuilder.Create().Generate("compounds-grid.pdf", doc => doc.Pages(page =>
{
    page.Size(300, 200);

    page.Content().Padding(15).Column(column =>
    {
        column.Spacing(10);

        column.Item().Row(row =>
        {
            row.Spacing(10);

            row.ConstantItem(100).Background(darkerBlue).Height(40);
            row.RelativeItem(4).Background(blue);
        });

        column.Item().Row(row =>
        {
            row.Spacing(10);

            row.RelativeItem(2).Background(blue).Height(60);
            row.RelativeItem(1);
            row.RelativeItem(2).Background(blue);
        });

        column.Item().Row(row =>
        {
            row.Spacing(10);

            row.RelativeItem(1).Background(blue).Height(50);
            row.RelativeItem(3).Background(blue);
            row.ConstantItem(50).Background(darkerBlue);
        });
    });
}));

Вы можете увидеть результат выполнения кода в compounds-grid.pdf.

Списки

Списки повышают читаемость, разбивая информацию на краткие пункты. Элементы списка могут иметь цифры, маркеры и другие символы рядом с текстом. Вы можете легко реализовать макет списка, используя контейнеры Column и Row. Layout API не предоставляет специальный тип контейнера для списков.

Ознакомьтесь с примером кода, который создает список месяцев по сезонам. Обратите внимание, что список имеет заголовок. Если элементы содержат текст, который может переноситься на следующую строку, используйте метод RelativeItem или ConstantItem для текстовой части элемента.

var monthNames = DateTimeFormatInfo.InvariantInfo.MonthNames.ToList();
monthNames.Insert(0, monthNames[11]);

PdfDocumentBuilder.Create().Generate("compounds-list.pdf", doc => doc.Pages(page =>
{
    page.Size(150, 200);

    page.Content().Padding(5).Column(column =>
    {
        column.Header()
            .Text("Months by seasons:")
            .Style(TextStyle.Parent.Underline());

        for (int i = 0; i < 4; i++)
        {
            var season = string.Join(", ", monthNames.Skip(i * 3).Take(3));
            column.Item().Row(row =>
            {
                row.Spacing(2);

                row.AutoItem().Text("•");
                row.RelativeItem().Text(season);
            });
        }
    });
}));

Вы можете увидеть результат выполнения кода в compounds-list.pdf.

Table

В макетах таблиц элементы организованы в столбцы и строки. Тип контейнера Table предоставляет обширный набор функций и может помочь вам в самых сложных случаях. Обо всех функциях читайте в статье Контейнер Table.

InlineContainer

Вы можете заполнить контейнер набором других контейнеров. Начните с вызова метода LayoutContainer.Inlined. Затем вызовите метод Item предоставленного InlineContainer, чтобы добавить дочерние контейнеры.

Дополнение Layout расставляет контейнеры один за другим. Если места для размещения элемента нет, библиотека начинает новую строку. Используйте методы Spacing/HorizontalSpacing/VerticalSpacing, чтобы добавить пространство между элементами.

Вы можете влиять на положение элементов в контейнере, используя методы выравнивания. Методы AlignTop/AlignMiddle/AlignBottom выравнивают элементы по вертикали. Для горизонтального направления используйте методы AlignLeft/AlignCenter/AlignRight/AlignJustify.

Есть особый случай. Метод AlignSpaceAround, выравнивающий элементы по горизонтали. Он также добавляет дополнительный интервал перед первым элементом и после последнего элемента.

var orange = new PdfRgbColor(250, 123, 5);
var brown = orange.Darken(50);
var itemProps = new (int Width, PdfColor Color)[] {
    (20, orange), (30, orange), (50, brown), (50, orange), (50, orange),
    (30, orange), (20, brown), (30, orange), (50, brown), (10, brown)
};

PdfDocumentBuilder.Create().Generate("compounds-inlined.pdf", doc => doc.Pages(page =>
{
    page.Size(150, 120);

    page.Content().Inlined(c =>
    {
        c.Spacing(5);

        foreach (var (Width, Color) in itemProps)
            c.Item().Height(30).Width(Width).Background(Color);
    });
}));

Вы можете увидеть результат выполнения кода в compounds-inlined.pdf.

LayerContainer

Возможно, вам потребуется разместить некоторый контент над и/или под главным содержимым страницы. Очевидным вариантом использования является добавление водяного знака поверх страниц PDF.

Сначала получите объект LayerContainer, вызвав метод LayoutContainer.Layers. Получив объект, вы можете начать добавлять слои. Вызов метода Layer добавляет дополнительный слой. Вызовите метод PrimaryLayer, чтобы добавить слой основного содержимого. Вы должны добавить ровно один основной слой.

Layout API будет компоновать слои в том же порядке, в котором вы их создаете. Слои, добавленные перед основным слоем, отойдут на задний план. Все слои, добавленные после основного слоя, будут располагаться над основным содержимым. Контейнер повторяет второстепенные слои на всех страницах, занятых основным контентом.

Вот пример кода для добавления водяных знаков на страницы PDF.

PdfDocumentBuilder.Create().Generate("compounds-layers.pdf", doc => doc.Pages(page =>
{
    var largeRedText = TextStyle.Parent.FontSize(48)
        .FontColor(new PdfRgbColor(235, 64, 52));

    page.Size(400, 250);

    page.Content()
        .Padding(25)
        .Layers(layers =>
        {
            layers.Layer()
                .AlignCenter()
                .Text(text => text.CurrentPageNumber().Style(largeRedText));

            layers.PrimaryLayer()
                .Background(new PdfGrayColor(85), 65)
                .Padding(25)
                .Text(new string('_', 790));

            layers.Layer()
                .AlignCenter()
                .AlignMiddle()
                .Text("Watermark")
                .Style(largeRedText);
        });
}));

Вы можете увидеть результат выполнения кода в compounds-layers.pdf.

Водяные знаки

Одним из распространенных требований является добавление водяного знака в PDF. Требование может возникнуть по разным причинам. Вы можете добавить водяной знак в PDF-файл, чтобы обозначить право собственности или указать на секретность информации в PDF-файле.

Одним из подходов к нанесению водяных знаков на PDF-файлы является использование слоев. Смотрите пример в предыдущем разделе. Позвольте мне показать вам другой подход.

Я буду использовать контейнеры фона и переднего плана страницы для добавления водяных знаков в PDF-файлы. Дополнение Layout повторяет эти контейнеры на следующих страницах. Каждая страница с основным содержимым документа также будет содержать контейнеры фонового и переднего плана. Это делает их пригодными для этой задачи.

Я начинаю с добавления изображения в фоновый контейнер. Таким же образом вы можете добавить свой логотип в PDF. Изображение появится позади содержимого страницы. Не имеет значения, на скольких страницах вашего документа используется фоновое изображение. API добавит только одну копию байтов изображения в сгенерированный PDF-файл.

Основное содержание документа может быть любым. В этом примере кода я использую знаменитый текст Lorem Ipsum.

Текстовый водяной знак попадает в контейнер переднего плана. Сам текст может быть любым, и вы можете использовать любой цвет и шрифт. Я использовал повернутый текст, нарисованный полупрозрачными красными буквами большего размера.

Пример кода асинхронно загружает изображение и текст из нашего репозитория примеров кода. Конечно, вы можете использовать локальное изображение и/или прочитать текст из файла.

var urlPrefix =
    "https://raw.githubusercontent.com/BitMiracle/Docotic.Pdf.Samples/master/Samples/Sample%20Data/";

using var client = new HttpClient();
using var imageResponse = await client.GetAsync(urlPrefix + "watermark-background.png");
using var imageStream = await imageResponse.Content.ReadAsStreamAsync().ConfigureAwait(false);

var loremIpsum = string.Empty;
using (var textResponse = await client.GetAsync(urlPrefix + "lorem-ipsum.txt"))
     loremIpsum = await textResponse.Content.ReadAsStringAsync().ConfigureAwait(false);

PdfDocumentBuilder.Create().Generate("compounds-watermarks.pdf", doc =>
{
    var image = doc.Image(imageStream);

    doc.Pages(page =>
    {
        page.Size(400, 250);

        page.Background()
            .AlignMiddle()
            .Image(image);

        page.Content()
            .Padding(25)
            .Text(loremIpsum);

        var largeRedText = TextStyle.Parent.FontSize(48)
            .FontColor(new PdfRgbColor(235, 64, 52), 50);
        page.Foreground()
            .Rotate(-30)
            .Translate(-50, 180)
            .Text("DO NOT COPY")
            .Style(largeRedText);
    });
});

Вы можете увидеть результат выполнения кода в compounds-watermarks.pdf.

Примеры кода

У нас есть несколько примеров, которые более подробно описывают вышеупомянутые функции. Пожалуйста, потратьте некоторое время на их изучение.