2010년 10월 22일 금요일

PDF 파일구조

다음과 같은 TeX파일이 있다고 하자.
\input ucsplain
\pdfcompresslevel=0
\pdfmapline{=ounbtm@Unicode@ <UnBatang.ttf}
\nopagenumbers

\bye

이것을 pdftex 1.4버전으로 컴파일하면 PDF파일이 생성된다. 생성된 PDF파일을 less로 열어보면 맨 마지막 부분에 trailer로 시작하는 부분이 있다.
trailer
<< /Size 12
/Root 10 0 R
/Info 11 0 R
... >>
startxref
7470
%%EOF

여기서 startxref 다음의 숫자는 xref(상호참조) 테이블이 시작하는 위치를 표시한다. 파일 처음부터 헤아려 7470바이트째부터 상호참조 테이블이 있다는 말이다. 이 테이블은 다음과 같이 표시된다.(%와 그 뒷부분은 내가 써넣은 것으로 원본 PDF에는 없다)
xref
0 12
0000000000 65535 f
0000000015 00000 n     % 1
0000000623 00000 n     % 2
0000000512 00000 n     % 3
0000000405 00000 n     % 4
0000006948 00000 n     % 5
0000007105 00000 n     % 6
0000000691 00000 n     % 7
0000000713 00000 n     % 8
0000006741 00000 n     % 9
0000007162 00000 n     % 10
0000007212 00000 n     % 11

15번째 바이트부터 1번 객체(object)가 시작하고 623번 바이트부터 2번 객체가 시작하고... 7162번째 바이트부터가 10번 객체이고 7212번째부터가 11번 객체라는 뜻이다. 이것을 참조하여 PDF리더는 필요한 객체를 찾는다.

다시 저 위의 trailer부분을 보자. /Info 11 0 R은 이 PDF의 생성정보가 11번 객체에 있다는 의미다. 이 정보에는 타이틀, 저자, 생성시간, PDF제작프로그램 등이 포함된다. 상호참조 테이블에 따라 7212바이트부터 읽으면:
11 0 obj <<
/Producer (pdfTeX-1.40.0)
/Creator (TeX)
/CreationDate (D:20061221133811+09'00')
...
>> endobj
이러하다. 여기에 지금은 없지만 /Title (...) 따위도 만들어 넣을 수 있다.

또한 trailer의 /Root 10 0 R에 의거, 10번 객체부터 시작하여 PDF 내용물이 읽혀진다.
10 0 obj <<
/Type /Catalog
/Pages 6 0 R
>> endobj
이것이 카탈로그 객체이다. 이에 따르면 페이지들을 기술한 객체가 6번이란다:
6 0 obj <<
/Type /Pages
/Count 1
/Kids [3 0 R]
>> endobj
페이지가 1개뿐이며 1페이지를 기술한 자식객체는 3번이다:
3 0 obj <<
/Type /Page
/Contents 4 0 R
/Resources 2 0 R
/MediaBox [0 0 595.276 841.89]
/Parent 6 0 R
>> endobj
/Resources 2 0 R은 이 페이지에 사용될 자원을 기술한다:
2 0 obj <<
/Font << /F51 5 0 R >>
/ProcSet [ /PDF /Text ]
>> endobj
이 페이지에는 폰트가 하나만 사용되고 있으며 그 내부적인 임의명칭은 F51이다. F51 폰트에 관한 정보는 5번객체에서 찾을 수 있다.

다시 저 위의 페이지 객체를 보면 /Contents 4 0 R에 의하여 페이지 콘텐츠가 4번객체에 기술되어 있음을 알 수 있다:
4 0 obj <<
/Length 50        
>>
stream
BT
/F51 9.9626 Tf 91.925 759.927 Td [(\000)]TJ
ET
endstream
endobj
4번객체의 구체적인 내용은 stream...endstream으로 감싸져 있으며 그중에서 BT...ET는 텍스트 정보를 감싸고 있다. 텍스트는 9.9626포인트(여기서 포인트는 big point, 즉 1/72인치) 크기의 F51폰트를 사용한다. 위치는 페이지의 왼쪽 아래에서부터 오른쪽으로 91.925포인트, 위쪽으로 759.927포인트 지점에 식자된다. 그리고 이 폰트의 0번 글리프를 그 자리에 그린다.

F51폰트 자원은 위에서 5번객체에서 기술된다고 하였다:
5 0 obj <<
/Type /Font
/Subtype /TrueType
/BaseFont /FKXPJX+UnBatang
/FontDescriptor 9 0 R
/FirstChar 0
/LastChar 0
/Widths 7 0 R
/ToUnicode 1 0 R
>> endobj
이 폰트는 트루타입이며 대외적 명칭은 FKXPJX+UnBatang이다. 이 폰트의 정보는 9번객체에 기술돼있고, 0번 글리프만 사용된다. 각 글리프의 Width들은 7번 객체에서 알 수 있다:
7 0 obj
[1000]
endobj
글리프 하나만 사용되었으니 width도 하나만 제시되고 있다(여기서는 1000). 글리프를 그린 다음에 얼마나 전진할지를 표시하는 것이다. 단위는 폰트크기/1000이다. 즉 9.9626*1000/1000포인트만큼 오른쪽으로 이동한다.

한편 이 폰트가 참조하는 ToUnicode 객체(1번)는 폰트의 각 글리프를 유니코드 포인트에 대응시키는 맵을 가지고 있다:
1 0 obj <<
/Length 332      
>>
stream
...
1 begincodespacerange <00> <FF> endcodespacerange
1 beginbfrange <00> <FF> <AC00> endbfrange
...
endstream
endobj
0부터 0xFF까지 순서대로 U+AC00부터 U+ACFF에 대응시킨다. 0번 글리프인 한글 “가”는 U+AC00에 대응하는 것이다. 이 맵을 통해서 우리는 PDF파일로부터 한글텍스트 검색·추출을 할 수 있게 된다. 참고로 이 ToUnicode 객체는 내가 작성한 ucsplain.tex에 의하여 삽입된 것이다.

그렇다면 구체적으로 글리프 모양을 어떻게 그릴 것인가. 위에서 폰트 정보가 9번객체에 기술돼 있다고 했다:
9 0 obj <<
/Type /FontDescriptor
...
/FontFile2 8 0 R
>> endobj
글리프 모양 정보는 8번객체에 담겨져 있다. 8번객체는 여기다 copy&paste 하지 않겠다. 보시면 아시겠지만 바이너리코드이기 때문이다. 이 코드는 은바탕 트루타입 폰트로부터 그대로 복제해 넣은 것이다. 어쨌든 이 8번객체가 포함하는 지시·명령에 따라 “가”라는 (텍스트가 아닌) 글자모양이 PDF리더 화면에 표현된다.

이처럼 PDF파일은 수많은 객체들의 상호참조로 구성되고 있다. 마치 웹이 수많은 링크들의 거미줄같은 상호참조로 거대한 집단지성을 이루듯이...  PDF가 이렇게 작은 객체들로 구성돼 있기에 우리는 원한다면 글리프를 그리는 객체와 텍스트를 지시하는 객체를 얼마든지 분리해 낼 수 있다.

이 글을 작성함에 있어서 EuroTeX2006에서 발표된 Hartmut Henkel의 프리젠테이션으로부터 많은 도움을 얻었다. 

댓글 없음:

댓글 쓰기