lopdfの話

 lopdfの話

Rust LT #2 〜いま使う!Rust〜 でのLT発表資料です。
https://rust.connpass.com/event/91177/

SpeakerDeck上では文字組みなどに問題が見られる場合があります。元のPDFデータはこちらをご参照ください。

https://github.com/skoji/rust_lt_20180801/blob/master/slide.pdf

98b698d47526f827586a7f3946607ef4?s=128

Satoshi KOJIMA

August 01, 2018
Tweet

Transcript

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

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

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

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

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

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

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

  8. VersaType HTML/CSSをページ組版するエンジン 原則としてCSS標準の範囲 JavaScript ‒ 7 ‒ Rust LT #2

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

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

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

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

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

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

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

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

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

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

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

  20. バイナリに組み込める PDFの低レイヤ操作ができる Linux/Windows/macOSのx86̲64 C++から呼べる Rustで書ける ‒ 19 ‒ Rust LT

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

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

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

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

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

    lopdfの話
  26. Prawn require 'prawn' Prawn::Document.generate('hello.pdf') do text "Hello World!" end ‒

    25 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話
  27. 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の話
  28. 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の話
  29. 機能比較: Outlines ‒ 28 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

  30. 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の話
  31. 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<Object> = 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の話
  32. }); 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の話
  33. なげえ ‒ 32 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

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

  35. 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の話
  36. C++から呼び出し ‒ 35 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

  37. 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の話
  38. 最初の案 ‒ 37 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

  39. Rust側で空のvectorを生成 C++からtitle, level, pageの組をvectorにひ とつずつ追加 C++からPDFのパスを渡してoutlines書き込 み処理 ‒ 38 ‒

    Rust LT #2 ~いま使う!Rust~ lopdfの話
  40. インタフェース 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の話
  41. 処理の流れ JavaScriptでbookmarks情報作成 オブジェクトの配列 C++のデータ構造に変換 Rustから出ているAPIを順次呼ぶ ‒ 40 ‒ Rust LT

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

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

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

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

    ~いま使う!Rust~ lopdfの話
  46. インタフェース bool add_outline(const char *src_path, const char *dst_path, const char

    *outline_json); ‒ 45 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話
  47. 今回はこれを採用 ‒ 46 ‒ Rust LT #2 ~いま使う!Rust~ lopdfの話

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

  49. 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の話
  50. リソース 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の話