Last active
September 12, 2025 01:46
-
-
Save kujirahand/5fa0f3618ed53dcd91eb5fe45f2e5e1c to your computer and use it in GitHub Desktop.
PDF分割プログラムです。このプログラムを元にして作った配布用の実行ファイルがあります。コメント欄をご覧ください。
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| use lopdf::{dictionary, Document, Object, ObjectId}; | |
| use std::collections::BTreeMap; | |
| use std::env; | |
| fn main() { | |
| let args: Vec<String> = env::args().collect(); | |
| if args.len() < 2 { | |
| println!("使い方: pdf_splitter <file.pdf>"); | |
| return; | |
| } | |
| let input_path = &args[1]; | |
| if let Err(e) = split_pdf(input_path) { | |
| eprintln!("エラー: {}", e); | |
| } | |
| } | |
| fn split_pdf(input_path: &str) -> Result<(), Box<dyn std::error::Error>> { | |
| let doc = Document::load(input_path)?; | |
| let pages = doc.get_pages(); | |
| let page_count = pages.len(); | |
| let base_name = input_path.trim_end_matches(".pdf"); | |
| for i in (0..page_count).step_by(10) { | |
| let mut new_doc = Document::with_version(doc.version.clone()); | |
| let mut copied_objects: BTreeMap<ObjectId, ObjectId> = BTreeMap::new(); | |
| let mut new_page_ids = vec![]; | |
| let start_page = i + 1; | |
| let end_page = (i + 10).min(page_count); | |
| for page_num in start_page..=end_page { | |
| if let Some(&page_id) = pages.get(&(page_num as u32)) { | |
| copy_object_and_dependencies(page_id, &doc, &mut new_doc, &mut copied_objects)?; | |
| new_page_ids.push(copied_objects.get(&page_id).cloned().unwrap()); | |
| } | |
| } | |
| if new_page_ids.is_empty() { | |
| continue; | |
| } | |
| let pages_id = new_doc.add_object(Object::Dictionary(dictionary! { | |
| "Type" => "Pages", | |
| "Kids" => new_page_ids.iter().map(|id| Object::Reference(*id)).collect::<Vec<_>>(), | |
| "Count" => new_page_ids.len() as i64, | |
| })); | |
| let catalog_id = new_doc.add_object(Object::Dictionary(dictionary! { | |
| "Type" => "Catalog", | |
| "Pages" => Object::Reference(pages_id), | |
| })); | |
| new_doc.trailer.set("Root", Object::Reference(catalog_id)); | |
| let output_path = format!("{}_{}.pdf", base_name, i / 10 + 1); | |
| new_doc.save(&output_path)?; | |
| println!("{} を保存しました。", output_path); | |
| } | |
| Ok(()) | |
| } | |
| fn copy_object_and_dependencies( | |
| object_id: ObjectId, | |
| old_doc: &Document, | |
| new_doc: &mut Document, | |
| copied_objects: &mut BTreeMap<ObjectId, ObjectId>, | |
| ) -> Result<ObjectId, Box<dyn std::error::Error>> { | |
| if let Some(new_id) = copied_objects.get(&object_id) { | |
| return Ok(*new_id); | |
| } | |
| // Crucially, insert a placeholder ID before recursing to break cycles. | |
| let new_id = new_doc.new_object_id(); | |
| copied_objects.insert(object_id, new_id); | |
| let obj = old_doc.get_object(object_id)?.clone(); | |
| let new_obj = deep_copy_object(obj, old_doc, new_doc, copied_objects)?; | |
| // Now, insert the fully processed object at the placeholder ID. | |
| new_doc.objects.insert(new_id, new_obj); | |
| Ok(new_id) | |
| } | |
| fn deep_copy_object( | |
| obj: Object, | |
| old_doc: &Document, | |
| new_doc: &mut Document, | |
| copied_objects: &mut BTreeMap<ObjectId, ObjectId>, | |
| ) -> Result<Object, Box<dyn std::error::Error>> { | |
| let new_obj = match obj { | |
| Object::Reference(id) => { | |
| let new_id = copy_object_and_dependencies(id, old_doc, new_doc, copied_objects)?; | |
| Object::Reference(new_id) | |
| } | |
| Object::Array(arr) => { | |
| let mut new_arr = vec![]; | |
| for item in arr { | |
| new_arr.push(deep_copy_object(item, old_doc, new_doc, copied_objects)?); | |
| } | |
| Object::Array(new_arr) | |
| } | |
| Object::Dictionary(mut dict) => { | |
| for (_, val) in dict.iter_mut() { | |
| *val = deep_copy_object(val.clone(), old_doc, new_doc, copied_objects)?; | |
| } | |
| Object::Dictionary(dict) | |
| } | |
| Object::Stream(mut stream) => { | |
| for (_, val) in stream.dict.iter_mut() { | |
| *val = deep_copy_object(val.clone(), old_doc, new_doc, copied_objects)?; | |
| } | |
| Object::Stream(stream) | |
| } | |
| _ => obj.clone(), | |
| }; | |
| Ok(new_obj) | |
| } |
macOS用のラッパーmain.m
#import <Cocoa/Cocoa.h>
@interface AppDelegate : NSObject <NSApplicationDelegate>
@end
@implementation AppDelegate
- (void)application:(NSApplication *)sender openFiles:(NSArray<NSString *> *)filenames {
NSString *execDir = [[[NSBundle mainBundle] executablePath]
stringByDeletingLastPathComponent];
NSString *binaryPath = [execDir stringByAppendingPathComponent:@"pdf_splitter_bin"];
if (![[NSFileManager defaultManager] fileExistsAtPath:binaryPath]) {
// サイレントに失敗するか、アラートを表示するか
// ここでは何もしない
[NSApp terminate:self];
return;
}
for (NSString *filename in filenames) {
@try {
NSTask *task = [[NSTask alloc] init];
[task setLaunchPath:binaryPath];
[task setArguments:@[filename]];
[task launch];
[task waitUntilExit];
} @catch (NSException *exception) {
// エラー処理
}
}
[NSApp terminate:self];
}
@end
int main(int argc, const char * argv[]) {
@autoreleasepool {
NSApplication *application = [NSApplication sharedApplication];
AppDelegate *appDelegate = [[AppDelegate alloc] init];
[application setDelegate:appDelegate];
[application run];
}
return 0;
}バンドルのための追加のファイルの説明
macOSのアプリケーションバンドル:
% tree pdf_splitter.app
pdf_splitter.app
└── Contents
├── Info.plist
└── MacOS
├── pdf_splitter
└── pdf_splitter_binmacOS用のInfo.plistファイル:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleExecutable</key>
<string>pdf_splitter</string>
<key>CFBundleIdentifier</key>
<string>com.example.pdfsplitter</string>
<key>CFBundleName</key>
<string>PDF Splitter</string>
<key>CFBundlePackageType</key>
<string>APPL</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>CFBundleDocumentTypes</key>
<array>
<dict>
<key>CFBundleTypeName</key>
<string>PDF Document</string>
<key>CFBundleTypeRole</key>
<string>Editor</string>
<key>LSHandlerRank</key>
<string>Owner</string>
<key>LSItemContentTypes</key>
<array>
<string>com.adobe.pdf</string>
<string>public.pdf</string>
</array>
</dict>
</array>
</dict>
</plist>
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
オープンソースで実行バイナリを公開しています
設定ファイルを用意すると、任意のページ数で分割できるように改造した、完全版を用意しました。以下よりダウンロードできます。