diff --git a/PLAN.md b/PLAN.md
index b578524d..377310fc 100644
--- a/PLAN.md
+++ b/PLAN.md
@@ -1,6 +1,6 @@
# JSX / md2pdf ロードマップ
-最終更新: 2026-05-09
+最終更新: 2026-05-11
## 目的
@@ -44,6 +44,13 @@ rich content の応用範囲を磨く。
- `Document` root、margin-aware な `Header` / `Footer`、page 全体座標の `Static` は入った。Templates starter / docs
を通じて repeated layer、margin area、`Absolute` origin が直感とズレないかを確認し、必要なら wording やサンプルを直す。
- `Header` / `Footer` の実用パターンとして、page number、repeated title、watermark / stamp などを starter に残す。
+- React に慣れた人と AI authoring を主な利用者として意識する。`Text` / MVT は JSX render 時に高さを測れるため、
+ starter / docs では「通常は `height` を書かない」体験に寄せる。固定 field、画像、absolute overlay など
+ 明示寸法が意味を持つ場所だけ `height` を書く。
+- POC playground (`react-pdfme-jsx`) の実用サンプルを参考に、invoice、form、report、research paper、Japanese business
+ document、header/footer/page number、watermark / badge などの starter を増やす。ただし POC 専用 API
+ (`autoHeight`, `staticHeader`, `For` など) はそのまま持ち込まず、現行の `Document` / `Header` / `Footer` /
+ 通常の JS 配列処理へ寄せる。
- 編集の正は generated `Template + inputs`。JSX source は初期生成 / regeneration 用の metadata として扱う。
Designer で編集した template を JSX に自動逆変換することは当面目指さない。
- JSX playground は source authoring と Viewer preview に寄せる。Form 入力確認は生成方法に依存しない共通 project 導線として
@@ -63,7 +70,8 @@ rich content の応用範囲を磨く。
- Form 入力や dynamic layout reflow で `Text` / MVT が runtime に `overflow: "expand"` した場合、
JSX の親 `Box` / container は自動では広がらない。生成後の `Box` は rectangle schema であり、
子 schema の実描画高さと親 container を結び直す contract がまだないため。親子 container dynamic
- layout は将来の設計課題として扱う。
+ layout は実装したい重要課題として扱う。まずは JSX から生成された visual `Box` と子 text/MVT の関係を
+ metadata として template に残すか、単一 text/MVT 子の decoration に畳むかを比較する。
- `Absolute` は `Page`, top `Static`, `Box` 内の小さな escape hatch として扱う。`Stack` / `Row`
直下対応、anchor / top-right / bottom-right shorthand、z-index 的な描画順制御は必要性が出てから検討する。
diff --git a/packages/generator/__tests__/__image_snapshots__/jsx-form-fields-1.png b/packages/generator/__tests__/__image_snapshots__/jsx-form-fields-1.png
index 11e518b2..45cd2608 100644
Binary files a/packages/generator/__tests__/__image_snapshots__/jsx-form-fields-1.png and b/packages/generator/__tests__/__image_snapshots__/jsx-form-fields-1.png differ
diff --git a/packages/generator/__tests__/__image_snapshots__/jsx-invoice-1.png b/packages/generator/__tests__/__image_snapshots__/jsx-invoice-1.png
index 1a7f03bb..f3ad3056 100644
Binary files a/packages/generator/__tests__/__image_snapshots__/jsx-invoice-1.png and b/packages/generator/__tests__/__image_snapshots__/jsx-invoice-1.png differ
diff --git a/packages/generator/__tests__/__image_snapshots__/jsx-invoice-2.png b/packages/generator/__tests__/__image_snapshots__/jsx-invoice-2.png
index 8fb73df3..77f7d9d0 100644
Binary files a/packages/generator/__tests__/__image_snapshots__/jsx-invoice-2.png and b/packages/generator/__tests__/__image_snapshots__/jsx-invoice-2.png differ
diff --git a/packages/generator/__tests__/__image_snapshots__/jsx-japanese-notice-1.png b/packages/generator/__tests__/__image_snapshots__/jsx-japanese-notice-1.png
index 54d9e5c7..8ae6a4cb 100644
Binary files a/packages/generator/__tests__/__image_snapshots__/jsx-japanese-notice-1.png and b/packages/generator/__tests__/__image_snapshots__/jsx-japanese-notice-1.png differ
diff --git a/packages/generator/__tests__/__image_snapshots__/jsx-report-1.png b/packages/generator/__tests__/__image_snapshots__/jsx-report-1.png
index ff610241..a3ea6b64 100644
Binary files a/packages/generator/__tests__/__image_snapshots__/jsx-report-1.png and b/packages/generator/__tests__/__image_snapshots__/jsx-report-1.png differ
diff --git a/packages/generator/__tests__/__image_snapshots__/jsx-research-paper-1.png b/packages/generator/__tests__/__image_snapshots__/jsx-research-paper-1.png
new file mode 100644
index 00000000..ecda0915
Binary files /dev/null and b/packages/generator/__tests__/__image_snapshots__/jsx-research-paper-1.png differ
diff --git a/packages/generator/__tests__/__image_snapshots__/jsx-research-paper-2.png b/packages/generator/__tests__/__image_snapshots__/jsx-research-paper-2.png
new file mode 100644
index 00000000..95c38fc6
Binary files /dev/null and b/packages/generator/__tests__/__image_snapshots__/jsx-research-paper-2.png differ
diff --git a/packages/jsx/src/__tests__/render.test.tsx b/packages/jsx/src/__tests__/render.test.tsx
index 97eb147e..9da85345 100644
--- a/packages/jsx/src/__tests__/render.test.tsx
+++ b/packages/jsx/src/__tests__/render.test.tsx
@@ -359,6 +359,23 @@ describe('@pdfme/jsx renderToTemplate', () => {
);
});
+ it('advances Stack children by auto-measured Text height', async () => {
+ const result = await renderToTemplate(
+
+
+
+ This text wraps and does not specify height.
+
+ After measured text
+
+ ,
+ );
+
+ const [measured, after] = result.template.schemas[0] ?? [];
+ expect(measured?.height).toBeGreaterThan(0);
+ expect(after?.position.y).toBeCloseTo((measured?.height ?? 0) + 2);
+ });
+
it('distributes Row width across flex children', async () => {
const result = await renderToTemplate(
@@ -555,6 +572,31 @@ describe('@pdfme/jsx renderToTemplate', () => {
});
});
+ it('grows Box background around auto-measured child content', async () => {
+ const result = await renderToTemplate(
+
+
+
+
+ Auto measured text inside a visual Box.
+
+
+ After box
+
+ ,
+ );
+
+ const [box, text, after] = result.template.schemas[0] ?? [];
+ expect(text?.height).toBeGreaterThan(0);
+ expect(box).toMatchObject({
+ type: 'rectangle',
+ color: '#eeeeee',
+ position: { x: 0, y: 0 },
+ height: (text?.height ?? 0) + 4,
+ });
+ expect(after?.position.y).toBeCloseTo(box?.height ?? 0);
+ });
+
it('does not render a rectangle schema for a Box without visual styles', async () => {
const result = await renderToTemplate(
diff --git a/playground/public/template-assets/jsx-form-fields/template.json b/playground/public/template-assets/jsx-form-fields/template.json
index 5726af55..fcfafa27 100644
--- a/playground/public/template-assets/jsx-form-fields/template.json
+++ b/playground/public/template-assets/jsx-form-fields/template.json
@@ -18,7 +18,7 @@
"y": 0
},
"width": 174,
- "height": 7,
+ "height": 3.273984,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -43,7 +43,7 @@
"y": 279
},
"width": 174,
- "height": 6,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -72,7 +72,7 @@
"y": 18
},
"width": 174,
- "height": 12,
+ "height": 9.003456,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -91,13 +91,13 @@
{
"name": "text_4",
"type": "text",
- "content": "Switch the preview to Form and edit the fields directly.",
+ "content": "Open this generated template in Form/Viewer to edit the fields.",
"position": {
"x": 18,
- "y": 37
+ "y": 34.003456
},
"width": 174,
- "height": 8,
+ "height": 3.683232,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -118,10 +118,10 @@
"type": "rectangle",
"position": {
"x": 18,
- "y": 52
+ "y": 44.686688000000004
},
"width": 84,
- "height": 24,
+ "height": 21.864736,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -136,10 +136,10 @@
"content": "Customer name",
"position": {
"x": 22,
- "y": 56
+ "y": 48.686688000000004
},
"width": 76,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -161,7 +161,7 @@
"content": "Mina Carter",
"position": {
"x": 22,
- "y": 63
+ "y": 53.551424000000004
},
"width": 76,
"height": 9,
@@ -198,10 +198,10 @@
"type": "rectangle",
"position": {
"x": 108,
- "y": 52
+ "y": 44.686688000000004
},
"width": 84,
- "height": 24,
+ "height": 21.864736,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -216,10 +216,10 @@
"content": "Email",
"position": {
"x": 112,
- "y": 56
+ "y": 48.686688000000004
},
"width": 76,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -241,7 +241,7 @@
"content": "mina@example.com",
"position": {
"x": 112,
- "y": 63
+ "y": 53.551424000000004
},
"width": 76,
"height": 9,
@@ -278,10 +278,10 @@
"type": "rectangle",
"position": {
"x": 18,
- "y": 83
+ "y": 73.551424
},
"width": 174,
- "height": 34.093024,
+ "height": 31.95776,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -296,10 +296,10 @@
"content": "Message",
"position": {
"x": 22,
- "y": 87
+ "y": 77.551424
},
"width": 166,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -321,7 +321,7 @@
"content": "{\"firstName\":\"Mina\",\"plan\":\"Growth\",\"status\":\"Ready for review\"}",
"position": {
"x": 22,
- "y": 94
+ "y": 82.41615999999999
},
"width": 166,
"height": 19.093024,
@@ -365,10 +365,10 @@
"type": "rectangle",
"position": {
"x": 18,
- "y": 124.093024
+ "y": 112.509184
},
"width": 56,
- "height": 43,
+ "height": 40.864736,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -383,10 +383,10 @@
"content": "Logo upload",
"position": {
"x": 22,
- "y": 128.093024
+ "y": 116.509184
},
"width": 48,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -408,7 +408,7 @@
"content": "",
"position": {
"x": 22,
- "y": 135.093024
+ "y": 121.37392
},
"width": 48,
"height": 28,
@@ -421,10 +421,10 @@
"type": "rectangle",
"position": {
"x": 80,
- "y": 124.093024
+ "y": 112.509184
},
"width": 112,
- "height": 38,
+ "height": 17.2588832,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -436,13 +436,13 @@
{
"name": "text_9",
"type": "text",
- "content": "This preset keeps the generated template editable. The Form preview writes changed input values back into the playground state, so Generate PDF uses the latest edits.",
+ "content": "Editable fields usually keep explicit heights for predictable input boxes. Read-only text can usually omit height and let JSX measure it.",
"position": {
"x": 84,
- "y": 128.093024
+ "y": 116.509184
},
"width": 104,
- "height": 30,
+ "height": 9.2588832,
"rotate": 0,
"opacity": 1,
"readOnly": true,
diff --git a/playground/public/template-assets/jsx-invoice/template.json b/playground/public/template-assets/jsx-invoice/template.json
index 37c81384..e6eb629c 100644
--- a/playground/public/template-assets/jsx-invoice/template.json
+++ b/playground/public/template-assets/jsx-invoice/template.json
@@ -15,10 +15,10 @@
"content": "@pdfme/jsx beta",
"position": {
"x": 16,
- "y": 3
+ "y": 0
},
"width": 80,
- "height": 6,
+ "height": 3.273984,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -37,13 +37,13 @@
{
"name": "text_2",
"type": "text",
- "content": "Header / Footer / Absolute",
+ "content": "Header and Footer repeat on every page",
"position": {
"x": 96,
- "y": 3
+ "y": 0
},
"width": 80,
- "height": 6,
+ "height": 3.273984,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -79,10 +79,10 @@
"content": "Generated from JSX",
"position": {
"x": 16,
- "y": 281.8
+ "y": 279.3
},
"width": 80,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -104,10 +104,10 @@
"content": "Page {currentPage} of {totalPages}",
"position": {
"x": 96,
- "y": 281.8
+ "y": 279.3
},
"width": 54,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -175,10 +175,10 @@
"content": "Invoice",
"position": {
"x": 16,
- "y": 19
+ "y": 19.659807999999998
},
"width": 92,
- "height": 12,
+ "height": 9.821952,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -200,10 +200,10 @@
"content": "A compact authoring example using Stack, Row, Table and visual schemas.",
"position": {
"x": 16,
- "y": 33
+ "y": 31.48176
},
"width": 92,
- "height": 6,
+ "height": 6.8584320000000005,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -241,7 +241,7 @@
"y": 47
},
"width": 82,
- "height": 30,
+ "height": 26.800336,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -259,7 +259,7 @@
"y": 51
},
"width": 74,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -281,10 +281,10 @@
"content": "Kumo Coffee\nAki Tanaka\naki@example.com",
"position": {
"x": 20,
- "y": 58
+ "y": 55.864736
},
"width": 74,
- "height": 15,
+ "height": 13.9356,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -314,7 +314,7 @@
"y": 47
},
"width": 90,
- "height": 39,
+ "height": 26.864736,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -332,7 +332,7 @@
"y": 51
},
"width": 82,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -351,13 +351,13 @@
{
"name": "list_1",
"type": "list",
- "content": "[\"Layout primitives create regular pdfme schemas.\",\"\\tNested rows and boxes stay readable.\",\"Download the generated template JSON.\"]",
+ "content": "[\"Text and MVT can omit height; JSX measures them while rendering.\",\"\\tBoxes without height grow around measured children.\",\"Download the generated template JSON.\"]",
"position": {
"x": 108,
- "y": 58
+ "y": 55.864736
},
"width": 82,
- "height": 24,
+ "height": 14,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -381,7 +381,7 @@
"content": "[[\"Design system setup\",\"1\",\"$800\"],[\"PDF template automation\",\"2\",\"$1,200\"],[\"QA and playground review\",\"1\",\"$350\"]]",
"position": {
"x": 16,
- "y": 93
+ "y": 80.864736
},
"width": 178,
"height": 36,
@@ -458,10 +458,10 @@
"type": "rectangle",
"position": {
"x": 16,
- "y": 136
+ "y": 123.864736
},
"width": 130,
- "height": 28,
+ "height": 12.4198784,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -476,10 +476,10 @@
"content": "**Note:** read-only Text can use inline-markdown. Editable Text intentionally cannot.",
"position": {
"x": 20,
- "y": 140
+ "y": 127.864736
},
"width": 122,
- "height": 20,
+ "height": 4.4198784,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -500,7 +500,7 @@
"type": "ellipse",
"position": {
"x": 152,
- "y": 136
+ "y": 123.864736
},
"width": 22,
"height": 22,
@@ -517,7 +517,7 @@
"type": "rectangle",
"position": {
"x": 176,
- "y": 136
+ "y": 123.864736
},
"width": 18,
"height": 22,
@@ -540,7 +540,7 @@
"y": 18
},
"width": 178,
- "height": 10,
+ "height": 7.366464,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -562,10 +562,10 @@
"content": "PageBreak creates another schemas array in the generated template. This page shows that JSX is only an authoring layer: the output remains a normal pdfme Template.",
"position": {
"x": 16,
- "y": 34
+ "y": 31.366464
},
"width": 178,
- "height": 22,
+ "height": 9.2588832,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -587,10 +587,10 @@
"type": "rectangle",
"position": {
"x": 16,
- "y": 62
+ "y": 46.6253472
},
"width": 178,
- "height": 34,
+ "height": 14.9723632,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -605,10 +605,10 @@
"content": "Try changing numbers, colors, Stack gaps, Row widths, or Table rows. The Viewer updates after a short debounce.",
"position": {
"x": 21,
- "y": 67
+ "y": 51.6253472
},
"width": 168,
- "height": 24,
+ "height": 4.9723632,
"rotate": 0,
"opacity": 1,
"readOnly": true,
diff --git a/playground/public/template-assets/jsx-japanese-notice/template.json b/playground/public/template-assets/jsx-japanese-notice/template.json
index 5bacaa4b..1a58e064 100644
--- a/playground/public/template-assets/jsx-japanese-notice/template.json
+++ b/playground/public/template-assets/jsx-japanese-notice/template.json
@@ -18,7 +18,7 @@
"y": 277
},
"width": 174,
- "height": 6,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -47,7 +47,7 @@
"y": 20
},
"width": 174,
- "height": 12,
+ "height": 9.003456,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -69,10 +69,10 @@
"content": "JSX から日本語を含むテンプレートを作成する例です。",
"position": {
"x": 18,
- "y": 39
+ "y": 36.003456
},
"width": 174,
- "height": 8,
+ "height": 3.683232,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -93,10 +93,10 @@
"type": "rectangle",
"position": {
"x": 18,
- "y": 54
+ "y": 46.686688000000004
},
"width": 174,
- "height": 46,
+ "height": 26.165296,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -111,10 +111,10 @@
"content": "pdfme の JSX authoring は、通常の pdfme Template と inputs を生成するための薄いレイヤーです。日本語を扱う場合は、Viewer や generator の options.font に NotoSansJP などのフォントを登録してください。",
"position": {
"x": 23,
- "y": 59
+ "y": 51.686688000000004
},
"width": 164,
- "height": 36,
+ "height": 16.165296,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -137,7 +137,7 @@
"content": "[[\"フォント\",\"NotoSansJP\"],[\"出力\",\"Template + inputs\"],[\"プレビュー\",\"Viewer / Form\"]]",
"position": {
"x": 18,
- "y": 107
+ "y": 79.851984
},
"width": 174,
"height": 36,
diff --git a/playground/public/template-assets/jsx-report/template.json b/playground/public/template-assets/jsx-report/template.json
index 98e9d750..3875195f 100644
--- a/playground/public/template-assets/jsx-report/template.json
+++ b/playground/public/template-assets/jsx-report/template.json
@@ -15,10 +15,10 @@
"content": "Quarterly product report",
"position": {
"x": 16,
- "y": 280.5
+ "y": 279
},
"width": 80,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -40,10 +40,10 @@
"content": "Page {currentPage} of {totalPages}",
"position": {
"x": 96,
- "y": 280.5
+ "y": 279
},
"width": 50,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -72,7 +72,7 @@
"y": 18
},
"width": 110,
- "height": 10,
+ "height": 8.18496,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -94,10 +94,10 @@
"content": "A layout-focused preset for reports and internal briefs.",
"position": {
"x": 16,
- "y": 30
+ "y": 28.18496
},
"width": 110,
- "height": 7,
+ "height": 3.683232,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -119,10 +119,10 @@
"content": "Healthy",
"position": {
"x": 126,
- "y": 29
+ "y": 28.594208000000002
},
"width": 42,
- "height": 8,
+ "height": 3.273984,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -143,10 +143,10 @@
"type": "rectangle",
"position": {
"x": 16,
- "y": 44
+ "y": 38.868192
},
"width": 56,
- "height": 24,
+ "height": 20.2312,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -161,10 +161,10 @@
"content": "Activation",
"position": {
"x": 20,
- "y": 48
+ "y": 42.868192
},
"width": 48,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -186,10 +186,10 @@
"content": "74%",
"position": {
"x": 20,
- "y": 55
+ "y": 47.732928
},
"width": 48,
- "height": 9,
+ "height": 7.366464,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -210,10 +210,10 @@
"type": "rectangle",
"position": {
"x": 77,
- "y": 44
+ "y": 38.868192
},
"width": 56,
- "height": 24,
+ "height": 20.2312,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -228,10 +228,10 @@
"content": "Retention",
"position": {
"x": 81,
- "y": 48
+ "y": 42.868192
},
"width": 48,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -253,10 +253,10 @@
"content": "61%",
"position": {
"x": 81,
- "y": 55
+ "y": 47.732928
},
"width": 48,
- "height": 9,
+ "height": 7.366464,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -277,10 +277,10 @@
"type": "rectangle",
"position": {
"x": 138,
- "y": 44
+ "y": 38.868192
},
"width": 56,
- "height": 24,
+ "height": 20.2312,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -295,10 +295,10 @@
"content": "Expansion",
"position": {
"x": 142,
- "y": 48
+ "y": 42.868192
},
"width": 48,
- "height": 5,
+ "height": 2.8647359999999997,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -320,10 +320,10 @@
"content": "28%",
"position": {
"x": 142,
- "y": 55
+ "y": 47.732928
},
"width": 48,
- "height": 9,
+ "height": 7.366464,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -344,10 +344,10 @@
"type": "rectangle",
"position": {
"x": 16,
- "y": 75
+ "y": 66.099392
},
"width": 178,
- "height": 77,
+ "height": 46.1698592,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -362,10 +362,10 @@
"content": "Notes",
"position": {
"x": 21,
- "y": 80
+ "y": 71.099392
},
"width": 168,
- "height": 7,
+ "height": 4.910976,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -387,10 +387,10 @@
"content": "The JSX authoring layer is useful when a document has repeated visual patterns but still needs to become a normal pdfme template. This example uses boxes, rows, static footer content, and simple visual bars.",
"position": {
"x": 21,
- "y": 91
+ "y": 80.010368
},
"width": 168,
- "height": 28,
+ "height": 9.2588832,
"rotate": 0,
"opacity": 1,
"readOnly": true,
@@ -410,13 +410,13 @@
{
"name": "list_1",
"type": "list",
- "content": "[\"Use Row and Stack for predictable layout.\",\"Use Box for padding, borders, and backgrounds.\",\"Use Static or Footer for repeated page content.\"]",
+ "content": "[\"Use Row and Stack for predictable layout.\",\"Use Box for padding, borders, and backgrounds.\",\"Use Footer for repeated page content.\"]",
"position": {
"x": 21,
- "y": 123
+ "y": 93.26925119999999
},
"width": 168,
- "height": 24,
+ "height": 14,
"rotate": 0,
"opacity": 1,
"readOnly": true,
diff --git a/playground/public/template-assets/jsx-research-paper/template.json b/playground/public/template-assets/jsx-research-paper/template.json
new file mode 100644
index 00000000..460d3444
--- /dev/null
+++ b/playground/public/template-assets/jsx-research-paper/template.json
@@ -0,0 +1,263 @@
+{
+ "basePdf": {
+ "width": 210,
+ "height": 297,
+ "padding": [
+ 18,
+ 20,
+ 18,
+ 20
+ ],
+ "staticSchema": [
+ {
+ "name": "text_1",
+ "type": "text",
+ "content": "Research brief — {currentPage} / {totalPages}",
+ "position": {
+ "x": 20,
+ "y": 279
+ },
+ "width": 170,
+ "height": 2.8647359999999997,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "right",
+ "verticalAlignment": "top",
+ "fontSize": 7,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1,
+ "characterSpacing": 0,
+ "fontColor": "#64748b",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ }
+ ]
+ },
+ "schemas": [
+ [
+ {
+ "name": "text_2",
+ "type": "text",
+ "content": "Practical Notes on JSX Authoring for PDF Templates",
+ "position": {
+ "x": 20,
+ "y": 18
+ },
+ "width": 170,
+ "height": 9.044380799999999,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "center",
+ "verticalAlignment": "top",
+ "fontSize": 17,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1.3,
+ "characterSpacing": 0,
+ "fontColor": "#111827",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ },
+ {
+ "name": "box_1",
+ "type": "rectangle",
+ "position": {
+ "x": 20,
+ "y": 32.0443808
+ },
+ "width": 170,
+ "height": 23.050448000000003,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "color": "#f3f4f6",
+ "borderColor": "#9ca3af",
+ "borderWidth": 0.2,
+ "radius": 0
+ },
+ {
+ "name": "text_3",
+ "type": "text",
+ "content": "Abstract — This preset mirrors a paper-style document and intentionally omits height on most Text nodes. JSX measures each block while rendering, and the parent Box grows around the abstract without manual geometry.",
+ "position": {
+ "x": 24,
+ "y": 36.0443808
+ },
+ "width": 162,
+ "height": 15.050448000000001,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "left",
+ "verticalAlignment": "top",
+ "fontSize": 9,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1.5,
+ "characterSpacing": 0,
+ "fontColor": "#374151",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ },
+ {
+ "name": "text_4",
+ "type": "text",
+ "content": "1. Introduction",
+ "position": {
+ "x": 20,
+ "y": 60.0948288
+ },
+ "width": 170,
+ "height": 5.8522464,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "left",
+ "verticalAlignment": "top",
+ "fontSize": 11,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1.3,
+ "characterSpacing": 0,
+ "fontColor": "#111827",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ },
+ {
+ "name": "text_5",
+ "type": "text",
+ "content": "Since structured templates are easier to review than absolute-position JSON, JSX can act as a readable authoring surface for humans and AI. The generated output is still an ordinary pdfme Template, so it can be opened in Designer after generation.",
+ "position": {
+ "x": 20,
+ "y": 67.4470752
+ },
+ "width": 170,
+ "height": 15.050448000000001,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "left",
+ "verticalAlignment": "top",
+ "fontSize": 9,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1.5,
+ "characterSpacing": 0,
+ "fontColor": "#374151",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ },
+ {
+ "name": "text_6",
+ "type": "text",
+ "content": "2. Layout model",
+ "position": {
+ "x": 20,
+ "y": 87.4975232
+ },
+ "width": 170,
+ "height": 5.8522464,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "left",
+ "verticalAlignment": "top",
+ "fontSize": 11,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1.3,
+ "characterSpacing": 0,
+ "fontColor": "#111827",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ },
+ {
+ "name": "text_7",
+ "type": "text",
+ "content": "Text and MultiVariableText can omit height. During JSX rendering, pdfme measures their content and advances the surrounding Stack or Box. Use explicit height only when you want a fixed field or a fixed visual area.",
+ "position": {
+ "x": 20,
+ "y": 94.8497696
+ },
+ "width": 170,
+ "height": 10.287648,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "left",
+ "verticalAlignment": "top",
+ "fontSize": 9,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1.5,
+ "characterSpacing": 0,
+ "fontColor": "#374151",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ }
+ ],
+ [
+ {
+ "name": "text_8",
+ "type": "text",
+ "content": "Appendix",
+ "position": {
+ "x": 20,
+ "y": 18
+ },
+ "width": 170,
+ "height": 5.320224,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "left",
+ "verticalAlignment": "top",
+ "fontSize": 13,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1,
+ "characterSpacing": 0,
+ "fontColor": "#111827",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ },
+ {
+ "name": "text_9",
+ "type": "text",
+ "content": "Designer edits are applied to the generated Template. The JSX source remains a seed for regeneration, not a lossless representation of later Designer changes.",
+ "position": {
+ "x": 20,
+ "y": 28.320224
+ },
+ "width": 170,
+ "height": 10.287648,
+ "rotate": 0,
+ "opacity": 1,
+ "readOnly": true,
+ "alignment": "left",
+ "verticalAlignment": "top",
+ "fontSize": 9,
+ "fontName": "NotoSansJP",
+ "lineHeight": 1.5,
+ "characterSpacing": 0,
+ "fontColor": "#374151",
+ "backgroundColor": "",
+ "textFormat": "plain",
+ "strikethrough": false,
+ "underline": false
+ }
+ ]
+ ],
+ "author": "pdfme"
+}
\ No newline at end of file
diff --git a/playground/public/template-assets/manifest.json b/playground/public/template-assets/manifest.json
index 8db5c12d..bc496eac 100644
--- a/playground/public/template-assets/manifest.json
+++ b/playground/public/template-assets/manifest.json
@@ -737,6 +737,31 @@
],
"title": "Report page"
},
+ {
+ "name": "jsx-research-paper",
+ "author": "pdfme",
+ "path": "jsx-research-paper/template.json",
+ "thumbnailPath": "jsx-research-paper/thumbnail.png",
+ "pageCount": 2,
+ "fieldCount": 9,
+ "schemaTypes": [
+ "rectangle",
+ "text"
+ ],
+ "fontNames": [
+ "NotoSansJP"
+ ],
+ "hasCJK": false,
+ "basePdfKind": "blank",
+ "description": "A paper-style document that leans on measured text height and PageBreak.",
+ "sourceKind": "jsx",
+ "tags": [
+ "Research",
+ "Article",
+ "Multi-page"
+ ],
+ "title": "Research paper"
+ },
{
"name": "z-fold-brochure",
"author": "hitomi-t260g",
diff --git a/playground/public/template-assets/manifests/6.1.2.json b/playground/public/template-assets/manifests/6.1.2.json
index 8db5c12d..bc496eac 100644
--- a/playground/public/template-assets/manifests/6.1.2.json
+++ b/playground/public/template-assets/manifests/6.1.2.json
@@ -737,6 +737,31 @@
],
"title": "Report page"
},
+ {
+ "name": "jsx-research-paper",
+ "author": "pdfme",
+ "path": "jsx-research-paper/template.json",
+ "thumbnailPath": "jsx-research-paper/thumbnail.png",
+ "pageCount": 2,
+ "fieldCount": 9,
+ "schemaTypes": [
+ "rectangle",
+ "text"
+ ],
+ "fontNames": [
+ "NotoSansJP"
+ ],
+ "hasCJK": false,
+ "basePdfKind": "blank",
+ "description": "A paper-style document that leans on measured text height and PageBreak.",
+ "sourceKind": "jsx",
+ "tags": [
+ "Research",
+ "Article",
+ "Multi-page"
+ ],
+ "title": "Research paper"
+ },
{
"name": "z-fold-brochure",
"author": "hitomi-t260g",
diff --git a/playground/public/template-assets/metadata.json b/playground/public/template-assets/metadata.json
index 7d54cbde..38279470 100644
--- a/playground/public/template-assets/metadata.json
+++ b/playground/public/template-assets/metadata.json
@@ -84,6 +84,12 @@
"sourceKind": "jsx",
"tags": ["CJK", "Table"]
},
+ "jsx-research-paper": {
+ "title": "Research paper",
+ "description": "A paper-style document that leans on measured text height and PageBreak.",
+ "sourceKind": "jsx",
+ "tags": ["Research", "Article", "Multi-page"]
+ },
"jsx-report": {
"title": "Report page",
"description": "A dashboard-style report with cards, progress bars, list content, and page footer.",
diff --git a/playground/src/routes/jsxPlaygroundExamples.ts b/playground/src/routes/jsxPlaygroundExamples.ts
index cb59c61d..82896cb0 100644
--- a/playground/src/routes/jsxPlaygroundExamples.ts
+++ b/playground/src/routes/jsxPlaygroundExamples.ts
@@ -13,23 +13,23 @@ export const jsxPlaygroundPresets: JsxPlaygroundPreset[] = [
source: `return (
-
-
+
+
@pdfme/jsx beta
-
- Header / Footer / Absolute
+
+ Header and Footer repeat on every page