该页面可以包含自动翻译的文本。
复合容器
复合容器在构建和组织内容方面起着关键作用。使用合适的容器,您可以轻松地以对用户友好的方式呈现文本和图像。
完全可以只使用简单的文本和图像容器来构建文档。不过,有些需求仅靠简单容器很难或无法实现。在这种情况下,复合容器会很有帮助。此外,复杂容器还能用更少的代码帮助您实现目标。

使用 LayoutContainer 类的方法,将 Row 和 Column 等复合容器添加到文档页面。借助这些容器,您可以实现网格和列表等其他容器。对于更少见但同样重要的场景,还有 Inlined 和 Layers 方法。
本文是关于用于 PDF 生成的 Layout API 系列的一部分。如果您是该 API 的新手,请先阅读 Layout API 入门 部分。
Row
Row 容器为水平排列在一行中的项提供空间。行中的每个项都是一个容器。这意味着您可以在一行中放置不同类型的内容。您可以使用 Spacing 方法指定项之间的间距。
行中的所有项具有相同的高度。库使用最高项的高度作为整行的高度。项宽度有三种指定方式。创建项时必须选择其中一种。一个行可以包含通过不同方式创建的项。
Row.AutoItem 方法会创建一个未显式指定宽度的项。对于此类项,库会计算其内容的固有尺寸。计算得到的内容宽度就是该项的宽度。请注意,使用 AutoItem 创建的项不会对长行进行换行。
使用 ConstantItem 方法创建一个宽度等于精确点数的项。
当您不知道行中项的确切宽度,也不想使用固有尺寸时,RelativeItem 很方便。相反,您可以为行中的项指定相对宽度。该方法接受该项应占用的份数。本行中所有 RelativeItem 调用的总份数之和就是总份数。
例如,如果只有一次 RelativeItem 调用,那么该数字并不重要。该项将占用全部可用宽度。对于两个或更多项,这些数字定义了比例。
row.RelativeItem(2)
row.RelativeItem(3)
row.RelativeItem(1)
使用上面代码创建的所有项共占 6 份(2 + 3 + 1 = 6)。各项分别占 6 份中的 2 份、3 份和 1 份。
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 容器类型提供了丰富的功能集,可帮助您处理最复杂的场景。请在 表格容器 文章中阅读所有功能。
InlineContainer
您可以用其他容器集合填充一个容器。先调用 LayoutContainer.Inlined 方法,然后调用所提供 InlineContainer 的 Item 方法来添加子容器。
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 页面上添加水印。
首先,通过调用 LayoutContainer.Layers 方法获取一个 LayerContainer 对象。拿到该对象后,就可以开始添加图层。调用 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 附加组件会在后续页面上重复这些容器。每一页只要包含主文档内容,也会包含背景和前景容器。这使它们非常适合此任务。
我先向 background container 添加一张图像。您也可以用同样的方法将徽标添加到 PDF。图像会显示在页面内容之后。文档使用多少页背景图像都没有关系。API 只会向生成的 PDF 添加一份图像字节副本。
主文档内容可以是任意内容。在这个示例代码中,我使用了著名的 Lorem Ipsum 文本。
文本水印放在 foreground container 中。文本本身可以是任意内容,您可以使用任何颜色或字体。我使用了旋转文本,以较大字号绘制半透明红色字母。
示例代码会从我们的示例代码仓库异步下载图像和文本。当然,您也可以使用本地图像和/或从文件中读取文本。
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 中查看代码结果。
示例代码
我们有一些示例应用更详细地涵盖了上述功能。请花些时间查看它们。