该页面可以包含自动翻译的文本。
容器的大小、位置和渲染
内容是文档最重要的部分。 这个毋庸置疑。 另一个关键部分是创建清晰、专业和有效沟通的格式。 格式正确的 文档具有视觉吸引力、可读性并且易于浏览。
您可能已经知道如何使用容器组织内容。 以及如何为它们应用背景颜色。 本文介绍如何指定容器的大小和位 置。 它还涵盖了内容的条件渲染等高级功能。 有有关支持从右到左内容方向的信息。
LayoutContainer 类提供了专业地安排容器所需的一 切。 通过应用填充和对齐,您可以创建给用户留下积极印象的文档。
本文是有关用于 PDF 生成的 Layout API 的系列文章的一部分。 如果您是 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 中。
要更改旋转原点,请在调用 Rotate 之前调用
Translate
方法之一。 不要忘记在调用后将原点翻译回来。
// 平移旋转原点
.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 方 法。
请注意,内容方向不会影响明确指定的对齐方式。 例如,无论您为其容器设置什么内容方向,右对齐的内容都会 粘在右侧。 子元素的视觉顺序会根据内容的方向而有所不同。