データ参照と式
テンプレートとは、穴の空いたページのことです。参照はその穴の空け方です。
レンダリングのたびに変わる文字を選んで参照を押すと、その文字は {{customer.name}}
になり、同じ操作でテンプレートのデータスキーマに customer.name が加わります。
歩調を合わせ続けるべき第二の文書は存在しません。
2種類のデータ
Section titled “2種類のデータ”変数は1文書につき1回現れる値です。顧客名、発行日、件名など。宣言して例の値を与えると、 設計中のプレビューはその例を使います。
リストは繰り返される値です。請求書の明細、支払明細の行、ラベル用紙の上の製品。 リストは表かグリッドに供給され、1要素につき1行または1マスが描かれます。
この区別が効いてくるのは、それが呼び出し側が送るものそのものだからです —— 名前の付いたいくつかのフィールドを持つオブジェクト1つと、繰り返す部分ごとの配列1本。
波括弧の中に書けるもの
Section titled “波括弧の中に書けるもの”式は意図的に小さく作られています。値を読む、四則演算する、比較する、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 ? "お支払い金額" : "領収済み" }}集計: sum、avg、min、max、count。算術の補助: abs、round、ceil、floor。
文字列: 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 という変数を
宣言してレンダリング時に埋めてください。片方が空欄なら結果も空で、エラーにはなりません。
書式は | で当てます: number、date、currency(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、そしてテストに貼れるサンプルペイロード。
呼び出し側はいつでも取得できます。
curl https://composepdf.com/v1/canvases/{canvasId}/schema \ -H "x-api-key: $COMPOSEPDF_API_KEY"スキーマはレイアウトが走る前に強制され、違反は原因になったパスを名指しします。 詳細はデータスキーマにあります。
型と選択肢の宣言
Section titled “型と選択肢の宣言”変数はそれぞれ型を持ちます — 文字列、数値、日付、真偽、オブジェクト、リスト — そして 必須を付けられます。文字列・数値・日付の変数はさらに選択肢、つまり許される値の 閉じた集合を宣言できます。選択肢は公開されるスキーマに列挙型として渡るので、フォーム側は セレクトとして描けますし、集合の外にある値は顧客の文書に刷られる前に API の境界で弾かれます。
画像で文字を塗る (imageFill)
Section titled “画像で文字を塗る (imageFill)”テキストボックスは色ではなく絵でも塗れます。文字の輪郭がそのまま画像の見える窓になる ——ベクターエディタのクリッピングマスクと同じ仕組みです。文字と絵の両方が束縛できるので、 同じテンプレートから「言葉も、その後ろの絵も」データで変わります。
{ "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) を枠の角から敷き詰めます。fit と focal は読みません |
絵はテキストの箱を最大スパンの 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回描けば、同じページが出ます。