PDFを読む

# はじめに
この記事は、[あくあたん工房 Advent Calendar 2022](https://adventar.org/calendars/7606)の14日目の記事です。
初めて記事を書くので、体裁等が読みづらいかと思いますが、ご了承ください。

## きっかけ

普段何気なく使っているPDFですが、どのようにデータを管理しているのでしょうか？
この記事は、**細かいことは別にいいけど、どんな感じかだけ知りたい人**を対象にしております。
ので、正確に読みたい人は[リファレンス](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf)をどうぞ。

とにかくしのごの言わずに、始めますね

## 対象

今回扱うPDFファイルは、以下のLaTeXをLuaLaTeXでコンパイルしたものです。
```latex:HelloWorld.tex
\documentclass[a4paper,11pt]{ltjsarticle}
\begin{document}
Hello World.
\end{document}
```



## 中身

とりあえず中身を見てみましょう。

.pdfから.txtに拡張子変えて
![スクリーンショット 2022-12-13 205918.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983722/d370234f-d8f2-2d15-6cb2-45b6087ec7ba.png)


**うわあああああぁぁぁぁぁぁぁぁぁぁ！！！！▂▅▇█▓▒░(’ω’)░▒▓█▇▅▂**

文字化けがすごいな…

とりあえず、読めるところだけ抜き出してみましょう。

![スクリーンショット 2022-12-13 210617.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/2983722/b71240f3-183c-7818-1f5f-7999fe97e7eb.png)


ふむφ(･ω･ )
なんか<<から>>までがブロックになってそうやな
もうちょい見やすくしてみるか
```txt:HelloWorld.txt
%PDF-1.5
3 0 obj
<< 
    /Filter /FlateDecode 
    /Length 112 
>>        
stream

// 文字化けしてたとこ

endstream
endobj
9 0 obj
<< 
    /Filter /FlateDecode 
    /Length 21 
>>         
stream

// 文字化けしてたとこ

endstream
endobj
10 0 obj
<< 
    /Subtype /CIDFontType0C 
    /Filter /FlateDecode 
    /Length 1269 
>>       
stream

// 文字化けしてたとこ

endstream
endobj
11 0 obj
<< 
    /Filter /FlateDecode 
    /Length 407 
>>        
stream

// 文字化けしてたとこ

endstream
endobj
14 0 obj
<< 
    /Producer (LuaTeX-1.15.0) 
    /Creator (TeX) 
    /CreationDate (D:20221213205749+09'00') 
    /ModDate (D:20221213205749+09'00') 
    /Trapped /False 
    /PTEX.FullBanner (This is LuaHBTeX, Version 1.15.0 (TeX Live 2022)) 
>>
endobj
6 0 obj
<< 
    /Type /ObjStm 
    /N 8 
    /First 47 
    /Filter /FlateDecode 
    /Length 500 
>>        
stream

// 文字化けしてたとこ

endstream
endobj
15 0 obj
<< 
    /Type /XRef 
    /Index [ 0 16 ] 
    /Size 16 
    /W [ 1 2 1 ] 
    /Root 13 0 R 
    /Info 14 0 R 
    /ID [ <C276673F1AFB1BCA87C4572C62F8DF7D> <C276673F1AFB1BCA87C4572C62F8DF7D> ] 
    /Filter /FlateDecode 
    /Length 60 
>>         
stream

// 文字化けしてたとこ

endstream
endobj
startxref
3006
%%EOF


```

## 読み解く

ふむふむφ(･ω･ )

とりあえず、以下の書式が連続してるみたいですね

```PDF
``{n}はなんかの数字``

{n} {n} obj
<<
なんか設定っぽいところ
>>
stream

文字化けしてたところ

endstream
endobject
```


あと全体的には、


```
%PDF-1.5

オブジェクト的なやついっぱい

startxref
{n}
%%EOF
```
こんな感じですね。
末尾のstartxrefってなんや？下の数字(今回だと3006)も気になる…
3006文字目に何かあるのでしょうか？でも3006文字もなさそうです

**これは( ﾟдﾟ)ﾊｯ!3006バイト目か！**

早速調べてみると

```
endobj
ここ
|
V
15 0 obj
<< 
    /Type /XRef 
    /Index [ 0 16 ] 
    /Size 16 
    /W [ 1 2 1 ] 
    /Root 13 0 R 
    /Info 14 0 R 
    /ID [ <C276673F1AFB1BCA87C4572C62F8DF7D> <C276673F1AFB1BCA87C4572C62F8DF7D> ] 
    /Filter /FlateDecode 
    /Length 60 
>>         
stream

// 文字化けしてたとこ

endstream
endobj
startxref
3006
%%EOF
```
`15 0 obj`の`1`が3006バイト目でした。おそらくここが重要なブロックなのでしょう。
よく見れば`/Type /XRef`とあります。
その中で、次に気になるのが、`/Filter /FlateDecode`の`Decode`の文字
`FlateDecode`でググると、[Deflate](https://ja.wikipedia.org/wiki/Deflate)にあたりました。まさか、圧縮されてます？？？

## 見えるぞ！私にもPDFが見える！

文字化けの原因は圧縮にあったようです。採用されている圧縮方法はそこまで難しいアルゴリズムではないようなので、自分で作れそうですが、今回はツールに頼りましょう。使ったツールは[PDFtk](https://www.pdflabs.com/tools/pdftk-the-pdf-toolkit/)の無料版です。

インストール後、以下のコマンドをたたくと、展開したPDFが生成されます。
```:cmd
pdftk HelloWorld.pdf output HelloWorld_uncompress.pdf uncompress
```
中身をすべて貼ると、なかなかに長いので、「Hello World.」に結びつくところを載せておきます。
```PDF:HelloWorld_uncompress.pdf
%PDF-1.5
%����
1 0 obj 
<<
    /Font 
        <<
            /F19 2 0 R
        >>
    /ProcSet [/PDF /Text]
>>
endobj

3 0 obj 
<<
    /Parent 4 0 R
    /MediaBox [0 0 595.276 841.89]
    /Resources 1 0 R
    /pdftk_PageNum 1
    /Contents 5 0 R
    /Type /Page
>>
endobj 

5 0 obj 
<<
    /Length 146
>>
stream
    BT
    /F19 10.9091 Tf
    1 0 0 1 85.794 718.818 Tm [<003E0032004800480051>-333<0071>83<005100600048002F0058>]TJ
    1 0 0 1 294.911 61.993 Tm [<0052>]TJ
    ET

endstream 
endobj 

2 0 obj 
<<
    /DescendantFonts [6 0 R]
    /BaseFont /SIIWUI+LMRoman10-Regular
    /Subtype /Type0
    /ToUnicode 7 0 R
    /Encoding /Identity-H
    /Type /Font
>>
endobj 

4 0 obj 
<<
    /Kids [3 0 R]
    /Count 1
    /Type /Pages
>>
endobj 

8 0 obj [47 [556] 50 [444] 62 [750] 72 [278] 81 [500 500] 88 [278] 96 [392] 113 [1028]]
endobj 

9 0 obj 
<<
    /FontName /SIIWUI+LMRoman10-Regular
    /StemV 93
    /FontFile3 10 0 R
    /Ascent 1127
    /Flags 4
    /XHeight 431
    /Descent -290
    /ItalicAngle 0
    /CIDSet 11 0 R
    /FontBBox [-430 -290 1417 1127]
    /Type /FontDescriptor
    /CapHeight 683
>>
endobj 

11 0 obj 
<<
    /Length 15
>>
stream
~~ 中略 ~~
endstream 
endobj
 
7 0 obj 
<<
    /Length 804
>>
stream

~~ 中略 ~~ 

>> def
    /CMapName /TeX-Identity-SIIWUI-LMRoman10-Regular 
    def
        /CMapType 2 
        def
            1 begincodespacerange
                <0000> <FFFF>
            endcodespacerange
            0 beginbfrange
            endbfrange
            9 beginbfchar
                <002F> <0064>
                <0032> <0065>
                <003E> <0048>
                <0048> <006C>
                <0051> <006F>
                <0052> <0031>
                <0058> <002E>
                <0060> <0072>
                <0071> <0057>
            endbfchar
        endcmap
        CMapName currentdict 
        /CMap defineresource pop
    end
end
%%EndResource
%%EOF

endstream 
endobj 

6 0 obj 
<<
    /BaseFont /SIIWUI+LMRoman10-Regular
    /CIDSystemInfo 
    <<
        /Supplement 0
        /Ordering (Identity)
        /Registry (Adobe)
    >>
    /Subtype /CIDFontType0
    /FontDescriptor 9 0 R
    /W 8 0 R
    /Type /Font
>>
endobj 

12 0 obj 
<<
    /Pages 4 0 R
    /Type /Catalog
>>
endobj 

~~ 中略 ~~

trailer

<<
    /Info 13 0 R
    /Root 12 0 R
    /Size 14
    /ID [<c276673f1afb1bca87c4572c62f8df7d> <c276673f1afb1bca87c4572c62f8df7d>]
>>
startxref
3915
%%EOF
```
**おぉ！(ﾟﾛﾟ屮)屮**
一部文字化けしているところはありましたが、大半が読めるようになりました。
## テキスト情報はどこ？
ここからは、「Hello World.」への道筋を説明します。
末尾にある`trailer`情報から`/Root`をみつけます。
```PDF:trailer
trailer

<<
/Info 13 0 R
/Root 12 0 R
/Size 14
/ID [<c276673f1afb1bca87c4572c62f8df7d> <c276673f1afb1bca87c4572c62f8df7d>]
>>
```
今回だと `12 0 R`です。ここから、`12 0 obj`を探します。同じ様にたどっていくと、
`12 0 obj` -> `4 0 obj` -> `3 0 obj` -> `5 0 obj`にたどり着くことができます。
```PDF:5 0 obj
5 0 obj 
<<
/Length 146
>>
stream
BT
/F19 10.9091 Tf
1 0 0 1 85.794 718.818 Tm [<003E0032004800480051>-333<0071>83<005100600048002F0058>]TJ
1 0 0 1 294.911 61.993 Tm [<0052>]TJ
ET

endstream 
endobj 
```

`[<003E0032004800480051>-333<0071>83<005100600048002F0058>]`がHello World.っぽい。ただなんだこれ？
## 謎コードの羅列はフォントのせい
そこで、`3 0 obj`->`1 0 obj`の方をたどってみましょう。
`3 0 obj`->`1 0 obj`->`2 0 obj`->`7 0 obj`
```PDF:7 0 obj
7 0 obj 
<<
    /Length 804
>>
stream

~~ 中略 ~~ 

>> def
    /CMapName /TeX-Identity-SIIWUI-LMRoman10-Regular 
    def
        /CMapType 2 
        def
            1 begincodespacerange
                <0000> <FFFF>
            endcodespacerange
            0 beginbfrange
            endbfrange
            9 beginbfchar
                <002F> <0064>
                <0032> <0065>
                <003E> <0048>
                <0048> <006C>
                <0051> <006F>
                <0052> <0031>
                <0058> <002E>
                <0060> <0072>
                <0071> <0057>
            endbfchar
        endcmap
        CMapName currentdict 
        /CMap defineresource pop
    end
end
%%EndResource
%%EOF

endstream 
endobj 
```
このstream内のdef以降が使用するフォント情報になっています。特に、
```PDF:CMap
9 beginbfchar
    <002F> <0064>
    <0032> <0065>
    <003E> <0048>
    <0048> <006C>
    <0051> <006F>
    <0052> <0031>
    <0058> <002E>
    <0060> <0072>
    <0071> <0057>
endbfchar
```
の部分が使用する文字コードとASCIIコードの対応表になっています。つまり、
```PDF:CMap_explain
{変換する数} beginbfchar
    <使用するコード> <対応するASCIIコード>
endbfchar
```
## そしてHello World.へ
この対応表を元に、先ほどの`[<003E0032004800480051>-333<0071>83<005100600048002F0058>]`を変換してみましょう
```PDF:translate
[<003E0032004800480051>-333<0071>83<005100600048002F0058>]
| (対応表で変換)
V
[<00480065006C006C006F>-333<0057>83<006F0072006C0064002E>]
| (ASCIIコードから変換)
V
[(Hello)-333(W)83(orld.)]
```
**ｷﾀ――(ﾟ∀ﾟ)――!!**
ちなみに間に挟まっている数字は、文字の位置を調整しています。(-333で半角スペース分移動させて、83でo以降をWに寄せている。)

## 最後に
Hello World.を見つけるだけでも結構な手間がかかることがわかりますね。(人がやることじゃねぇ…)
今回読み解いたPDFのバージョンは1.5ですが、現行は1.7であり、互換性があるため、1.7でも正常に動作すると思われます。
また、今回は概略を書きましたが、テキストの位置の決め方や画像データのとり方なども今後タイミングがあれば書いてみようと思います。

## 参考
 - [PDF Reference, version 1.7](https://opensource.adobe.com/dc-acrobat-sdk-docs/pdfstandards/pdfreference1.7old.pdf)
 - [トラスト・ソフトウェア・システム](https://www.pdf-tools.trustss.co.jp/Syntax/parsePdfProc.html#proc)
 - [golden-luckyの日記](https://golden-lucky.hatenablog.com/entry/2019/12/03/144020)