基于TCPDF+FPDI为现有PDF加图片水印部分不生效 需移除末页水印
TCPDF + FPDI 存量PDF水印兼容性问题排查与实现
水印跨PDF兼容性异常的核心原因
你遇到的“部分PDF水印正常、部分无显示”的现象,是FPDI与TCPDF的图层绘制顺序、坐标适配规则、图形状态管理逻辑共同导致的,具体对应4个明确问题:
- 图层绘制顺序颠倒
你将水印逻辑放在默认执行的Header()方法中,TCPDF的Header()会在页面内容流最底层最先执行绘制;而FPDI导入存量PDF内容的useTemplate()方法,是在Header()执行完成后才调用的。这意味着所有原PDF的内容层会直接盖在你提前画好的logo、页码、水印之上。遇到满幅不透明内容的存量PDF,水印会被完全遮盖看不到;遇到存在大面积留白的PDF,水印在留白处露出来,就表现为“正常生效”。 - 水印坐标硬编码未适配源页面
你目前写死了水印位置(x=0、y=110)、尺寸(50x50),但不同存量PDF的页面尺寸、旋转角度、坐标原点存在差异:如果是旋转90度的PDF页面,写死的y轴位置会直接落到可视区域外;如果是自定义非标准尺寸的页面,居中计算的水印位置也会偏移到页面外,自然无法显示。 - 透明度状态未正确复位
你在绘制水印时调用了SetAlpha(0.3)设置半透明效果,但画完水印后没有将透明度重置回1.0。这种未闭合的图形状态会导致PDF流结构异常,部分严格的PDF解析器遇到这类结构错误时,会直接跳过之前绘制的内容块。 - 冗余逻辑无实际作用
原有代码中获取bMargin、auto_page_break的变量未被后续逻辑调用,属于冗余代码,不会直接导致水印失效,但会增加不必要的内存开销。
修正后可跨PDF兼容的水印实现逻辑
核心调整思路是所有上层元素(页眉、页码、水印)必须在原PDF内容导入完成后再绘制,同时增加末页跳过水印的判断逻辑:
- 重写TCPDF的默认
Header()方法为空,避免自动在底层绘制元素 - 把页眉、页码、水印的绘制逻辑抽为独立的公共方法
- 逐页导入原PDF内容时,先调用
useTemplate()铺入原PDF内容,再调用独立方法绘制上层元素 - 动态计算水印位置适配当前页面尺寸,画完水印后立刻重置透明度状态
- 增加末页判断逻辑:如果当前绘制的是PDF最后一页,直接跳过水印绘制
对应的修正代码示例:
use \setasign\Fpdi\Fpdi; class WatermarkPdf extends Fpdi { // 自定义页眉文本 public $customHeaderText = ''; public function Header() { // 留空,避免自动触发底层绘制 } /** * 逐页绘制上层元素(页眉、页码、水印) * @param bool $isLastPage 是否为最后一页(是则跳过水印) * @return void */ public function renderPageOverlays(bool $isLastPage = false): void { // 绘制logo $logoPath = FCPATH . 'logo.png'; $this->Image($logoPath, 10, 10, 20, 15, 'PNG'); // 绘制页眉文本 $this->SetFont('freeserif', 'B', 15); $this->Cell(0, 20, $this->customHeaderText, 0, false, 'C', 0, '', 0, false, 'T', 'C'); // 绘制页码 $this->SetFont('freeserif', 'B', 9); $this->Cell(0, 10, $this->getAliasNumPage() . '/' . $this->getAliasNbPages(), 0, false, 'C', 0, '', 0, false, 'T', 'M'); // 非末页才绘制水印 if (!$isLastPage) { $this->SetAlpha(0.3); $watermarkPath = FCPATH . 'watermark.png'; // 动态计算水印居中位置,适配当前页面尺寸 $pageWidth = $this->getPageWidth(); $pageHeight = $this->getPageHeight(); $watermarkSize = 50; $x = ($pageWidth - $watermarkSize) / 2; $y = ($pageHeight - $watermarkSize) / 2; $this->Image($watermarkPath, $x, $y, $watermarkSize, $watermarkSize, 'PNG'); // 立即重置透明度为1.0,避免状态异常 $this->SetAlpha(1); } } }
逐页处理PDF的调用逻辑:
// 实例化处理类 $pdf = new WatermarkPdf(); $pdf->customHeaderText = '你的自定义页眉内容'; // 加载存量PDF $sourcePdfPath = 'path/to/your_existing.pdf'; $pdf->setSourceFile($sourcePdfPath); $totalPages = $pdf->getSourcePageCount(); // 逐页处理 for ($pageNo = 1; $pageNo <= $totalPages; $pageNo++) { // 导入当前页模板 $tplId = $pdf->importPage($pageNo); $tplSize = $pdf->getTemplateSize($tplId); // 新增和源页面尺寸完全一致的页面 $pdf->AddPage($tplSize['orientation'], [$tplSize['width'], $tplSize['height']]); // 先把原PDF内容铺到页面底层 $pdf->useTemplate($tplId); // 再绘制上层元素,判断是否为最后一页 $isLast = ($pageNo == $totalPages); $pdf->renderPageOverlays($isLast); } // 输出最终PDF $pdf->Output('final_output.pdf', 'I');
内容的提问来源于stack exchange,提问作者prachi
相关产品推荐
相关产品推荐

