Rustのprintpdfクレートがサブセットフォントの埋め込みに対応して歓喜した話

# 背景
テキストファイルを手軽にPDF化したくて、以下のprintpdfを試していた。
ただ、日本語を含む場合フォントが埋め込まれるため数ページのPDFが数MBになってしまい、頭を抱えていた。
後処理として、lopdfを使ってフォントを取り除けないか調査していたところ、表題に戻る。

https://crates.io/crates/printpdf

# おまけ
printpdfがv0.8.1になったのは10時間(v0.8.0も2日)ぐらい前のため、README.mdにあるFontの部分がコピペでは動かない。
とりあえず、ReadMe.mdを動くように変更したものとテキストファイルをPDF化したものを置いておく

## ReadMeを修正したもの
```rust
use printpdf::*;

fn main() {
    let mut doc = PdfDocument::new("My first PDF");

    let roboto_bytes = include_bytes!("assets/fonts/RobotoMedium.ttf").unwrap()
    let font_index = 0;
    let mut warnings = Vec::new();
    let font = ParsedFont::from_bytes(&roboto_bytes, font_index, &mut warnings).unwrap();　// 引数の数が違う 参照渡し

    // If you need custom text shaping (uses the `allsorts` font shaper internally)
    // let glyphs = font.shape(text);

    // printpdf automatically keeps track of which fonts are used in the PDF
    let font_id = doc.add_font(&font); // 参照渡し

    let text_pos = Point {
        x: Mm(10.0).into(),
        y: Mm(100.0).into(),
    }; // from bottom left

    let page1_contents = vec![
        Op::SetLineHeight { lh: Pt(33.0) },
        Op::SetWordSpacing { pt: Pt(33.0) },　// percent から pt に変更
        Op::SetCharacterSpacing { multiplier: 10.0 },
        Op::SetTextCursor { pos: text_pos },

        // Op::WriteCodepoints { ... }
        // Op::WriteCodepointsWithKerning { ... }
        // 構造体（とenum)がネストされていて形が異なる
        Op::WriteText {
            items: vec![TextItem::Text("Lorem ipsum".to_string())],
            font: font_id.clone(),
        },
        Op::AddLineBreak,
        Op::WriteText {
            items: vec![TextItem::Text("dolor sit amet".to_string())],
            font: font_id.clone(),
        },
        Op::AddLineBreak,
    ];

    let save_options = PdfSaveOptions {
        subset_fonts: true, // auto-subset fonts on save
        ..Default::default()
    };

    let page1 = PdfPage::new(Mm(10.0), Mm(250.0), page1_contents);
    let mut warnings = Vec::new();
    let pdf_bytes: Vec<u8> = doc
        .with_pages(vec![page1])
        .save(&save_options, &mut warnings); // 引数の数が違う
}
```

## テキスト変換
```rust
use printpdf::*;
use std::fs::File;
use std::io::{BufReader, Read};

fn main() -> Result<(), Box<dyn std::error::Error>> {
    // PDFドキュメントを作成
    let mut doc = PdfDocument::new("from text file");

    // フォントをシステムから読み込む
    let font_bytes = std::fs::read("C:/Windows/Fonts/UDDigiKyokashoN-R.ttc")?;

    // 警告メッセージの格納場所を作成
    let font_index = 0;
    let mut warnings = Vec::new();

    // フォントをロード - ParsedFontを使って処理します（追加の引数が必要）
    let font = match ParsedFont::from_bytes(&font_bytes, font_index, &mut warnings) {
        Some(font) => font,
        None => {
            let msg = format!("フォントの読み込みに失敗しました : {:?}", warnings);
            eprintln!("{}", msg);
            return Err(msg.into());
        }
    };

    // フォントをドキュメントに追加
    let font_id = doc.add_font(&font);

    // テキストファイルを読み込む
    let input_file = File::open("src/main.rs")?;
    let mut reader = BufReader::new(input_file);
    let mut text = String::new();
    reader.read_to_string(&mut text)?;

    // 複数のページを格納するベクター
    let mut pages = Vec::new();

    // テキスト行のイテレータを作成
    let lines = text.lines().collect::<Vec<_>>();
    let mut line_index = 0;

    // ページを作成してテキストを追加する
    while line_index < lines.len() {
        // 新しいページのコンテンツを準備
        let mut page_contents = vec![
            Op::SetLineHeight { lh: Pt(14.0) },          // 適切な行間に調整
            Op::SetWordSpacing { pt: Pt(0.0) },          // 単語間隔を標準に
            Op::SetCharacterSpacing { multiplier: 0.0 }, // 文字間隔を標準に
        ];

        // フォントを設定
        page_contents.push(Op::SetFontSize {
            font: font_id.clone(),
            size: Pt(12.0),
        });

        // 各ページの開始Y位置
        let mut y_pos = Mm(280.0); // A4の上部から開始（余白を考慮）

        // このページに表示する行を追加
        while line_index < lines.len() {
            let line = lines[line_index];

            // テキストセクション開始
            page_contents.push(Op::StartTextSection);

            // テキスト位置を設定
            let text_pos = Point {
                x: Mm(10.0).into(),
                y: y_pos.into(),
            };
            page_contents.push(Op::SetTextCursor { pos: text_pos });

            // テキストを追加
            page_contents.push(Op::WriteText {
                items: vec![TextItem::Text(line.to_string())],
                font: font_id.clone(),
            });

            // テキストセクション終了
            page_contents.push(Op::EndTextSection);

            // Y位置を更新
            y_pos -= Mm(6.0); // 行間を適切に調整

            // 次の行に進む
            line_index += 1;

            // ページ下限に達したら、このページを終了
            if y_pos < Mm(20.0) {
                break;
            }
        }

        // ページを作成して追加
        let page = PdfPage::new(Mm(210.0), Mm(297.0), page_contents); // A4サイズ
        pages.push(page);

        println!("ページ {} 完成", pages.len());
    }

    // 保存オプションを設定
    let save_options = PdfSaveOptions {
        subset_fonts: false,
        // optimize: false, // デバッグのために最適化を無効化
        ..Default::default()
    };

    // PDFを生成して保存
    let mut warnings = Vec::new();
    let page_length = pages.len();
    let pdf_bytes = doc.with_pages(pages).save(&save_options, &mut warnings);
    // 警告メッセージがあれば表示
    if !warnings.is_empty() {
        println!("警告メッセージ: {:?}", warnings);
    }

    // ファイルに書き込み
    match std::fs::write("output.pdf", pdf_bytes) {
        Ok(_) => println!("PDF作成完了: output.pdf (全{}ページ)", page_length),
        Err(e) => {
            eprintln!("PDFファイルの保存に失敗しました: {}", e);
            return Err(e.into());
        }
    };

    Ok(())
}

```