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页面的拆分和重组任务。
相关推荐
liu****6 小时前
18.HTTP协议(一)
linux·网络·网络协议·http·udp·1024程序员节
洛_尘7 小时前
JAVA EE初阶 6: 网络编程套接字
网络·1024程序员节
2301_8002561119 小时前
关系数据库小测练习笔记(1)
1024程序员节
金融小师妹1 天前
基于多源政策信号解析与量化因子的“12月降息预期降温”重构及黄金敏感性分析
人工智能·深度学习·1024程序员节
GIS数据转换器1 天前
基于GIS的智慧旅游调度指挥平台
运维·人工智能·物联网·无人机·旅游·1024程序员节
南方的狮子先生2 天前
【C++】C++文件读写
java·开发语言·数据结构·c++·算法·1024程序员节
Neil今天也要学习2 天前
永磁同步电机无速度算法--基于三阶LESO的反电动势观测器
算法·1024程序员节
开开心心_Every2 天前
专业视频修复软件,简单操作效果好
学习·elasticsearch·pdf·excel·音视频·memcache·1024程序员节
liu****3 天前
16.udp_socket(三)
linux·开发语言·数据结构·c++·1024程序员节
草莓熊Lotso3 天前
《算法闯关指南:优选算法--位运算》--38.消失的两个数字
服务器·c++·算法·1024程序员节