技术要点:在PDF中找到一个固定锚点,在需要放置图片的地方找到测试出锚点对应的XY位
// 使用了poppler方法,其他PDF库在获取坐标方面有各种问题,他的安装是在Linux底层,比在PHP项目中用Composer安装的库看上去更稳定,功能更强
// pdftohtml方法需要先将PDF缓存成一个XML文件来进行坐标及其他具体数据的获取,所以要新建一个$TempPath做为缓存位置可以方便后期统一清理
// $filePath = 'file/SL/06/SL1.01.06.V01_测试'; // PDF文件实际位置
$filePath = 'file/' . input('post.fcate') . '/' . input('post.fcode') . '/' . input('post.fname'); // 自动获取文件+路径
$fname = str_replace('.pdf', '', input('post.fname')); // 文件名去掉PDF后缀
$TempPath = 'cache/' . $fname; // PDF文件缓存位置
$fNo = explode('_', $fname);
$fNo = $fNo[0]; // 只取文件名前面的编号
// 设置PDF文件路径
$pdfFilePath = $filePath;
// 执行pdftohtml命令,转换为HTML并输出坐标
// -c:保持原始布局
// -hidden:隐藏元素
// -xml:生成 XML 格式的输出,适合后续解析
// -noimages:不包含图片
// -stdout:将输出发送到标准输出而不是文件
// -xml方法会生成PDF中的图片,其他不生成图片的方法会与-xml方法冲突导致无法生成xml文件,所以使用缓存方法统一对xml文件进行处理
$command = "pdftohtml -xml -c -hidden $pdfFilePath $TempPath";
// 在缓存位置自动生成XML文件
exec($command);
// 解析生成的XML文件获取坐标信息
$xml = simplexml_load_file($TempPath . '.xml');
// 输出坐标信息
//var_dump($xml->page->text);
// 自动计算用值
$left = '4.2';
$top = '4.06';
$signTime = '11';
// print_r($xml->page->text);
// 获取所有签名附近的唯一标识的坐标,通过计算得出签名和签名日期真正的坐标
foreach ($xml->page->text as $v) {
// 文件编号
if (strstr($v, 'Procedure No.')) {
$No_L = (float)($v['left'] / $left) + 19.6;
$No_T = (float)($v['top'] / $top) - 0.46;
// echo 'Procedure No. $No_L = ' . $No_L . ' | $No_T = ' . $No_T . '<br />';
} elseif (strstr($v, 'File No.')) {
$No_L = (float)($v['left'] / $left) + 11;
$No_T = (float)($v['top'] / $top) - 0.46;
// echo 'File No. $No_L = ' . $No_L . ' | $No_T = ' . $No_T . '<br />';
}
// 编辑者签名
if ($v == 'EDITED BY') {
$Edit_L = (float)($v['left'] / $left);
$Edit_T = (float)($v['top'] / $top);
// echo '$Edit_L = ' . $Edit_L . ' | $Edit_T = ' . $Edit_T . '<br />';
}
// 审批签名
if ($v == 'REVIEWED BY') {
$Revi_L = (float)($v['left'] / $left);
$Revi_T = (float)($v['top'] / $top);
// echo '$Revi_L = ' . $Revi_L . ' | $Revi_T = ' . $Revi_T . '<br />';
}
// 批准签名
if ($v == 'APPROVED BY') {
$Appr_L = (float)($v['left'] / $left);
$Appr_T = (float)($v['top'] / $top);
// echo '$Appr_L = ' . $Appr_L . ' | $Appr_T = ' . $Appr_T . '<br />';
}
}
// 创建 TCPDF 和 FPDI 的实例
// 在刚才获取的坐标中插入对应的签名及日期
$pdf = new TcpdfFpdi();
// 加载现有的 PDF 文件
$file = $pdfFilePath;
$pageCount = $pdf->setSourceFile($file); // 获取 PDF 文件的页数
// 遍历每一页
for ($pageNo = 1; $pageNo <= $pageCount; $pageNo++) {
// 导入页面
$templateId = $pdf->importPage($pageNo);
// 添加新页面
$pdf->addPage();
// 使用导入的页面
$pdf->useTemplate($templateId);
// 只修改第一页
if ($pageNo == 1) {
// 设置插入图片的位置和大小
$pic1 = 'signature/【pic1】.png';
$pic2 = 'signature/【pic2】.png';
$pic3 = 'signature/【pic3】.png';
$pdf->Image($pic1, $Edit_L, $Edit_T, 16, 9); // Image(签名地址, X坐标, Y坐标, 宽, 高) 位置插入图片,宽高为 50
$pdf->SetFont('', '', 9); // SetFont(字体, 粗/斜 , 字号)
$pdf->Text($Edit_L, $Edit_T + $signTime, date('Y-m-s', input('post.addtime_int'))); // Text(X坐标, Y坐标, 内容)
$pdf->Image($pic2, $Revi_L, $Revi_T, 16, 9);
$pdf->SetFont('', '', 9);
$pdf->Text($Revi_L, $Revi_T + $signTime, date('Y-m-s', input('post.revtime_int')));
$pdf->Image($pic3, $Appr_L, $Appr_T, 16, 9);
$pdf->SetFont('', '', 9);
$pdf->Text($Appr_L, $Appr_T + $signTime, date('Y-m-s'));
}
$pdf->SetFont('', '', 9);
$pdf->Text($No_L, $No_T, $fNo);
// $pdf->SetMargins(10, 10, 10); // 设置页面边距
$pdf->SetDrawColor(255, 255, 255); // 设置边框颜色为白色
// $pdf->SetAutoPageBreak(true, 10); // 自动分页的底部边距
}
// 输出修改后的 PDF 文件 Output实际使用的是fopen()方法,所以要用绝对路径,不要用相对路径会报错
$pdf->Output($_SERVER['DOCUMENT_ROOT'] . '/' . $pdfFilePath, 'F'); // 'I' 为直接输出到浏览器,'F' 为保存文件