PHP中如何扁平化含预填表单域的PDF并保留数据?
问题背景
我需要在将预填好的PDF表单发送至外部签名服务前,用PHP做扁平化处理。先后尝试了两个库都没达到预期:
1. php-pdftk库
执行代码:
<?php require __DIR__ . '/vendor/autoload.php'; use mikehaertl\pdftk\Pdf; ($pdf = new Pdf('/app/pdf-prefilled.pdf'))->flatten()->saveAs('/app/pdf-flat.pdf'); echo $pdf->getError();
出现错误:
Unhandled Java Exception in create_output():
java.lang.ClassCastException: class pdftk.com.lowagie.text.pdf.PdfString cannot be cast to class pdftk.com.lowagie.text.pdf.PdfDictionary (pdftk.com.lowagie.text.pdf.PdfString and pdftk.com.lowagie.text.pdf.PdfDictionary are in unnamed module of loader 'app')
at pdftk.com.lowagie.text.pdf.PdfStamperImp.flatFields(PdfStamperImp.java:738)
at pdftk.com.lowagie.text.pdf.PdfStamperImp.close(PdfStamperImp.java:163)
at pdftk.com.lowagie.text.pdf.PdfStamperImp.close(PdfStamperImp.java:304)
at com.gitlab.pdftk_java.filter.create_output_filter(filter.java:424)
at com.gitlab.pdftk_java.TK_Session.create_output(TK_Session.java:1445)
at com.gitlab.pdftk_java.pdftk.main_noexit(pdftk.java:192)
at com.gitlab.pdftk_java.pdftk.main(pdftk.java:163)
There was a problem with pdftk-java. Please report it at
https://gitlab.com/pdftk-java/pdftk/issues
including the message above, the version of pdftk-java (3.2.2), and if possible steps to reproduce the error.Xdebug: [Step Debug] Time-out connecting to debugging clien
2. tcpdi库
能成功扁平化但丢失预填数据,且不支持解析表单域,代码:
<?php use TCPDI; $pdf = new TCPDI(); $pagecount = $pdf->setSourceData($data); for ($i = 1; $i <= $pagecount; $i++) { $tplidx = $pdf->importPage($i); $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); $pdf->AddPage(); $pdf->importAnnotations($i); $pdf->useTemplate($tplidx); } $data = $pdf->Output('', 'S');
可行替代方案
1. FPDF + FPDI组合方案
借助FPDF和FPDI的组合,先解析PDF表单域的位置、字体等信息,再将预填数据直接绘制到PDF对应位置,实现扁平化。示例代码思路:
<?php require_once('fpdf.php'); require_once('fpdi.php'); class PDF_Flatten extends FPDI { private $formData; public function setFormData($data) { $this->formData = $data; } } $pdf = new PDF_Flatten(); $pageCount = $pdf->setSourceFile('/app/pdf-prefilled.pdf'); // 通过pdftk的dump_data_fields命令解析表单域数据和位置,再转为数组 $formData = $this->parseFormFields('/app/pdf-prefilled.pdf'); $pdf->setFormData($formData); for ($i = 1; $i <= $pageCount; $i++) { $tplIdx = $pdf->importPage($i); $size = $pdf->getTemplateSize($tplIdx); $pdf->AddPage($size['w'] > $size['h'] ? 'L' : 'P', array($size['w'], $size['h'])); $pdf->useTemplate($tplIdx); // 遍历表单数据,在对应坐标绘制文本 foreach ($formData as $field) { $pdf->SetFont($field['font'], '', $field['font_size']); $pdf->SetXY($field['x'], $field['y']); $pdf->Write(0, $field['value']); } } $pdf->Output('/app/pdf-flat.pdf', 'F');
关键是先通过命令行工具获取表单域的详细属性,再映射到PDF绘制逻辑中。
2. 调用Ghostscript命令行
利用Ghostscript的PDF处理能力,通过PHP执行shell命令实现扁平化,兼容性强且稳定。示例代码:
<?php $inputFile = '/app/pdf-prefilled.pdf'; $outputFile = '/app/pdf-flat.pdf'; // Ghostscript扁平化命令 $command = "gs -dNOPAUSE -dBATCH -sDEVICE=pdfwrite -dPreserveAnnots=false -sOutputFile={$outputFile} {$inputFile}"; exec($command, $output, $returnVar); if ($returnVar !== 0) { echo "扁平化失败:" . implode("\n", $output); } else { echo "扁平化成功"; }
注意:服务器需安装Ghostscript,且PHP需具备执行shell命令的权限。
3. TCPDF重绘方案
如果PDF是标准AcroForm,先解析表单域数据和位置,用TCPDF导入原PDF内容后,将预填数据绘制到对应位置,重新生成扁平化PDF。示例思路:
<?php require_once('tcpdf.php'); // 解析表单域数据和位置(可通过pdftk dump_data_fields获取并解析) $formFields = $this->getFormFields('/app/pdf-prefilled.pdf'); $pdf = new TCPDF(); $pdf->setPrintHeader(false); $pdf->setPrintFooter(false); $pdf->AddPage(); // 导入原PDF作为背景 $pdf->Image('/app/pdf-prefilled.pdf', 0, 0, 210, 297); // 绘制表单数据 foreach ($formFields as $field) { $pdf->SetFont($field['font'], '', $field['font_size']); $pdf->SetXY($field['x'], $field['y']); $pdf->Write(0, $field['value']); } $pdf->Output('/app/pdf-flat.pdf', 'F');
此方案适合结构规范的PDF表单,需确保表单域属性解析准确。
内容的提问来源于stack exchange,提问作者Theo

