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

容器的大小、位置和渲染

內容是文件最重要的部分。 這毋庸置疑。 另一個關鍵部分是創建清晰、專業和有效溝通的格式。 格式正確的文 件具有視覺吸引力、可讀性且易於瀏覽。

您可能已經知道如何使用容器組織內容。 以及如何為它們應用背景顏色。 本文介紹如何指定容器的大小和位 置。 它還涵蓋了內容的條件渲染等高級功能。 有有關支持從右到左內容方向的資訊。

貨櫃定位

LayoutContainer 類別提供了專業地安排容器所需的一切。 透過套用填滿和對齊,您可以建立給使用者留下正 面印象的文件。

本文是用於 PDF 產生的 La​​yout API 的系列文章的一部分。 如果您是 API 新手,請先閱讀 Layout API 入 門 部分。

Docotic.Pdf 函式庫 9.4.17351-dev 佈局附加元件 9.4.17351-dev
回歸測試 14,730 已通過 NuGet 總下載量 4,297,043

尺寸

預設情況下,容器佔據其內容所需的最小區域。 換句話說,容器的大小等於其內容的固有大小。

影像的固有大小由影像檔案本身的尺寸決定。 文字範圍的固有大小是覆蓋該範圍中所有字形的區域的大小。

ColumnTable 這樣的複合容器的大小取決於容器部件的大小。

Width & Height

可以使用 WidthHeight 方法指定容器的精確寬度和高度。 這對於佔位符容器來說非常方便。

精確的尺寸也適用於影像。 這是因為 Layout API 根據它們的 ImageContentMode 縮放它們以適合或填充容 器。

您應該注意複合容器和帶有文字的容器的確切尺寸。 當無法適應提供的尺寸的內容時,您將收到 LayoutException

在某些情況下,您只想設定寬度或高度的限制。 您可以使用 MinWidthMinHeightMaxWidthMaxHeight 方法設定約束。

請注意,當無法滿足約束時,程式庫將拋出 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 方法影響水平和垂直方向的內容。 使用 ScaleHorizontalScaleVertical方法僅在一個方向上更改內容。 最後兩種方法不保留內容的縱橫比。

小於 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 度

RotateRightRotateLeft 方法分別將內容順時針和逆時針旋轉 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

從某種意義上說,EnsureSpaceShowEntire 方法的一個特例。 不同之處在於 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 方法。

請注意,內容方向不會影響明確指定的對齊方式。 例如,無論您為其容器設定什麼內容方向,右對齊的內容都會 黏在右側。 子元素的視覺順序會根據內容的方向而有所不同。