Certificate
Produce one certificate per student from a single request. You learn repeating sections, starting each item on a new page, logo images, and a QR code for verification.
このレシピで身につくこと
エディタで作る
- Add a rectangle covering the whole page. This rectangle is the frame for one person — everything inside it is copied along with it.
- In the rectangle properties set the array path to data.students and the item name to st. Inside, you now write {{ st.name }}.
- Turn on "start each item on a new page". A certificate must be one page per person.
- Inside the frame add labels for the heading and the name. Make the name large and centred. Turning on italic for the citation gives it the tone of a certificate.
- Add an image and pick your logo file. The editor converts it to a data URI and stores it in the document — an external address is rejected at render time, so picking a file is the only way.
- Add a QR code and set the value to your verification address. Use {{ st.code }} so each copy carries a different code.
- Raise the QR error correction to Q. Printed paper gets folded and scuffed, so a slightly denser pattern that survives more damage is the better trade.
- Use a circle and a line to mark the seal area. Only the first copy is editable on screen; the others are made by your data and there is nothing there to edit.
スキーマと API で作る
- A repeating section is turned on by props.repeat on a rect node. There is no separate node type.
- A repeating block expands at most 100 times per render. With props.break set to "page" each copy starts a new page, so that is 100 pages. This is a separate budget from the table row limit (2,000 rows) and the two never eat each other — 100 recipients with 8 line items each is 100 blocks and 800 rows, both within limits. Copies past the limit are dropped and reported in warnings.
- props.as is the name for the current item. Leave it empty and it is block, so you write {{ block.name }}.
- Setting props.break to "page" starts every copy on a new page.
- Nodes inside set parent to the frame id, and their coordinates become relative to that frame.
- An image src accepts data: URIs only. An http or https address fails the render with E:render.compile#external-ref, because we send no outbound request while rendering.
出来上がったドキュメント
下の JSON は、2 つの道筋が生むドキュメントそのものです。テストがこれを検証し、警告なしでレンダリングされることも確認しているので、そのまま貼り付けてお使いいただけます。
{
"v": 1,
"unit": "px",
"page": {
"size": "a4",
"orientation": "landscape",
"margin": {
"t": 48,
"r": 48,
"b": 56,
"l": 48
},
"header": "",
"footer": ""
},
"fonts": [],
"styles": [
{
"id": "s_kicker",
"fontFamily": "Liberation Sans",
"fontSize": 13,
"fontWeight": 400,
"italic": false,
"color": "#666666",
"bg": "transparent",
"align": "center",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_name",
"fontFamily": "Liberation Sans",
"fontSize": 40,
"fontWeight": 700,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "center",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_cite",
"fontFamily": "Liberation Sans",
"fontSize": 14,
"fontWeight": 400,
"italic": true,
"color": "#333333",
"bg": "transparent",
"align": "center",
"valign": "top",
"lineHeight": 1.8,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_cap",
"fontFamily": "Liberation Sans",
"fontSize": 10,
"fontWeight": 400,
"italic": false,
"color": "#777777",
"bg": "transparent",
"align": "center",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
}
],
"nodes": [
{
"id": "n_frame",
"parent": "",
"order": 0,
"type": "rect",
"x": 0,
"y": 0,
"w": 1026,
"h": 600,
"show": "",
"styleId": "s_cap",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 2,
"stroke": "#c9a227",
"fill": "",
"repeat": "data.students",
"as": "st",
"break": "page",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "One person frame"
},
{
"id": "n_logo",
"parent": "n_frame",
"order": 1,
"type": "image",
"x": 453,
"y": 40,
"w": 120,
"h": 60,
"show": "",
"styleId": "s_cap",
"props": {
"text": "",
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAgNjAiPjxyZWN0IHdpZHRoPSIxMjAiIGhlaWdodD0iNjAiIHJ4PSI4IiBmaWxsPSIjMWYyOTM3Ii8+PHRleHQgeD0iNjAiIHk9IjM4IiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIyMCIgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+TE9HTzwvdGV4dD48L3N2Zz4=",
"fit": "contain",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Logo"
},
{
"id": "n_kicker",
"parent": "n_frame",
"order": 2,
"type": "label",
"x": 0,
"y": 130,
"w": 1026,
"h": 20,
"show": "",
"styleId": "s_kicker",
"props": {
"text": "{{ data.org }}",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Organisation"
},
{
"id": "n_head",
"parent": "n_frame",
"order": 3,
"type": "label",
"x": 0,
"y": 160,
"w": 1026,
"h": 30,
"show": "",
"styleId": "s_kicker",
"props": {
"text": "CERTIFICATE OF COMPLETION",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Heading"
},
{
"id": "n_name",
"parent": "n_frame",
"order": 4,
"type": "label",
"x": 0,
"y": 210,
"w": 1026,
"h": 56,
"show": "",
"styleId": "s_name",
"props": {
"text": "{{ st.name }}",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Name"
},
{
"id": "n_cite",
"parent": "n_frame",
"order": 5,
"type": "label",
"x": 163,
"y": 290,
"w": 700,
"h": 0,
"show": "",
"styleId": "s_cite",
"props": {
"text": "has successfully completed the {{ data.course }} course, and is awarded this certificate.",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Citation"
},
{
"id": "n_seal",
"parent": "n_frame",
"order": 6,
"type": "circle",
"x": 820,
"y": 420,
"w": 110,
"h": 110,
"show": "",
"styleId": "s_cap",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 2,
"stroke": "#c9a227",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Seal area"
},
{
"id": "n_qr",
"parent": "n_frame",
"order": 7,
"type": "code",
"x": 80,
"y": 420,
"w": 110,
"h": 110,
"show": "",
"styleId": "s_cap",
"props": {
"text": "https://brewmypdf.com/verify/{{ st.code }}",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "qr",
"ecc": "Q",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Verification QR"
},
{
"id": "n_qr_l",
"parent": "n_frame",
"order": 8,
"type": "label",
"x": 50,
"y": 536,
"w": 170,
"h": 16,
"show": "",
"styleId": "s_cap",
"props": {
"text": "Verify · {{ st.code }}",
"src": "",
"fit": "",
"thickness": 0,
"stroke": "",
"fill": "",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 0,
"padX": 0,
"align": "",
"valign": "",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 0,
"max": 0,
"columns": [],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "QR caption"
}
]
}リクエストの本文です。上のドキュメントを template に、下のデータを data に入れてください。
{
"template": "(the document above)",
"data": {
"org": "brewmypdf Academy",
"course": "Document Automation in Practice",
"students": [
{
"name": "Do-hyun Kim",
"code": "C-2026-0001"
},
{
"name": "Seo-yeon Lee",
"code": "C-2026-0002"
},
{
"name": "Jun-ho Park",
"code": "C-2026-0003"
}
]
}
}