このページには自動翻訳されたテキストを含めることができます。
コンテナーのサイズ、位置、およびレンダリング
文書において最も重要なのはコンテンツです。それは間違いありません。もう 1 つの重要な要素は、明確でプロフェッショナルかつ効果的な伝達を実現する書式設定です。適切に書式設定された文書は、見た目がよく、読みやすく、移動しやすくなります。
コンテナーを使ってコンテンツを整理する方法は、すでにご存じかもしれません。コンテナーに背景色を適用する方法も同様です。この記事では、コンテナーのサイズと位置を指定する方法を説明します。さらに、コンテンツの条件付きレンダリングのような高度な機能も扱います。右から左へのコンテンツ方向のサポートについても説明します。

LayoutContainer クラスは、コンテナーをプロフェッショナルに配置するために必要なすべてを提供します。パディングと配置を適用することで、ユーザーに好印象を与える文書を作成できます。
この記事は、PDF 生成向け Layout API シリーズの一部です。API を初めて使う場合は、先に Layout API の使い始め をお読みください。
サイズ
既定では、コンテナーはコンテンツに必要な最小限の領域を占有します。言い換えると、コンテナーのサイズはコンテンツの固有サイズに等しくなります。
画像の固有サイズは、画像ファイル自体の寸法によって決まります。テキスト スパンの固有サイズは、そのスパン内のすべてのグリフを囲む領域のサイズです。
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 にあります。ご覧のとおり、4 ページそれぞれに同じテキストがグレーの背景で表示されます。ただし、コンテナーのサイズは各ページで異なります。
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 の呼び出しにより、1 ページ目のテキストは必要なスペースだけを占有します。2 ページ目では、テキストがページ全体を覆います。
Scale
コンテナー内の任意のコンテンツを拡大縮小できます。Scale メソッドは、水平方向と垂直方向の両方のコンテンツに影響します。1 方向のみにコンテンツを変更したい場合は、ScaleHorizontal または ScaleVertical メソッドを使用します。後者 2 つのメソッドは、コンテンツのアスペクト比を保持しません。
1 未満の Scale 値は、コンテナーが占有する領域を縮小します。1 より大きい値は領域を拡大します。コンテナー内のコンテンツを反転するには、負の Scale 値を使用します。たとえば、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");
// 3 番目の項目には空行を使用
column.Item().Text(new string(' ', 20))
.BackgroundColor(new PdfRgbColor(187, 237, 237), 50);
column.Item().Text("Fourth item");
});
});
});
上記コードの結果は positioning-unconstrained.pdf にあります。コードでは、列の 3 番目の項目に半透明の背景を持つスペース 1 行を使用しました。ご覧のとおり、3 番目の項目は 2 番目の(制約のない)項目の一部を覆っています。
位置
コンテナーの位置は、いくつかの要素に依存します。配置、パディング、親コンテナーの位置、コンテンツ方向などです。既定では、どのコンテナーも使用可能な最も左上の位置に配置されます。
Padding
最も一般的な要件の 1 つは、コンテナーのコンテンツの周囲に余白を追加することです。LayoutContainer クラスには、コンテナーのパディング領域を設定するための一連のメソッドがあります。パディング領域は、コンテンツと境界線の間のスペースです。言い換えると、パディングはコンテンツを取り囲む内側のスペースです。
Padding メソッドは、コンテナーの 4 辺すべてのパディングを一度に設定します。左右だけのパディングを指定したい場合は PaddingHorizontal メソッドを使用します。PaddingVertical は上下だけに同じことを行います。各辺ごとに個別にパディングを設定するには、PaddingTop/Bottom/Left/Right メソッドのいずれかを使用します。
Align
コンテナーの位置を変更するには、配置メソッドを使用します。AlignLeft/AlignCenter/AlignRight メソッドは水平方向の配置を適用し、入れ子コンテナーを返します。AlignTop/AlignMiddle/AlignBottom メソッドは、対応する垂直方向の配置を持つ入れ子コンテナーを返します。
明示的に配置を適用したコンテナーは、必要最小限の幅および/または高さの領域を取ります。次のコードは 2 つの項目を持つ列を作成します。そのうち 1 つには明示的な配置が適用されています。
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 の呼び出しにより、両方の項目がページ全体を占有します。2 つ目の項目には AlignLeft メソッドを呼び出しています。この呼び出しでは位置は変わりません。項目のコンテナーは既定でテキストを左揃えにするためです。しかし、明示的に適用した配置により、2 つ目の項目が占有する領域は変わります。
上記コードの結果は positioning-alignment.pdf にあります。
Translate
コンテナーを水平方向および/または垂直方向に再配置するには、Translate/TranslateX/TranslateY メソッドを使用します。最初のメソッドは、コンテナーを水平方向と垂直方向の両方に移動します。残りの 2 つは、1 方向のみに移動します。
これらのメソッドはすべて位置を上書きしますが、サイズ制約は保持します。移動したコンテナーは他のコンテナーと重なり合う場合があります。負の値を使うと左および/または上に移動します。正の値を使うと右および/または下に移動します。
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 クラスには、コンテンツを回転する 2 つの方法があります。どちらの方法を使っても、回転したコンテンツを含むコンテナーは位置とサイズ制約を尊重します。
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
文書の一部は、同じレイアウトを使えます。たとえば、見た目が同じ境界線を設定したり、同じ書式を使ったりできます。DRY 原則に従い、共通コードはメソッドに切り出すことをお勧めします。
共通コードに拡張メソッドを使うと、次の 2 つの利点があります。
- メソッド呼び出しのチェーン内でそのメソッドを使えます
- メソッド呼び出しのまとまりに意味のある名前を付けられます
これらの利点により、ドメイン固有言語 (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 の項目を新しいページから開始するために使えます。
次のサンプル コードは、各ページに 2 行だけが入るように列を分割します。
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 メソッドを使うと、レイアウト エンジンにコンテンツを 1 回だけ完全にレンダリングするよう指示できます。
次のコードを見ると、ShowOnce 呼び出しによって "Environment" が 2 ページ目に表示されないことが分かります。
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
既定の動作では、1 ページに収まらないコンテンツはページ間で分割されます。ShowEntire メソッドを使用すると、コンテナー全体を 1 ページにレンダリングできます。
ライブラリは、コンテンツ全体を 1 ページに収められない場合、LayoutException をスローすることに注意してください。
次のコードでは ShowEntire を呼び出しているため、2 つ目の項目のテキストは 2 ページ目から始まります。呼び出しがなければ、1 つ目の項目のテキストの直後に 1 ページ目から始まります。
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 メソッドを使用すると、最大でも 1 ページだけに出力できます。コンテナーに対してこのメソッドを呼び出すと、ページ分割が防止されます。Layout アドオンは、このコンテナーのコンテンツをページ間で分割しません。1 ページに収まらないデータはレンダリングされません。
次のサンプル コードは、文書に 2 組のページを追加します。どちらの組も、曜日名の一覧をコンテンツとして使います。最初の組は 1 ページだけです。ページのコンテンツ コンテナーに対して 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 を使って 1 ページ目をスキップし、2 ページ目以降すべてにコンテンツをレンダリングします。
この機能はあらゆる種類のヘッダーに有用ですが、他のコンテンツにも使えます。次のコードで、SkipOnce 呼び出しがヘッダーを 1 ページ目に表示しないことを確認してください。
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 メソッドを使用します。
コンテンツ方向は、明示的に指定した配置には影響しないことに注意してください。たとえば、右揃えのコンテンツは、コンテナーにどのコンテンツ方向を設定しても右側に配置されます。子要素の表示順は、コンテンツの方向によって異なります。