FPDF实现旋转透明文本报No page has been added yet解决方法
报错根因
- 核心错误1:对象实例反复重建。你在调用
AddPage()给初始$pdf对象添加页面后,多次执行$pdf = new AlphaPDF()、$pdf = new PDF(),相当于把原有带页面的对象替换成了全新的空对象,空对象没有执行过AddPage(),直接调用需要页面上下文的方法就会触发「No page has been added yet」致命错误。 - 核心错误2:类继承结构设计错误。你写的
AlphaPDF、PDF_Rotate两个功能类是平行关系,都直接继承原生FPDF,最终使用的PDF类只继承了PDF_Rotate分支,根本不包含AlphaPDF实现的透明度能力,就算不报错也没法同时使用两个功能。
正确实现方式
在FPDF 1.84 + PHP 8.1环境下同时实现旋转、透明效果,只需要做两处调整:
- 调整类继承链,把两个功能合并到同一条继承线上,不要做平行拆分
- 整个PDF生成流程只实例化一次PDF对象,全程复用该实例,不要中途新建对象覆盖变量
修正后代码
类定义部分
use setasign\FpdiProtection\FpdiProtection; require_once('../../pdf/fpdf/fpdf.php'); require_once('../../pdf/fpdi/src/autoload.php'); require_once('../../pdf/fpdi-protection/src/autoload.php'); require('../../pdf/fpdf/fpdf_merge.php'); // 第一层:实现透明度能力,继承原生FPDF class AlphaPDF extends FPDF { protected $extgstates = array(); function SetAlpha($alpha, $bm='Normal') { $gs = $this->AddExtGState(array('ca'=>$alpha, 'CA'=>$alpha, 'BM'=>'/'.$bm)); $this->SetExtGState($gs); } function AddExtGState($parms) { $n = count($this->extgstates)+1; $this->extgstates[$n]['parms'] = $parms; return $n; } function SetExtGState($gs) { $this->_out(sprintf('/GS%d gs', $gs)); } function _enddoc() { if(!empty($this->extgstates) && $this->PDFVersion<'1.4') $this->PDFVersion='1.4'; parent::_enddoc(); } function _putextgstates() { for ($i = 1; $i <= count($this->extgstates); $i++) { $this->_newobj(); $this->extgstates[$i]['n'] = $this->n; $this->_put('<</Type /ExtGState'); $parms = $this->extgstates[$i]['parms']; $this->_put(sprintf('/ca %.3F', $parms['ca'])); $this->_put(sprintf('/CA %.3F', $parms['CA'])); $this->_put('/BM '.$parms['BM']); $this->_put('>>'); $this->_put('endobj'); } } function _putresourcedict() { parent::_putresourcedict(); $this->_put('/ExtGState <<'); foreach($this->extgstates as $k=>$extgstate) $this->_put('/GS'.$k.' '.$extgstate['n'].' 0 R'); $this->_put('>>'); } function _putresources() { $this->_putextgstates(); parent::_putresources(); } } // 第二层:实现旋转能力,继承已经带透明功能的AlphaPDF,不要直接继承FPDF class PDF_Rotate extends AlphaPDF { var $angle=0; function Rotate($angle,$x=-1,$y=-1) { if($x==-1) $x=$this->x; if($y==-1) $y=$this->y; if($this->angle!=0) $this->_out('Q'); $this->angle=$angle; if($angle!=0) { $angle*=M_PI/180; $c=cos($angle); $s=sin($angle); $cx=$x*$this->k; $cy=($this->h-$y)*$this->k; $this->_out(sprintf('q %.5F %.5F %.5F %.5F %.2F %.2F cm 1 0 0 1 %.2F %.2F cm',$c,$s,-$s,$c,$cx,$cy,-$cx,-$cy)); } } function _endpage() { if($this->angle!=0) { $this->angle=0; $this->_out('Q'); } parent::_endpage(); } } // 第三层:实现旋转文本快捷方法,继承带旋转+透明能力的PDF_Rotate class PDF extends PDF_Rotate { function RotatedText($x,$y,$txt,$angle) { $this->Rotate($angle,$x,$y); $this->Text($x,$y,$txt); $this->Rotate(0); } } // 整个流程只实例化一次,不要后续重复new $pdf = new PDF();
页面生成逻辑部分
删掉所有重复实例化的代码,直接复用全局$pdf对象:
if ($height>=$width) { $pdf->AddPage('P','A4'); $pdf->Image($imagefilePath,2,4,208,293); // 直接在原有实例上调用透明度方法,不要新建对象 $pdf->SetAlpha(0.5); $pdf->SetFont('Arial', '',15); $pdf->SetTextColor(255,0,0); $pdf->RotatedText(105-90, 148.5-120,'CONTROLLED COPY',45); $pdf->RotatedText(105-90, 148.5+7-120,$nmrdvl,45); $pdf->RotatedText(105-25, 148.5,'CONTROLLED COPY',45); $pdf->RotatedText(105-25, 148.5+7,$nmrdvl,45); $pdf->RotatedText(105+35, 148.5+120,'CONTROLLED COPY',45); $pdf->RotatedText(105+35, 148.5+7+120,$nmrdvl,45); // 恢复完全不透明状态 $pdf->SetAlpha(1); $pdf->SetTextColor(0,0,0); } else { $pdf->AddPage('L','A4'); $pdf->Image($imagefilePath,2,4,293,208); // 直接在原有实例上调用透明度方法 $pdf->SetAlpha(0.5); $pdf->SetFont('Arial', '',15); $pdf->SetTextColor(255,0,0); $pdf->RotatedText(148.5-120, 105-85, 'CONTROLLED COPY',45); $pdf->RotatedText(148.5-120, 105+7-85, $nmrdvl,45); $pdf->RotatedText(148.5-25, 105, 'CONTROLLED COPY',45); $pdf->RotatedText(148.5-25, 105+7, $nmrdvl,45); $pdf->RotatedText(148.5+70, 105+85, 'CONTROLLED COPY',45); $pdf->RotatedText(148.5+70, 105+7+85, $nmrdvl,45); // 恢复完全不透明状态 $pdf->SetAlpha(1); $pdf->SetTextColor(0,0,0); }
注意事项
- 调整继承链后,最终的PDF实例同时拥有
SetAlpha、Rotate、RotatedText以及原生FPDF的所有方法,不需要切换对象调用 - 旋转方法基于PDF图形状态栈实现,旋转结束后调用
Rotate(0)即可恢复原有坐标状态,和透明效果互不冲突 - FPDF 1.84版本已兼容PHP 8.1,只要保证所有方法都在同一个实例、且已调用
AddPage()的前提下执行,就不会触发页面不存在的报错
内容的提问来源于stack exchange,提问作者Pup In The Tree
相关产品推荐
相关产品推荐

