3.5K Views
August 10, 25
スライド概要
ShizuokaTECH#1
PDFのよみかた ShizuokaTECH#1 Shotaro Hirukawa(@shotanue) 1
自己紹介 • 蛭川尚太郎(@shotanue) • 富士宮から来ました • 弁護士ドットコム株式会社 ◦ 育休中 ◦ フロントエンドエンジニア(自称) ▪ (直近はひたすらTerraformとGoを書いてました) ◦ フルリモート • MAZDAロードスター 2
PDFのよみかた とは 3
PDF(のコンテンツを)読む? よくあると思います 4
PDF(をプログラムで扱う対象 として)読む まれによくあると思います 5
ライブラリ探しの旅 6
とあるPDFライブラリのサンプルコード
// https://github.com/J-F-Liu/lopdf/blob/c9a44ec72e80a9d7096cd56fabfae02ac18d0e96/README.md
use lopdf::{Document, Object, Stream, dictionary};
use lopdf::content::{Content, Operation};
let mut doc = Document::with_version("1.5");
let pages_id = doc.new_object_id();
let font_id = doc.add_object(dictionary! {
"Type" => "Font",
"Subtype" => "Type1",
"BaseFont" => "Courier",
});
let resources_id = doc.add_object(dictionary! {
"Font" => dictionary! {
"F1" => font_id,
},
});
let content = Content {
operations: vec![
Operation::new("BT", vec![]),
Operation::new("Tf", vec!["F1".into(), 48.into()]),
Operation::new("Td", vec![100.into(), 600.into()]),
Operation::new("Tj", vec![Object::string_literal("Hello World!")]),
Operation::new("ET", vec![]),
],
};
let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode().unwrap()));
7
• Object...? • Operation...? • Stream...? 8
リテラシーを求められる 9
PDFを読めるようになればいいじゃないの(?) 10
PDFとは 11
PDF = Portable Document Format %PDF-1.7 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /Contents 4 0 R >> endobj 4 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello!) Tj ET endstream xref 0 5 0000000000 65535 f 0000000015 00000 n 0000000074 00000 n 00000000173 00000 n 00000000301 00000 n trailer << /Size 5 /Root 1 0 R >> startxref 380 %%EOF 12
テキストとバイナリが混在したファイルフォーマット エディタで開けます 13
``` %PDF-1.7 ``` ↓↓↓↓↓↓ 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /Contents 4 0 R >> endobj 4 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello!) Tj ET endstream ``` ``` xref 0 5 0000000000 65535 f 0000000015 00000 n 0000000074 00000 n 00000000173 00000 n 00000000301 00000 n ``` trailer << /Size 5 /Root 1 0 R >> startxref 380 %%EOF ``` 14
Object 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /Contents 4 0 R >> endobj 4 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello!) Tj ET endstream 15
Object
1 0 obj
<< /Type /Catalog /Pages 2 0 R >> # {Type: "Catalog", Pages: "2 0 R"}
endobj
2 0 obj
<< /Type /Pages /Kids [3 0 R] /Count 1 >> # {Type: "Pages", Kids: ["3 0 R"], Count: 1}
endobj
3 0 obj
<< /Type /Page /Parent 2 0 R /Contents 4 0 R >> # {Type: "Page", Parent: "2 0 R", Contents: "4 0 R"}
endobj
2 0 Rや3 0 Rはオブジェクトの参照
16
Stream 4 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello!) Tj ET endstream コンテンツを格納 命令やバイナリを含む 17
手続型のような記述 4 0 obj << /Length 44 >> stream BT ← テキスト描画開始 /F1 12 Tf ← フォントF1、サイズ12を設定 72 720 Td ← 座標(72,720)に移動 (Hello!) Tj ← 文字列"Hello!"を描画 ET ← テキスト描画終了 endstream 上から順番に実行される 18
Stream(バイナリ)
5 0 obj
<<
/Type /XObject
/Subtype /Image
/Width 100
/Height 100
/Length 1024
>>
stream
ÿØÿàJFIFHhÿÛC
ÿÄ@÷}!1AQa"q2#BR¡±ÁÑðñ$3br
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz¢£¤¥¦§¨©ª«¬®¯°±²³´µ¶·¸¹º
endstream
19
参照を解決すると右のような木構造に 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /Contents 4 0 R >> endobj 4 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello!) Tj ET endstream 1 0 obj カタログ /Type /Catalog /Pages 2 0 R 2 0 obj ページツリー /Type /Pages /Kids 3 0 R 3 0 obj ページ /Type /Page /Contents 4 0 R 4 0 obj コンテンツ Hello!の描画命令 20
``` %PDF-1.7 ``` ``` 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /Contents 4 0 R >> endobj 4 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello!) Tj ET endstream ``` ``` ↓↓↓↓↓↓ xref 0 5 0000000000 65535 f 0000000015 00000 n 0000000074 00000 n 00000000173 00000 n 00000000301 00000 n ``` ``` trailer << /Size 5 /Root 1 0 R >> startxref 380 %%EOF ``` 21
XRef(クロスリファレンス) xref 0 5 0000000000 65535 f 0000000015 00000 n ← オブジェクト1は15バイト目 0000000074 00000 n ← オブジェクト2は74バイト目 00000000173 00000 n ← オブジェクト3は173バイト目 00000000301 00000 n ← オブジェクト4は301バイト目 仕組み: バイトオフセットでO(1)アクセス • オブジェクト1を取得したい→ XRefで位置検索 → 直接 ジャンプ • PDFはランダムアクセスに強い設計になっている 22
``` %PDF-1.7 ``` ``` 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /Contents 4 0 R >> endobj 4 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello!) Tj ET endstream ``` ``` xref 0 5 0000000000 65535 f 0000000015 00000 n 0000000074 00000 n 00000000173 00000 n 00000000301 00000 n ``` ``` ↓↓↓↓↓↓ trailer << /Size 5 /Root 1 0 R >> startxref 380 %%EOF ``` 23
trailer trailer ← トレーラ << /Size 5 /Root 1 0 R >> startxref 380 %%EOF ルートノードの参照IDやクロスリファレンスのオフセット 位置を示す 24
ここまでくるとPDFが読めると言っても過言ではない 25
PDFのよみかた %PDF-1.7 1 0 obj << /Type /Catalog /Pages 2 0 R >> endobj 2 0 obj << /Type /Pages /Kids [3 0 R] /Count 1 >> endobj 3 0 obj << /Type /Page /Parent 2 0 R /Contents 4 0 R >> endobj 4 0 obj << /Length 44 >> stream BT /F1 12 Tf 72 720 Td (Hello!) Tj ET endstream xref ← クロスリファレンス 0 5 0000000000 65535 f 0000000015 00000 n 0000000074 00000 n 00000000173 00000 n 00000000301 00000 n trailer ← トレーラー << /Size 5 /Root 1 0 R >> startxref 380 %%EOF 1. トレーラーをファイル末尾から 取得する 2. トレーラーから以下を取得する ◦ ルートノードの参照ID ◦ クロスリファレンスのオフ セット位置 3. ルートノードの参照IDを起点に PDFの木構造を探索する 26
ライブラリのI/Fも読めるような気がしてきませんか
use lopdf::{Document, Object, Stream, dictionary};
use lopdf::content::{Content, Operation};
let mut doc = Document::with_version("1.5");
let pages_id = doc.new_object_id();
// 意味がわかるようになる!
let font_id = doc.add_object(dictionary! { // ← オブジェクト辞書の作成
"Type" => "Font", // ← PDF仕様のType/Subtype
"Subtype" => "Type1",
"BaseFont" => "Courier",
});
let resources_id = doc.add_object(dictionary! { // ← リソース辞書
"Font" => dictionary! { "F1" => font_id, } // ← フォント参照
});
let content = Content { // ← コンテンツストリーム
operations: vec![ // ← PDF描画オペレーター
Operation::new("BT", vec![]), // ← Begin Text
Operation::new("Tf", vec!["F1".into(), 48.into()]), // ← Text Font
Operation::new("Tj", vec![Object::string_literal("Hello!")]),
Operation::new("ET", vec![]), // ← End Text
],
};
let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode().unwrap()));
27
参考 『詳細PDF入門』 - itchyny https://itchyny.hatenablog.com/ entry/2015/09/16/100000 『PDF構造解説』 - O'Reilly 180ページと意外にコンパクト 28
面白いと思った方は チャンネル登録(?)と高評価(?) よろしくお願いします(?) X: @shotanue 29