PHP在PDF指定位置添加数字签名/图片

基本业务需求:按指定范本填充生成word文档,word转pdf后,在指定字符位置添加数字签名。

难点:获取pdf需要插入数字签名的坐标

使用到的第三方依赖:
phpoffice/phpword
tecnickcom/tcpdf
setasign/fpdi
smalot/pdfparser

代码示例:


use setasign\Fpdi\Tcpdf\Fpdi;
use Smalot\PdfParser\Parser;

/**
     * 执行签署
     * @param array $document
     * @param array $orderInfo
     * @param array $userInfo
     * @param array $notarial
     * @param string $tagStr 多个使用$1、$2... 一个pdf中只能签署一次
     * @return bool
     */
    public function doSign($document=[], $orderInfo=[], $userInfo=[], $notarial=[], $width=38, $height=15, $tagStr = "$", $snap=[], $platform='self', $verifyType=0, $signType=2)
    {
        $pdfPath = public_path().str_replace('docx', 'pdf', $document['url']);
        //获取签名位置 暂定模版设置白色$字符,如多个则使用$1、$2...
        $parser = new Parser();
        $pdf = $parser->parseFile($pdfPath);
        $pages = $pdf->getPages();

        //考虑最后一页可能跨页导致无法签署
        /*$signPage = count($pages) - 1;
        $lastPageText = $pages[$signPage]->getDataTm();*/

        //获取源文档页面宽高 暂不考虑横版
        $mediaBox = [];
        foreach ($pages as $page) {
            $details = $page->getDetails();
            // If Mediabox is not set in details of current $page instance, get details from the header instead
            if (!isset($details['MediaBox'])) {
                $pages = $pdf->getObjectsByType('Pages');
                $details = reset($pages)->getHeader()->getDetails();
            }
            $mediaBox[] = [
                'width' => $details['MediaBox'][2],
                'height' => $details['MediaBox'][3]
            ];
        }
        //获取页面宽高
        $sourceW = $mediaBox[0]['width']; //595.3
        $sourceH = $mediaBox[0]['height']; // 841.89

        //创建fpdi实例
        $pdf = new Fpdi();
        $pageCount = $pdf->setSourceFile($pdfPath);
        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(false);
        $pdfW = $pdf->getPageWidth(); //210
        $pdfH = $pdf->getPageHeight(); //297

        //遍历全部页面中的文字
        $positions = [];
        for ($i=0; $i<count($pages); $i++){
            $texts = $pages[$i]->getDataTm();
            foreach ($texts as $text){
                if(str_contains($text[1], $tagStr)){
                    $positions[] = [
                        'x' => $text[0][4],
                        'y' => $text[0][5],
                        'real_x' => (($text[0][4] / $sourceW) * $pdfW) + 6.36, //按源文件、输出文件比例、页边距等处理
                        'real_y' => ((($sourceH-$text[0][5]) / $sourceH) * $pdfH) - 5.08
                    ];
                }
            }
        }

        //临时测试使用
        /*if(!$positions){
            $positions[0] = [
                'x' => 10,
                'y' => 10,
                'real_x' => 10,
                'real_y' => 10
            ];
        }*/

        //创建页面
        for ($pageNumber=1; $pageNumber <= $pageCount; $pageNumber++){
            $templateId = $pdf->importPage($pageNumber);
            $pdf->AddPage();
            $pdf->useTemplate($templateId, 0, 0, $pdfW, $pdfH);
        }

        //获取证书路径
        if($notarial){
            $certInfo = DigitalCertificate::where(['port'=>3, 'mtable'=>'notarial'])->order('id desc')->find();
            $signature = [
                'id' => 0,
                'path'=>$notarial['signature']
            ];
        }else{
            $certInfo = DigitalCertificate::where(['port'=>$userInfo['port'], 'port_id'=>$userInfo['id']])->whereIn('mtable', ['admin', 'users'])->order('id desc')->find();
            //获取用户签名图
            if($userInfo['port'] == 4){
                $signature = UserSignature::where(['port'=>$userInfo['port'], 'port_id'=>$userInfo['id'], 'order_id'=>$orderInfo['id']])->order('id desc')->find();
            }elseif($userInfo['port'] == 2 || $userInfo['port'] == 3){
                $signature = [
                    'id'=>0,
                    'path'=>$userInfo['signature']
                ];
            }else{
                $signature = [
                    'id' => 0,
                    'path'=>$userInfo['signature']
                ];
            }
            if(!$signature['path']){
                throw new AdminException('用户未上传签名');
            }
        }

        //todo 判断证书是否存在或过期
        $certificate = 'file://'.root_path().$certInfo['crt_path'];
        $privateKey = 'file://'.root_path().$certInfo['key_path'];

        $certificateImage = public_path().substr($signature['path'], 1);
        //设置证书信息
        $info = [
            'Name' => 'xx系统',
            'Location' => '山东',
            'Reason' => '禁止修改文档',
            'ContactInfo' => 'r1989.com'
        ];
        //$pdf->setTimeStamp("http://cn.pool.ntp.org", "ntp.org");
        $pdf->setSignature($certificate, $privateKey, '', '', 2, $info);

        // 设置签名外观
        // 创建签名内容(图像和/或文本) 一个pdf文档只能添加一个数字签名
        $pdf->Image($certificateImage, $positions[0]['real_x'], $positions[0]['real_y'], $width, $height, 'PNG');
        // 定义签名外观的活动区域
        $pdf->setSignatureAppearance($positions[0]['real_x'], $positions[0]['real_y'], $width, $height);
        // 设置空签名外观
        //$pdf->addEmptySignatureAppearance(180, 90, 15, 15);
        // 四种模式 I输出、D下载、F保存本地、S输出二进制字符串
        $signedUrl = dirname($document['url']).'/'.pathinfo($document['name_origin'], PATHINFO_FILENAME).'_'.md5($document['id'].'_'.$orderInfo['id']).'.pdf';
        $pdf->Output(public_path().substr($signedUrl, 1), 'F');
        //更新主表信息
        $documentData = ['signature_id'=>$signature['id'], 'signed_url'=>$signedUrl, 'sign_time'=>get_date(), 'verify_type'=>$verifyType, 'sign_type'=>$signType, 'sign_platform'=>$platform];
        if($snap){
            $documentData['sign_snap'] = json_encode($snap, 320);
        }
        //更新签名版对应的hash
        $documentData['signed_hash'] = hash_file('sha256', public_path().substr($signedUrl, 1));
        $res = NotarialDocument::edit($documentData, $document['id'], 'id');
        return $res;
    }

Tags: PHP

添加新评论