メインコンテンツまでスキップ

レイアウトXML

レイアウトルート

「レイアウトルート」はレイアウトXMLのルート要素で、用紙レベルのレイアウトを設定します。 <LinearLayout><StackLayout> の2つがありますが、通常は取り扱いが簡単な <LinearLayout> の使用を推奨します。

また、レイアウトルートでは、以下のようにXML Schemaを参照することができます。 XML Schemaは必須ではありませんが、参照することで、XML Schema対応エディターで入力補完やバリデーションが効くようになります。

<LinearLayout
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schemas.yagisan.app/2025.1/layout.xsd"
>

<LinearLayout>

リニアルールに基づいてレイアウトを行います。

  • 改ページに対応しており、各ページに繰り返し表示するヘッダーとフッターを定義することができます。
  • 請求書や一覧表のような明細が動的に変化し、複数ページにわたる可能性がある帳票に向いています。

以下が記述例です。

  • <LayoutBody> は必須です。
  • <LayoutHeader><LayoutFooter> は省略可能です。記述する場合は height 属性が必須です。
<LinearLayout
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schemas.yagisan.app/2025.1/layout.xsd"
size="A4"
orientation="portrait"
>
<LayoutHeader height="15">
<Text>ヘッダー</Text>
</LayoutHeader>
<LayoutBody>
<Text>本文</Text>
</LayoutBody>
<LayoutFooter height="20">
<Text>フッター</Text>
</LayoutFooter>
</LinearLayout>

<StackLayout>

スタックルールに基づいてレイアウトを行います。

  • 改ページには対応していませんが、任意のPDFを背景として使用することができます。
  • 既存PDFをひな形にして追記したり、窓付き封筒の宛名面のような厳密に位置指定をしたい帳票に向いています。

以下が記述例です。

  • <StackLayout> の直下に描画要素を記述します。
<StackLayout
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:noNamespaceSchemaLocation="https://schemas.yagisan.app/2025.1/layout.xsd"
size="A4"
orientation="portrait"
>
<Text x="20" y="30" width="100" height="40">本文</Text>
</StackLayout>

以下のように basePdf 属性を使うことで、既存のPDFをページの背景に設定できます。 basePdf 属性には、アセットやテンプレート変数を指定しますが、これらの指定方法については後ほど解説します。

<StackLayout
size="A4"
orientation="portrait"
basePdf="@{pdf}"
>

用紙の設定

レイアウトルート要素では、用紙関連の属性を指定できます。

  • size : 用紙のサイズ
    • A4, B5, Letter など、一般的な用紙サイズを指定できます。
    • スペース区切りで2値を記述することで、カスタムサイズも指定可能です。例えば 91 55 と記載すると「91mm × 55mm」となります。
  • orientation : 用紙の向き
    • portrait(縦向き)または landscape(横向き)を指定できます。
  • margin : 用紙の余白
    • CSSの margin のように、1-4個の値で上下左右の余白の大きさを指定できます。
    • 例えば 10 と指定すると、4辺すべての余白が10mmになります。10 20 と指定すると、上下10mm・左右20mmの余白となります。

コンポーネント

帳票内に配置するテキスト、画像、図形、表、レイアウト用のコンテナなどの要素を総称して「コンポーネント」と呼びます。 コンポーネントはレイアウトルートやコンテナ系コンポーネントの配下に記述して使います。

提供されているコンポーネントは以下の通りです。詳細はテンプレートリファレンスを参照してください。

  • テキスト系コンポーネント
    • <Text> : 横書きテキスト
    • <VText> : 縦書きテキスト
    • <Link> : ハイパーリンク
    • <ColumnText> : 文字を1文字ずつセルに分割して配置
      • 法人番号のようなコードを桁ごとに分割して取り扱うことに特化しています。
    • <RichText> : 装飾付きテキスト
  • 図画系コンポーネント
    • <Image> : 画像
    • <Rectangle> : 長方形
    • <Ellipse> : 楕円
  • コンテナ系コンポーネント
    • <LinearBlock> : リニアルールのシンプルなコンテナ
    • <StackBlock> : スタックルールのシンプルなコンテナ
    • <Grid> : 行と列で構成されるグリッド構造を持つコンテナ
    • <Table> : 入力したデータを表形式で描画(可変行明細)
    • <Frame> : <Grid><Table> に改ページ対応のヘッダー・フッターを追加するコンテナ
  • その他のコンポーネント
    • <Spacer> : 余白の挿入

yagisan-reportsのレイアウトシステムの大きな特長として、コンテナ系コンポーネントが入れ子にできる(ネスト可能)であることが挙げられます。 これにより、複雑なレイアウトを柔軟に表現することができます。

テンプレート変数

帳票テンプレートでは、変数(テンプレート変数)を使用して、動的に内容を変更することができます。

テンプレート変数は ${value} の形式で記述します。

<Text color="${color}">${message}</Text>

アプリケーション側では、テンプレート変数へ代入する値は generate() の引数でオブジェクトを入力します。

テンプレート変数へ入力できるデータは、JSONとして表現できるデータ型およびバイナリデータ(ArrayBufferUint8Array)です。 バイナリデータは、画像やPDFを入力する際に使用します。

const report = await generator.generate({
color: 'red',
message: 'hello!\nyagisan-reports!!'
});

テンプレート変数はレイアウトXMLの以下の場所で指定できます。

  • テキストコンテンツ(テキスト系コンポーネントのコンテンツ)
  • 各要素の属性値のうち、「テンプレート変数が使用可能」と指定があるもの

テキストコンテンツでは以下のように、文字列の一部としてテンプレート変数を埋め込むことも可能です。

<Text>Hello, ${name}!</Text>

ドット記法

${foo.bar.baz.qux} のように「ドット区切り」で記述することで、ネストしたオブジェクトのプロパティを参照させることができます。

<Text>${currentUser.name}</Text>
const report = await generator.generate({
currentUser: {
id: 123,
name: '帳票太郎',
}
});

ブラケット記法

${foo[0]} のように「ブラケット」で配列添字を記述することで、配列の要素を参照させることができます。

<Text>${users[0]}</Text>
const report = await generator.generate({
users: ['帳票太郎', '帳票花子'],
});

演算子

! 演算子(NOT演算子)のみサポートしています。

${!enabled} のように記述します。 Boolean 型の属性でのみ使用可能です。

定義済みのテンプレート変数

yagisan-reportsでは、定義済みテンプレート変数が用意されています。 テキスト系コンポーネントのコンテンツで使用可能です。

ページ情報

以下のテンプレート変数は、<StackLayout> <LayoutHeader> <LayoutFooter> 要素の配下でのみ使用できます。

変数説明
${Report.page}帳票全体のページ番号
${Report.totalPages}帳票全体の総ページ数
${Layout.page}レイアウトごとのページ番号
${Layout.totalPages}レイアウトごとの総ページ数

例えば、帳票テンプレート内に「表紙」「明細」「裏表紙」という3つのレイアウトXMLが存在し、「表紙と裏表紙は1ページで、明細が3ページに渡る場合」では、変数の値は以下のようになります。

  • 表紙
    • ${Report.page} : 1
    • ${Layout.page} : 1
  • 明細1枚目:
    • ${Report.page} : 2
    • ${Layout.page} : 1
  • 明細2枚目:
    • ${Report.page} : 3
    • ${Layout.page} : 2
  • 明細3枚目:
    • ${Report.page} : 4
    • ${Layout.page} : 3
  • 裏表紙:
    • ${Report.page} : 5
    • ${Layout.page} : 1

アセット

帳票テンプレートに登録されているアセットは、レイアウトXMLから参照できます。

アセットの登録は、帳票テンプレートの作成時に行います。 以下のように --asset path@name の形式で、アセットのパスと名称を指定します。

npx yagisan yrt pack \
--asset /path/to/asset.png@asset1 \
--out template.yrt \
layout.xml

レイアウトXMLからアセットを参照するには @{name} の形式で指定します。

<Image src="@{asset1}" width="100" height="100" />

制御構造

logic 属性により制御構造を実現できます。 この属性は大半の要素で使用可能ですが、一部の要素は非対応です。

if

logic="if:${template_variable}" を指定した要素は、真と判定された場合のみ、XML上に存在するものとして処理されます。

  • テンプレート変数は Boolean 型の値として評価されます。
    • どのように評価されるかについては、 Boolean 型のドキュメントを参照してください。
  • テンプレート変数が false と評価された場合、要素がXML上に存在しないものとして扱われ、後続のレイアウト処理や描画処理の対象となりません。
<Text logic="if:${enabled}">trueの場合だけ描画されます</Text>

unless

logic="unless:${template_variable}" を指定した要素は、偽と判定された場合のみ、XML上に存在しないものとして処理されます。 if とは反対の動作をします。

<Text logic="if:${disabled}">falseの場合だけ描画されません</Text>

for

for:${template_variable} を指定した要素は、入力した配列の要素ごとに繰り返し展開されます。

  • テンプレート変数は Array 型の値として評価されます。
  • テンプレート変数に入力した配列の要素が0個だった場合は、要素がXML上に存在しないものとして処理されます。
<Text logic="for:${items}">こんにちは!</Text>

また、forでループを展開する際に、テンプレート変数のコンテキストが配列の要素に変更されます。

<Text logic="for:${items}">${name}</Text>
const report = await generator.generate({
items: [
{name: 'タンバリン' },
{name: 'ピアノ' },
{name: 'シンバル' },
{name: 'ドラム' },
],
});

このような場合、以下のように描画されます。

タンバリン
ピアノ
シンバル
ドラム

改ページ制御

<LinearLayout> では、コンテンツが1ページ内に収まらなかった場合に、自動で改ページを行います。

要素の途中で改ページが必要になった場合、改ページ位置は自動的に決定します。 制御方法は複雑ですが、基本的には以下のように動作します。

  • <Spacer> の場合、現在のページには余白の途中までを描画し、次のページでは残りの余白を描画しません。
  • <Text> <Grid> <Table> などの行で区切り位置が判定できる要素の場合、ページを跨いでしまう行は次のページに送ります。
  • <Image> <Rectangle> <Ellipse> などの明確な区切り位置が判定できない要素の場合、要素全体を次のページに送ります。

ただし、<Image> が元々用紙よりも大きなサイズであるようなケースなど、どうやっても改ページ制御がうまくいかない場合は、強制的に要素を分割して描画します。

また、<Text> <Grid> <Table> などの改ページ位置が自動判定できる要素では、breakInside 属性を指定することで改ページルールを変更できます。 breakInside="avoid" を指定した要素は、<Image>などと同様に、要素の途中での改ページをできるだけ避けるように動作します。