該頁面可以包含自動翻譯的文字。

複合容器

複合容器在結構化與組織內容方面扮演關鍵角色。使用合適的容器,您可以以友善的方式輕鬆呈現文字與圖片。

當然,只使用簡單的文字與圖片容器也完全可以建構文件。不過,有些需求僅靠簡單容器很難或根本無法實作。在這類情況下,複合容器就很有幫助。此外,複合容器也能以更少的程式碼達成您的目標。

複合容器

使用 LayoutContainer 類別的方法,將 RowColumn 這類複合容器加入文件頁面。借助這些容器,您可以實作網格與清單等其他容器。對於較少見但同樣重要的情況,還有 InlinedLayers 方法。

本文是關於 PDF 產生的 Layout API 系列文章之一。如果您是 API 新手,請先閱讀 Getting Started with Layout API 部分。

Row

Row 容器為水平排列在同一行中的項目提供空間。Row 中的每個項目都是一個容器,這表示您可以在同一個 Row 中放入不同類型的內容。您可以使用 Spacing 方法指定項目之間的間距。

Row 中所有項目的高度都相同。程式庫會使用最高項目的高度作為 Row 的高度。項目寬度有三種指定方式。建立項目時必須選擇其中一種。Row 可以包含以不同方式建立的項目。

Row.AutoItem 方法會建立未明確指定寬度的項目。對於這類項目,程式庫會計算其內容的內在大小。計算出的內容寬度就是項目的寬度。請注意,使用 AutoItem 建立的項目不會自動換行長行。

使用 ConstantItem 方法可建立寬度等於精確點數的項目。

當您不知道 Row 中項目的確切寬度,且又不想使用內在大小時,RelativeItem 就很方便。相反地,您可以為 Row 中的項目指定相對寬度。此方法接受項目應佔用的份數。總份數是此 Row 中所有 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 = Row 容器的寬度
AutoWidth = 使用 AutoItem 方法建立的所有項目的寬度
ConstantWidth = 使用 ConstantItem 方法建立的所有項目的寬度

以下範例建立了一個包含三種項目的 Row。

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 中的每個項目都是一個容器,因此您可以在同一個 Column 中放入不同類型的內容。

每個項目的寬度都等於 Column 寬度。每個項目的高度取決於項目的內容與屬性。Column 容器支援分頁,因此 Layout 附加元件可以將 Column 的項目渲染在多於一頁上。

預設情況下,Column 容器沒有頁首或頁尾內容。使用 HeaderFooter 方法可存取並設定對應的容器。當 Column 項目跨越多頁時,程式庫會在每一頁重複頁首與頁尾。

使用 Spacing 方法可在 Column 項目之間加入垂直空間。請注意,程式庫不會在頁首與第一個項目之間加入間距;也不會在頁尾前加入空間。

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 並未為網格提供特殊的容器類型。您可以使用 ColumnRow 容器實作網格版面。

可以把網格想成一個 Column,而每個項目都是一個 Row。ColumnRow 容器都提供設定項目間距的能力。若需要,也可以有頁首與頁尾。

每個 Row 都可以有獨立的版面。每一列的項目數量可以不同。項目可以有不同的寬度與高度。您也可以在 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 中看到程式碼的結果。

清單

清單可將資訊拆分成簡潔的要點,進而提升可讀性。清單項目可在文字旁顯示數字、項目符號及其他符號。您可以輕鬆使用 ColumnRow 容器實作清單版面。Layout API 並未為清單提供特殊的容器類型。

請查看建立依季節分類月份清單的範例程式碼。請注意,這個清單有頁首。如果項目包含可能換行到下一行的文字,請對項目的文字部分使用 RelativeItemConstantItem 方法。

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 方法,然後呼叫所提供 InlineContainerItem 方法來加入子容器。

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 中看到程式碼的結果。

範例程式碼

我們有一些示範應用程式,更詳細地涵蓋了前述功能。請花一些時間檢視它們。