Slide 1

Slide 1 text

lopdfの話 2018‑08‑01 Rust LT #2 ~いま使う!Rust~ 小嶋智

Slide 2

Slide 2 text

自己紹介 skoji プログラマ 主にRuby テキスト処理や電子出版の周辺を漂っています ‒ 1 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 3

Slide 3 text

話す内容 ‒ 2 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 4

Slide 4 text

lopdf PDFライブラリ 製品で使った話をします ‒ 3 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 5

Slide 5 text

背景 ‒ 4 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 6

Slide 6 text

対象の製品 ‒ 5 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 7

Slide 7 text

https://trim‑marks.com/ ‒ 6 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 8

Slide 8 text

VersaType HTML/CSSをページ組版するエンジン 原則としてCSS標準の範囲 JavaScript ‒ 7 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 9

Slide 9 text

VersaType Converter HTML/CSSをPDFに変換 JSの組版エンジン + 組み込み用Chromium Win/Linux/macOSのバイナリ ‒ 8 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 10

Slide 10 text

なぜPDF処理(1) PDF生成はChromium任せ PDF出力をもっと強くしたい 後処理 vs Chromiumへのパッチ 向き不向きがある ‒ 9 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 11

Slide 11 text

なぜPDF処理(2) Chromiumへのパッチ Chromiumは大きい上に変化が激しい 本家に取り込まれない場合は追従がしんどい 後処理でできることは後処理で ‒ 10 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 12

Slide 12 text

PDF後処理: 要件 ‒ 11 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 13

Slide 13 text

組み込んで配布 ‒ 12 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 14

Slide 14 text

PDFの低レイヤ ‒ 13 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 15

Slide 15 text

Win/Linux/macOS ‒ 14 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 16

Slide 16 text

C++から使う ‒ 15 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 17

Slide 17 text

C++書きたくない (できるだけ) ‒ 16 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 18

Slide 18 text

探した結果 ‒ 17 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 19

Slide 19 text

lopdf! ‒ 18 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 20

Slide 20 text

バイナリに組み込める PDFの低レイヤ操作ができる Linux/Windows/macOSのx86̲64 C++から呼べる Rustで書ける ‒ 19 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 21

Slide 21 text

懸念 ‒ 20 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 22

Slide 22 text

PDF操作の機能が本当に足りているか C++からの呼び出し CSSのbookmarks指定をPDF Outlinesに反映 そこそこ複雑な構造を渡すインタフェース ‒ 21 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 23

Slide 23 text

機能が足りているか ‒ 22 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 24

Slide 24 text

結論から言うと、足りている。 低レイヤなのでコード量は多い RubyのPrawnと比較してみる PDF生成専用 簡単に書けるが低レイヤ操作は難しい ‒ 23 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 25

Slide 25 text

機能比較: Hello World ‒ 24 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 26

Slide 26 text

Prawn require 'prawn' Prawn::Document.generate('hello.pdf') do text "Hello World!" end ‒ 25 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 27

Slide 27 text

Prawn : 位置とフォントを指定 require 'prawn' Prawn::Document.generate('hello2.pdf') do font("Courier") do font_size 48 draw_text "Hello World!", :at => [100,600] end end ‒ 26 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 28

Slide 28 text

lopdf fn hello() { 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())); let page_id = doc.add_object(dictionary! { "Type" => "Page", "Parent" => pages_id, "Contents" => content_id, }); let pages = dictionary! { "Type" => "Pages", "Kids" => vec![page_id.into()], "Count" => 1, "Resources" => resources_id, "MediaBox" => vec![0.into(), 0.into(), 595.into(), 842.into()], }; doc.objects.insert(pages_id, Object::Dictionary(pages)); let catalog_id = doc.add_object(dictionary! { "Type" => "Catalog", "Pages" => pages_id, }); doc.trailer.set("Root", catalog_id); doc.compress(); doc.save("hello-lopdf.pdf").unwrap(); } ‒ 27 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 29

Slide 29 text

機能比較: Outlines ‒ 28 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 30

Slide 30 text

Prawn require 'prawn' Prawn::Document.generate('outline.pdf') do (1..3).each do |index| text "Page #{index}" start_new_page end outline.define do section('Section 1', destination: 1) do page title: 'Page 2', destination: 2 page title: 'Page 3', destination: 3 end end ‒ 29 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 31

Slide 31 text

lopdf fn outline() { 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 mut pages_list: Vec = Vec::new(); for x in 0..3 { let str = format!("Page {}", x + 1); 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(str)]), Operation::new("ET", vec![]), ], }; let content_id = doc.add_object(Stream::new(dictionary! {}, content.encode().unwrap())); let page_id = doc.add_object(dictionary! { "Type" => "Page", "Parent" => pages_id, "Contents" => content_id, }); pages_list.push(page_id.into()); } let outline_id = { let outline_id = doc.new_object_id(); let outline_first_id = doc.new_object_id(); let outline_second_id = doc.new_object_id(); let outline_third_id = doc.new_object_id(); let action_first_id = doc.add_object(dictionary!{ "D" => vec![pages_list[0].clone(), "FitH".into(), Object::Null], "S" => "GoTo" }); let action_second_id = doc.add_object(dictionary!{ "D" => vec![pages_list[1].clone(), "FitH".into(), Object::Null], "S" => "GoTo" ‒ 30 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 32

Slide 32 text

}); let action_third_id = doc.add_object(dictionary!{ "D" => vec![pages_list[2].clone(), "FitH".into(), Object::Null], "S" => "GoTo" }); let outline_second = dictionary! { "Title" => Object::string_literal("Page 2"), "Parent" => outline_first_id, "A" => action_second_id, "Next" => outline_third_id }; let outline_third = dictionary! { "Title" => Object::string_literal("Page 3"), "Parent" => outline_first_id, "A" => action_third_id, "Prev" => outline_second_id }; let outline_first = dictionary! { "Title" => Object::string_literal("Section 1"), "Parent" => outline_id, "A" => action_first_id, "First" => outline_second_id, "Last" => outline_third_id }; let outline = dictionary! { "Count" => 2, "First" => outline_first_id, "Last" => outline_first_id, }; doc.objects.insert(outline_first_id, Object::Dictionary(outline_first)); doc.objects.insert(outline_second_id, Object::Dictionary(outline_second)); doc.objects.insert(outline_third_id, Object::Dictionary(outline_third)); doc.objects.insert(outline_id, Object::Dictionary(outline)); outline_id }; let pages = dictionary! { "Type" => "Pages", "Kids" => pages_list, "Count" => 3, "Resources" => resources_id, "MediaBox" => vec![0.into(), 0.into(), 595.into(), 842.into()], }; doc.objects.insert(pages_id, Object::Dictionary(pages)); let catalog_id = doc.add_object(dictionary! { "Type" => "Catalog", "Pages" => pages_id, "Outlines" => outline_id }); doc.trailer.set("Root", catalog_id); doc.compress(); doc.save("outline.pdf").unwrap(); } ‒ 31 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 33

Slide 33 text

なげえ ‒ 32 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 34

Slide 34 text

とはいえ ‒ 33 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 35

Slide 35 text

lopdfは低レイヤを触れる PDF outlinesのアクションはなんでもあり。 Prawnでは固定。 コードの短さは優先度低い let action_first_id = doc.add_object(dictionary!{ "D" => vec![pages_list[0].clone(), "FitH".into(), Object::Null], "S" => "GoTo" }); ‒ 34 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 36

Slide 36 text

C++から呼び出し ‒ 35 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 37

Slide 37 text

Bookmarksのデータ ラベル、階層、ページ番号 の配列 [{ title: '第1章', level: 1, page :1 }, { title: '第1章第1節', level: 2, page: 1 }, { title: '第1章第2節', level: 2, page: 9 }, { title: '第2章', level:1, page: 20 }] ‒ 36 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 38

Slide 38 text

最初の案 ‒ 37 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 39

Slide 39 text

Rust側で空のvectorを生成 C++からtitle, level, pageの組をvectorにひ とつずつ追加 C++からPDFのパスを渡してoutlines書き込 み処理 ‒ 38 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 40

Slide 40 text

インタフェース void *new_vector(); bool add_outline_to_vector(const char *title, int_32t level, int_32t page, void *vector); bool write_outline(const char *src_path, const char *dst_path, void *vector); ‒ 39 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 41

Slide 41 text

処理の流れ JavaScriptでbookmarks情報作成 オブジェクトの配列 C++のデータ構造に変換 Rustから出ているAPIを順次呼ぶ ‒ 40 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 42

Slide 42 text

だるい ‒ 41 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 43

Slide 43 text

欠点 C++コード上で目次の構造を作る必要がある JSとRustの間に入るC++のコードでは、本来知 らなくても良い知識 ‒ 42 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 44

Slide 44 text

2つ目の案 ‒ 43 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 45

Slide 45 text

JS側でJSON.stringify() C++では文字列の中身に関知しない Rust側ではパースしてVectorを作る serde-json ‒ 44 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 46

Slide 46 text

インタフェース bool add_outline(const char *src_path, const char *dst_path, const char *outline_json); ‒ 45 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 47

Slide 47 text

今回はこれを採用 ‒ 46 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 48

Slide 48 text

デモ ‒ 47 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 49

Slide 49 text

cssの指定 h1 { bookmark-level: 1; } h2 { bookmark-level: 2; } h3 { bookmark-level: 3; } .cover-page h1 { bookmark-label: content(text) " 表紙"; } この資料のPDF outlinesはこの指定で生成した。 ‒ 48 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

Slide 50

Slide 50 text

リソース lopdf https://github.com/J‑F‑Liu/lopdf Prawn http://prawnpdf.org/ Prawnとlopdf比較のソース https://github.com/skoji/compare‑prawn‑lopdf このスライドのソース https://github.com/skoji/rust̲lt̲20180801 VersaType https://trim‑marks.com ‒ 49 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話