在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>
功能特点说明
- 灵活的页面选择:支持单个页面、页面范围、多个指定页面以及全部页面的选择方式
- 多种输出模式:支持下载(D)、浏览器预览(I)、服务器保存(F)等输出方式
- 异常处理完善:包含完整的错误捕获和处理机制
- 内存管理优化:使用closeParsers()方法及时释放资源
- 自动尺寸适配 :根据原PDF页面尺寸自动设置新页面大小
该方案适用于文档管理系统、在线PDF处理服务等场景,能够高效地完成PDF页面的拆分和重组任务。