PHP拆分重组pdf,php拆分pdf指定页数,并合并成新pdf

在ThinkPHP5中实现PDF拆分与合并功能,主要使用FPDI库来处理PDF页面操作。以下是完整的实现流程和代码示例。

环境准备与依赖安装

首先通过Composer安装必要的依赖包

cpp 复制代码
composer require setasign/fpdi
composer require setasign/fpdf

核心功能实现

以下是完整的PDF处理类,包含拆分指定页面和合并多个PDF的功能:

cpp 复制代码
<?php
namespace app\common\util;

use setasign\Fpdi\Tcpdf\Fpdi;

class PdfHandler
{
    /**
     * 拆分PDF指定页面并合并为新PDF
     * @param array $pdfFiles PDF文件路径数组
     * @param array $pageRules 页面规则 ['file1.pdf' => '1,3,5', 'file2.pdf' => '2-4']
     * @param string $outputName 输出文件名
     * @param string $outputMode 输出模式 D-下载 I-预览 F-保存
     * @return mixed
     */
    public function splitAndMergePdf($pdfFiles, $pageRules, $outputName = 'merged.pdf', $outputMode = 'D')
    {
        try {
            $pdf = new Fpdi();
            
            foreach ($pdfFiles as $index => $pdfFile) {
                $fileKey = basename($pdfFile);
                
                if (!isset($pageRules[$fileKey])) {
                    continue;
                }
                
                $pageCount = $pdf->setSourceFile($pdfFile);
                $selectedPages = $this->parsePageRules($pageRules[$fileKey], $pageCount);
                
                foreach ($selectedPages as $pageNo) {
                    $templateId = $pdf->importPage($pageNo);
                    $size = $pdf->getTemplateSize($templateId);
                    
                    $pdf->AddPage($size['orientation'], $size);
                    $pdf->useTemplate($templateId);
                }
            }
            
            $pdf->Output($outputName, $outputMode);
            $pdf->closeParsers();
            
            return true;
        } catch (\Exception $e) {
            throw new \Exception("PDF处理失败: " . $e->getMessage());
        }
    }
    
    /**
     * 解析页面规则
     * @param string $rule 页面规则 '1,3,5' 或 '2-4' 或 'all'
     * @param int $totalPages 总页数
     * @return array
     */
    private function parsePageRules($rule, $totalPages)
    {
        $pages = [];
        
        if ($rule === 'all') {
            for ($i = 1; $i <= $totalPages; $i++) {
                $pages[] = $i;
            }
            return $pages;
        }
        
        if (strpos($rule, '-') !== false) {
            list($start, $end) = explode('-', $rule);
            $start = max(1, intval(trim($start)));
            $end = min($totalPages, intval(trim($end)));
            
            for ($i = $start; $i <= $end; $i++) {
                $pages[] = $i;
            }
            return $pages;
        }
        
        if (strpos($rule, ',') !== false) {
            $pageArray = explode(',', $rule);
            foreach ($pageArray as $page) {
                $page = intval(trim($page));
                if ($page >= 1 && $page <= $totalPages) {
                    $pages[] = $page;
                }
            }
            return $pages;
        }
        
        $page = intval(trim($rule));
        if ($page >= 1 && $page <= $totalPages) {
            $pages[] = $page;
        }
        
        return $pages;
    }
    
    /**
     * 简单合并多个PDF(全部页面)
     * @param array $pdfFiles PDF文件路径数组
     * @param string $outputName 输出文件名
     * @param string $outputMode 输出模式
     * @return mixed
     */
    public function simpleMergePdf($pdfFiles, $outputName = 'merged.pdf', $outputMode = 'D')
    {
        try {
            $pdf = new Fpdi();
            
            foreach ($pdfFiles as $pdfFile) {
                $pageCount = $pdf->setSourceFile($pdfFile);
                
                for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
                    $templateId = $pdf->importPage($pageNo);
                    $size = $pdf->getTemplateSize($templateId);
                    
                    $pdf->AddPage($size['orientation'], $size);
                    $pdf->useTemplate($templateId);
                }
            }
            
            $pdf->Output($outputName, $outputMode);
            $pdf->closeParsers();
            
            return true;
        } catch (\Exception $e) {
            throw new \Exception("PDF合并失败: " . $e->getMessage());
        }
    }
}

控制器调用示例

cpp 复制代码
<?php
namespace app\index\controller;

use think\Controller;
use think\Request;
use app\common\util\PdfHandler;

class Pdf extends Controller
{
    /**
     * 拆分合并PDF页面
     */
    public function splitMerge()
    {
        if (Request::instance()->isPost()) {
            try {
                $pdfHandler = new PdfHandler();
                
                // PDF文件路径
                $pdfFiles = [
                    ROOT_PATH . 'public/uploads/employee_agreement.pdf',
                    ROOT_PATH . 'public/uploads/work_summary.pdf',
                    ROOT_PATH . 'public/uploads/rental_agreement.pdf'
                ];
                
                // 页面规则:指定每个PDF要提取的页面
                $pageRules = [
                    'employee_agreement.pdf' => '1,3',      // 第1页和第3页
                    'work_summary.pdf' => '2-4',             // 第2页到第4页
                    'rental_agreement.pdf' => 'all'         // 所有页面
                ];
                
                $result = $pdfHandler->splitAndMergePdf(
                    $pdfFiles, 
                    $pageRules, 
                    'custom_merged.pdf', 
                    'D'
                );
                
                if ($result) {
                    return json(['code' => 1, 'msg' => 'PDF处理成功']);
                }
                
            } catch (\Exception $e) {
                return json(['code' => 0, 'msg' => $e->getMessage()]);
            }
        }
        
        return $this->fetch();
    }
    
    /**
     * 简单合并多个PDF
     */
    public function simpleMerge()
    {
        try {
            $pdfHandler = new PdfHandler();
            
            $pdfFiles = [
                ROOT_PATH . 'public/uploads/doc1.pdf',
                ROOT_PATH . 'public/uploads/doc2.pdf',
                ROOT_PATH . 'public/uploads/doc3.pdf'
            ];
            
            $result = $pdfHandler->simpleMergePdf(
                $pdfFiles,
                'simple_merged.pdf',
                'D'
            );
            
            if ($result) {
                return json(['code' => 1, 'msg' => 'PDF合并成功']);
            }
            
        } catch (\Exception $e) {
            return json(['code' => 0, 'msg' => $e->getMessage()]);
        }
    }
}

前端表单页面

cpp 复制代码
<!DOCTYPE html>
<html lang="zh-CN">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>PDF拆分合并工具</title>
    <style>
        .container { max-width: 800px; margin: 50px auto; padding: 20px; }
        .form-group { margin-bottom: 20px; }
        label { display: block; margin-bottom: 5px; font-weight: bold; }
        input, select { width: 100%; padding: 8px; border: 1px solid #ddd; border-radius: 4px; }
        .btn { background: #007bff; color: white; padding: 10px 20px; border: none; border-radius: 4px; cursor: pointer; }
        .btn:hover { background: #0056b3; }
        .file-item { background: #f8f9fa; padding: 15px; margin-bottom: 10px; border-radius: 4px; }
    </style>
</head>
<body>
    <div class="container">
        <h2>PDF拆分重组工具</h2>
        
        <form id="pdfForm" action="{:url('index/pdf/splitMerge')}" method="post">
            <div class="form-group">
                <label>选择PDF文件:</label>
                <input type="file" id="pdfFiles" multiple accept=".pdf">
                
                <div id="fileList"></div>
            </div>
            
            <div class="form-group">
                <label>输出文件名:</label>
                <input type="text" name="output_name" value="custom_merged.pdf" required>
            </div>
            
            <button type="submit" class="btn">生成新PDF</button>
        </form>
    </div>

    <script>
        document.getElementById('pdfFiles').addEventListener('change', function(e) {
            const fileList = document.getElementById('fileList');
            fileList.innerHTML = '';
            
            Array.from(e.target.files).forEach((file, index) => {
                const fileItem = document.createElement('div');
                fileItem.className = 'file-item';
                fileItem.innerHTML = `
                    <strong>${file.name}</strong>
                    <div style="margin-top: 10px;">
                        <label>页面选择:</label>
                        <input type="text" name="page_rules[${file.name}]" 
                           placeholder="例如: 1,3,5 或 2-4 或 all">
                    </div>
                `;
                fileList.appendChild(fileItem);
            });
        });
    </script>
</body>
</html>

功能特点说明

  1. 灵活的页面选择‌:支持单个页面、页面范围、多个指定页面以及全部页面的选择方式‌
  2. 多种输出模式‌:支持下载(D)、浏览器预览(I)、服务器保存(F)等输出方式
  3. 异常处理完善‌:包含完整的错误捕获和处理机制
  4. 内存管理优化‌:使用closeParsers()方法及时释放资源
  5. 自动尺寸适配‌ :根据原PDF页面尺寸自动设置新页面大小
    该方案适用于文档管理系统、在线PDF处理服务等场景,能够高效地完成PDF页面的拆分和重组任务。
相关推荐
CoderYanger3 小时前
动态规划算法-子数组、子串系列(数组中连续的一段):21.乘积最大子数组
开发语言·算法·leetcode·职场和发展·动态规划·1024程序员节
CoderYanger3 小时前
A.每日一题——3432. 统计元素和差值为偶数的分区方案
java·数据结构·算法·leetcode·1024程序员节
CoderYanger4 小时前
动态规划算法-子数组、子串系列(数组中连续的一段):26.环绕字符串中唯一的子字符串
java·算法·leetcode·动态规划·1024程序员节
韩家阿杰1 天前
RabbitMQ技术的使用
1024程序员节
CoderYanger2 天前
动态规划算法-简单多状态dp问题:15.买卖股票的最佳时机含冷冻期
开发语言·算法·leetcode·动态规划·1024程序员节
CoderYanger2 天前
递归、搜索与回溯-FloodFill:33.太平洋大西洋水流问题
java·算法·leetcode·1024程序员节
CoderYanger2 天前
动态规划算法-斐波那契数列模型:2.三步问题
开发语言·算法·leetcode·面试·职场和发展·动态规划·1024程序员节
CoderYanger2 天前
动态规划算法-简单多状态dp问题:16.买卖股票的最佳时机含手续费
开发语言·算法·leetcode·动态规划·1024程序员节
CoderYanger2 天前
C.滑动窗口-求子数组个数-越短越合法——3258. 统计满足 K 约束的子字符串数量 I
java·开发语言·算法·leetcode·1024程序员节
CoderYanger2 天前
动态规划算法-路径问题:9.最小路径和
开发语言·算法·leetcode·动态规划·1024程序员节