Skip to content

Instantly share code, notes, and snippets.

@kujirahand
Last active September 12, 2025 01:46
Show Gist options
  • Save kujirahand/5fa0f3618ed53dcd91eb5fe45f2e5e1c to your computer and use it in GitHub Desktop.
Save kujirahand/5fa0f3618ed53dcd91eb5fe45f2e5e1c to your computer and use it in GitHub Desktop.
PDF分割プログラムです。このプログラムを元にして作った配布用の実行ファイルがあります。コメント欄をご覧ください。
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)
}
@kujirahand
Copy link
Author

kujirahand commented Sep 10, 2025

オープンソースで実行バイナリを公開しています

設定ファイルを用意すると、任意のページ数で分割できるように改造した、完全版を用意しました。以下よりダウンロードできます。

@kujirahand
Copy link
Author

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;
}

@kujirahand
Copy link
Author

バンドルのための追加のファイルの説明

macOSのアプリケーションバンドル:

% tree pdf_splitter.app
pdf_splitter.app
└── Contents
    ├── Info.plist
    └── MacOS
        ├── pdf_splitter
        └── pdf_splitter_bin

macOS用の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