pdf 파일 구조
- Header: PDF 버전 정보 (예:
%PDF-1.7)를 담고 있다. - Body: 실제 콘텐츠가 들어있는 부분. 텍스트, 이미지, 폰트, 벡터 그래픽 등이 ‘객체(Object)’ 단위로 저장된다. 객체 간의 순서는 중요하지 않다.
- Xref Table (Cross-reference Table): 파일 내 각 객체들이 어느 위치(바이트 오프셋)에 있는지 기록한 인덱스. 라이브러리가 특정 페이지나 이미지를 즉시 찾을 수 있게 해준다.
- Trailer: 파일의 끝을 알리며, Xref Table의 시작 위치와 파일의 루트 객체(Catalog)를 가리킨다.
pdf 파일 구조 이미지
PDF 파일 구조 예시
%PDF-1.4 (Header: PDF 버전 정보)
1 0 obj (Body: Catalog 객체 - 전체 문서의 루트)
<<
/Type /Catalog
/Pages 2 0 R
>>
endobj
2 0 obj (Body: Pages 객체 - 페이지 트리)
<<
/Type /Pages
/Kids [3 0 R]
/Count 1
>>
endobj
3 0 obj (Body: Page 객체 - 개별 페이지 정보)
<<
/Type /Page
/Parent 2 0 R
/Resources << >>
/MediaBox [0 0 612 792]
/Contents 4 0 R
>>
endobj
4 0 obj (Body: Stream 객체 - 실제 페이지에 그려질 내용)
<< /Length 44 >>
stream
BT
/F1 12 Tf
70 700 Td
(Hello, PDF World!) Tj
ET
endstream
endobj
xref (Xref Table: 각 객체의 바이트 오프셋 위치)
0 5
0000000000 65535 f
0000000015 00000 n (1번 객체 위치)
0000000060 00000 n (2번 객체 위치)
0000000120 00000 n (3번 객체 위치)
0000000230 00000 n (4번 객체 위치)
trailer (Trailer: 루트 객체 지정 및 Xref 시작점)
<<
/Size 5
/Root 1 0 R
>>
startxref
320
%%EOF (% 그대로 하면 현재 페이지 코드블럭이 이상하게 보여서 %(Fullwidth Percent Sign, U+FF05)으로 대체해서 나타냄)각 부분에 대한 설명
- Header:
%PDF-1.4는 이 파일이 PDF 1.4 규격을 따름을 의미. - Body:
1 0 obj부터4 0 obj까지가 실제 데이터.1 0 obj: 문서의 시작점인 Catalog.3 0 obj: 페이지의 크기(MediaBox)와 내용(Contents)을 정의.4 0 obj:stream내부에 실제 텍스트인 “Hello, PDF World!”를 출력하는 명령어가 들어있다.
- Xref Table: 각 객체(1번~4번)가 파일의 몇 번째 바이트에 위치하는지 기록하여 빠른 접근을 돕는다.
- 엔트리 포멧 :
nnnnnnnnnn ggggg n eol- nnnnnnnnnn은 문서 시작 부분부터 시작하는 오브젝트의 10자리 바이트 오프셋
- ggggg는 5자리 세대 숫자이며, 현재 오브젝트가 어떤 세대인지 나타낸다. 오브젝트가 삭제된 다음 재사용 될 때마다 새로운 세대 번호가 부여된다.
- n은 사용 중, f는 free(not used)를 나타낸다.
- Unix EOL 포맷의 경우
<space><linefeed>, Windows EOL 포맷의 경우<carriage return><linefeed>
- 엔트리 포멧 :
- Trailer:
/Root 1 0 R을 통해 1번 객체가 루트임을 알려주고,startxref뒤의 숫자(320)는xref테이블이 시작되는 위치를 가리킨다.%%EOF는 파일의 끝을 의미. (객체번호 세대번호 R 순서이며 R은 참조를 나타냄.)
Xref Table이 여러 개일 때 어떻게 읽나요?
PDF를 수정하면 파일 뒷부분에 새로운 xref 테이블이 추가된다. 하지만 단순히 순서대로 읽는 것이 아니라 ‘역순 연결 리스트’ 구조를 가진다.
/Prev 키워드의 등장
새로운 xref 테이블 뒤에 오는 새로운 trailer에는 이전 xref 테이블의 위치를 가리키는 /Prev라는 항목이 추가된다.
- Original Body & Xref (최초 저장)
- New Body (수정된 내용)
- New Xref Table (수정된 객체들의 새 위치)
- New Trailer (여기에
/Prev [예전 startxref 위치]가 들어감) - New startxref (가장 최신 xref 위치)
- %%EOF
PDF object types
- bools:
truefalse - ints:
420-1 - scalars:
0.001 - strings:
(strings are in parentheses or byte encoded)<74657374> - name:
/Name/Name#20with#20spaces - array:
[/Foo 42 (arrays can contain multiple types)] - dictionary:
<</Key1 (value1) /key2 42>> - indirect object:
5 0 obj (An indirect string. Indirect objects have an object number and a generation number, Skia always uses generation 0 objects) endobj - object reference:
5 0 R - stream:
<</Length 56>> stream ...stream contents can be arbitrary, including binary... endstream
pdf 파일 읽기
Body에 들어가는 객체(Object)들의 순서는 정해져 있지 않다. 1번 객체가 반드시 루트(Catalog)일 필요도 없고, 페이지 순서대로 객체가 나열될 필요도 없다.
그 이유는 PDF의 Xref Table(교차 참조 테이블) 구조 때문이다.
- 가장 마지막의
startxref를 읽어 **최신 지도(New Xref)**를 먼저 본다. - 최신
trailer에 있는/Prev값을 보고 **이전 지도(Old Xref)**의 위치로 거슬러 올라간다. - 이렇게 거슬러 올라가며 모든 지도를 합쳐서 하나의 완벽한 ‘최신 지도’를 완성.
→ 찾아가는 과정: Trailer → Root ID 확인(예: /Root 1 0 R) → Xref Table에서 위치 확인 → Body의 해당 위치로 점프.
압축 형태
125 0 obj
<</C 115/Filter/FlateDecode/I 137/Length 104/O 99/S 38>>stream
h�b``b``���Ǧ0�F fa�h@�bFe~+�'\�T:�0]����R���ؑ�Xz�ܠ���b&�S� |V�Ѭ@ɛPU��A�~
endstream
endobjpdf 파일을 텍스트 에디터로 열어보면 위와 같이 객체들이 이상한 형태로 보이는데, 이는 스트림 데이터가 압축되어 저장되었기 때문에 그렇다. /Filter 부분을 살펴보면 /FlateDecode로 ‘(PDF 1.2) Decompresses data encoded using the zlib/deflate compression method, reproducing the original text or binary data. ’ 임을 알 수 있다.
만약 같은 객체 번호가 여러 곳에 있다면?
- 리더기는 가장 나중에 추가된(가장 뒤에 있는) 정보를 우선시.
- 이 방식을 통해 기존 데이터를 지우지 않고도 내용을 수정하거나 삭제(객체 위치를 0으로 표시)할 수 있다.
PDFALYZER로 살펴보기
- (A PDF analysis tool for visualizing the inner tree-like data structure1 of a PDF in spectacularly large and colorful diagrams as well as scanning the binary streams embedded in the PDF for hidden potentially malicious content.)
simple table pdf를 대상으로 하여 살펴보면,
┏━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━┓
│ │
│ Simple tree view of table.pdf │
│ │
└────────────────────────────────────────────────────────────────────────────┘
<136:Trailer(Dictionary)@/>
╠══ <97:Catalog(Dictionary)@/Root>
║ ╠══ <126:AcroForm(Dictionary)@/Root/AcroForm>
║ ║ ╠══
<132:Encoding(Dictionary)@/Root/AcroForm/DR[/Encoding][/PDFDocEncoding]>
║ ║ ╠══ <130:Font:Type1(Dictionary)@/Root/AcroForm/DR[/Font][/Helv]>
║ ║ ║ ╚══ /Encoding => <132:Encoding(Dictionary)> (Non Child Reference)
║ ║ ╚══ <131:Font:Type1(Dictionary)@/Root/AcroForm/DR[/Font][/ZaDb]>
║ ╠══ <3:Metadata:XML(DecodedStream)@/Root/Metadata>
║ ╠══ <7:Outlines(Dictionary)@/Root/Outlines>
║ ║ ╚══ <8:First(Dictionary)@/Root/Outlines/First>
║ ║ ╚══ <9:A:GoTo(Dictionary)@/Root/Outlines/First/A>
║ ║ ╚══ /D[0] => <98:Page(Dictionary)> (Non Child Reference)
║ ╠══ <94:Pages(Dictionary)@/Root/Pages>
║ ║ ╚══ <98:Page(Dictionary)@/Root/Pages/Kids[0]>
║ ║ ╠══ <129:Contents(EncodedStream)@/Root/Pages/Kids[0]/Contents>
║ ║ ╠══
<117:Resources(Array)@/Root/Pages/Kids[0]/Resources[/ColorSpace][/CS0]>
║ ║ ║ ╚══
<108:UnlabeledArrayElement[1](EncodedStream)@/Root/Pages/Kids[0]/Resources[/Co
lorSpace][/CS0][1]>
║ ║ ╠══
<118:Resources(Array)@/Root/Pages/Kids[0]/Resources[/ColorSpace][/CS1]>
║ ║ ║ ╚══
<109:UnlabeledArrayElement[1](EncodedStream)@/Root/Pages/Kids[0]/Resources[/Co
lorSpace][/CS1][1]>
║ ║ ╠══
<120:Font:TrueType(Dictionary)@/Root/Pages/Kids[0]/Resources[/Font][/TT0]>
║ ║ ║ ╠══
<119:FontDescriptor(Dictionary)@/Root/Pages/Kids[0]/Resources[/Font][/TT0]/Fon
tDescriptor>
║ ║ ║ ║ ╚══
<110:FontFile2(EncodedStream)@/Root/Pages/Kids[0]/Resources[/Font][/TT0]/FontD
escriptor/FontFile2>
║ ║ ║ ╚══
<111:ToUnicode(EncodedStream)@/Root/Pages/Kids[0]/Resources[/Font][/TT0]/ToUni
code>
║ ║ ╠══
<122:Font:TrueType(Dictionary)@/Root/Pages/Kids[0]/Resources[/Font][/TT1]>
║ ║ ║ ╠══
<121:FontDescriptor(Dictionary)@/Root/Pages/Kids[0]/Resources[/Font][/TT1]/Fon
tDescriptor>
║ ║ ║ ║ ╚══
<112:FontFile2(EncodedStream)@/Root/Pages/Kids[0]/Resources[/Font][/TT1]/FontD
escriptor/FontFile2>
║ ║ ║ ╚══
<113:ToUnicode(EncodedStream)@/Root/Pages/Kids[0]/Resources[/Font][/TT1]/ToUni
code>
║ ║ ╚══
<124:Font:TrueType(Dictionary)@/Root/Pages/Kids[0]/Resources[/Font][/TT2]>
║ ║ ╠══
<123:FontDescriptor(Dictionary)@/Root/Pages/Kids[0]/Resources[/Font][/TT2]/Fon
tDescriptor>
║ ║ ║ ╚══
<114:FontFile2(EncodedStream)@/Root/Pages/Kids[0]/Resources[/Font][/TT2]/FontD
escriptor/FontFile2>
║ ║ ╚══
<115:ToUnicode(EncodedStream)@/Root/Pages/Kids[0]/Resources[/Font][/TT2]/ToUni
code>
║ ╚══ <10:StructTreeRoot(Dictionary)@/Root/StructTreeRoot>
║ ╠══ <11:StructElem:Sect(Dictionary)@/Root/StructTreeRoot/K>
║ ║ ╠══ <15:StructElem:H1(Dictionary)@/Root/StructTreeRoot/K/K[0]>
║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ╠══ <16:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[1]>
║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ╠══ <127:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[2]>
║ ║ ║ ╠══ <128:A(Dictionary)@/Root/StructTreeRoot/K/K[2]/A>
║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ╠══ <60:StructElem:Table(Dictionary)@/Root/StructTreeRoot/K/K[3]>
║ ║ ║ ╠══ <61:A(Dictionary)@/Root/StructTreeRoot/K/K[3]/A>
║ ║ ║ ╠══
<62:StructElem:TR(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]>
║ ║ ║ ║ ╠══
<17:StructElem:TH(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[0]>
║ ║ ║ ║ ║ ╠══
<93:A(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[0]/A>
║ ║ ║ ║ ║ ╠══
<18:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[0]/K>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ║ ╠══
<19:StructElem:TH(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[1]>
║ ║ ║ ║ ║ ╠══
<92:A(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[1]/A>
║ ║ ║ ║ ║ ╠══
<20:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[1]/K>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ║ ╠══
<21:StructElem:TH(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[2]>
║ ║ ║ ║ ║ ╠══
<91:A(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[2]/A>
║ ║ ║ ║ ║ ╠══
<22:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[2]/K>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ║ ╠══
<23:StructElem:TH(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[3]>
║ ║ ║ ║ ║ ╠══
<24:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[3]/K>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ║ ╠══
<25:StructElem:TH(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[4]>
║ ║ ║ ║ ║ ╠══
<90:A(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[4]/A>
║ ║ ║ ║ ║ ╠══
<26:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[0]/K[4]/K>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ╠══
<63:StructElem:TR(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[1]>
║ ║ ║ ║ ╠══
<27:StructElem:TH(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[1]/K[0]>
║ ║ ║ ║ ║ ╠══
<28:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[1]/K[0]/K>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ║ ╠══
<29:StructElem:TH(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[1]/K[1]>
║ ║ ║ ║ ║ ╠══
<30:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[1]/K[1]/K>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ╠══
<64:StructElem:TR(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]>
║ ║ ║ ║ ╠══
<79:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[0]>
║ ║ ║ ║ ║ ╚══
<31:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[0]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<80:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[1]>
║ ║ ║ ║ ║ ╚══
<32:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[1]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<81:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[2]>
║ ║ ║ ║ ║ ╚══
<33:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[2]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<82:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[3]>
║ ║ ║ ║ ║ ╚══
<34:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[3]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<83:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[4]>
║ ║ ║ ║ ║ ╚══
<35:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[4]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<84:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[5]>
║ ║ ║ ║ ║ ╚══
<36:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[2]/K[5]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ╠══
<65:StructElem:TR(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]>
║ ║ ║ ║ ╠══
<73:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[0]>
║ ║ ║ ║ ║ ╚══
<37:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[0]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<74:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[1]>
║ ║ ║ ║ ║ ╚══
<38:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[1]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<75:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[2]>
║ ║ ║ ║ ║ ╚══
<39:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[2]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<76:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[3]>
║ ║ ║ ║ ║ ╚══
<40:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[3]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<77:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[4]>
║ ║ ║ ║ ║ ╠══
<41:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[4]/K[0]>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══
<42:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[4]/K[1]>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<78:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[5]>
║ ║ ║ ║ ║ ╠══
<43:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[5]/K[0]>
║ ║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ║ ╚══
<44:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[3]/K[5]/K[1]>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ╠══
<66:StructElem:TR(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]>
║ ║ ║ ║ ╠══
<67:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[0]>
║ ║ ║ ║ ║ ╚══
<45:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[0]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<68:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[1]>
║ ║ ║ ║ ║ ╚══
<46:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[1]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<69:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[2]>
║ ║ ║ ║ ║ ╚══
<47:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[2]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<70:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[3]>
║ ║ ║ ║ ║ ╚══
<48:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[3]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<71:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[4]>
║ ║ ║ ║ ║ ╚══
<49:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[4]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╠══
<72:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[5]>
║ ║ ║ ║ ║ ╚══
<50:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[4]/K[5]/K>
║ ║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ║ ╚══
<59:StructElem:TR(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]>
║ ║ ║ ╠══
<85:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[0]>
║ ║ ║ ║ ╚══
<51:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[0]/K>
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ╠══
<86:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[1]>
║ ║ ║ ║ ╚══
<52:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[1]/K>
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ╠══
<87:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[2]>
║ ║ ║ ║ ╚══
<53:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[2]/K>
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ╠══
<88:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[3]>
║ ║ ║ ║ ╚══
<54:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[3]/K>
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ╠══
<89:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[4]>
║ ║ ║ ║ ╚══
<55:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[4]/K>
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ╠══
<58:StructElem:TD(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[5]>
║ ║ ║ ║ ╚══
<56:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[3]/K[5]/K[5]/K>
║ ║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child
Reference)
║ ║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ║ ╚══ <57:StructElem:P(Dictionary)@/Root/StructTreeRoot/K/K[4]>
║ ║ ╚══ /Pg => <98:Page(Dictionary)> (Non Child Reference)
║ ╠══ <12:ParentTree(Dictionary)@/Root/StructTreeRoot/ParentTree>
║ ║ ╚══ <14:Nums(Array)@/Root/StructTreeRoot/ParentTree/Nums[0]>
║ ║ ╠══ [0] => <15:StructElem:H1(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [1] => <16:StructElem:P(Dictionary)> (Non Child Reference)
║ ║ ╠══ [147] => <127:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [144] => <57:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [145] => <18:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [145] => <18:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [8] => <20:StructElem:P(Dictionary)> (Non Child Reference)
║ ║ ╠══ [12] => <22:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [16] => <24:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [20] => <26:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [33] => <28:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [37] => <30:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [42] => <31:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [46] => <32:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [50] => <33:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [54] => <34:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [58] => <35:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [62] => <36:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [67] => <37:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [71] => <38:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [75] => <39:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [79] => <40:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [83] => <41:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [85] => <42:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [89] => <43:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [91] => <44:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [96] => <45:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [100] => <46:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [104] => <47:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [108] => <48:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [112] => <49:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [116] => <50:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [121] => <51:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [125] => <52:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [129] => <53:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [133] => <54:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╠══ [137] => <55:StructElem:P(Dictionary)> (Non Child
Reference)
║ ║ ╚══ [141] => <56:StructElem:P(Dictionary)> (Non Child
Reference)
║ ╚══ <13:RoleMap(Dictionary)@/Root/StructTreeRoot/RoleMap>
╠══ <95:Info(Dictionary)@/Info>
╠══ <1:ObjStm(EncodedStream)@/ObjStm>
╠══ <2:ObjStm(EncodedStream)@/ObjStm>
╠══ <4:ObjStm(EncodedStream)@/ObjStm>
╠══ <5:ObjStm(EncodedStream)@/ObjStm>
╠══ <96:Linearized(Dictionary)@/Linearized>
╠══ <99:ObjStm(EncodedStream)@/ObjStm>
╠══ <133:ObjStm(EncodedStream)@/ObjStm>
╚══ <134:ObjStm(EncodedStream)@/ObjStm>
위와 같이 간단한 트리 형태로 시각화하여 볼 수 있을 뿐만 아니라 보다 자세한 정보들도 확인할 수 있다.
tagged pdf - marked content
위 예시 table pdf에서 StructTreeRoot 아래 위치한 내용들을 보면 테이블의 구조 정보를 tag로 하여 담고 있음을 알 수 있다. 이는 해당 pdf가 tagged pdf이기 때문에 구조 정보를 가지고 있는 것으로, 다음 Marked Content와 결합해 논리적 구조를 나타낼 수 있다.
qpdf를 이용해서 스트림 데이터를 압축해제해 QDF(Qpdf Document Format)으로 살펴보자.
qpdf --qdf table.pdf table.qdfBT
/P <</MCID 147 >>BDC
/CS1 cs 0 scn
/TT2 1 Tf
0.004 Tc -0.004 Tw 12 0 0 12 90 621.24 Tm
[(D)4(is)3(a)8(b)1(il)10(it)1(y)8( )]TJ
-0.004 Tc 0.004 Tw 0 -1.22 TD
[(C)-5(at)-7(e)-1(go)-6(r)-9(y)]TJ
0 Tc 0 Tw ( )Tj
EMC
ETpage → Contents의 내용 중 일부로, 다음 이미지에서 빨간색으로 동그라미 친 텍스트 부분을 담고 있다.
BT/ET: Begin Text / End Text. 이 사이에 있는 것들이 실제 텍스트 데이터.- /P << /MCID 147 >> BDC : 9.5 Marked Content 603p. 단락(Paragraph)이며, Marked Content ID를 147로 설정.
BDC/EMC: Begin Document Content / End Marked Content/CS1 cs 0 scn: 글자 색상 검정색 설정./TT2 1 Tf: 텍스트 폰트랑 크기 설정. 1은 베이스 크기 뒤에 나오는 tm이랑 결합해야 실제 크기 결정됨.Tc/Tw: 문자 간 간격(Character Spacing), 단어 간 간격(Word Spacing) 설정.m(Text Matrix): 텍스트의 위치, 스케일(크기), 회전, 기울기를 결정90 621.24는 페이지 내의 x, y 좌표
TJ(Show text with individual glyph positioning): 출력 및 글자 사이의 미세한 간격 설정. 배열에 텍스트 내용 및 간격.TD(Move text position and set leading): 현재 위치에서 상대적으로 텍스트 줄을 이동시킨다.
⇒ 좌표 (90, 621) 위치에서 시작해서, 12pt 크기의 검정색 글자로 ‘Disability’라고 쓰고, 그 바로 아랫줄에 ‘Category’라고 써라.
⇒⇒
- tagged pdf는 marked content(초록색)와 StructTreeRoot를 결합해서 구조적 정보를 유지할 수 있다.
- untagged pdf는 구조적 정보를 유지할 수 없고,
어떤 위치에어떤 모양이 있다 / 텍스트가 있다의 정보를 가지고 있다.- → 렌더링을 위한 포멧
정리
정리
pdf 문서는 편집을 위한 파일 형태가 아니라 렌더링을 위한 형태(‘어느 위치에 어떤 도형 혹은 텍스트가 있다’는 정보를 담고 있음)이다. 따라서 구조적 정보를 가지고 있지 않은 것이 일반적이다.
또한 tagged pdf라고 해도, (pdf 표준 구조가 있긴 하지만) pdf standard 문서에 Producer applications are free to define additional structure types 라고 하고 있다. 인코딩 하는 도구마다 tagged pdf의 형태가 달라질 수도 있을 것 같다.