Эта страница может содержать автоматически переведенный текст.
Размер, положение и рендеринг контейнеров
Содержание — самая важная часть документа. В этом нет никаких сомнений. Другая важная часть — это форматирование, которое обеспечивает четкое, профессиональное и эффективное общение. Правильно отформатированный документ визуально привлекателен, читаем и по нему легко ориентироваться.
Возможно, вы уже знаете, как организовать контент с помощью контейнеров. И как применить к ним цвет фона. В этой статье описывается, как указать размер и положение контейнеров. Она также охватывает расширенные возможности, такие как условный рендеринг контента. Есть информация о поддержке направления контента справа налево.
Класс LayoutContainer предоставляет все необходимое для профессионального размещения контейнеров. Применяя отступы и выравнивание, вы можете создать документ, который оставит положительное впечатление у пользователей.
Эта статья - часть серии статей про Layout API для генерации PDF-файлов. Если вы новичок в работе с API, то сначала прочитайте часть Начало работы с Layout API.
9.5.17548-dev 9.5.17548-dev14,726 прошло Всего загрузок NuGet 4,514,921
Размер
По умолчанию контейнеры занимают наименьшую площадь, необходимую для их содержимого. Другими словами, размер контейнера равен внутреннему размеру его содержимого.
Внутренний размер изображения определяется размерами самого файла изображения. Внутренний размер текстового интервала — это размер области, охватывающей все глифы в этом интервале.
Размер составного контейнера, такого как Column или Table, зависит от размера частей контейнера.
Width & Height
Можно указать точную ширину и высоту контейнера, используя методы Width и Height. Это очень удобно для контейнеров-заполнителей.
Точный размер также хорошо подходит для изображений. Это связано с тем, что Layout API масштабирует их, чтобы они помещались в контейнере или заполняли его в зависимости от ImageContentMode.
Будьте осторожны с точными размерами составных контейнеров и контейнеров с текстом. Вы получите LayoutException, если невозможно уместить контент в указанный размер.
Бывают случаи, когда вы хотите лишь установить ограничение по ширине или высоте. Вы можете установить ограничения, используя методы MinWidth, MinHeight, MaxWidth и MaxHeight.
Обратите внимание, что библиотека выдаст исключение LayoutException
, если невозможно
соответствовать всем ограничениям.
Extend
Контейнер может расширяться, чтобы занять максимально доступное пространство. Это пригодится, если вы не знаете точный размер или ограничения по размеру.
Используйте метод ExtendHorizontal, если вы хотите, чтобы контейнер занимал все доступное пространство только в горизонтальном направлении. ExtendVertical полезен, когда вы хотите расширить контейнер только в вертикальном направлении. Метод Extend заставляет контейнер занимать все доступное пространство в обоих направлениях.
var gray = new PdfGrayColor(75);
var text = "Content goes here";
var size = new PdfSize(150, 50);
PdfDocumentBuilder.Create().Generate("positioning-extend.pdf", doc =>
{
for (int i = 0; i < 4; i++)
{
doc.Pages(page =>
{
page.Size(size);
page.Content().Row(r =>
{
var container = r.AutoItem().Background(gray);
switch (i)
{
case 0:
container.Text(text);
break;
case 1:
container.ExtendHorizontal().Text(text);
break;
case 2:
container.ExtendVertical().Text(text);
break;
case 3:
container.Extend().Text(text);
break;
}
});
});
}
});
Результат приведенного выше кода находится в positioning-extend.pdf. Как видите, каждая из четырех страниц содержит один и тот же текст на сером фоне. Но размер контейнера на каждой странице разный.
MinimalBox
LayoutContainer предоставляет метод
MinimalBox. Это своего рода
противоположность методам Extend.
MinimalBox
создает вложенные контейнеры, которые используют только минимально необходимое
пространство для содержимого.
var gray = new PdfGrayColor(75);
var size = new PdfSize(150, 50);
PdfDocumentBuilder.Create().Generate("positioning-minimalbox.pdf", doc =>
{
doc.Pages(page =>
{
page.Size(size);
page.Content().MinimalBox().Background(gray).Text("I don't want more space");
});
doc.Pages(page =>
{
page.Size(size);
page.Content().Background(gray).Text("I'll take everything");
});
});
Результат приведенного выше кода находится в positioning-minimalbox.pdf. Из-за вызова MinimalBox текст на первой странице занимает только необходимое пространство. На второй странице текст занимает всю страницу.
Scale
В контейнере можно масштабировать любой контент. Метод Scale влияет на контент как в горизонтальном, так и в вертикальном направлении. Используйте метод ScaleHorizontal или ScaleVertical для изменения содержимого только в одном направлении. Последние два метода не сохраняют соотношение сторон контента.
Значения масштаба меньше 1 уменьшают площадь, занимаемую контейнером. Значения больше 1 увеличивают
площадь. Чтобы перевернуть содержимое контейнера, используйте отрицательное значение масштаба.
Например, ScaleVertical(-1)
приводит к перевернутой версии исходного содержимого.
PdfDocumentBuilder.Create().Generate("positioning-scale.pdf", doc =>
{
doc.Pages(page =>
{
page.Content()
.MinimalBox()
.Column(column =>
{
var scales = new[] { 0.5f, 0.75f, 1, 1.3f, 1.5f };
foreach (var scale in scales)
{
var percent = (int)(scale * 100);
column.Item()
.Scale(scale)
.Text(FormattableString.Invariant($"Scale equals {scale} ({percent}%)."))
.FontSize(20);
column.Item().LineHorizontal(0.5);
}
});
});
});
Результат приведенного выше кода находится в файле positioning-scale.pdf.
ScaleToFit
Вы можете уменьшить содержимое, чтобы оно поместилось в доступное пространство. Например, если у вас есть фиксированная область для вывода имени или адреса человека. Конечно, вы можете увеличить площадь в случае более длинного имени. Но более простым подходом может быть немного уменьшить размер текста. Используйте метод ScaleToFit, чтобы уменьшить масштаб содержимого.
ScaleToFit
сохраняет соотношение сторон контента. Этот метод никогда не увеличивает содержимое.
Обратите внимание, что этот метод выполняет итеративные вычисления. Это может замедлить процесс
создания PDF-файла.
PdfDocumentBuilder.Create().Generate("positioning-scaletofit.pdf", doc =>
{
doc.Pages(page =>
{
page.Content().Column(column =>
{
for (int i = 0; i < 5; i++)
{
column.Item()
.Width(230 - 20 * i)
.Height(20)
.ScaleToFit()
.Border(b => b.Thickness(0.5))
.Text(" This text should fit into the changing width.");
}
});
});
});
Результат приведенного выше кода находится в файле positioning-scaletofit.pdf.
AspectRatio
Соотношение сторон определяет пропорциональное соотношение между шириной и высотой контейнера. Чтобы узнать соотношение сторон контейнера, разделите его ширину на высоту.
Используйте метод AspectRatio, чтобы указать соотношение сторон контейнера. Это помогает при разработке многоразового контейнера для разных макетов или размеров страниц.
PdfDocumentBuilder.Create().Generate("positioning-aspectratio.pdf", doc =>
{
var ratios = new double[] { 0.25, 0.5, 1, 2 };
foreach (var ratio in ratios)
{
var ratioText = ratio.ToString(CultureInfo.InvariantCulture);
doc.Pages(page =>
{
page.Size(200, 200);
page.Content().Column(column =>
{
column.Item()
.AspectRatio(ratio)
.Background(new PdfGrayColor(75))
.Text($"Width / Heigth = {ratioText}");
});
});
}
});
Результат приведенного выше кода находится в positioning-aspectratio.pdf.
Метод имеет необязательный параметр типа AspectRatioMode. Используйте этот параметр, чтобы указать, как изменить размер содержимого с сохранением соотношения сторон.
Контейнер с указанным соотношением сторон занимает как можно больше места. В зависимости от режима контейнер попытается занять всю доступную область (по умолчанию), ширину или высоту.
Обратите внимание, что библиотека может выдать исключение LayoutException. Это происходит, когда она не может удовлетворить требования к размеру, соотношению сторон и режиму соотношения сторон.
Unconstrained
Контейнеры могут вообще не иметь ограничений по размеру. Используйте метод Unconstrained, чтобы удалить все ограничения размера из контейнера.
Содержимое в неограниченном контейнере занимает пространство, размер которого равен внутреннему размеру содержимого. Неограниченный контейнер сам по себе не занимает места. В результате соседние контейнеры могут перекрывать содержимое неограниченного контейнера.
PdfDocumentBuilder.Create().Generate("positioning-unconstrained.pdf", doc =>
{
doc.Pages(page =>
{
page.Content().MinimalBox()
.Border(b => b.Thickness(0.5))
.Column(column =>
{
column.Item().Text("First item");
column.Item().Unconstrained()
.Text("Second item ignores all size constraints");
// используем пустую строку для третьего элемента
column.Item().Text(new string(' ', 20))
.BackgroundColor(new PdfRgbColor(187, 237, 237), 50);
column.Item().Text("Fourth item");
});
});
});
Результат приведенного выше кода находится в файле positioning-unconstrained.pdf. В коде я использовал строку пробелов с полупрозрачным фоном для третьего элемента в столбце. Как видите, третий элемент частично перекрывает второй (неограниченный) элемент.
Положение
Положение контейнера зависит от множества вещей. Некоторые из них — выравнивание, заполнение, положение родительского контейнера и направление содержимого. По умолчанию любой контейнер занимает самое левое и самое верхнее доступное положение.
Padding
Одним из наиболее распространенных требований является добавление некоторого пространства вокруг содержимого контейнера. LayoutContainer предоставляет набор методов для настройки области заполнения контейнера. Область заполнения — это пространство между содержимым и границей. Другими словами, отступы представляют собой внутреннее пространство, окружающее контент.
Метод Padding устанавливает отступы сразу
со всех четырех сторон контейнера. Используйте метод
PaddingHorizontal, чтобы
указать отступы только слева и справа.
PaddingVertical делает то же
самое только с верхней и нижней стороны. Чтобы настроить отступы по отдельности на каждой стороне,
используйте один из методов PaddingTop/Bottom/Left/Right
.
Align
Чтобы изменить положение контейнера, используйте методы выравнивания. Методы
AlignLeft/AlignCenter/AlignRight
применяют горизонтальное выравнивание и возвращают вложенный
контейнер. Методы AlignTop/AlignMiddle/AlignBottom
возвращают вложенный контейнер с
соответствующим вертикальным выравниванием.
Контейнер с явно заданным выравниванием занимает область с минимально необходимой шириной и/или высотой. Следующий код создает столбец с двумя элементами. К одному элементу явно применено выравнивание.
PdfDocumentBuilder.Create().Generate("positioning-alignment.pdf", doc =>
{
var color = new PdfRgbColor(187, 237, 237);
var text = "Hello";
doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Column(c =>
{
c.Item().Extend().Background(color).Text(text);
c.Item().Extend().AlignLeft().Background(color).Text(text);
});
});
});
Вызов Extend заставляет оба элемента занимать всю страницу. Я вызываю метод AlignLeft для второго элемента. Этот вызов не меняет положение, поскольку контейнер по умолчанию выравнивает текст по левому краю. Но явно примененное выравнивание меняет область, занимаемую вторым элементом.
Результат приведенного выше кода находится в файле positioning-alignment.pdf.
Translate
Чтобы переместить контейнер по горизонтали и/или по вертикали, используйте методы
Translate/TranslateX/TranslateY
. Первый перемещает контейнеры как по горизонтали, так и по
вертикали. Два других перемещают контейнеры только в одном направлении.
Все эти методы переопределяют положение, но сохраняют ограничения по размеру. Смещенный контейнер может перекрываться другими контейнерами. Используйте отрицательные значения параметров для перемещения влево и/или вверх. Положительные значения вызывают сдвиг вправо и/или вниз.
PdfDocumentBuilder.Create().Generate("positioning-translate.pdf", doc =>
{
doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Row(r =>
{
r.ConstantItem(50)
.Background(new PdfRgbColor(187, 237, 237))
.Text("Left");
r.ConstantItem(50)
// перемещение этого предмета на 10 пунктов влево и на 5 пунктов вниз
.Translate(-10, 5)
.Background(new PdfRgbColor(15, 130, 9))
.Text("Right");
});
});
});
Результат работы приведенного выше кода находится в файле positioning-translate.pdf.
Rotate
Повернутый контент, особенно текст, может разнообразно улучшить ваши документы. Например, вы можете сэкономить место и сделать документ более привлекательным и эстетичным.
LayoutContainer предоставляет два подхода к повороту контента. Независимо от того, какой подход вы используете, контейнер с повернутым содержимым учитывает ограничения на положение и размер.
Поворот на 90 градусов
Методы RotateRight и RotateLeft поворачивают содержимое на 90 градусов по часовой стрелке и против часовой стрелки соответственно.
Следующий код показывает, как создать документ с вертикальным текстом рядом с главным содержимым страницы.
PdfDocumentBuilder.Create().Generate("positioning-rotate.pdf", doc =>
{
var lightGray = new PdfGrayColor(90);
doc.Pages(page =>
{
page.Size(298, 210);
page.Content().Row(r =>
{
r.AutoItem()
.RotateLeft()
.Background(lightGray)
.Text("This content goes up");
r.RelativeItem(1)
.ExtendVertical()
.PaddingHorizontal(10)
.Column(t =>
{
for (int i = 0; i < 15; i++)
t.Item().Text("The main content line goes here");
});
r.AutoItem()
.RotateRight()
.Background(lightGray)
.Text("This content goes down");
});
});
});
Результат приведенного выше кода находится в positioning-rotate.pdf.
Поворот на любой угол
Метод Rotate поворачивает содержимое на произвольное количество градусов. Положительные числа вызывают вращение по часовой стрелке. Отрицательные числа соответствуют вращению против часовой стрелки.
Точка вращения — верхний левый угол контейнера. Повернутое содержимое может перекрываться другими контейнерами.
PdfDocumentBuilder.Create().Generate("positioning-rotate2.pdf", doc =>
{
doc.Pages(page =>
{
page.Size(298, 210);
page.Content()
.Padding(25)
.Background(new PdfGrayColor(70)) // серый
.AlignCenter()
.AlignMiddle()
.Background(new PdfGrayColor(100)) // белый
.Rotate(30)
.Width(100)
.Height(100)
.Background(new PdfRgbColor(187, 237, 237)); // синий
});
});
Результат приведенного выше кода находится в positioning-rotate2.pdf.
Чтобы изменить точку вращения, вызовите один из методов Translate
перед вызовом
Rotate. Не забудьте после вызова перевести
исходную точку обратно.
// перенести точку вращения
.TranslateX(50)
.TranslateY(50)
.Rotate(30)
// вернуть обратно
.TranslateX(-50)
.TranslateY(-50)
Условная компоновка
Макет PDF-документа может зависеть от условий. Например, вы можете использовать разное выравнивание или цвет фона для четных и нечетных строк в столбце.
Используйте метод Container, чтобы вставить вложенный контейнер с макетом, который зависит от условия. Вызов этого метода не разорвет цепочку вызовов.
PdfDocumentBuilder.Create().Generate("positioning-container.pdf", doc =>
{
doc.Pages(page =>
{
page.Content().Column(c =>
{
for (int i = 0; i < 15; i++)
{
c.Item()
.TextStyle(TextStyle.Parent.FontSize(14))
.Container(x => i % 2 == 0 ? x.Background(new PdfGrayColor(70)) : x)
.Text($"Row {i + 1}");
}
});
});
});
Результат приведенного выше кода находится в positioning-container.pdf.
DSL
Некоторые части документа могут использовать один и тот же макет. Например, они могут установить одинаковую рамку или использовать одинаковое форматирование. По принципу «не повторяйся» я рекомендую выносить общий код в метод.
Использование метода расширения для общего кода дает два преимущества:
- Вы можете использовать метод в цепочках вызовов методов.
- Вы можете иметь осмысленное имя для набора вызовов методов.
Учитывая эти преимущества, вы можете создать предметно-ориентированный язык (DSL). Благодаря DSL ваш код макета может стать короче и проще для понимания.
static class LayoutHelpers
{
public static LayoutContainer NumberCell(this Table table)
=> table.Cell().Border(b => b.Thickness(0.5)).PaddingHorizontal(10);
}
PdfDocumentBuilder.Create().Generate("positioning-dsl.pdf", doc => doc.Pages(page =>
{
page.Content().Table(t =>
{
t.Columns(c =>
{
for (int i = 0; i < 4; ++i)
c.ConstantColumn(50);
});
for (int i = 0; i < 16; i++)
t.NumberCell().Text($"{i + 1}");
});
}));
Процесс рендеринга
Надстройка Layout упорядочивает любой контент, который вы помещаете в контейнер, в соответствии с ограничениями по размеру и положению. Некоторый контент может занимать несколько страниц. Существует строгий порядок отображения контента. Поток контента — другое название этого порядка.
LayoutContainer предоставляет некоторые методы для настройки потока контента. Возможно, они не нужны вам каждый раз, но бывают случаи, когда без них нет способа добиться необходимой планировки.
PageBreak
Если вам нужно начать блок контента с новой страницы, используйте метод PageBreak. Например, вы можете использовать этот метод для того, чтобы некий элемент в контейнере Column начался с новой страницы.
Вот пример кода, который делит столбец так, чтобы каждая страница содержала только две строки.
PdfDocumentBuilder.Create().Generate("positioning-pagebreak.pdf", doc => doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Column(c =>
{
for (int i = 1; i <= 10; ++i)
{
c.Item().Text($"Item {i}");
if (i % 2 == 0)
c.Item().PageBreak();
}
});
}));
Результат приведенного выше кода находится в файле positioning-pagebreak.pdf.
ShowIf
Вам может потребоваться показать/скрыть контейнер в зависимости от условия. Существует метод ShowIf, который по сути является синтаксическим сахаром для этого особого случая условной компоновки.
В следующем коде я использую метод ShowIf для вставки вертикальной линии после каждых 5 элементов в ряду.
PdfDocumentBuilder.Create().Generate("positioning-showif.pdf", doc => doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Row(r =>
{
for (int i = 0; i < 10; ++i)
{
r.AutoItem().Text(i.ToString());
r.AutoItem().ShowIf(i > 0 && (i + 1) % 5 == 0).LineVertical(0.5);
}
});
}));
Результат приведенного выше кода находится в positioning-showif.pdf.
ShowOnce
Вы можете предотвратить повторение некоторого контента на последующих страницах. Используйте метод ShowOnce, чтобы указать механизму компоновки что нужно полностью отобразить контент только один раз.
Изучите следующий код, чтобы увидеть, как ShowOnce
предотвращает появление «Environment» на
второй странице.
PdfDocumentBuilder.Create().Generate("positioning-showonce.pdf", doc => doc.Pages(page =>
{
page.Size(200, 100);
page.Content().Row(r =>
{
r.RelativeItem()
.Background(new PdfGrayColor(75))
.Border(b => b.Thickness(0.5))
.Padding(5)
.ShowOnce()
.Text("Environment");
r.RelativeItem()
.Border(b => b.Thickness(0.5))
.Padding(5)
.Column(c =>
{
c.Item().Text(Environment.OSVersion.VersionString);
c.Item().Text(string.Empty);
c.Item().Text(
Environment.GetEnvironmentVariable("PROCESSOR_IDENTIFIER")
?? string.Empty
);
});
});
}));
Результат приведенного выше кода находится в positioning-showonce.pdf.
ShowEntire
По умолчанию содержимое разделяется на несколько страниц, если оно не умещается на одной странице. Используйте метод ShowEntire, чтобы отобразить весь контейнер на одной странице.
Обратите внимание, что библиотека выдает исключение LayoutException, когда невозможно уместить весь контент на одной странице.
Из-за вызова ShowEntire
в следующем коде текст второго элемента начинается со второй страницы.
Без вызова он начинался бы на первой странице, сразу после текста первого элемента.
PdfDocumentBuilder.Create().Generate("positioning-showentire.pdf", doc => doc.Pages(page =>
{
page.Size(100, 100);
page.Content().Column(c =>
{
c.Item().Text(t =>
{
for (var i = 0; i < 4; i++)
t.Line($"First item line {i + 1}");
});
c.Item()
.Background(new PdfRgbColor(250, 123, 5))
.ShowEntire()
.Text(t =>
{
for (var i = 0; i < 4; i++)
t.Line($"Second item line {i + 1}");
});
});
}));
Результат работы приведенного выше кода находится в файле positioning-showentire.pdf.
EnsureSpace
В некотором смысле EnsureSpace — это
частный случай метода ShowEntire.
Разница в том, что EnsureSpace
не требует, чтобы весь контент помещался на странице. Метод
пытается разместить только часть содержимого указанной высоты. Все остальное будет на следующей
странице.
Если незанятая область текущей страницы имеет высоту меньше запрошенной, весь контент будет
отображен на новой странице. Здесь метод даст тот же результат, что и метод ShowEntire
.
StopPaging
Используйте метод StopPaging чтобы получить вывода не более чем на одной странице. Вызов этого метода в контейнере предотвращает его разбиение на страницы. Надстройка Layout не будет разделять содержимое этого контейнера между страницами. Она не будет отображать данные, которые не поместятся на одной странице.
Следующий пример кода добавляет в документ два набора страниц. Оба набора используют в качестве
содержимого список названий дней недели. Первый набор содержит только одну страницу, поскольку код
вызывает метод StopPaging
для контейнера содержимого страниц.
PdfDocumentBuilder.Create().Generate("positioning-stoppaging.pdf", doc =>
{
static Action<TextContainer> produceText(string heading)
{
var text = string.Join('\n', DateTimeFormatInfo.InvariantInfo.DayNames);
return t =>
{
t.Line(heading).BackgroundColor(new PdfRgbColor(250, 123, 5));
t.Span(text);
};
}
doc.Pages(page =>
{
page.Size(100, 100);
page.Content()
.StopPaging()
.Text(produceText("Without paging:"));
});
doc.Pages(page =>
{
page.Size(100, 100);
page.Content()
.Text(produceText("Default behaviour:"));
});
});
Результат приведенного выше кода находится в positioning-stoppaging.pdf.
SkipOnce
Вы можете отложить появление контента. Если контейнер отображается более чем на одной странице, используйте SkipOnce, чтобы пропустить первую страницу и отобразить содержимое на всех страницах, начиная со второй.
Эта возможность полезна для всех типов заголовков, но вы можете использовать ее и с другим
контентом. Изучите следующий код, чтобы увидеть, как SkipOnce
предотвращает появление заголовка
на первой странице.
PdfDocumentBuilder.Create().Generate("positioning-skiponce.pdf", doc => doc.Pages(page =>
{
page.Size(298, 210);
page.Header()
.SkipOnce()
.Text("This header will appear starting from page 2")
.Style(TextStyle.Parent.Underline());
page.Content().Column(c =>
{
for (int i = 0; i < 5; i++)
{
if (i > 0)
c.Item().PageBreak();
c.Item().Text($"Page {i + 1}");
}
});
}));
Результат приведенного выше кода находится в файле positioning-skiponce.pdf.
Направление контента
Направление контента по умолчанию — слева направо. Контейнеры выравнивают свой текст и другое содержимое по левому краю.
Но есть языки, в которых пишут справа налево (например, арабский и иврит). При создании контента на этих языках используйте метод ContentFromRightToLeft. Вызов его переключит направление содержимого контейнера справа налево. Этот метод также переключит выравнивание по умолчанию.
Если большая часть контента на ваших страницах написана на языке RTL, вы можете установить направление контента справа налево в качестве направления контента по умолчанию для страниц. Используйте для этого метод PageLayout.ContentFromRightToLeft. Затем, чтобы переопределить направление содержимого по умолчанию для некоторых контейнеров, используйте метод ContentFromLeftToRight.
Обратите внимание, что направление содержимого не влияет на явно указанное выравнивание. Например, контент, выровненный по правому краю, будет придерживаться правой стороны независимо от того, какое направление контента вы установили для его контейнера. Визуальный порядок дочерних элементов будет отличаться в зависимости от направления контента.