コンテンツにスキップ

データ参照と式

テンプレートとは、穴の空いたページのことです。参照はその穴の空け方です。 レンダリングのたびに変わる文字を選んで参照を押すと、その文字は {{customer.name}} になり、同じ操作でテンプレートのデータスキーマに customer.name が加わります。 歩調を合わせ続けるべき第二の文書は存在しません。

変数は1文書につき1回現れる値です。顧客名、発行日、件名など。宣言して例の値を与えると、 設計中のプレビューはその例を使います。

リストは繰り返される値です。請求書の明細、支払明細の行、ラベル用紙の上の製品。 リストは表かグリッドに供給され、1要素につき1行または1マスが描かれます。

この区別が効いてくるのは、それが呼び出し側が送るものそのものだからです —— 名前の付いたいくつかのフィールドを持つオブジェクト1つと、繰り返す部分ごとの配列1本。

式は意図的に小さく作られています。値を読む、四則演算する、比較する、2つの結果から選ぶ、 書式を当てる、リストを集計する。できるのはそれだけです。

{{ customer.name }}
{{ item.qty * item.price }}
{{ subtotal * 0.1 | number(0) }}
{{ issueDate | date("yyyy/MM/dd") }}
{{ issueDate | date("Gee年MM月dd日") }}
{{ total | currency("JPY") }}
{{ sum(items, i => i.qty * i.price) }}
{{ balance > 0 ? "お支払い金額" : "領収済み" }}

集計: sumavgminmaxcount。算術の補助: absroundceilfloor。 文字列: substr(text, start[, len]) — 文字単位の切り出しで、範囲外はクランプ。 1つの値を複数の枠グループへ割る様式(郵便番号の 3-4、年月日)で、テキストの マス目(comb)と組み合わせて使います。 contains(text, part)startsWith(text, part)endsWith(text, part)== では 書けない部分一致。「株式会社あいうえお牧場」も「株式会社あいうえお第二牧場」も contains(customer.name, "あいうえお") で拾えます。 日付: age(誕生日, 基準日) は満年齢(誕生日当日に繰り上がる)、dateDiff(開始日, 終了日) は日数(終了日が前なら負)。どちらも ISO 日付を受けます。today() はありません — エンジンは 時計を読まないので、基準日はデータです。issueDate を渡すか、today という変数を 宣言してレンダリング時に埋めてください。片方が空欄なら結果も空で、エラーにはなりません。 書式は | で当てます: numberdatecurrency(JPY, USD, EUR)。 日付には和暦トークンがあります: G が元号、ee が元号年(ゼロ詰め)で、 date("Gee年MM月dd日")令和08年08月09日 になります。データは ISO 日付 (2026-08-09)のまま渡してください — 書式はテンプレートが持ちます。 パターンに書いたトークンだけが出るので、date("ee") は元号年の 08 だけを 返します — 年・月・日を別々の枠グループに書く様式では、substr の代わりに これをマス目(comb)と組み合わせてください。書式は空文字を素通しします: 未入力の欄は空欄のまま紙に出て、エラーにはなりません。

**ループも代入も、関数を定義する手段もありません。**この言語にはそれらが存在しません。 これは足りない機能ではなく意図的な境界です。他人が書いたテンプレートを安全に実行できるのは このためで、また、何も実行せずに「そのテンプレートがどのデータパスを読むか」を正確に 割り出せるのもこのためです。

スキーマはテンプレートから落ちてくる

Section titled “スキーマはテンプレートから落ちてくる”

すべての参照が静的に見つかるので、ペイロードの形は手書きではなく導出されます。 スタジオでデータスキーマを開くと、それが4つの形で得られます: JSON Schema、 TypeScript、Zod、そしてテストに貼れるサンプルペイロード。

呼び出し側はいつでも取得できます。

Terminal window
curl https://composepdf.com/v1/canvases/{canvasId}/schema \
-H "x-api-key: $COMPOSEPDF_API_KEY"

スキーマはレイアウトが走る前に強制され、違反は原因になったパスを名指しします。 詳細はデータスキーマにあります。

変数はそれぞれ型を持ちます — 文字列、数値、日付、真偽、オブジェクト、リスト — そして 必須を付けられます。文字列・数値・日付の変数はさらに選択肢、つまり許される値の 閉じた集合を宣言できます。選択肢は公開されるスキーマに列挙型として渡るので、フォーム側は セレクトとして描けますし、集合の外にある値は顧客の文書に刷られる前に API の境界で弾かれます。

テキストボックスは色ではなくでも塗れます。文字の輪郭がそのまま画像の見える窓になる ——ベクターエディタのクリッピングマスクと同じ仕組みです。文字と絵の両方が束縛できるので、 同じテンプレートから「言葉も、その後ろの絵も」データで変わります。

{
"id": "headline", "type": "text",
"frame": { "x": 15, "y": 36, "w": 180, "h": 46 },
"para": { "align": "center" },
"imageFill": { "src": "{{candy}}", "tile": { "w": 15, "h": 15 } },
"spans": [{ "text": "{{title}}", "style": { "size": 96 } }]
}

src は画像の id か、それに解決される式です。絵の入れ方は4つあります。

モード 何が起きるか
cover (既定) 枠を埋めて溢れを切ります。どこを残すかは focal ([x, y]、既定 [0.5, 0.5])
contain 絵の全体が枠に収まり、余った文字は空きます
stretch 絵を枠に合わせて変形します
tile tile: { w, h } (mm) を枠の角から敷き詰めます。fitfocal は読みません

絵はテキストの箱を最大スパンの 1em ぶん四方に広げた矩形に置かれるので、行の箱からはみ出すアクセントやディセンダにも絵が乗ります。タイルの位相は枠の角に揃ったままです。

  • 文字は本物のテキストのままです。輪郭に沿って切り抜かれるだけで図形にはならないので、 PDF の中で検索・コピー・抽出ができ、タグ付けも残ります。
  • tile は1つの枠あたり 4096 枚までです。枚数はデータではなく枠 (1em の余白込み。 grow するノードは紙の高さで数えます) ÷ タイルの割り算で決まるので、4096 枚を超える タイルは描く前に弾かれます (数百万個の描画命令を持つ文書になるのを防ぐため)。
  • スパンの文字色は使われません。エラーにはなりません — 絵が塗りの代わりになるだけです。 文字の後ろに敷く蛍光ペンはそのまま描かれます。

文字の内側に素材を撒く (scatterFill)

Section titled “文字の内側に素材を撒く (scatterFill)”

見出しのもう1つの塗り方は、文字を物の集まりで作ることです。エンジンが文字の輪郭を 歩き、内側を格子で埋めて、その各点にベクターの素材を1つずつ落とします。結果として、 言葉が活字ではなく「キャンディの山」「紙吹雪」「葉の重なり」として読めます。imageFill が文字の縁で平らに切るのに対し、撒かれた素材は縁からはみ出すので、形が 「マスクで抜いた」ではなく「積み上げてできた」ように見えます。

{
"id": "headline", "type": "text",
"frame": { "x": 15, "y": 44, "w": 180, "h": 56 },
"para": { "align": "center" },
"scatterFill": {
"stamps": [
{ "viewBox": [100, 100],
"paths": [
{ "d": "M46 52L54 52L54 100L46 100Z", "fill": { "space": "rgb", "r": 0.85, "g": 0.78, "b": 0.6 } },
{ "d": "M85 40C85 59.33 69.33 75 50 75C30.67 75 15 59.33 15 40C15 20.67 30.67 5 50 5C69.33 5 85 20.67 85 40Z", "fill": { "space": "rgb", "r": 0.93, "g": 0.25, "b": 0.38 } }
] }
],
"size": { "min": 5, "max": 8 },
"spacing": 4,
"seed": 7
},
"spans": [{ "text": "{{title}}", "style": { "size": 96, "font": "noto-bold" } }]
}
何が起きるか
stamps 撒く素材。1つ以上。それぞれ viewBox ([w, h]) と、vector ノードと同じ形の平坦化済み paths を持ちます。1回の配置ごとに等確率で1つ選ばれます
size { min, max } (mm)。置いた素材の長辺をこの範囲から引きます
spacing 配置点の間隔 (mm)。輪郭の上も内側の格子も同じ間隔です。既定は size.max × 0.7 — 最大の大きさでわずかに重なるので、字が点線ではなく1つの塊に見えます
density 配置点に実際に素材を置く確率 ([0, 1])。既定の 1 は全部の点に置きます
rotate 素材ごとに 5°刻み 72 段のどれかへ回します。既定 true
seed 配置の種 (既定 1)。同じ種・同じ文字なら、どの機械で何度刷っても同じ並びになります。並びを変えたいときはここを変えます
  • 文字は本物のテキストのままです。中身が空のクリップとして出す — 開いて、何も塗らずに 閉じる — ので、活字は1画も見えないのに、PDF の中では検索・コピー・抽出ができ、タグ付けも 残ります。文字は他のテキストと同じように束縛でき、言葉が変われば新しい字の形に沿って 撒き直されます。
  • 素材は埋め込みで、参照ではありません。 stamps の各要素が自分のパスを持つので、 素材の出どころに何があってもテンプレートは同じ結果を刷り続けます。
  • imageFill とは排他です。1つのテキストボックスの塗りは1つだけで、両方書いた テンプレートは描く前に弾かれます (text-paint-exclusive)。黙ってどちらかを選ぶことは しません。
  • 1つの枠あたり 素材 3000個・項目 20000・素材の合計 4MB までです。4MB は stamps そのものの重さで、コピーは素材を繰り返さず参照で置くので、重いイラストでも置ける数は 軽い素材と変わりません。数はデータではなく 枠の半分 ÷ 間隔² (墨が枠の半分を超えることはまず無い) で 決まるので、0.1mm の間隔を書いても1語が数百万個の描画命令にはなりません。見積りが上限の 4倍を超えるテンプレートは描く前に弾かれます (scatter-fill-count) —— これは設計ではなく 書き損じだからです。その内側なら刷れます: 上限に届く散布は間隔を広げて全文に素材を乗せ、 実際に使った間隔を scatter-fill-thinned の warning で知らせます。狭い枠で密な見た目が 欲しいときは、間隔を自分で指定してください。
  • スパンの文字色は使われません (imageFill と同じ)。素材が自分の色を持っています。 文字の後ろに敷く蛍光ペンはそのまま描かれます。
  • パスは宣言して初めてスキーマの一部になります。 スタジオはテンプレートが読むパスを一覧し、 1操作で宣言することを提案するので、黙って未宣言のまま残るものはありません。
  • 宣言の無いテンプレートも描けます。 宣言が1つも無いテンプレートはスキーマと照合されません。 スキーマが無かった頃の古い挙動で、既存のテンプレートが動き続けるように残してあります。
  • 式はペイロードの外へ手を伸ばせません。 ネットワークもファイルも時計もありません。 同じペイロードを2回描けば、同じページが出ます。