背景
テキストファイルを手軽にPDF化したくて、以下のprintpdfを試していた。
ただ、日本語を含む場合フォントが埋め込まれるため数ページのPDFが数MBになってしまい、頭を抱えていた。
後処理として、lopdfを使ってフォントを取り除けないか調査していたところ、表題に戻る。
おまけ
printpdfがv0.8.1になったのは10時間(v0.8.0も2日)ぐらい前のため、README.mdにあるFontの部分がコピペでは動かない。
とりあえず、ReadMe.mdを動くように変更したものとテキストファイルをPDF化したものを置いておく
ReadMeを修正したもの
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); // 引数の数が違う
}
テキスト変換
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(())
}