Simple Tools Hub - Simple Online Tools

general

PDF結合・分割ツール完全ガイド2025|文書管理を効率化する最強ツール

PDF結合、分割、回転、並べ替え、ページ抽出まで完全対応。圧縮、暗号化、透かし追加、OCR処理も可能。リモートワーク時代の文書管理に必須のプロフェッショナルツール。

19 min read
PDF結合・分割ツール完全ガイド2025|文書管理を効率化する最強ツール

PDF結合・分割ツール完全ガイド2025|文書管理を効率化する最強ツール

PDFツールが業務効率化の鍵となる理由

デジタルトランスフォーメーション時代において、PDF文書管理は業務効率を左右する重要要素です。契約書、報告書、プレゼンテーション資料など、あらゆるビジネス文書がPDF形式で共有されています。

PDF管理の現状と課題

統計データ(2025年)

  • **95%**の企業がPDFを主要文書形式として使用
  • 平均的なビジネスパーソンは週15時間をPDF作業に費やす
  • PDF関連作業の**40%**が結合・分割・編集
  • ペーパーレス化により年間2.3兆円のコスト削減(日本)

一般的な課題

  • 🔴 複数PDFファイルの管理困難
  • 🔴 大容量ファイルの送信制限
  • 🔴 ページの並べ替え・削除の手間
  • 🔴 セキュリティと共有の両立
  • 🔴 異なるソースからの統合困難

i4u PDF結合・分割ツールは、これらの課題を完全に解決する包括的ソリューションです。

PDF処理の基本機能

1. PDF結合(マージ)

基本的な結合処理

// PDF結合の実装例(PDF.js + pdf-lib)
import { PDFDocument } from 'pdf-lib';

class PDFMerger {
  async mergePDFs(pdfFiles) {
    const mergedPdf = await PDFDocument.create();

    for (const file of pdfFiles) {
      const pdfBytes = await file.arrayBuffer();
      const pdf = await PDFDocument.load(pdfBytes);
      const pages = await mergedPdf.copyPages(pdf, pdf.getPageIndices());

      for (const page of pages) {
        mergedPdf.addPage(page);
      }
    }

    const mergedPdfBytes = await mergedPdf.save();
    return mergedPdfBytes;
  }

  async mergeWithOptions(pdfFiles, options = {}) {
    const {
      pageRanges = null,
      orientation = 'portrait',
      pageSize = 'A4',
      compression = true,
      metadata = {}
    } = options;

    const mergedPdf = await PDFDocument.create();

    // メタデータ設定
    if (metadata.title) mergedPdf.setTitle(metadata.title);
    if (metadata.author) mergedPdf.setAuthor(metadata.author);
    if (metadata.subject) mergedPdf.setSubject(metadata.subject);
    if (metadata.keywords) mergedPdf.setKeywords(metadata.keywords);

    for (let i = 0; i < pdfFiles.length; i++) {
      const file = pdfFiles[i];
      const pdfBytes = await file.arrayBuffer();
      const pdf = await PDFDocument.load(pdfBytes);

      // ページ範囲の処理
      let pageIndices = pdf.getPageIndices();
      if (pageRanges && pageRanges[i]) {
        pageIndices = this.parsePageRange(pageRanges[i], pdf.getPageCount());
      }

      const pages = await mergedPdf.copyPages(pdf, pageIndices);

      for (const page of pages) {
        // ページの向きを統一
        if (orientation === 'landscape' && page.getWidth() < page.getHeight()) {
          page.setRotation(degrees(90));
        }

        mergedPdf.addPage(page);
      }
    }

    // 圧縮処理
    const saveOptions = compression ?
      { useObjectStreams: true, addDefaultPage: false } : {};

    const mergedPdfBytes = await mergedPdf.save(saveOptions);
    return mergedPdfBytes;
  }

  parsePageRange(range, totalPages) {
    // "1-3,5,7-9" のような範囲指定をパース
    const indices = [];
    const parts = range.split(',');

    for (const part of parts) {
      if (part.includes('-')) {
        const [start, end] = part.split('-').map(n => parseInt(n) - 1);
        for (let i = start; i <= Math.min(end, totalPages - 1); i++) {
          indices.push(i);
        }
      } else {
        const index = parseInt(part) - 1;
        if (index < totalPages) {
          indices.push(index);
        }
      }
    }

    return indices;
  }
}

2. PDF分割

高度な分割オプション

import PyPDF2
import os
from pathlib import Path

class PDFSplitter:
    def __init__(self):
        self.output_dir = Path("output")
        self.output_dir.mkdir(exist_ok=True)

    def split_by_pages(self, pdf_path, pages_per_file=1):
        """指定ページ数ごとに分割"""
        with open(pdf_path, 'rb') as file:
            pdf = PyPDF2.PdfReader(file)
            total_pages = len(pdf.pages)

            file_count = 0
            for start in range(0, total_pages, pages_per_file):
                writer = PyPDF2.PdfWriter()
                end = min(start + pages_per_file, total_pages)

                for page_num in range(start, end):
                    writer.add_page(pdf.pages[page_num])

                output_filename = f"split_{file_count + 1}.pdf"
                output_path = self.output_dir / output_filename

                with open(output_path, 'wb') as output_file:
                    writer.write(output_file)

                file_count += 1
                yield {
                    'filename': output_filename,
                    'pages': f"{start + 1}-{end}",
                    'size': output_path.stat().st_size
                }

    def split_by_size(self, pdf_path, max_size_mb=10):
        """ファイルサイズで分割"""
        max_size_bytes = max_size_mb * 1024 * 1024

        with open(pdf_path, 'rb') as file:
            pdf = PyPDF2.PdfReader(file)
            current_writer = PyPDF2.PdfWriter()
            current_size = 0
            file_count = 0

            for page_num, page in enumerate(pdf.pages):
                # ページを一時的に追加してサイズを確認
                temp_writer = PyPDF2.PdfWriter()
                temp_writer.add_page(page)

                temp_path = self.output_dir / "temp.pdf"
                with open(temp_path, 'wb') as temp_file:
                    temp_writer.write(temp_file)

                page_size = temp_path.stat().st_size
                temp_path.unlink()

                if current_size + page_size > max_size_bytes and current_size > 0:
                    # 現在のPDFを保存
                    output_filename = f"split_size_{file_count + 1}.pdf"
                    output_path = self.output_dir / output_filename

                    with open(output_path, 'wb') as output_file:
                        current_writer.write(output_file)

                    yield {
                        'filename': output_filename,
                        'pages': len(current_writer.pages),
                        'size': output_path.stat().st_size
                    }

                    # 新しいライターを開始
                    current_writer = PyPDF2.PdfWriter()
                    current_size = 0
                    file_count += 1

                current_writer.add_page(page)
                current_size += page_size

            # 最後のファイルを保存
            if len(current_writer.pages) > 0:
                output_filename = f"split_size_{file_count + 1}.pdf"
                output_path = self.output_dir / output_filename

                with open(output_path, 'wb') as output_file:
                    current_writer.write(output_file)

                yield {
                    'filename': output_filename,
                    'pages': len(current_writer.pages),
                    'size': output_path.stat().st_size
                }

    def split_by_bookmarks(self, pdf_path):
        """しおり(ブックマーク)で分割"""
        with open(pdf_path, 'rb') as file:
            pdf = PyPDF2.PdfReader(file)
            bookmarks = pdf.outline

            if not bookmarks:
                raise ValueError("PDFにしおりが存在しません")

            splits = []
            for i, bookmark in enumerate(bookmarks):
                if isinstance(bookmark, list):
                    continue

                page_num = pdf.get_destination_page_number(bookmark)
                splits.append({
                    'title': bookmark.title,
                    'start_page': page_num
                })

            # ページ範囲を計算
            for i in range(len(splits)):
                if i < len(splits) - 1:
                    splits[i]['end_page'] = splits[i + 1]['start_page'] - 1
                else:
                    splits[i]['end_page'] = len(pdf.pages) - 1

            # 分割実行
            for split in splits:
                writer = PyPDF2.PdfWriter()

                for page_num in range(split['start_page'], split['end_page'] + 1):
                    writer.add_page(pdf.pages[page_num])

                # ファイル名をしおりタイトルから生成
                safe_title = "".join(c for c in split['title'] if c.isalnum() or c in (' ', '-', '_'))
                output_filename = f"{safe_title}.pdf"
                output_path = self.output_dir / output_filename

                with open(output_path, 'wb') as output_file:
                    writer.write(output_file)

                yield {
                    'filename': output_filename,
                    'title': split['title'],
                    'pages': f"{split['start_page'] + 1}-{split['end_page'] + 1}",
                    'size': output_path.stat().st_size
                }

3. ページ操作

回転・並べ替え・削除

// ページ操作の実装
class PDFPageManager {
  async rotatePage(pdfBytes, pageNumber, degrees) {
    const pdfDoc = await PDFDocument.load(pdfBytes);
    const page = pdfDoc.getPage(pageNumber - 1);

    const currentRotation = page.getRotation().angle;
    page.setRotation(degrees(currentRotation + degrees));

    return await pdfDoc.save();
  }

  async reorderPages(pdfBytes, newOrder) {
    const pdfDoc = await PDFDocument.load(pdfBytes);
    const totalPages = pdfDoc.getPageCount();

    // 全ページをコピー
    const pages = [];
    for (let i = 0; i < totalPages; i++) {
      pages.push(pdfDoc.getPage(i));
    }

    // 新しいPDFドキュメントを作成
    const newPdfDoc = await PDFDocument.create();

    // 新しい順序でページを追加
    for (const index of newOrder) {
      if (index >= 0 && index < totalPages) {
        const [copiedPage] = await newPdfDoc.copyPages(pdfDoc, [index]);
        newPdfDoc.addPage(copiedPage);
      }
    }

    return await newPdfDoc.save();
  }

  async deletePages(pdfBytes, pagesToDelete) {
    const pdfDoc = await PDFDocument.load(pdfBytes);

    // ページを降順でソートして削除(インデックスのずれを防ぐ)
    const sortedPages = [...pagesToDelete].sort((a, b) => b - a);

    for (const pageNum of sortedPages) {
      if (pageNum > 0 && pageNum <= pdfDoc.getPageCount()) {
        pdfDoc.removePage(pageNum - 1);
      }
    }

    return await pdfDoc.save();
  }

  async extractPages(pdfBytes, pageRange) {
    const pdfDoc = await PDFDocument.load(pdfBytes);
    const newPdfDoc = await PDFDocument.create();

    const pages = this.parsePageRange(pageRange, pdfDoc.getPageCount());

    for (const pageIndex of pages) {
      const [copiedPage] = await newPdfDoc.copyPages(pdfDoc, [pageIndex]);
      newPdfDoc.addPage(copiedPage);
    }

    return await newPdfDoc.save();
  }
}

高度な機能

1. PDF圧縮

画像品質最適化

from PIL import Image
import fitz  # PyMuPDF
import io

class PDFCompressor:
    def __init__(self):
        self.quality_presets = {
            'high': {'dpi': 150, 'quality': 85},
            'medium': {'dpi': 100, 'quality': 70},
            'low': {'dpi': 72, 'quality': 50}
        }

    def compress_pdf(self, input_path, output_path, quality='medium'):
        """PDFを圧縮"""
        preset = self.quality_presets[quality]

        # PDFを開く
        pdf_document = fitz.open(input_path)

        # 新しいPDFを作成
        new_pdf = fitz.open()

        for page_num in range(pdf_document.page_count):
            page = pdf_document[page_num]

            # ページを画像として取得
            mat = fitz.Matrix(preset['dpi'] / 72.0, preset['dpi'] / 72.0)
            pix = page.get_pixmap(matrix=mat, alpha=False)

            # PIL画像に変換
            img_data = pix.pil_tobytes(format="JPEG", optimize=True)
            img = Image.open(io.BytesIO(img_data))

            # 画像を圧縮
            img_buffer = io.BytesIO()
            img.save(img_buffer, format="JPEG",
                    quality=preset['quality'],
                    optimize=True,
                    progressive=True)
            img_buffer.seek(0)

            # 新しいページを作成
            img_pdf = fitz.open("pdf", img_buffer.read())
            new_pdf.insert_pdf(img_pdf)
            img_pdf.close()

        # メタデータをコピー
        new_pdf.set_metadata(pdf_document.metadata)

        # 保存
        new_pdf.save(output_path,
                     garbage=4,  # 最大圧縮
                     deflate=True,  # Deflate圧縮を使用
                     clean=True)  # 不要なオブジェクトを削除

        new_pdf.close()
        pdf_document.close()

        # 圧縮統計を返す
        original_size = os.path.getsize(input_path)
        compressed_size = os.path.getsize(output_path)
        compression_ratio = (1 - compressed_size / original_size) * 100

        return {
            'original_size': original_size,
            'compressed_size': compressed_size,
            'compression_ratio': f"{compression_ratio:.1f}%",
            'quality_preset': quality
        }

    def optimize_images(self, pdf_path, max_width=1920, max_height=1080):
        """PDF内の画像を最適化"""
        pdf_document = fitz.open(pdf_path)

        for page_num in range(pdf_document.page_count):
            page = pdf_document[page_num]
            image_list = page.get_images()

            for img_index, img in enumerate(image_list):
                xref = img[0]
                pix = fitz.Pixmap(pdf_document, xref)

                if pix.width > max_width or pix.height > max_height:
                    # 画像をリサイズ
                    img_data = pix.pil_tobytes(format="PNG")
                    img = Image.open(io.BytesIO(img_data))

                    # アスペクト比を保持してリサイズ
                    img.thumbnail((max_width, max_height), Image.Resampling.LANCZOS)

                    # 画像を置換
                    img_buffer = io.BytesIO()
                    img.save(img_buffer, format="JPEG", quality=85, optimize=True)
                    img_buffer.seek(0)

                    # PDFに画像を挿入
                    page.replace_image(xref, stream=img_buffer.read())

                pix = None

        return pdf_document

2. セキュリティ機能

暗号化とパスワード保護

// PDF暗号化の実装
class PDFSecurity {
  async encryptPDF(pdfBytes, options = {}) {
    const {
      userPassword = '',
      ownerPassword = '',
      permissions = {
        print: true,
        modify: false,
        copy: true,
        annotate: true,
        fillForms: true,
        extractPages: false,
        assemble: false,
        printHighQuality: true
      },
      encryptionLevel = 'AES256'  // RC4_40, RC4_128, AES128, AES256
    } = options;

    const pdfDoc = await PDFDocument.load(pdfBytes);

    // 暗号化設定
    pdfDoc.encrypt({
      userPassword,
      ownerPassword: ownerPassword || userPassword,
      permissions: {
        printing: permissions.print ? 'highResolution' : 'none',
        modifying: permissions.modify,
        copying: permissions.copy,
        annotating: permissions.annotate,
        fillingForms: permissions.fillForms,
        contentAccessibility: true,
        documentAssembly: permissions.assemble
      }
    });

    return await pdfDoc.save();
  }

  async addWatermark(pdfBytes, watermarkText, options = {}) {
    const {
      fontSize = 50,
      color = { r: 0.5, g: 0.5, b: 0.5 },
      opacity = 0.3,
      rotation = 45,
      position = 'center'  // center, diagonal, header, footer
    } = options;

    const pdfDoc = await PDFDocument.load(pdfBytes);
    const pages = pdfDoc.getPages();

    for (const page of pages) {
      const { width, height } = page.getSize();

      // 透かし位置を計算
      let x, y;
      switch (position) {
        case 'diagonal':
          x = width / 4;
          y = height / 4;
          break;
        case 'header':
          x = width / 2;
          y = height - 50;
          break;
        case 'footer':
          x = width / 2;
          y = 50;
          break;
        default:  // center
          x = width / 2;
          y = height / 2;
      }

      // 透かしを描画
      page.drawText(watermarkText, {
        x,
        y,
        size: fontSize,
        color: rgb(color.r, color.g, color.b),
        opacity,
        rotate: degrees(rotation)
      });
    }

    return await pdfDoc.save();
  }

  async addDigitalSignature(pdfBytes, certificateData, signatureImage) {
    // デジタル署名の実装
    const pdfDoc = await PDFDocument.load(pdfBytes);

    // 署名フィールドを作成
    const form = pdfDoc.getForm();
    const signatureField = form.createSignature('signature');

    // 署名画像を追加
    if (signatureImage) {
      const img = await pdfDoc.embedPng(signatureImage);
      const page = pdfDoc.getPage(0);

      signatureField.addImage(img, {
        x: 50,
        y: 50,
        width: 150,
        height: 50
      });
    }

    // 証明書情報を設定
    signatureField.setCertificate(certificateData);

    return await pdfDoc.save();
  }
}

3. OCR処理

テキスト認識と検索可能PDF

import pytesseract
from pdf2image import convert_from_path
import PyPDF2
from reportlab.pdfgen import canvas
from reportlab.lib.pagesizes import letter
import io

class PDFOCRProcessor:
    def __init__(self):
        self.languages = {
            'ja': 'jpn',
            'en': 'eng',
            'zh': 'chi_sim',
            'ko': 'kor'
        }

    def ocr_pdf(self, pdf_path, language='ja', output_path=None):
        """スキャンPDFをOCR処理して検索可能PDFに変換"""

        # PDFを画像に変換
        pages = convert_from_path(pdf_path, dpi=300)

        # 新しいPDFを作成
        output = PyPDF2.PdfWriter()

        for page_num, page_image in enumerate(pages):
            # OCR実行
            text = pytesseract.image_to_string(
                page_image,
                lang=self.languages.get(language, 'eng')
            )

            # OCR結果の詳細情報を取得
            ocr_data = pytesseract.image_to_data(
                page_image,
                lang=self.languages.get(language, 'eng'),
                output_type=pytesseract.Output.DICT
            )

            # 透明テキストレイヤーを作成
            text_layer = self.create_text_layer(
                ocr_data,
                page_image.width,
                page_image.height
            )

            # 元の画像と透明テキストレイヤーを結合
            combined_page = self.combine_image_and_text(
                page_image,
                text_layer
            )

            output.add_page(combined_page)

            yield {
                'page': page_num + 1,
                'text_extracted': len(text),
                'words_found': len([w for w in ocr_data['text'] if w.strip()])
            }

        # PDFを保存
        if output_path:
            with open(output_path, 'wb') as output_file:
                output.write(output_file)

        return output

    def create_text_layer(self, ocr_data, width, height):
        """透明テキストレイヤーを作成"""
        packet = io.BytesIO()
        can = canvas.Canvas(packet, pagesize=(width, height))

        # テキストを透明に設定
        can.setFillAlpha(0)

        for i in range(len(ocr_data['text'])):
            if ocr_data['text'][i].strip():
                x = ocr_data['left'][i]
                y = height - ocr_data['top'][i] - ocr_data['height'][i]

                can.drawString(x, y, ocr_data['text'][i])

        can.save()
        packet.seek(0)

        return PyPDF2.PdfReader(packet).pages[0]

    def extract_text_from_pdf(self, pdf_path):
        """PDFからテキストを抽出"""
        text_content = []

        with open(pdf_path, 'rb') as file:
            pdf = PyPDF2.PdfReader(file)

            for page_num in range(len(pdf.pages)):
                page = pdf.pages[page_num]
                text = page.extract_text()

                if text.strip():
                    text_content.append({
                        'page': page_num + 1,
                        'text': text,
                        'word_count': len(text.split())
                    })

        return text_content

実用的な使用例

1. 契約書管理

// 契約書の統合と管理
class ContractManager {
  async processContracts(contracts) {
    const processed = [];

    for (const contract of contracts) {
      // 1. 各ページに契約番号を追加
      const numbered = await this.addContractNumber(
        contract.pdf,
        contract.number
      );

      // 2. 署名ページを結合
      const withSignature = await this.mergeSignaturePage(
        numbered,
        contract.signaturePage
      );

      // 3. タイムスタンプを追加
      const timestamped = await this.addTimestamp(withSignature);

      // 4. 暗号化
      const encrypted = await this.encryptContract(timestamped, {
        password: contract.accessCode,
        permissions: {
          print: true,
          modify: false,
          copy: false
        }
      });

      processed.push({
        id: contract.id,
        pdf: encrypted,
        metadata: {
          number: contract.number,
          date: new Date(),
          parties: contract.parties,
          expiry: contract.expiryDate
        }
      });
    }

    // 5. 全契約書を1つのPDFに統合(インデックス付き)
    return await this.createContractBundle(processed);
  }

  async createContractBundle(contracts) {
    const bundle = await PDFDocument.create();

    // 目次ページを作成
    const tocPage = bundle.addPage();
    let yPosition = 700;

    tocPage.drawText('契約書一覧', {
      x: 50,
      y: yPosition,
      size: 20,
      font: await bundle.embedFont(StandardFonts.HelveticaBold)
    });

    yPosition -= 40;

    // 各契約書を追加
    let currentPage = 2;  // 目次の次から

    for (const contract of contracts) {
      // 目次にエントリを追加
      tocPage.drawText(
        `${contract.metadata.number} - ${contract.metadata.parties.join(', ')}`,
        {
          x: 50,
          y: yPosition,
          size: 12
        }
      );

      tocPage.drawText(`ページ ${currentPage}`, {
        x: 450,
        y: yPosition,
        size: 12
      });

      yPosition -= 20;

      // 契約書を追加
      const contractPdf = await PDFDocument.load(contract.pdf);
      const pages = await bundle.copyPages(
        contractPdf,
        contractPdf.getPageIndices()
      );

      for (const page of pages) {
        bundle.addPage(page);
      }

      currentPage += pages.length;
    }

    return await bundle.save();
  }
}

2. レポート生成

class ReportGenerator:
    def __init__(self):
        self.template_path = "templates/"
        self.output_path = "reports/"

    def generate_monthly_report(self, data, month, year):
        """月次レポートの生成"""

        # 1. カバーページを作成
        cover_page = self.create_cover_page(month, year)

        # 2. サマリーページを生成
        summary = self.create_summary(data)

        # 3. 詳細セクションを生成
        sections = []
        for category in data['categories']:
            section = self.create_section(category)
            sections.append(section)

        # 4. グラフとチャートを生成
        charts = self.create_charts(data)

        # 5. 付録を追加
        appendix = self.create_appendix(data)

        # 6. 全セクションを結合
        report_parts = [cover_page, summary] + sections + [charts, appendix]
        final_report = self.merge_sections(report_parts)

        # 7. ヘッダー/フッターを追加
        final_report = self.add_headers_footers(final_report, month, year)

        # 8. 目次を自動生成
        final_report = self.generate_table_of_contents(final_report)

        # 9. ページ番号を追加
        final_report = self.add_page_numbers(final_report)

        return final_report

    def create_charts(self, data):
        """データからチャートを生成"""
        import matplotlib.pyplot as plt
        from matplotlib.backends.backend_pdf import PdfPages

        charts_pdf = PdfPages('temp_charts.pdf')

        # 売上グラフ
        fig, ax = plt.subplots(figsize=(10, 6))
        ax.plot(data['dates'], data['sales'], marker='o')
        ax.set_title('月間売上推移')
        ax.set_xlabel('日付')
        ax.set_ylabel('売上(万円)')
        ax.grid(True)
        charts_pdf.savefig(fig)
        plt.close()

        # カテゴリー別円グラフ
        fig, ax = plt.subplots(figsize=(8, 8))
        ax.pie(data['category_sales'],
               labels=data['categories'],
               autopct='%1.1f%%')
        ax.set_title('カテゴリー別売上構成')
        charts_pdf.savefig(fig)
        plt.close()

        charts_pdf.close()

        return 'temp_charts.pdf'

    def add_headers_footers(self, pdf_path, month, year):
        """ヘッダーとフッターを追加"""
        pdf_document = fitz.open(pdf_path)

        for page_num in range(pdf_document.page_count):
            page = pdf_document[page_num]

            # ヘッダー
            header_text = f"月次レポート - {year}年{month}月"
            page.insert_text(
                (50, 30),
                header_text,
                fontsize=10,
                color=(0.5, 0.5, 0.5)
            )

            # フッター
            footer_text = f"ページ {page_num + 1} / {pdf_document.page_count}"
            page.insert_text(
                (500, 800),
                footer_text,
                fontsize=10,
                color=(0.5, 0.5, 0.5)
            )

        output_path = pdf_path.replace('.pdf', '_with_headers.pdf')
        pdf_document.save(output_path)
        pdf_document.close()

        return output_path

3. 教育資料の作成

// 教材PDFの作成と管理
class EducationalMaterialCreator {
  async createCourseMaterial(course) {
    const materials = await PDFDocument.create();

    // 1. 講義スライドを追加
    for (const lecture of course.lectures) {
      const slides = await this.convertPresentationToPDF(lecture.slides);
      const pages = await materials.copyPages(slides, slides.getPageIndices());

      for (const page of pages) {
        materials.addPage(page);
      }
    }

    // 2. 演習問題を挿入
    const exercises = await this.createExercisePages(course.exercises);
    const exercisePages = await materials.copyPages(
      exercises,
      exercises.getPageIndices()
    );

    for (const page of exercisePages) {
      materials.addPage(page);
    }

    // 3. 参考資料を追加
    for (const reference of course.references) {
      const refPdf = await fetch(reference.url).then(res => res.arrayBuffer());
      const refDoc = await PDFDocument.load(refPdf);

      // 該当ページのみ抽出
      const relevantPages = await materials.copyPages(
        refDoc,
        reference.pages.map(p => p - 1)
      );

      for (const page of relevantPages) {
        // 出典を追加
        page.drawText(`出典: ${reference.title}`, {
          x: 50,
          y: 20,
          size: 8,
          color: rgb(0.5, 0.5, 0.5)
        });

        materials.addPage(page);
      }
    }

    // 4. インタラクティブ要素を追加
    await this.addInteractiveElements(materials, course);

    // 5. 学習進捗トラッカーを埋め込み
    await this.embedProgressTracker(materials, course.id);

    return await materials.save();
  }

  async addInteractiveElements(pdf, course) {
    const form = pdf.getForm();

    // 各章の終わりにチェックボックスを追加
    let checkboxCount = 0;

    for (const chapter of course.chapters) {
      const checkbox = form.createCheckBox(`completed_${chapter.id}`);
      checkbox.addToPage(pdf.getPage(chapter.endPage - 1), {
        x: 500,
        y: 50,
        width: 20,
        height: 20
      });

      // ラベルを追加
      const page = pdf.getPage(chapter.endPage - 1);
      page.drawText('学習完了', {
        x: 430,
        y: 55,
        size: 10
      });

      checkboxCount++;
    }

    // クイズセクション
    for (const quiz of course.quizzes) {
      const page = pdf.getPage(quiz.pageNumber - 1);

      for (const question of quiz.questions) {
        if (question.type === 'multiple_choice') {
          const radioGroup = form.createRadioGroup(`question_${question.id}`);

          question.options.forEach((option, index) => {
            radioGroup.addOptionToPage(option.value, page, {
              x: 50,
              y: question.y - (index * 30),
              width: 15,
              height: 15
            });
          });
        } else if (question.type === 'text') {
          const textField = form.createTextField(`answer_${question.id}`);
          textField.addToPage(page, {
            x: 50,
            y: question.y,
            width: 400,
            height: 50
          });
          textField.setMultiline();
        }
      }
    }
  }
}

パフォーマンス最適化

メモリ効率的な処理

import gc
from contextlib import contextmanager

class PDFStreamProcessor:
    """大容量PDFのストリーム処理"""

    @contextmanager
    def process_large_pdf(self, file_path, chunk_size=10):
        """大容量PDFを効率的に処理"""
        pdf = None
        try:
            pdf = PyPDF2.PdfReader(file_path)
            total_pages = len(pdf.pages)

            for start in range(0, total_pages, chunk_size):
                end = min(start + chunk_size, total_pages)
                chunk_writer = PyPDF2.PdfWriter()

                for page_num in range(start, end):
                    chunk_writer.add_page(pdf.pages[page_num])

                yield chunk_writer, start, end

                # メモリクリーンアップ
                del chunk_writer
                gc.collect()

        finally:
            if pdf:
                del pdf
                gc.collect()

    async def process_pdf_async(self, file_path, operation):
        """非同期PDF処理"""
        import asyncio
        import aiofiles

        async with aiofiles.open(file_path, 'rb') as file:
            content = await file.read()

            # 非同期タスクのキューを作成
            queue = asyncio.Queue()

            # ワーカータスク
            async def worker():
                while True:
                    page_data = await queue.get()
                    if page_data is None:
                        break

                    result = await operation(page_data)
                    yield result

                    queue.task_done()

            # ワーカーを起動
            workers = [asyncio.create_task(worker()) for _ in range(4)]

            # ページデータをキューに追加
            pdf = PyPDF2.PdfReader(io.BytesIO(content))
            for page in pdf.pages:
                await queue.put(page)

            # 終了シグナル
            for _ in workers:
                await queue.put(None)

            # 全ワーカーの完了を待つ
            await asyncio.gather(*workers)

Security and Privacy

All processing is done within your browser, and no data is sent externally. You can safely use it with personal or confidential information.

Troubleshooting

Common Issues

  • Not working: Clear browser cache and reload
  • Slow processing: Check file size (recommended under 20MB)
  • Unexpected results: Verify input format and settings

If issues persist, update your browser to the latest version or try a different browser.

まとめ:PDF処理効率化の3つの鍵

鍵1: 適切なツール選択

  • 用途に応じた機能選択
  • 処理速度と品質のバランス
  • セキュリティ要件の考慮

鍵2: 自動化の推進

  • バッチ処理の活用
  • テンプレート化
  • APIとの連携

鍵3: 品質管理

  • 圧縮と品質の最適化
  • メタデータの保持
  • アクセシビリティ確保

今すぐ始める

  1. i4u PDF結合・分割ツールにアクセス
  2. PDFファイルをアップロード
  3. 必要な操作を選択
  4. 処理済みPDFをダウンロード

カテゴリ別ツール

他のツールもご覧ください:

関連ツール

効率的なPDF管理で、ペーパーレス時代をリード。

i4u PDF結合・分割ツールで、文書管理を革新しましょう。

この記事は定期的に更新され、最新のPDF処理技術とベストプラクティスを反映しています。最終更新日:2025年1月24日