LaTeX으로 PDF 폼——입력 가능한 텍스트 칸이나 체크박스——을 만들 때 처음 밟는 지뢰는 늘 같습니다. \TextField{Name}이라고 쓰고 컴파일하면 오류도 경고도 없이, 입력란이 어디에도 없는 PDF가 나옵니다. 라벨 “Name”만 본문에 조판되고, pdfinfo는 Form: none이라고 답하며, PDF 안을 들여다봐도 입력 부품(Widget 주석)은 하나도 없습니다. Form 환경 밖에서는 hyperref의 필드 명령이 조용히 아무것도 만들지 않기 때문입니다. 이 페이지에서는 \begin{Form}에서 시작해 각 필드가 실제로 PDF에 무엇을 써 넣는지를 실측으로 따라가고, 전송 버튼이 기본으로 어디에 무엇을 보내는지, 그리고 어느 지점에서 포기하고 웹 폼을 만들어야 하는지까지 다룹니다.
Form 환경이 없으면 필드는 만들어지지 않습니다
대화형 필드는 모두 \begin{Form} … \end{Form} 안에 둡니다. 이것은 서술 관습이 아니라 실제로 출력이 달라지는 문제입니다. 환경 밖에 \TextField와 \CheckBox를 둔 문서를 컴파일하면 pdflatex는 종료 코드 0으로 정상 종료하고 경고도 전혀 내지 않지만, 만들어진 PDF에 담긴 /Widget 주석은 0개이고 pdfinfo의 Form: 칸도 none 그대로입니다. 같은 명령을 환경 안으로 옮기면 pdfinfo가 Form: AcroForm이라고 답하기 시작합니다. PDF 폼은 AcroForm이라는 사전 하나가 모든 필드를 묶는 구조이고, Form 환경이 바로 그 사전을 만드는 역할이기 때문입니다. 또한 \usepackage{hyperref}만 써도 충분하며 [pdftex] 같은 드라이버 지정은 필요 없습니다. 로그를 보면 hpdftex.def가 자동으로 읽힙니다.
Form 환경이 받는 옵션은 hyperref 소스를 세어 보면 네 개뿐입니다. action(보낼 곳), method, encoding, NeedAppearances입니다. 필드의 겉모습과 동작은 각 명령의 [...]에서 지정하므로, 환경의 옵션은 전송 설정이라고 생각해 두면 충분합니다.
\documentclass{article}
\usepackage{hyperref} % no driver option needed
\begin{document}
\begin{Form}[action={https://example.org/collect},method=post]
\TextField[name=fullname,width=6cm]{Name}\par
\CheckBox[name=agree]{I agree}\par
\ChoiceMenu[combo,name=affil]{Affiliation}{University,Company,Other}\par
\Submit{Send}\quad\Reset{Clear}
\end{Form}
\end{document}필드의 종류와 PDF에 기록되는 것
필드 명령은 다섯 개이고, PDF 쪽에서는 세 가지 필드 형으로 내려앉습니다. 텍스트는 /Tx, 선택은 /Ch, 버튼 모양인 것——체크박스, 누름 버튼, 전송, 초기화——은 모두 /Btn입니다. 위 예제를 컴파일해 객체를 꺼내 보면 정확히 그렇습니다. /Tx 둘, /Ch 하나, /Btn 넷입니다. 버튼을 한데 묶는 것은 PDF 명세 그대로이며, 체크박스와 누름 버튼의 차이는 형이 아니라 플래그(/Ff)의 비트로 표현됩니다. 이 “플래그가 성격을 정한다”는 구조가 다음 절에서 효력을 발휘합니다.
| 명령 | PDF 필드 형 | 생성되는 것 |
|---|---|---|
\TextField | /Tx | 텍스트 입력란. multiline, password, maxlen으로 성격을 바꿉니다 |
\CheckBox | /Btn | 체크박스. 기본값은 /Off이고 checked로 처음부터 켤 수 있습니다 |
\ChoiceMenu | /Ch 또는 /Btn | combo는 편집 가능한 드롭다운, popdown은 리스트 박스, radio는 라디오 그룹(/Btn이 됩니다) |
\PushButton | /Btn | 누름 버튼. onclick=에 JavaScript를 쓰면 /S /JavaScript 액션이 됩니다 |
\Submit / \Reset | /Btn | /S /SubmitForm과 /S /ResetForm. 필드 이름은 항상 Submit과 Reset이며 인수는 표시되는 글자일 뿐입니다 |
name=을 빼면 라벨이 필드 이름이 됩니다 — 라디오 그룹의 함정
name=을 쓰지 않으면 라벨 문자열이 그대로 필드 이름이 됩니다. \TextField{Your name}으로 만든 PDF를 들여다보면 필드 이름은 /T (Your name), 공백까지 그대로입니다. 데이터를 받는 쪽에서 보면 다루기 나쁜 이름이고, 한국어 라벨을 쓰면 한국어 이름이 됩니다. 필드 이름은 반드시 name=으로 ASCII 식별자를 명시하는 것이 실무의 관행입니다. 결과가 하나 더 있습니다. 같은 name=을 두 곳에 쓰면 PDF는 이름이 같은 필드를 동일한 필드로 간주합니다. name=dup인 \TextField를 둘 두면 /T (dup)을 가진 객체가 둘 생기고, 한쪽에 입력하면 다른 쪽도 같은 값이 됩니다. 같은 값을 두 곳에 보이고 싶을 때는 유용한 성질이지만, 실수로 이름이 겹치면 원인을 찾기 어려운 문제가 됩니다.
라디오 버튼에는 더 깊은 문제가 있습니다. \ChoiceMenu[radio,name=r1]{Pick}{a,b,c}를 조판하면 r1이라는 같은 이름을 가진 /Btn 객체가 셋 생깁니다. 그런데 AcroForm 사전의 /Fields 배열에 오르는 것은 맨 처음 하나뿐입니다. 나머지 둘은 어느 필드에서도 참조되지 않은 채 떠 있습니다. 실제로 qpdf에 걸면 WARNING: this widget annotation is not reachable from /AcroForm in the document catalog라는 경고가 두 번 나옵니다. PDF 명세에서는 라디오 그룹을 부모 필드 하나가 /Kids로 자식을 묶는 형태로 표현해야 하지만, hyperref는 평평하게 늘어놓습니다. 표시해 주는 뷰어가 많아 알아차리기 어렵지만, 엄격한 PDF 처리기나 자동 추출에 걸면 깨질 수 있는 구조입니다. 선택지가 고정이라면 라디오보다 combo나 popdown이 더 정직한 도구입니다.
자주 쓰는 옵션 — 플래그가 되는 것과 되지 않는 것
각 필드는 [...]에 많은 옵션을 받습니다(hyperref가 정의한 키는 서른 개에 가깝습니다). 자주 쓰는 것은 name=, width=/height=, default=(초깃값), bordercolor/backgroundcolor, charsize(글자 크기), align(0=왼쪽, 1=가운데, 2=오른쪽), maxlen=(최대 글자 수), menulength=(목록에 보이는 줄 수) 정도입니다. 이 가운데 multiline, readonly, password 셋만이 값을 받지 않는 스위치이며 PDF 플래그와 일대일로 대응합니다. hyperref.sty 5283행 이후에 ReadOnly가 비트 1, Multiline이 비트 13, Password가 비트 14로 정의되어 있고, 실제로 조판해 /Ff를 읽으면 각각 1, 4096, 8192가 나옵니다. 반면 maxlen=5는 플래그가 아니라 /MaxLen 5라는 독립 항목으로 기록됩니다. 이 구별을 알면 옵션이 기대대로 듣지 않을 때 어디를 봐야 할지 정해집니다.
\begin{Form}
\TextField[name=notes,multiline,width=8cm,height=3cm]{Notes}\par
\TextField[name=locked,readonly,width=4cm,default={fixed}]{Locked}\par
\TextField[name=short,maxlen=5,width=3cm]{Max 5}\par
\TextField[name=email,width=5cm,align=0,
bordercolor={0 0 0},backgroundcolor={1 1 0.9}]{Email}
\end{Form}\Submit은 기본으로 FDF를 보냅니다 — method=post만으로는 부족합니다
이 페이지에서 가장 중요한 한 가지입니다. \begin{Form}[action={https://example.org/collect},method=post]이라고 쓰고 전송 버튼을 눌러도, 서버에 도착하는 것은 HTML 폼의 POST가 아니라 FDF라는 아크로뱃 고유 형식의 데이터입니다. 원인은 hyperref.sty 5371행의 \def\Fld@export{fdf}입니다. 기본 내보내기 형식이 FDF로 설정되어 있습니다. 실제로 컴파일해 전송 액션을 꺼내 보면 /S /SubmitForm에 /Flags 항목 자체가 없어 모든 플래그가 0, 곧 FDF입니다. 그렇다면 method=post는 어떻게 되는가. 5378행부터의 \HyField@FlagsSubmit을 읽으면 알 수 있습니다. method가 세우는 GetMethod 플래그는 HTML과 PDF 분기에서만 쓰이고 FDF 분기에서는 완전히 무시됩니다. 즉 method=post는 단독으로는 아무 효과도 없습니다.
평범한 웹 서버에서 받고 싶다면 Form 환경에 encoding=html을 더합니다. 이것은 hyperref.sty 5665행 부근에서 \def\Fld@export{html}을 실행하는 전용 키로, 이를 붙여 다시 조판하면 전송 액션에 /Flags 4가 나타납니다. 비트 3의 ExportFormat이 세워져 HTML 형식이 되었다는 뜻입니다. 참고로 encoding에 html 이외를 쓰면 Form 'encoding' key with unknown value라는 경고만 나오고 조용히 무시됩니다. 내보내기 형식으로는 그 밖에 xfdf(FDF의 XML판)와 pdf(작성된 PDF를 통째로 보냄)도 고를 수 있습니다.
% FDF (the default) -- your endpoint receives an Acrobat-specific blob
\begin{Form}[action={https://example.org/collect},method=post]
% an ordinary HTML form post -- note encoding=html
\begin{Form}[action={https://example.org/collect},encoding=html,method=post]실제 뷰어에서 무슨 일이 일어나는가, 그리고 언제 포기할 것인가
hyperref가 만드는 폼은 입력란의 겉모습 자체를 파일에 써 넣지 않습니다. AcroForm 사전에 /NeedAppearances true를 세워 “그리는 일은 뷰어 쪽에서 해 달라”고 부탁하는 방식입니다. Acrobat Reader는 이 부탁에 응하지만 대응 정도는 뷰어마다 다르며, 브라우저 내장 PDF 표시나 가벼운 뷰어에서는 테두리가 나오지 않거나 입력이 되지 않는 일이 생깁니다. \PushButton[onclick=...]의 JavaScript에 이르러서는 실행해 주는 뷰어가 소수입니다. 같은 성질이 다른 곳으로도 번집니다. /NeedAppearances에 기대는 구조는 PDF/A에 적합할 수 없습니다. 입력란 하나만 두어도 veraPDF는 6.3.3 조항 “An annotation does not contain an appearance dictionary”로 불합격을 돌려줍니다(PDF/A 페이지에 자세히 적었습니다).
여기까지를 종합하면 PDF 폼을 고를 이유는 꽤 좁아집니다. 검증이나 스크립트 같은 복잡한 일을 하고 싶다면 insdljs나 AcroTeX의 eforms라는 선택지가 있지만, 거기까지 쌓아 올려도 “상대의 뷰어에서 동작할지”는 끝까지 보장할 수 없습니다. 응답을 온라인으로 모으고 싶을 뿐이라면 웹 폼이 더 확실하고 빠르다는 것이 정직한 결론입니다. 반대로 PDF 폼이 어울리는 곳은 종이로 배포할 것을 전제로 한 양식인데, 받는 쪽이 마침 컴퓨터로 작성해 인쇄하거나 PDF로 저장하는 경우——곧 전송 기능을 쓰지 않는 경우입니다. 이 용도라면 기입란이 입력 가능하다는 것만으로도 충분히 쓸모가 있고, readonly로 고정한 칸과 조합하면 템플릿으로 안정적으로 쓸 수 있습니다.