PDF 파일의 해부학
한눈에 보기
섹션 제목: “한눈에 보기”평범한 텍스트 편집기에서 아무 PDF나 열어 보면 가장 먼저 보이는 것은 안심이 됩니다.
%PDF-1.x 또는 %PDF-2.0 헤더입니다. 가장 마지막에 보이는 것은 %%EOF입니다.
그 두 줄 사이의 모든 것은 객체를 번호로 찾기 위한 작고 깔끔한 기계입니다.
이 페이지는 해부입니다. 우리는 파일을 열어, 각 기관에 이름을 붙이고, 그것들이 어떻게
연결되는지 보여 줍니다.
이것은 두 이웃 페이지의 구조적 동반자입니다. PDF의 실체는 파일을 객체 그래프로 다루고, 증분 업데이트는 파일이 시간에 따라 어떻게 자라는지를 다룹니다. 이 페이지는 바이트에 바짝 붙어 있습니다 — 파서가 디스크에 놓인 순서대로 가로지르는 물리적 영역들입니다.
이것이 중요한 이유
섹션 제목: “이것이 중요한 이유”PDF를 사용하기 위해 이것이 필요한 경우는 거의 없습니다. 그것이 필요한 것은 하나가 잘못되는 날입니다. 파일이 한 뷰어에서는 열리고 다른 뷰어에서는 열리지 않거나, 검증기가 “손상된 상호 참조 테이블”을 보고하거나, 서명된 문서가 갑자기 검증에 실패합니다. 이 중 어느 것도, 해부학을 읽을 수 있게 되면 더 이상 수수께끼가 아닙니다. 그것들은 더 이상 위치와 일치하지 않는 숫자, 잘못된 곳에 놓인 영역, 또는 아무것도 가리키지 않는 꼬리입니다.
배치를 안다는 것은 “PDF가 손상되었다”를 여러분이 손쓸 수 있는 진단으로 바꿉니다. 그것은 블랙박스 앞에서 어깨를 으쓱하는 것과, 거짓말을 하고 있는 바로 그 바이트를 가리키는 것 사이의 차이입니다.
짧게 요약하면
섹션 제목: “짧게 요약하면”적합한 PDF는 이 파일 순서에 따라 네 개의 물리적 부분을 갖습니다 (Spec: ISO 32000-2, §7.5.1ISO 32000-2 §7.5.1):
- 헤더 — 한 줄,
%PDF-2.0, 버전을 명시합니다. - 본문 — 파일의 대부분: 번호가 매겨진 간접 객체의 연속입니다.
- 상호 참조 섹션 — 객체 번호에서 그 객체가 위치한 바이트 오프셋으로의 인덱스입니다. 고전적인 PDF는 텍스트 테이블을 사용하고, PDF 2.0은 압축된 xref 스트림을 사용합니다.
- 트레일러 — 진입점을 명시하는 작은 딕셔너리로, 그 뒤에
startxref, 오프셋, 그리고%%EOF가 따라옵니다.
반전은 이렇습니다. 리더는 맨 위에서 시작하지 않습니다. 그것은 맨 아래에서 시작해,
startxref를 읽어 인덱스를 찾고, 그 인덱스를 사용해 어떤 객체에든 직접 도달합니다.
파일은 앞에서 뒤로 쓰이지만 뒤에서 앞으로 읽힙니다.
NextPDF가 이를 다루는 방식
섹션 제목: “NextPDF가 이를 다루는 방식”바이트를 눈앞에 둔 채, 네 영역을 순서대로 걸어 봅시다.
헤더는 한 줄입니다. NextPDF는 %PDF-2.0을, 그리고 관례에 따라 높은 비트 바이트로 된
두 번째 주석 줄을 기록하여, 순진한 전송 도구가 파일을 텍스트가 아니라 바이너리로 다루게 합니다.
그 두 번째 줄이 바로 평범한 텍스트로 연 PDF가 버전 바로 뒤에 약간의 깨진 글자를 보여 주는
이유입니다.
본문은 문서가 사는 곳입니다. 각 간접 객체는 번호, 세대, 키워드 obj, 값, 그리고
endobj입니다(Spec: ISO 32000-2, §7.3.10ISO 32000-2 §7.3.10). 값은 몇 가지
형태 중 하나입니다 — 하지만 두 가지가 거의 모든 무게를 짊어집니다:
- 딕셔너리,
<< /Key value … >>는 이름에서 값으로의 맵입니다. 페이지 트리, 카탈로그, 폰트 디스크립터: 모두 딕셔너리입니다. - 스트림은 딕셔너리 뒤에
stream, 임의 바이트의 블록, 그리고endstream이 따라오는 것입니다. 페이지 콘텐츠, 내장 폰트, 이미지는 스트림이며, 거의 항상 압축되어 있습니다. (그것들의 필터는 그 자체로 하나의 이야기로, 스트림과 필터에서 다룹니다.)
객체는 간접 참조로 서로를 가리킵니다 — 2 0 R은 “객체 2,
세대 0”을 뜻합니다. 그것이 객체의 평평한 목록을 그래프로 바꾸는 배선입니다.
상호 참조 섹션은 대부분의 사람들이 결코 정확히 그려 내지 못하는 부분입니다.
고전적인 형태에서 그것은 평범한 텍스트입니다. 키워드 xref, 그다음 고정 폭 20 바이트
줄의 서브섹션입니다(Spec: ISO 32000-2, §7.5.4ISO 32000-2 §7.5.4). 각 줄은 열 자리 바이트 오프셋, 다섯 자리
세대, 그리고 단일 플래그 — 사용 중을 뜻하는 n, 비어 있음을 뜻하는 f — 로, 리더가
산술만으로 어떤 항목에든 탐색할 수 있도록 정확히 스무 바이트로 패딩됩니다.
PDF 2.0은 이를 상호 참조 스트림으로 대체합니다. 같은 인덱스이지만, /Type /XRef로
표시된 스트림 객체 안에 바이너리로 압축되어 있습니다
(Spec: ISO 32000-2, §7.5.8ISO 32000-2 §7.5.8). 더 작고, 객체 스트림 안에 담긴
객체를 기술할 수 있습니다.
트레일러는 파일의 목차입니다(Spec: ISO
32000-2, §7.5.5ISO
32000-2 §7.5.5). 그것은 /Root — 문서 카탈로그, 다른 모든 것이 거기에
매달려 있는 단일 객체 — 와 /Size, 객체 수를 명시합니다. 그다음 역방향 읽기를 가능하게
하는 악수가 옵니다. startxref, 자기 줄에 놓인 바이트 오프셋, 그리고 %%EOF입니다.
리더는 끝으로 탐색해 그 오프셋을 읽고, 곧장 상호 참조 섹션으로 점프하여, 출발합니다.
- HeaderOne line, %PDF-2.0, naming the version. A binary-marker comment usually follows.
- BodyNumbered indirect objects — dictionaries and streams — referenced by N G R.
- Cross-reference sectionA text table of 20-byte entries, or a compressed /Type /XRef stream in PDF 2.0.
- TrailerNames /Root and /Size, then startxref + offset + %%EOF.
- Read orderA reader starts at %%EOF, follows startxref to the index, then reaches each object directly.
오래 사는 파일이 키우는 다섯 번째 영역이 있습니다. 증분 업데이트입니다. 변경은 제자리에
쓰이지 않습니다. 변경된 객체, 새 상호 참조 섹션, 그리고 새 트레일러가 첫 번째 %%EOF 뒤에
덧붙여지며, 그 새 트레일러는 /Prev — 이전 상호 참조
섹션의 오프셋 — 를 지닙니다(Spec: ISO 32000-2, §7.5.6ISO 32000-2 §7.5.6). 섹션들은
역방향 체인을 이루고, 어떤 객체 번호에 대해서든 가장 최신 항목이 이깁니다. 원래 바이트가
결코 움직이지 않으므로, 서명이 실제로 다루는 바이트 범위에 대한 암호화 검증은 업데이트
이후에도 여전히 유효합니다. 이후의 증분 업데이트는 문서 전체에 대해 검증기가 보고하는 내용 —
서명 이후의 변경이 허용되는지, 그리고 서명이 무엇을 증명하는 것으로 간주되는지 — 을 여전히
바꿀 수 있지만, 서명된 바이트 자체를 바꿀 수는 없습니다. 그 속성이 바로 증분
업데이트의 주제 전부입니다.
실제 예시
섹션 제목: “실제 예시”여기 전체 해부학이 하나의 최소 파일에 담겨 있습니다. xref 아래의 숫자는 바이트
오프셋이며, 정확해야 합니다 — 객체가 시작되는 곳에서 한 글자라도 지나친 곳을 가리키면 엄격한
리더는 포기합니다.
%PDF-2.01 0 obj<< /Type /Catalog /Pages 2 0 R >>endobj2 0 obj<< /Type /Pages /Kids [3 0 R] /Count 1 >>endobj3 0 obj<< /Type /Page /Parent 2 0 R /MediaBox [0 0 612 792] >>endobjxref0 40000000000 65535 f0000000009 00000 n0000000058 00000 n0000000115 00000 ntrailer<< /Size 4 /Root 1 0 R >>startxref186%%EOF파서가 읽는 방식대로 읽어 보세요. 마지막 줄: %%EOF. 그 위: startxref 186,
따라서 바이트 186으로 탐색하면 거기서 xref가 시작됩니다. 테이블은 객체 1이
바이트 9에 있다고 말합니다. 트레일러의 /Root 1 0 R이 그곳 — 카탈로그 — 을 가리키며,
카탈로그에서 /Pages를 따라 페이지 트리로 가서 단 하나의 페이지를 찾습니다. 객체 0은
항상 세대 65535를 갖는 프리 리스트 헤드이며, 포맷의 첫 설계에서 비롯된 화석으로
모든 리더가 여전히 보기를 기대합니다.
흔한 오해
섹션 제목: “흔한 오해”함정은 PDF를 이야기처럼 — 위에서 아래로, 순서대로 — 읽는 것입니다. 그것은 이야기가
아닙니다. 그것은 역방향 포인터를 가진 인덱스입니다. 객체 번호는 파일 안에서 순차적일 필요가
없고, 객체는 어떤 물리적 순서로든 나타날 수 있으며, 리더는 결코 그것들의 위치에 의존하지
않습니다. 유일하게 권위 있는 지도는
상호 참조 섹션이며, 그 지도를 찾는 유일한 방법은 맨 끝에 있는 startxref
오프셋입니다.
그 귀결은 사람들을 놀라게 합니다. 흠 없는 본문과 startxref의 잘못된 숫자 하나를 가진
PDF는 읽을 수 없습니다 — 리더가 인덱스를 찾을 수 없기 때문입니다. 객체가 뒤섞인 순서로
놓여 있지만 상호 참조 섹션이 올바른 PDF는 완벽히 괜찮습니다. 물리적 위치는 아무 의미도
나르지 않습니다. 기록된 위치가 그 전부를 나릅니다.
한계와 경계
섹션 제목: “한계와 경계”이 페이지는 페이지 콘텐츠가 아니라 물리적 구조를 설명합니다. 마크가 페이지 위에 어떻게
놓이는지 — 콘텐츠 스트림 연산자, 텍스트 표시, 그래픽 상태 — 는 별개의 주제입니다. 또한
이것은 잘 형성된 파일을 설명합니다. 현실의 PDF는 흔히 조금 깨져 있고, 너그러운 뷰어가
obj 키워드를 스캔해 상호 참조 테이블을 재구성하는 덕분에만 살아남습니다. 그 구제는
뷰어의 동작이지, 포맷이 보장하는 것이 아닙니다.
| Edition | Availability |
|---|---|
| Core | NextPDF는 writer입니다. 그것은 각 객체가 출력되는 순간에 출력 버퍼로부터 모든 오프셋을 기록하므로, 그것이 생산하는 파일은 본문과 구성상 일치하는 상호 참조 섹션을 갖습니다. |
| Pro | NextPDF가 작성하지 않은 파일에서 손상된 상호 참조 테이블을 파싱하거나, 재구성하거나, 복원하는 것은 모든 에디션에서 범위 밖입니다. |
| Enterprise | 기존 파일의 구조를 검사하려면, 전용 파서나 검증기를 사용하세요. NextPDF는 그것이 읽는 것이 아니라 그것이 작성하는 것에 대해 정확성을 보장합니다. |
미니 FAQ
섹션 제목: “미니 FAQ”왜 PDF에는 헤더 뒤에 바이너리 마커 줄이 있을까요? 일부 오래된 전송 도구는 평범한 텍스트라고 여긴 파일을 망가뜨리곤 했습니다. 높은 비트 주석은 파일을 명백히 바이너리로 보이게 하여, 변경 없이 여정을 견디게 합니다.
xref 스트림은 그저 더 작은 테이블일 뿐일까요? 대체로 그렇지만, 한 가지 추가 능력이 있습니다. 압축되어 있다는 점을 넘어, xref 스트림은 객체 스트림 안에 저장된 객체를 기술할 수 있습니다 — 고전적인 20 바이트 텍스트 테이블로는 표현할 길이 없는 항목들입니다.
한 파일에 테이블과 스트림을 둘 다 둘 수 있을까요? 단일 리비전은 둘 중 하나를 사용합니다. 하지만 하이브리드 파일은 오래된 리더를 위한 고전적인 테이블과 새 리더를 위한 상호 참조 스트림을 짝지을 수 있어, 각 종류의 리더가 자신이 이해하는 인덱스를 찾게 합니다.
관련 문서
섹션 제목: “관련 문서”- PDF의 실체 — 같은 네 부분을 물리적 레이아웃이 아니라 객체 그래프로 본 것.
- 증분 업데이트와 그것이 중요한 이유 — 덧붙여진 다섯 번째 영역이 파일을 어떻게 키우고 서명을 어떻게 보호하는지.
- 스트림과 필터 — 본문의 스트림 객체 안에 무엇이 있고 그것들이 어떻게 압축되는지.
- PDF 2.0: 무엇이 바뀌었나 — 왜 상호 참조 스트림이 NextPDF가 기록하는 기본 구조인지.
용어집
섹션 제목: “용어집”- 헤더 — 첫 줄,
%PDF-2.0, 버전을 명시하며, 대개 바이너리 마커 주석이 뒤따릅니다. - 간접 객체 — 본문에 있는 번호가 매겨진 객체로,
N G obj … endobj로 작성되며, 여기서N은 객체 번호이고G는 세대입니다. - 딕셔너리 — 이름에서 값으로의
<< /Key value … >>맵으로, 가장 흔한 객체 형태입니다. - 스트림 — 딕셔너리에 더해
stream과endstream사이의 바이트 블록으로, 콘텐츠, 폰트, 이미지에 쓰입니다. - 상호 참조 테이블(xref) — 객체 번호에서 바이트 오프셋으로의
인덱스로, 고전적으로는 항목당 20 바이트의 텍스트 테이블이고, PDF 2.0에서는
/Type /XRef스트림입니다. - 트레일러 —
/Root과/Size를 명시하는 딕셔너리로, 파일 끝에 있는startxref오프셋을 통해 찾습니다. - 증분 업데이트 — 변경된 객체, 새 상호 참조 섹션, 그리고
새 트레일러가
%%EOF뒤에 덧붙여진 것으로,/Prev가 앞선 섹션으로 거꾸로 연결됩니다.