Technical FormatPDF

PDF에 대한 이해

·9 min read

PDF 가지고 만지작 거리다 보니깐 이놈이 어떻게 동작되는지 궁금했다.

"아니 텍스트 이거 여기 쫙 긁을 수 있는데 수정도 되야하는거 아냐?"

PDF 란

애는 목적 자체가 명확하다

"어떤 컴퓨터든, 어떤 프로그램이든 원본과 똑같이 보이고 인쇄되어야 한다"

PDF가 나온 시대가 1990년도 초반인데 당시에 이 보이고 인쇄하는게 문제가 많았다.

어떤 컴퓨터든 = 윈도우, Mac, Unix 3국시대. 표준이 없었음

어떤 프로그램이든 = MS 워드 독점이 아니라서 별에별 워드 프로세서가 많았음

인쇄되어야한다 = 문서 공유하는 확실한 방법이 종이로 인쇄하고 퀵으로 보내는 거

이때 어도비가 당시에는 인쇄 기술 회사였는데, 프린터에 페이지 어떻게 그릴지 명령하는 페이지 기술 언어를 가지고 있었다.

그래서 존 위녹이라는 사람이 "어차피 우리 그리는 명령어 있으니깐, 이걸로 컴퓨터 화면에 그리는건 어떰?"라고 아이디어를 냈다.

지금도 인쇄물 주고 받는데 그냥 디지털로 인쇄물을 주고 받자가 PDF의 시초다.

즉 애초에 접근이 문서가 아닌 인쇄다 보니깐 원본 유지에 치중되서 수정 기능이 없다.

어케 그림?

일단 HTML이나 DOCX가 유동 레이아웃 구조라서 컨텐츠가 브라우저 창이나 페이지 크기에 따라서 유동적으로 바뀐다.

PDF는? 고정 레이아웃이다.

한번 인쇄된 페이지를 기준으로 모든 내용물은 절대적인 좌표값으로 고정된다..

말 그대로 디지털로 인쇄된 디지털 페이퍼다.

명령으로 따지자면

"A4 용지(210, 297mm) 위의 (30, 50) 좌표에 정확히 '안녕' 이라는 글씨 그려라)

벡터로 그려요

PDF는 그려진 요소를 가지는게 아닌 그리는 명령어를 가진다.

즉 (30, 50) 좌표에는 그려진 픽셀 데이터가 아니라, 그리라는 명령이 들어가고

명령은 Vector 기반으로 글씨를 그린다.

필요한건 다 가졌다.

PDF 보면 내가 폰트 없어도 정확하게 폰트가 보이는데, 이유는 바로 PDF 문서 내에 폰트가 존재하기 때문이다.

그리고 폰트에는 'ㄱ'이나 'A'를 어떻게 그릴지에 대한 수학적 곡선 정보가 존재한다.

즉 Vector라는 말이다.

문서에 사용된 JPG, PNG 등의 이미지나 간단한 도형을 그리는 명령어 조차 포함되어 있다.

데이터가 어떻게 되어있길래?

멀리 갈 필요없이 vscode 가져다가 PDF를 text editor로 열어보자

경고 뭐라뭐라 할텐데 '아무튼 오픈' 해주면 된다.

PDF 파일은 다음의 네 가지 주요 구성요소를 가진다.

  • 헤더

  • 본문

  • 상호 참조 테이블

  • 트레일러

헤더는 가장 첫번쨰 줄에 위치해서 %PDF-1.7 형식으로 나 1.7 버전임 이라고 명시하는 정도다.

본문이 실제 내용이 들어가다보니 많은데,

텍스트 내용부터 폰트, 이미지, 명령어 등이 들어간다.

그리고 각 내용물은 객체(Object)라는 단위로 나뉘어 저장되는데

예를들어

1 0 obj ... 여기에 내용 들어감 endobj

형식으로 저장된다.

다음으로 중요한게 상호 참조 테이블인데 PDF 파일 구조 핵심이라고 보면 된다

아까 본문에서 1 0 obj, 2 0 obj... 으로 구성된 파일 내용들이 있는데

xref 라고 부르는 이 테이블에서 해당 객체가 어느 위치에 저장되어 있는지 주소 알려주는 거다.

예시를 보자면 다음과 같다.

xref 0 6 0000000000 65535 f 0000000018 00000 n 0000000077 00000 n 0000000178 00000 n 0000000390 00000 n 0000000472 00000 n

0 6 뜻은 0번부터 6개의 객체가 있다는 뜻이다.

0 6 다음에 보면 진짜 6개 객체가 있다

본문의 1번 객체를 기준으로 보자

0000000018 00000 n: 1번 객체(1 0 obj)는 파일 시작부터 18번째 바이트 위치해 있으며 현재 사용중(n)

사용중이 아니면(f)다

참고로 0번 객체는 연결 리스트의 헤드같은 별도 용도다. 그래서 f로 사용중이 아니다.

이러단 구조 덕분에 PDF 리더기가 전체 구조 읽을 필요 없이 원하는 객체 주소를 xref로 읽어서 해당 부분으로 점프해서 읽을 수 있는 것이다.(본문 썡까고 역순으로 xref 부터 참조)

트레일러는 파일 거의 마지막 부분인데

PDF 리더기가 파일을 열 떄 가장 먼저 읽는 부분이다.

즉 역순으로 읽는다는 거다.(왜 인지는 모르겠고)

trailer
<<
  /Size 6
  /Root 1 0 R
  /Info 2 0 R
>>
startxref
572
%%EOF

문서의 메타 데이터 같은 영역이고

Size로 문서에 객체가 몇개 있는지 확인하고

Root로 문서의 뿌리가 되는 객체를 식별한다.

startxref는 말 그대로 xref가 어느 바이트 위치부터 시작하느냐 알려주기 때문에 본문 읽지 않고 바로 xref 위치 찾아서 참조가 가능한 것이다.

본문에 들어가는 내용들

일단 본문은 객체로 구성된다.

객체가 크게 2가지로 나뉜다.

기본 객체: 데이터를 표현하는 일반적인 요소들

간접 객체: 기본 객체 가지고 포장해서 주소 붙인거(아까 본 1 0 obj ~ endobj 요거)

기본 객체

우리가 흔하게 생각하는 자료유형 생각하면 편하다

Booleans: true/false

Numbers: 숫자(실수도 포함임)

String: 문자열

Names: 슬래시로 시작해서 PDF 내부 특정 값을 가리키는 키로 사용

Arrays: 배열

Dictionaries: 사전(Key / Value)

Streams: 이미지, 압축 텍스트, 글꼴 파일 등 무겁고 큰 바이너리 데이터

간접 객체는 아까 봤으니 설명은 생략한다.

참조라는 개념이 있는데 2 0 R 형식으로 "2번 객체, 0 세대를 참조하라" 라는 뜻으로

1번 객체가 2번 객체 참조하고...2번은 3번 아무튼 기본 객체들을 조합하고 포장하고

내부적으로 참조 라고 부르는 식별자로 참조하는 구조로 PDF 전체 계층 구조가 마치 레고 블록 마냥 조합된다.

마무리

실제 데이터 까보면 상당이 복잡한게 많은데(특히나 Streams)

"아니 PDF Parse 왤케 멍청함?" 이라고 욕했던 과거의 나 자신을 반성한다.

← Previous
쉽게푼_Character Consistency가 어려운 이유
Next →
돌고 돌아 트랜스포머