PDF 파일은 어떻게 생겼나: 객체, 교차 참조표, 그리고 편집이 어려운 이유
PDF 파일을 열어 보면 번호 붙은 객체와 바이트 위치표, 압축된 그리기 명령이 나옵니다. 1993년 Acrobat부터 ISO 32000-2까지의 역사와 함께, PDF가 왜 워드 문서처럼 고쳐지지 않는지 설명합니다.
PDF를 메모장으로 열어 본 적이 있다면 첫 줄에서 %PDF-1.7 같은 글자를 보고, 그 뒤로 알아볼 수 없는 문자가 쏟아지는 것을 봤을 겁니다. 그 안에는 생각보다 단순한 구조가 있습니다. 이 구조를 알면 「PDF를 합쳤더니 책갈피가 사라졌다」, 「나눴더니 용량이 오히려 늘었다」, 「글자 하나 고치는 게 왜 이렇게 어렵나」 같은 일상의 불편이 왜 생기는지 설명이 됩니다.
출발점: 어느 컴퓨터에서나 똑같이 보이는 문서
1991년 Adobe 공동 창업자 존 워녹은 「Camelot」이라는 내부 메모에서, 기종과 운영체제가 달라도 문서를 똑같이 주고받을 방법이 필요하다고 썼습니다. 당시 Adobe에는 이미 PostScript라는 프린터용 페이지 기술 언어가 있었습니다. PostScript는 완전한 프로그래밍 언어라서 페이지를 그리려면 프로그램을 처음부터 실행해야 했고, 10쪽을 보려면 앞의 9쪽을 먼저 처리해야 하는 식이었습니다.
PDF는 PostScript의 그리기 모델(선, 곡선, 글자, 이미지를 좌표에 그리는 방식)은 가져오되, 반복문이나 조건문 같은 프로그래밍 기능은 빼고, 페이지마다 독립적으로 찾아갈 수 있게 파일 구조를 새로 짠 결과입니다. Adobe는 1993년 6월 Acrobat 1.0과 함께 PDF를 내놓았습니다.
그 뒤 Adobe가 사양을 공개한 채로 PDF 1.1부터 1.7까지 버전을 올렸고, 2008년에는 PDF 1.7이 국제 표준 ISO 32000-1이 됐습니다. 다음 버전인 PDF 2.0(ISO 32000-2)은 처음으로 Adobe 한 회사가 아니라 ISO 위원회에서 만든 판으로 2017년에 나왔고, 오류와 모호한 부분을 고친 개정판이 2020년에 나왔습니다. 2023년 4월부터는 PDF Association이 Adobe, Apryse, Foxit의 후원을 받아 ISO 32000-2를 무료로 내려받을 수 있게 하고 있습니다.
파일을 열면 보이는 네 부분
PDF 파일은 크게 네 덩어리로 이뤄집니다.
| 부분 | 내용 |
|---|---|
| 헤더 | %PDF-1.7처럼 버전을 적은 첫 줄 |
| 본문 | 번호가 붙은 객체들 |
| 교차 참조표(xref) | 각 객체가 파일의 몇 번째 바이트에서 시작하는지 적은 표 |
| 트레일러 | 문서의 시작점(카탈로그)이 몇 번 객체인지, xref가 어디 있는지 |
본문의 객체는 이렇게 생겼습니다.
4 0 obj
<< /Type /Page /Parent 2 0 R /MediaBox [0 0 595 842]
/Contents 5 0 R /Resources << /Font << /F1 6 0 R >> >> >>
endobj
4 0 obj는 「4번 객체(세대 번호 0)」라는 뜻이고, << >> 안은 이름과 값을 짝지은 사전입니다. 5 0 R은 「5번 객체를 보라」는 참조입니다. 이 페이지는 A4 크기(595×842pt, 1pt는 1/72인치)이고, 그릴 내용은 5번 객체에, 쓰는 글꼴은 6번 객체에 있다고 말하고 있습니다. 문서 전체는 이런 참조로 이어진 그래프입니다. 트레일러가 카탈로그를 가리키고, 카탈로그는 페이지 트리와 책갈피(Outlines), 양식(AcroForm)을 가리키고, 페이지 트리는 각 페이지를, 각 페이지는 내용과 글꼴과 이미지를 가리킵니다.
뷰어는 파일을 처음부터 읽지 않습니다. 파일 끝의 startxref 뒤에 적힌 숫자로 교차 참조표를 찾고, 거기서 필요한 객체의 바이트 위치를 얻어 곧장 뛰어갑니다. 교차 참조표의 각 줄은 규격상 정확히 20바이트로 고정돼 있어서, n번 객체의 위치를 표 안에서 계산으로 바로 찾을 수 있습니다. 500쪽짜리 PDF에서 300쪽을 바로 여는 것이 가능한 이유입니다.
PDF 1.5부터는 작은 객체 여러 개를 하나의 압축 스트림에 묶는 객체 스트림(object stream)과, 교차 참조표 자체를 압축한 교차 참조 스트림이 생겼습니다. 요즘 PDF를 메모장으로 열었을 때 객체가 거의 보이지 않는 것은 대부분이 이렇게 압축돼 있기 때문입니다. 이것으로 얼마나 줄어드는지는 PDF 압축률이 제각각인 이유에서 다룹니다.
교차 참조표가 파일 끝에 있다는 점은 웹에서는 약점이 됩니다. 다운로드가 끝나야 표를 읽을 수 있으니 첫 페이지도 늦게 뜹니다. 그래서 규격 부록 F는 「선형화(Linearized) PDF」라는 배치를 정해 둡니다. 첫 페이지를 그리는 데 필요한 객체와 그 위치표를 파일 앞쪽에 모아, 나머지를 받는 동안 첫 페이지를 먼저 보여줄 수 있게 한 형태입니다. Acrobat의 「빠른 웹 보기」 설정이 이것입니다. 다만 선형화된 파일도 증분 업데이트로 한 번 저장하면 뒤에 붙은 부분 때문에 그 이점이 흐트러집니다.
고친 내용을 뒤에 덧붙이는 증분 업데이트
PDF에는 파일 전체를 다시 쓰지 않고 바뀐 부분만 끝에 덧붙이는 방식이 있습니다. 증분 업데이트(incremental update)라고 합니다. 양식에 이름을 적고 저장하면, 바뀐 필드 객체와 그 객체만 담은 새 교차 참조 구역, 새 트레일러가 파일 끝에 추가됩니다. 새 트레일러는 이전 교차 참조표의 위치를 기억하므로 뷰어는 최신 것부터 거꾸로 따라가며 객체의 최신판을 찾습니다.
이 방식에는 두 가지 결과가 따릅니다. 하나는 전자서명입니다. 서명은 서명 시점까지의 바이트에 대해 계산되므로, 그 뒤에 덧붙인 수정은 서명을 깨지 않으면서도 「서명 이후 변경됨」으로 식별할 수 있습니다. 다른 하나는 사생활 문제입니다. 증분 업데이트로 저장된 파일에서 지운 텍스트나 이전 값은 파일 앞부분에 그대로 남아 있을 수 있습니다. 민감한 내용을 지웠다면 파일을 처음부터 다시 쓰는(「다른 이름으로 저장」, 최적화) 과정을 거쳐야 옛 판이 사라집니다.
글자는 「문장」이 아니라 「좌표에 찍힌 도장」이다
페이지 내용 스트림을 풀어 보면 이런 명령이 나옵니다.
BT
/F1 12 Tf
72 770 Td
(Hello) Tj
ET
「텍스트 시작, F1 글꼴 12pt, 좌표 (72, 770)으로 이동, Hello를 찍기, 텍스트 끝」이라는 뜻입니다. 여기에는 문단도, 줄바꿈도, 「이 줄 다음에 저 줄이 온다」는 정보도 없습니다. 워드프로세서가 문단을 줄로 나누고 글자 간격을 맞춘 최종 결과를, 글자 하나하나 또는 조각 단위로 위치를 박아서 저장할 뿐입니다. 양쪽 정렬된 문단이라면 단어 사이 간격까지 숫자로 따로 적혀 있습니다.
그래서 PDF에서 한 단어를 고치면 그 뒤 글자들이 알아서 밀려나지 않습니다. 「서울」을 「서울특별시」로 바꾸면 늘어난 폭만큼 뒤 글자와 겹치고, 편집기가 그 줄을 다시 배치하려 해도 원래 문단이 어디서 시작하고 끝나는지부터 추측해야 합니다. 표는 더 심해서, 칸과 선은 따로 그려진 선분이고 글자는 그 위에 우연히 놓여 있을 뿐입니다. PDF를 워드로 변환했을 때 문단이 줄마다 끊기거나 글상자투성이가 되는 것은 변환기가 이 추측에 실패한 흔적입니다.
글자를 복사할 때 엉뚱한 문자가 나오는 문제도 같은 뿌리입니다. (Hello) 안의 바이트는 문자 코드가 아니라 글꼴 안의 글리프(모양) 번호일 수 있습니다. 어떤 글리프가 어떤 유니코드 문자인지는 ToUnicode라는 별도 대응표가 알려주는데, 이 표가 없거나 틀리면 화면에는 멀쩡히 보여도 복사하면 깨진 문자가 나옵니다.
글꼴은 「쓴 글자만」 들어 있다
받는 사람 컴퓨터에 없는 글꼴도 똑같이 보이게 하려고 PDF는 글꼴 파일을 안에 넣습니다(임베딩). 한글 글꼴 하나는 수천 자를 담고 있어 수 MB가 되기도 하므로, 보통은 문서에 실제로 쓴 글자만 뽑은 부분집합(subset)을 넣습니다. 규격은 부분집합 글꼴 이름 앞에 대문자 여섯 개와 +를 붙이도록 정했습니다. PDF 속성에서 ABCDEF+NanumGothic 같은 이름을 봤다면 그것이 부분집합입니다.
부분집합은 편집을 어렵게 만드는 또 하나의 이유입니다. 문서에 한 번도 쓰이지 않은 「뷁」을 넣으려고 하면, 넣어 둔 글꼴에 그 글자의 모양이 없습니다. 편집기는 다른 글꼴로 대신하거나 컴퓨터에 설치된 원래 글꼴을 찾아야 하고, 그마저 없으면 글자를 넣을 수 없습니다.
이 구조가 일상 작업에서 드러나는 곳
합치기. 두 PDF를 합치는 도구는 두 번째 파일의 페이지 객체와 그 페이지가 참조하는 객체를 첫 번째 파일로 옮기고 번호를 새로 매깁니다. 책갈피와 양식 정의는 페이지가 아니라 카탈로그에 있으므로, 페이지만 옮기면 따라오지 않습니다. PDF 합치기에서 맨 위 파일의 책갈피만 남는 것이 이 때문입니다.
나누기. 글꼴과 로고 이미지는 한 번 저장되고 여러 페이지가 같은 번호로 가리킵니다. 문서를 열 개로 나누면 각 파일이 공유 리소스를 한 벌씩 가져가야 하므로 합계가 원본보다 커집니다. PDF 나누기의 결과 용량이 예상보다 큰 이유입니다.
압축. 텍스트와 벡터는 원래 작습니다. PDF 용량의 대부분은 이미지 스트림과 글꼴이 차지하므로, PDF 압축이 실제로 줄일 수 있는 곳도 주로 이미지 해상도와 품질입니다. 증분 업데이트로 여러 번 저장해 옛 판이 쌓인 파일은 다시 쓰기만 해도 줄어듭니다.
점검. 파일이 어느 버전인지, 어떤 글꼴이 부분집합으로 들어 있는지, 페이지마다 MediaBox와 CropBox가 어떻게 잡혀 있는지는 PDF 정보에서 볼 수 있습니다. 인쇄소에서 「글꼴이 임베딩되지 않았다」는 연락을 받았을 때 먼저 확인할 곳입니다.
흔한 오해 몇 가지
- 「PDF는 수정할 수 없는 형식이다.」 아닙니다. 증분 업데이트와 양식, 주석은 모두 수정을 전제로 한 기능입니다. 다만 수정의 단위가 「문단」이 아니라 「객체」라서 워드 같은 편집이 어렵습니다.
- 「PDF로 저장하면 원본 문서가 들어 있다.」 일반적으로는 인쇄 결과만 들어 있습니다. 일부 프로그램은 편집용 원본 데이터를 따로 넣기도 하지만 PDF 규격이 요구하는 것은 아닙니다.
- 「버전 번호가 높을수록 좋다.」 헤더의 버전은 그 파일이 쓰는 기능의 상한일 뿐입니다. 지금도 많은 프로그램이 1.x 버전으로 저장하며, PDF 2.0으로 저장한다고 화질이 좋아지지 않습니다.
- 「잘라내면 지워진다.」 페이지 자르기는 대개 CropBox라는 보이는 영역만 줄입니다. 잘린 바깥 내용은 파일에 남아 있습니다.
출처
- ISO 32000-1:2008 Document management — Portable document format — Part 1: PDF 1.7 (Adobe 공개본)
- PDF Association — ISO 32000-2 (PDF 2.0)
- PDF Association — Announcing no-cost access to ISO 32000-2 (PDF 2.0)
- PDF Association — ISO 32000-1
- Adobe Blog — Evolution of the Digital Document: Celebrating Adobe Acrobat's 25th Anniversary (2018)
- Library of Congress — PDF 2.0, ISO 32000-2 (2017, 2020)