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