포장 명세서
상자에 넣는 포장 명세서를 만듭니다. 행이 아무리 많아도 종이를 넘기지 않도록 상한을 두고, 긴 품명을 한 줄로 자르고, 칸 안에 작은 그림을 넣는 법을 배웁니다.
여기서 배우는 것
편집기로 만들기
- 반복 표를 만들고 최대 행 수를 정합니다. 상한을 넘는 행은 버리고 응답의 warnings 로 알려 드립니다 — 조용히 자르면 상자에 빠진 물건이 생깁니다.
- 품명이 길면 줄이 늘어 표가 종이를 넘깁니다. 표 전체 줄바꿈을 끄면 한 줄로 두고 넘치는 만큼 잘라 … 를 붙입니다.
- 넘친 글자를 그냥 흘리지 않는 이유는, 종이에서는 옆 칸 위로 겹쳐 인쇄되고 그것이 오류로 보이지 않기 때문입니다.
- 특정 칸만 줄바꿈을 되살리고 싶으면 그 칸을 고르고 칸 줄바꿈을 켭니다. 칸이 정하면 칸이 이깁니다.
- 손으로 짜는 표에서는 칸에 그림을 넣을 수 있습니다. 취급 주의 표시처럼 작은 아이콘에 씁니다.
- 주소 묶음은 사각형 안에 라벨을 담아 만듭니다. 부모를 옮기면 안에 든 것이 함께 움직이므로 배치를 한 번에 바꿀 수 있습니다.
스키마 · API 로 만들기
- props.max 가 이 표가 그릴 최대 행 수입니다. 0 이면 여러분이 정한 상한은 없지만 저희 상한은 그대로입니다 — 표 하나는 500행, 한 렌더의 모든 표를 합해 2,000행까지입니다. 표가 여럿이면 그 합이 먼저 끊기니 표 하나만 보고 계산하면 안 됩니다. 넘는 행은 버리고 warnings 로 알려 드립니다.
- props.wrap 을 "off" 로 두면 표 전체가 한 줄로 잘립니다. 칸에서 되살리려면 cells[].wrap 을 "on" 으로 둡니다.
- cells[].src 는 data:image/ 로 시작하는 값만 받습니다. 칸에서는 던지지 않고 그 칸만 비우며 경고합니다 — 그림 하나 때문에 명세서 전체를 잃지 않기 위해서입니다.
완성 문서
아래 JSON 이 위 두 순서가 만드는 바로 그 문서입니다. 저희 테스트가 이 문서를 검증하고 경고 없이 렌더되는지 확인하므로, 그대로 붙여 넣어 쓰셔도 됩니다.
{
"v": 1,
"unit": "px",
"page": {
"size": "a4",
"orientation": "portrait",
"margin": {
"t": 48,
"r": 48,
"b": 56,
"l": 48
},
"header": "",
"footer": "{{ data.order }} · [[page]] / [[pages]]"
},
"fonts": [],
"styles": [
{
"id": "s_h1",
"fontFamily": "Liberation Sans",
"fontSize": 22,
"fontWeight": 700,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "left",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_l",
"fontFamily": "Liberation Sans",
"fontSize": 12,
"fontWeight": 400,
"italic": false,
"color": "#333333",
"bg": "transparent",
"align": "left",
"valign": "top",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
},
{
"id": "s_tbl",
"fontFamily": "Liberation Sans",
"fontSize": 11,
"fontWeight": 400,
"italic": false,
"color": "#111111",
"bg": "transparent",
"align": "left",
"valign": "middle",
"lineHeight": 1.5,
"border": "none",
"radius": 0,
"opacity": 1
}
],
"nodes": [
{
"id": "n_title",
"parent": "",
"order": 0,
"type": "label",
"x": 0,
"y": 0,
"w": 400,
"h": 28,
"show": "",
"styleId": "s_h1",
"props": {
"text": "Packing slip",
"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": "Title"
},
{
"id": "n_addr",
"parent": "",
"order": 1,
"type": "rect",
"x": 400,
"y": 0,
"w": 297,
"h": 96,
"show": "",
"styleId": "s_l",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 1,
"stroke": "#dfe3e8",
"fill": "#fafbfc",
"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": "Ship-to block"
},
{
"id": "n_addr_1",
"parent": "n_addr",
"order": 2,
"type": "label",
"x": 12,
"y": 10,
"w": 273,
"h": 18,
"show": "",
"styleId": "s_l",
"props": {
"text": "{{ data.shipTo.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": "Recipient"
},
{
"id": "n_addr_2",
"parent": "n_addr",
"order": 3,
"type": "label",
"x": 12,
"y": 30,
"w": 273,
"h": 0,
"show": "",
"styleId": "s_l",
"props": {
"text": "{{ data.shipTo.address }}",
"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": "Address"
},
{
"id": "n_addr_3",
"parent": "n_addr",
"order": 4,
"type": "label",
"x": 12,
"y": 70,
"w": 273,
"h": 18,
"show": "",
"styleId": "s_l",
"props": {
"text": "Order {{ data.order }}",
"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": "Order number"
},
{
"id": "n_items",
"parent": "",
"order": 5,
"type": "table",
"x": 0,
"y": 120,
"w": 697,
"h": 220,
"show": "",
"styleId": "s_tbl",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 1,
"stroke": "#c9ced6",
"fill": "#f3f5f7",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 5,
"padX": 8,
"align": "",
"valign": "middle",
"wrap": "off",
"bind": "data.items",
"headerHeight": 26,
"rowHeight": 22,
"max": 40,
"columns": [
{
"header": "SKU",
"cell": "{{ item.sku }}",
"width": 140,
"align": "left"
},
{
"header": "Product",
"cell": "{{ item.name }}",
"width": 397,
"align": "left"
},
{
"header": "Qty",
"cell": "{{ item.qty }}",
"width": 160,
"align": "right"
}
],
"rows": 0,
"cells": [],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Line items"
},
{
"id": "n_care",
"parent": "",
"order": 6,
"type": "table",
"x": 0,
"y": 360,
"w": 340,
"h": 90,
"show": "",
"styleId": "s_tbl",
"props": {
"text": "",
"src": "",
"fit": "",
"thickness": 1,
"stroke": "#c9ced6",
"fill": "#f3f5f7",
"repeat": "",
"as": "",
"break": "",
"keep": "",
"padding": 0,
"padY": 8,
"padX": 10,
"align": "",
"valign": "middle",
"wrap": "",
"bind": "",
"headerHeight": 0,
"rowHeight": 44,
"max": 0,
"columns": [
{
"header": "",
"cell": "",
"width": 110,
"align": "center"
},
{
"header": "",
"cell": "",
"width": 230,
"align": "left"
}
],
"rows": 2,
"cells": [
{
"r": 0,
"c": 0,
"span": 1,
"rowspan": 1,
"text": "",
"align": "left",
"valign": "",
"wrap": "",
"pad": -1,
"padY": -1,
"padX": -1,
"src": "data:image/svg+xml;base64,PHN2ZyB4bWxucz0iaHR0cDovL3d3dy53My5vcmcvMjAwMC9zdmciIHZpZXdCb3g9IjAgMCAxMjAgNjAiPjxyZWN0IHdpZHRoPSIxMjAiIGhlaWdodD0iNjAiIHJ4PSI4IiBmaWxsPSIjMWYyOTM3Ii8+PHRleHQgeD0iNjAiIHk9IjM4IiBmb250LWZhbWlseT0ic2Fucy1zZXJpZiIgZm9udC1zaXplPSIyMCIgZmlsbD0iI2ZmZiIgdGV4dC1hbmNob3I9Im1pZGRsZSI+TE9HTzwvdGV4dD48L3N2Zz4=",
"fit": "contain",
"bg": "#ffffff",
"head": false,
"border": ""
},
{
"r": 0,
"c": 1,
"span": 1,
"rowspan": 1,
"text": "Handle with care — do not lay the box flat.",
"align": "left",
"valign": "",
"wrap": "",
"pad": -1,
"padY": -1,
"padX": -1,
"src": "",
"fit": "",
"bg": "",
"head": true,
"border": ""
},
{
"r": 1,
"c": 0,
"span": 1,
"rowspan": 1,
"text": "↑",
"align": "center",
"valign": "",
"wrap": "",
"pad": -1,
"padY": -1,
"padX": -1,
"src": "",
"fit": "",
"bg": "",
"head": false,
"border": ""
},
{
"r": 1,
"c": 1,
"span": 1,
"rowspan": 1,
"text": "This side up.",
"align": "left",
"valign": "",
"wrap": "",
"pad": -1,
"padY": -1,
"padX": -1,
"src": "",
"fit": "",
"bg": "",
"head": false,
"border": ""
}
],
"chartKind": "",
"codeKind": "",
"ecc": "",
"fieldKind": "",
"fieldName": "",
"options": [],
"required": false,
"readonly": false,
"html": ""
},
"name": "Handling"
}
]
}요청 본문입니다. template 자리에 위 문서를, data 자리에 아래 데이터를 넣습니다.
{
"template": "(the document above)",
"data": {
"order": "SO-2026-8841",
"shipTo": {
"name": "Hanbit Corp. warehouse",
"address": "118 Seongsui-ro, Seongdong-gu, Seoul, 3F"
},
"items": [
{
"sku": "BM-0001",
"name": "brewmypdf mug (white, 350ml, individually boxed)",
"qty": 12
},
{
"sku": "BM-0002",
"name": "Sticker set",
"qty": 40
},
{
"sku": "BM-0003",
"name": "Notebook A5",
"qty": 25
}
]
}
}