템플릿 문서
템플릿은 JSON 객체 하나입니다. 페이지, 재사용 스타일 목록, 그리고 노드의 평면 배열로 이루어집니다. 노드 목록은 중첩하지 않습니다 — 노드가 parent 에 부모의 id 를 적어 관계를 나타냅니다.
아래 필드는 전부 필수입니다. 노드의 props 는 모든 노드 타입의 props 를 합친 것이라, 그 타입이 쓰지 않는 키도 들고 있어야 합니다. 안 쓰는 것은 비워 두십시오 — 문자열은 "", 숫자는 0, 참·거짓은 false, 배열은 []. 키를 지우면 스키마 위반이지만 비우는 것은 아닙니다.
최상위
v integer
스키마 버전. 현재 1. 버전이 없으면 추측하지 않고 거부합니다.
unit const
좌표 단위. 언제나 "px" 이며 96 dpi 기준입니다 — A4 는 794 × 1123 입니다.
px
lang enum
문서의 언어. CJK 자형을 고르는 데 씁니다. 없으면 글자로 추측하며, 가나나 한글이 있으면 그것으로 충분합니다. 일본어가 한자로만 쓰였을 때 선언하십시오 — 청구서 제목 請求書 은 중국어로도 유효한 글자열이라 추측이 빗나가고, 문서가 중국어 자형으로 인쇄됩니다. "ko" · "ja" · "zh-Hans" · "zh-Hant" 를 받습니다.
kojazh-Hanszh-Hant
page object
용지, 여백, 그리고 모든 페이지에 반복되는 머리글·바닥글.
fonts array
이 문서가 필요로 하는 폰트. 빈 배열이면 기본 폰트를 씁니다.
styles array
재사용하는 글자·상자 스타일. 노드가 id 로 참조합니다.
nodes array
문서의 모든 노드를 담은 평면 배열. 중첩은 parent 로 나타냅니다.
page
size enum
용지 규격. a4 · a5 · letter · legal 중 하나이며, 그 밖의 값은 거부됩니다. 96dpi 기준으로 A4 794×1123, A5 559×794, Letter 816×1056, Legal 816×1344 px 입니다. 생략하면 a4 입니다.
a4a5letterlegal
orientation enum
용지 방향. size 뒤에 적용되므로 a5 + landscape 는 가로로 긴 A5 입니다.
portraitlandscape
margin object
인쇄 여백(px). 노드 좌표는 여백 안쪽에서 시작합니다.
margin.t number
위 여백.
margin.r number
오른쪽 여백.
margin.b number
아래 여백. 바닥글 자리를 위해 보통 위쪽보다 큽니다.
margin.l number
왼쪽 여백.
header string
모든 페이지 상단에 반복되는 문구. 빈 문자열이면 없음. {{ … }} 를 쓸 수 있습니다.
footer string
모든 페이지 하단에 반복되는 문구. 빈 문자열이면 없음. {{ … }} 와 [[page]] · [[pages]] 를 쓸 수 있습니다.
backgrounds array
배경 이미지 목록. 모든 쪽에 깔립니다. 최대 3개이며, 안 쓰면 빈 배열입니다. ⚠ 좌표가 노드와 다릅니다 — 배경은 용지 전체 기준이고 노드는 여백 안쪽 기준입니다. 관공서 서식처럼 용지를 덮는 그림을 깔아야 하기 때문입니다.
backgrounds[].assetId string
POST /v1/assets 로 올린 자산의 id.
backgrounds[].x number
용지 좌상단 기준 가로 위치(px). 노드와 달리 여백 바깥에서 시작합니다.
backgrounds[].y number
용지 좌상단 기준 세로 위치(px).
backgrounds[].w number
너비(px).
backgrounds[].h number
높이(px).
backgrounds[].opacity number
0~1. 1 이 불투명입니다. 서식이 진해서 얹은 값이 안 읽힐 때 낮추십시오.
backgrounds[].rotate number
회전 각도(도). 스캔본이 기울어졌을 때 보정합니다. 0 이 기본.
backgrounds[].print boolean
false 면 편집기에는 보이고 출력에는 없습니다. 원본 용지에 인쇄할 때 칸 위치만 맞추는 용도입니다.
backgrounds[].locked boolean
편집기에서 고정합니다. 렌더에는 영향이 없습니다.
fonts
family string
폰트 이름. styles[].fontFamily 가 찾는 이름과 같아야 합니다.
src enum
폰트의 출처. 지금은 "preinstalled" 만 동작합니다 — 편집기에 나오는 기본 제공 글꼴입니다. "r2" 는 직접 올린 글꼴을 위해 비워 둔 값이며 아직 지원하지 않습니다. 그 값을 쓴 문서는 대체 서체로 그려지고 warnings 로 알려 드립니다.
preinstalledr2
styles
id string
문서 내 유일한 스타일 id. nodes[].styleId 가 가리킵니다.
fontFamily string
폰트 이름. 한글·중국어·일본어가 들어갈 수 있는 글자에는 "Noto Sans CJK KR" 을 쓰십시오 — 라틴 전용 폰트는 그 글자를 통째로 떨어뜨립니다.
fontSize number
글자 크기(px).
fontWeight integer
글자 굵기. 400 이 보통, 700 이 굵게.
italic boolean
기울임.
color string
글자 색 #RRGGBB.
bg string
배경색 #RRGGBB 또는 "transparent".
align enum
가로 정렬.
leftcenterrightjustify
valign enum
노드 상자 안에서의 세로 정렬.
topmiddlebottom
lineHeight number
줄 간격. 글자 크기의 배수입니다.
border string
CSS border 축약형 — 예: "1px solid #ddd". 없으면 "none".
radius number
모서리 둥글기(px).
opacity number
불투명도 0~1.
nodes
id string
문서 내 유일한 노드 id. n_ 접두는 관례일 뿐 규칙은 아닙니다.
name string
편집기 개체 트리에 보일 이름입니다. 식별자가 아니라 라벨이라 유일할 필요가 없고 아무것도 이것을 참조하지 않습니다. 비워 두면 트리에 id 가 보입니다.
parent string
부모 노드의 id. 빈 문자열이면 페이지 직속입니다. 없는 부모를 가리키거나 순환이 생기면 위반입니다.
order integer
같은 부모 안에서의 순서. 겹칠 때 무엇이 위에 그려지는지도 이 값이 정합니다.
type enum
노드의 종류.
labelimagelinerectcircletablechartcodehtmlfield
x number
부모 기준 X 좌표(px).
y number
부모 기준 Y 좌표(px).
w number
너비(px).
h number
높이(px). 0 이면 자동 높이입니다. 배열이 바인딩된 표에서는 예약 높이가 됩니다 — "늘어나는 표" 참조.
show string
표시 조건. 빈 문자열이면 항상 표시합니다. 그 밖의 값은 data 기준으로 계산하는 표현식입니다.
styleId string
styles[] 의 id. 빈 문자열이면 기본 스타일.
props object
타입별 설정. 모든 키가 필수이며 안 쓰는 것은 비워 둡니다.
nodes[].props
text string
label·code. label 은 그릴 텍스트, code 는 인코딩할 값입니다. {{ … }} 가능. 줄바꿈 문자는 그 자리에서 줄을 바꿉니다. 그 밖의 타입은 "".
src string
image 전용. data: URI 만 받습니다 — 렌더 중에 외부로 요청을 내보내지 않기 때문입니다. 그림은 base64 로 바꿔 넣으십시오. 템플릿에 직접 적어 넣은 http · https 주소는 렌더가 E:render.compile#external-ref 로 실패합니다. {{ data.logo }} 같은 바인딩으로 들어온 주소는 그 그림만 비우고 응답에 external-ref 경고를 싣습니다 — 레코드 하나 때문에 배치 전체가 실패하지 않습니다. 그 밖의 타입은 "".
fit enum
image 전용. 이미지를 상자에 채우는 방식. 그 밖의 타입은 "".
""containcoverfill
thickness number
line · rect · circle · table · field. 선 두께(px). 표에서는 격자선, 입력칸에서는 칸 테두리의 두께입니다. 그 밖의 타입은 0.
stroke string
line · rect · circle · table · field. 선 색 #RRGGBB. 표에서는 격자선, 입력칸에서는 칸 테두리의 색입니다. 그 밖의 타입은 "".
fill string
rect · circle · table. 채움 색 #RRGGBB. 표에서는 머리 칸의 배경색입니다. 그 밖의 타입은 "".
repeat string
rect 전용. 이 경로의 배열 항목마다 그룹을 한 벌씩 그립니다 — 예: "data.companies". 안에 든 것이 함께 반복됩니다. 그 밖의 타입은 "".
as string
rect 전용. 반복 그룹 안에서 지금 항목을 부를 이름 — "co" 로 두면 {{ co.name }} 으로 씁니다. 비우면 "block". "item" 은 쓸 수 없습니다: 그 이름은 표의 행이 쓰므로, 나눠 두어야 그룹 안의 표가 둘 다 읽습니다.
break enum
rect 전용. "page" 면 반복할 때마다 새 페이지에서 시작합니다. 비우면 이어 붙습니다.
""page
keep enum
rect 전용. "together" 면 한 벌이 페이지 경계에서 갈리지 않습니다. 한 페이지보다 큰 벌은 어차피 갈립니다.
""together
padding number
table 전용. 칸 안 여백(px). 키가 없으면 선이 있을 때만 기본 여백이 붙습니다. 그 밖의 타입은 0.
padY number
table 전용. 칸 안 상하 여백(px)입니다. -1 이면 padding 을 따릅니다 — 0 은 "여백 없음"이라는 진짜 값이라 "따른다"로 쓸 수 없습니다.
padX number
table 전용. 칸 안 좌우 여백(px)입니다. -1 이면 padding 을 따릅니다.
align enum
table 전용. 격자 표 칸의 가로 정렬 기본값. 칸이 따로 정하면 그쪽이 이깁니다. 반복 표는 열마다(columns[].align) 정합니다.
""leftcenterright
valign enum
table 전용. 칸 안 세로 정렬. 비우면 가운데. 칸이 따로 정하면 그쪽이 이깁니다.
""topmiddlebottom
wrap enum
table 전용. 칸 안 줄바꿈. 비우거나 "on" 이면 넘치는 글이 다음 줄로, "off" 면 한 줄로 두고 넘치는 만큼 잘라 … 를 붙입니다.
""onoff
bind string
table 전용. 반복할 배열의 경로 — 예: "data.items". 그 밖의 타입은 "".
headerHeight number
table 전용. 머리 행 높이(px). 그 밖의 타입은 0.
rowHeight number
table 전용. 본문 행 높이(px). 그 밖의 타입은 0.
max integer
table 전용. 그릴 최대 행 수. 넘는 행은 버립니다. 그 밖의 타입은 0.
columns array
table 전용. 왼쪽부터의 열 정의. 그 밖의 타입은 [].
columns[].header string
머리 셀 문구.
columns[].cell string
본문 셀 템플릿. item 이 현재 행이므로 "{{ item.name }}" 처럼 그 필드를 읽습니다.
columns[].width number
열 너비(px). 합이 표 너비와 맞아야 합니다.
columns[].align enum
셀 정렬.
leftcenterright
rows integer
table 전용. bind 가 빈 격자 표의 행 수. 반복 표와 그 밖의 타입은 0.
cells array
table 전용. 격자 표의 칸. 없는 자리는 빈 칸이므로 다 적지 않아도 됩니다. 반복 표와 그 밖의 타입은 [].
cells[].r integer
행 번호. 0부터.
cells[].c integer
열 번호. 0부터.
cells[].span integer
가로 병합 칸 수. 1 이 기본.
cells[].rowspan integer
세로 병합 칸 수. 1 이 기본.
cells[].text string
칸 내용. "{{ data.x }}" 로 값을 넣을 수 있습니다.
cells[].align enum
칸 글자 정렬.
leftcenterright
cells[].valign enum
이 칸만 세로 정렬. 비우면 표의 값을 따릅니다.
""topmiddlebottom
cells[].wrap enum
이 칸만 줄바꿈. 비우면 표의 값을 따릅니다.
""onoff
cells[].pad number
이 칸만의 안 여백(px)입니다. -1 이거나 없으면 표의 padding 을 따릅니다 — 0 은 "여백 없음"이라는 진짜 값이라 "따른다"로 쓸 수 없기 때문입니다. 표 전체를 넓히지 않고 한 칸만 붙이거나 띄울 때 씁니다.
cells[].padY number
이 칸만의 상하 여백(px)입니다. -1 이거나 없으면 pad → 표 순으로 따릅니다.
cells[].padX number
이 칸만의 좌우 여백(px)입니다. -1 이거나 없으면 pad → 표 순으로 따릅니다.
cells[].src string
칸 안 이미지. data:image/ 로 시작하는 값만 받습니다 — 외부 주소는 요청이 나가므로 렌더가 거부합니다.
cells[].fit enum
칸 이미지 맞춤. 비우면 contain.
""containcoverfill
cells[].bg string
칸 배경색 #RRGGBB. 비우면 배경 없음.
cells[].head boolean
머리 칸. 머리 행은 r=0 줄에, 머리 열은 c=0 줄에 켭니다.
cells[].border enum
"none" 이면 이 칸만 선을 그리지 않습니다.
""none
chartKind enum
chart 전용. "bar"(기본) · "line" · "pie" · "donut". 그 밖의 타입은 "".
""barlinepiedonut
codeKind enum
code 전용. 그릴 코드 종류 — "qr"(기본) · "code128" · "ean13". 그 밖의 타입은 "".
""qrcode128ean13
ecc enum
code 전용이고 QR 에만. 오류정정 수준 L · M · Q · H — 비우면 M 입니다. 높을수록 더 많이 훼손돼도 읽히지만 모듈이 늘어 같은 글자가 더 촘촘한 코드가 됩니다.
""LMQH
html string
html 전용. 서식 있는 본문. 허용목록에 있는 태그만 남습니다. 그 밖의 태그는 글은 남고 태그만 사라지며, script · style · iframe 은 내용까지 사라집니다. 그 밖의 타입은 "".
fieldKind enum
field 전용. 채워 넣을 수 있는 입력의 종류 — "text"(기본) · "check" · "select" · "list" · "radio" · "button" · "sign". "sign" 은 서명할 자리를 만들 뿐 전자서명을 하지는 않습니다. 그 밖의 타입은 "".
""textcheckselectlistradiobuttonsign
fieldName string
field 전용. PDF 안에서 이 입력의 이름입니다 — 받는 쪽 프로그램이 값을 읽을 때 쓰는 열쇠입니다. 비우면 노드 id 를 씁니다. 한 문서 안에서 겹치면 뒤엣것이 앞엣것과 같은 값을 갖게 됩니다. 그 밖의 타입은 "".
options array
field 전용이고 "select" · "list" · "radio" 에만. 고를 수 있는 항목들입니다. 그 밖에는 [].
required boolean
field 전용. 필수 입력으로 표시합니다. 저희가 검사하지는 않습니다 — 표시일 뿐이고, 강제 여부는 PDF 를 여는 프로그램이 정합니다. 그 밖의 타입은 false.
readonly boolean
field 전용. 보이기만 하고 고칠 수 없게 합니다. 그 밖의 타입은 false.
표현식
모든 문자열 필드에 {{ … }} 표현식을 넣을 수 있습니다. 렌더 요청과 함께 보낸 data 를 기준으로 계산됩니다.
표의 셀 안에서 item 은 바인딩된 배열의 현재 행을 가리킵니다. 표 밖에서는 정의되지 않습니다.
쪽번호는 문법이 다릅니다 — [[page]] 와 [[pages]] 이며 page.header · page.footer 에서만 동작합니다. 전체 쪽수가 정해지는 배치 이후에 채워지기 때문입니다.
표시
format(value, kind, pattern?, locale?)- 값을 사람이 읽는 형태로 만듭니다. kind 는 number, currency, date 입니다. currency 는 패턴 자리에 통화코드(KRW, USD)를, date 는 short, medium, long, iso 를 받습니다. 통화코드가 틀리면 문서를 실패시키지 않고 코드를 그대로 찍습니다.
숫자
abs(n)- 부호를 뗀 거리.
ceil(n)- 올림해서 정수로.
floor(n)- 내림해서 정수로.
trunc(n)- 반올림 없이 소수를 버립니다.
sign(n)- -1, 0, 1 중 하나.
sqrt(n)- 제곱근. 음수를 넣으면 오류가 아니라 빈 값입니다.
pow(n, exp)- 거듭제곱.
round(n, digits?)- 주어진 소수 자리까지 반올림합니다(기본 0).
min(a, b, ...)- 인자 중 가장 작은 값.
max(a, b, ...)- 인자 중 가장 큰 값.
clamp(n, lo, hi)- 범위 안으로 가둡니다. lo 보다 작으면 lo, hi 보다 크면 hi.
글자
upper(s)- 대문자로.
lower(s)- 소문자로.
trim(s)- 양 끝 공백을 없앱니다.
len(s)- 글자 수.
sub(s, start, end?)- 글자 위치로 잘라낸 조각.
replace(s, find, with)- 모두 바꿉니다. 찾는 값은 글자 그대로이며 패턴이 아닙니다.
contains(s, find)- 그 글자가 들어 있으면 참.
startsWith(s, find)- 그 글자로 시작하면 참.
endsWith(s, find)- 그 글자로 끝나면 참.
padStart(s, width, fill?)- 너비에 닿을 때까지 왼쪽을 채웁니다. 000042 같은 청구서 번호에 씁니다.
padEnd(s, width, fill?)- 너비에 닿을 때까지 오른쪽을 채웁니다.
split(s, sep)- 구분자로 잘라 배열로 만듭니다.
배열
count(array)- 개수.
sum(array, prop?)- 전부 더합니다. prop 을 주면 각 항목의 그 필드를 더합니다.
sumProduct(…)- 각 항목의 두 필드를 곱해서 더합니다. 세율이 섞인 청구서의 총 세액(sumProduct(items, "amount", "vat") / 100)이나, 합계용 필드를 따로 저장하지 않는 줄 금액(sumProduct(items, "qty", "price"))에 씁니다. 세금을 아는 함수가 아닙니다 — 세율은 데이터가 나르는 숫자입니다. 나라마다 다르고 법으로 바뀌기 때문입니다. 반올림은 맨 끝에서 한 번만 하므로, 세율 묶음마다 반올림하도록 요구하는 제도에서는 filter 로 묶음을 각각 그리십시오.
avg(array, prop?)- 평균. 빈 배열은 오류가 아닙니다 — 자리를 비우고 nan 경고를 냅니다. 계산할 수 없는 다른 숫자와 같은 처리입니다.
minOf(array, prop?)- 가장 작은 항목, 또는 그 필드의 최솟값.
maxOf(array, prop?)- 가장 큰 항목, 또는 그 필드의 최댓값.
first(array)- 첫 항목.
last(array)- 마지막 항목.
at(array, index)- 0 부터 세어 그 자리의 항목.
join(array, sep?, prop?)- 구분자로 이어 하나의 글자로 만듭니다(기본은 쉼표와 공백).
filter(array, prop, value)- 그 필드가 값과 같은 항목만 남깁니다.
sortBy(array, prop, desc?)- 필드로 정렬합니다. 세 번째 인자에 true (또는 "desc") 를 주면 내림차순입니다.
slice(array, start, end?)- 위치로 잘라낸 항목 범위.
unique(array, prop?)- 중복을 없앱니다.
고르기
if(test, then, else)- 첫 인자가 참이면 둘째를, 아니면 셋째를 고릅니다.
coalesce(a, b, ...)- 비어 있지 않은 첫 인자. 비었다는 것은 null, undefined, 빈 글자, 빈 배열입니다 — isEmpty 와 같은 기준입니다. 0 은 비어 있지 않으므로 금액 0 은 남습니다.
ifEmpty(value, fallback)- 값을 주되 비어 있으면 대체값을 줍니다. 비었다는 것은 null, undefined, 빈 글자, 빈 배열입니다.
isEmpty(value)- 값이 null, undefined, 빈 글자, 빈 배열이면 참.
날짜
dateAdd(date, n, unit)- 날짜에 기간을 더해 YYYY-MM-DD 로 돌려줍니다. unit 은 day, week, month, quarter, year 이고 음수를 주면 뺍니다. 달 계산은 말일로 자릅니다 — 2026-01-31 에 1개월을 더하면 2026-03-03 이 아니라 2026-02-28 입니다.
dateDiff(from, to, unit?)- 두 날짜 사이를 만 단위로 셉니다. 앞에서 뒤로 세며 기본 단위는 날입니다. 0 방향으로 내리므로 29일은 0개월입니다.
dateStart(date, unit)- 그 주, 달, 분기, 해의 첫날. 주는 월요일에 시작합니다.
dateEnd(date, unit)- 그 주, 달, 분기, 해의 마지막 날 — 28, 29, 30, 31 을 직접 세지 않고 말일을 얻는 방법입니다.
datePart(date, part)- 날짜에서 숫자 하나를 꺼냅니다. part 는 year, month, day, weekday, quarter, week, dayOfYear 입니다. 달은 1~12, 요일은 월요일 1 부터 일요일 7 까지로, 자바스크립트의 0 부터 세는 방식이 아니라 ISO-8601 을 따릅니다.
today(timezone?)- 오늘을 YYYY-MM-DD 로 줍니다. 한 번의 렌더 안에서는 고정이라 두 번 불러도 자정을 넘나들지 않습니다. 기본은 UTC 이고, 그것이 어디서나 같은 날은 아닙니다 — 서울의 24일 오전 8시는 UTC 로 아직 23일입니다. 문서에 찍힐 날짜가 읽는 분의 날짜여야 한다면 today("Asia/Seoul") 처럼 IANA 타임존을 주십시오. 날짜만 주고 시각은 주지 않습니다. 시간대가 적히지 않은 시각은 아무도 읽을 수 없기 때문입니다.
서식 있는 글
html 노드는 마크업 한 덩어리를 받습니다. 라벨을 줄지어 놓는 것으로는 못 하는 일입니다 — 줄 하나가 늘면 손으로 잡아 둔 배치가 전부 다시 흐르기 때문입니다. 약관, 안내문, 각주처럼 흘러야 하는 글에 씁니다.
저희는 마크업을 걸러내지 않고 다시 짓습니다. 보내신 HTML 을 읽어서, 목록에 있는 태그와 속성만 새로 씁니다. 이 차이가 중요합니다 — 저희가 이해하지 못한 것은 *저희가 미리 생각해 뒀는지에 기대는 대신* 아예 출력에 존재할 수 없습니다.
허용: p · div · span · br · hr · blockquote · pre · h1~h6 · b · strong · i · em · u · s · small · sub · sup · code · a · ul · ol · li · table 과 그 행·칸 · img. 목록 밖 태그는 태그만 사라지고 글은 남습니다 — 쓰신 글을 잃지 않습니다. script · style · iframe 같은 것은 내용까지 사라집니다. 그 내용은 글이 아니기 때문입니다.
속성은 더 좁습니다: style(선언마다 검사하고 position 은 제외) · 칸의 colspan · rowspan · 링크의 href · 이미지의 src · alt · width · height. class 와 id 는 없습니다 — 페이지를 배치하는 저희 클래스와 부딪힙니다. 이미지는 data: URI 여야 합니다. image 노드와 같은 규칙이고, 원격 이미지를 그리려면 렌더 도중에 가져와야 하기 때문입니다. 링크는 어디든 가리켜도 됩니다. 링크는 가져오기가 아닙니다.
html 안의 {{ … }} 는 데이터가 무엇이든 글자가 되지 마크업이 되지 않습니다. 속성 안에 있으면 그 값도 직접 쓴 값과 똑같이 검사합니다 — 값으로 style 에 url(…) 을 밀어 넣거나 href 에 javascript: 를 넣을 수 없습니다. 상한: 20,000자 · 중첩 32단계 · 태그 2,000개. 넘으면 자르고 warnings 로 알려 드립니다.
차트
chart 노드는 표가 쓰는 두 필드를 그대로 씁니다. bind 가 배열이고, columns 가 각 항목에서 무엇을 읽을지 정합니다 — columns[0].cell 이 라벨, columns[1].cell 이 값, columns[1].header 가 제목입니다. 새 필드는 chartKind 하나입니다: "bar" · "line" · "pie" · "donut".
축은 둥근 최댓값을 고릅니다 — 1 · 2 · 5 에 10의 거듭제곱을 곱한 값 — 그래야 눈금이 사람이 고를 법한 숫자로 읽힙니다. 숫자가 아닌 값은 렌더를 실패시키지 않고 0으로 셉니다. 읽을 수 없는 칸이 빈 칸으로 나오는 것과 같습니다. 파이와 도넛은 절댓값을 씁니다 — 음수 조각은 뜻이 없기 때문입니다.
차트는 SVG 로 그리므로 PDF 안에서 벡터입니다. 항목이 많으면 라벨을 겹쳐 찍는 대신 솎아 냅니다. 범례는 파이·도넛 아래 한 줄에 고정이고 움직이지 않습니다 — 스스로 자리를 찾는 범례는 여러분의 조판을 함께 움직입니다.
QR 코드와 바코드
code 노드는 글자를 스캔되는 그림으로 바꿉니다. 값을 text 에 넣고 codeKind 를 고르면 됩니다 — "qr" · "code128" · "ean13". SVG 로 그리므로 PDF 에 벡터로 들어갑니다. 어떤 인쇄 해상도에서도 선명하고, 막대 경계가 규격이 말하는 자리에 정확히 놓입니다. 마지막이 중요합니다 — 엉뚱한 시점에 래스터로 바뀐 바코드는 스캐너가 잘못 읽는 바코드입니다.
QR 은 정사각형으로 두십시오. 상자를 채우려고 코드를 늘리지 않습니다 — 찌그러지면 스캔이 안 됩니다 — 그래서 직사각형 상자는 긴 쪽에 여백이 남습니다. 코드 둘레의 여백(quiet zone)은 규격의 일부라 저희가 확보해 둡니다. 잘라내려 하지 마십시오.
ean13 은 12자리를 받아 체크digit 을 저희가 계산합니다. 13자리를 주면 마지막 자리를 검사합니다. code128 은 ASCII 32~126 을 담습니다. 인코딩할 수 없는 값이면 — 길이가 틀렸거나, 담을 수 없는 글자거나, QR 용량을 넘겼거나 — 빈 상자를 그리는 대신 오류로 실패합니다. 빈 상자는 라벨이 인쇄된 뒤에야 발견되기 때문입니다.
지원하는 것은 이 셋입니다. 100종을 다루는 라이브러리가 있지만, 가장 작은 것도 저희 워커 전체보다 무겁습니다. 저희가 그리지 않는 것이 필요하면 어떤 것이고 왜 필요한지 알려 주십시오.
표
표는 두 가지 모드로 씁니다. bind 가 비어 있으면 손으로 짜는 격자 표이고, bind 에 배열 경로를 주면 데이터가 행을 만드는 반복 표입니다. 이 키 하나가 표의 성격을 바꾸며, 두 모드는 서로 다른 필드를 씁니다 — 격자는 rows 와 cells, 반복은 columns 와 max 입니다.
격자 표는 rows 로 행 수를, columns 로 열 수와 너비를 정합니다. 칸 내용은 cells 배열에 r · c 좌표로 넣습니다 — 없는 자리는 빈 칸이므로 모든 칸을 적을 필요는 없습니다. span 과 rowspan 으로 옆칸·아랫칸과 합칩니다. head 를 켜면 머리 칸이 되고 fill 색이 그 칸에만 칠해집니다.
반복 표는 columns 하나가 열 하나입니다. header 가 머리글, cell 이 본문 템플릿이고, 템플릿 안에서 item 이 현재 행을 가리킵니다 — "{{ item.name }}" 처럼 씁니다. 행 수는 데이터가 정하며 max 로 상한을 둘 수 있습니다. 넘는 행은 버리고 응답의 warnings 로 알려 드립니다.
열 너비의 합이 표 너비와 맞아야 합니다. 어긋나면 브라우저가 알아서 늘리거나 줄이는데, 그 결과는 설계한 것과 다릅니다.
여백은 세 단계로 정해집니다: 칸(cells[].padY · padX · pad) → 표(padY · padX · padding) 순으로 찾고, 없으면 선이 있을 때만 기본 여백이 붙습니다. -1 이 "따른다"는 표시입니다 — 0 은 "여백 없음"이라는 진짜 값이라 센티널로 쓸 수 없기 때문입니다.
정렬도 같은 순서입니다. 칸이 정하면 칸이 이기고, 아니면 표의 align · valign 을 따릅니다. wrap 을 "off" 로 두면 그 칸은 한 줄로 두고 넘치는 만큼 잘라 … 를 붙입니다 — 넘친 글자를 옆 칸 위에 겹쳐 인쇄하지 않기 위해서입니다.
채울 수 있는 칸
field 노드는 받는 사람이 PDF 를 열어 직접 채울 수 있는 칸을 만듭니다. 계약서·신청서·설문지처럼 값을 나중에 넣는 문서를 위한 것입니다. 종류는 fieldKind 로 정합니다 — "text"(기본) · "check" · "select" · "list" · "radio" · "button" · "sign".
fieldName 이 PDF 안에서의 이름이고, 받는 쪽 프로그램이 값을 읽을 때 쓰는 열쇠입니다. 비우면 노드 id 를 씁니다. 영문·숫자·밑줄·하이픈 64자까지만 쓸 수 있고, 그 밖의 글자가 섞이면 저희가 고치지 않고 노드 id 로 떨어뜨립니다 — 고쳐 드리면 적으신 이름과 PDF 안의 이름이 달라지고, 그 차이는 받는 쪽에서야 드러나기 때문입니다. 점(.)은 PDF 에서 이름의 계층 구분자라 특히 쓸 수 없습니다.
"select" · "list" · "radio" 는 options 에 선택지를 넣어야 합니다. 비어 있으면 고를 것이 없는 칸이 되므로 그 칸만 건너뛰고 응답의 warnings 로 알려 드립니다. "radio" 는 그린 상자를 선택지 수만큼 세로로 나눠 채웁니다.
선은 stroke 와 thickness 로 그으십시오. 화면에서는 PDF 뷰어가 입력칸 모양을 그려 주지만, 인쇄한 종이에는 뷰어가 없습니다 — 선을 안 그으면 빈 서식을 출력했을 때 채울 자리가 보이지 않습니다. 특히 "sign" 은 뷰어도 아무것도 그리지 않습니다.
값을 미리 채워 둘 수 있습니다 — label 과 같은 text 속성을 쓰십시오. {{ data.payment }} 처럼 바인딩하면 데이터의 값이 그 칸에 들어간 채로 PDF 가 나갑니다(받는 사람은 그 위에서 고칠 수 있습니다). "check" 는 "false"·"0"·"no"·빈 값이면 꺼진 것으로 읽고, "select"·"list"·"radio" 는 options 에 있는 값만 넣습니다 — 없는 값을 넣으면 사람이 고를 수 없는 값이 든 문서가 되기 때문입니다. 한글도 됩니다.
못 하는 것: "sign" 은 서명할 자리를 만들 뿐 전자서명을 하지 않습니다. required 는 표시일 뿐 저희가 검사하지 않으며, 강제 여부는 PDF 를 여는 프로그램이 정합니다. 계산식이나 스크립트가 붙은 칸은 만들지 않습니다. 그림(PNG·JPEG)으로 받으면 칸은 사라집니다 — 그림에는 채울 자리라는 개념이 없습니다. 한 문서에 500칸까지이며, 넘으면 자르고 알려 드립니다.
늘어나는 표
배열이 바인딩된 표는 높이가 정해져 있지 않습니다 — 3행일 수도 300행일 수도 있습니다. 반복 그룹도 마찬가지입니다. 그래서 그런 노드가 처음 나오는 자리부터 그 뒤 전부가 고정 좌표가 아니라 문서 흐름으로 배치되고, 앞의 것이 길어지면 뒤의 내용이 함께 밀려납니다.
표에 적은 h 는 상한이 아니라 예약 높이입니다. 표는 그보다 길어질 수 있습니다. 표 뒤에 놓은 노드는 그 예약 높이의 아래끝을 기준으로, 그려 둔 간격을 그대로 유지합니다.
늘어나는 표에는 현실적인 h 를 주십시오. 0 으로 두면 표 아래에 배치한 노드들이 표 뒤가 아니라 표의 시작 지점에서 그려집니다.
구역 전체를 반복하기
표는 행을 반복합니다. 사각형은 그 안에 든 것을 통째로 반복합니다 — props.repeat 에 배열 경로를 적으면 항목마다 그룹이 한 벌씩, 자식까지 함께 그려집니다. 요청 하나로 여러 레코드를 담은 문서를 만드는 방법입니다. 지점별 명세서, 회사별 요약처럼요. 레코드마다 요청을 나눌 필요가 없습니다.
그룹 안에서 지금 항목은 props.as 에 적은 이름으로 부릅니다. as 가 "co" 면 {{ co.company }} 가 지금 항목의 company 를 읽습니다. 비우면 "block" 입니다. "item" 이 기본값이 아닌 것은 일부러입니다 — 그 이름은 표의 행이 쓰므로, 나눠 두어야 그룹 안의 표가 둘을 함께 읽을 수 있습니다. 레코드는 {{ co.company }}, 행은 {{ item.name }} 입니다. data · item · index 는 이름으로 쓸 수 없습니다.
props.break 를 "page" 로 두면 반복할 때마다 새 페이지에서 시작합니다. 한 페이지보다 큰 그룹은 알아서 나뉘므로 직접 셀 필요가 없습니다. 그룹 안의 표도 흐르기 때문에, 그려 둔 그룹보다 길어지면 그 그룹 안의 나머지를 밀어냅니다.
한 벌 안에서의 쪽번호는 [[bpage]] · [[bpages]] 입니다 — "이 청구서의 1/2쪽"이지 문서 전체의 번호가 아닙니다. page.footer 에서만 됩니다. [[page]] 는 Chromium 이 인쇄하면서 채우는데, 그쪽은 어디서 한 벌이 시작했는지 모릅니다. 그래서 이 바닥글은 인쇄가 끝난 뒤 저희가 직접 그립니다. 같은 이유로 이때의 바닥글은 Latin-1 만 쓸 수 있습니다 — 표준 폰트는 실을 수 있어도 한글 폰트는 못 싣습니다.
경로가 없거나, 배열이 아니거나, 비어 있으면 그룹은 그냥 그려지지 않습니다 — 오류가 아닙니다. 반복은 표의 행과 같은 노드 예산을 쓰므로, 배열이 아주 크면 무한히 늘어나는 대신 잘립니다. 그룹끼리 겹쳐 넣을 수는 없습니다.
편집기가 더 쓰는 키
위 스키마는 렌더에 필요한 것 전부입니다. 다만 편집기에서 저장한 템플릿을 API 로 꺼내 보면 여기 없는 키가 셋 더 붙어 있습니다 — name · hidden · lockOwn. 저희 검증기는 이 셋을 받아들이므로 그대로 두셔도 되고, 지우셔도 렌더 결과는 같습니다.
name 은 객체 목록에 보일 이름이고, lockOwn 은 편집기에서 잠갔는지입니다. 둘 다 렌더에는 아무 영향이 없습니다.
hidden 은 다릅니다 — true 면 그 노드와 그 안에 든 것이 출력에서 통째로 빠집니다. 편집기의 눈 아이콘이 이 값을 씁니다. show 와 헷갈리기 쉬운데, show 는 데이터에 따라 나올지 말지를 정하는 조건식이고 hidden 은 작성자가 지금 안 보겠다는 뜻입니다. API 로 만드는 문서라면 hidden 대신 show 를 쓰십시오.