PHP导出之---PDF导出

204 阅读4分钟

写在前面

在学习和工作中,对于PDF的识别和导出都是相对让人头疼的事情;

在此,给大家安利一种鄙人认为还比较不错的 pdf导出方式;

本文基于 命令行导出工具 进行PDF导出;只是进行简单使用,没有进行深入探究;

如果需要进行深入学习的盆友,可移步官方文档进行学习。

GitHub:github.com/mikehaertl/…

wkhtmltopdf:wkhtmltopdf.org/

开发环境

  • 服务器环境:CentOS7
  • php版本:8.1.19
  • phpwkhtmltopdf 扩展版本:2.5.0
  • wkhtmltopdf 工具版本:wkhtmltox-0.12.6-1.centos7.x86_64.rpm

实操

安装扩展包

composer require mikehaertl/phpwkhtmltopdf

安装导出工具

  • 下载

    根据自己的实际开发环境,下载对应的工具即可

    https://wkhtmltopdf.org/downloads.html

  • 安装

    • 上传导出工具到服务器

    • 执行安装

      rpm -ivh wkhtmltox-0.12.6-1.centos7.x86_64.rpm

    • 安装问题

      安装报错.png

      • 原因:缺少依赖

      • 解决:安装依赖即可

        yum install -y libXrender xorg-x11-fonts-75dpi xorg-x11-fonts-Type1

      • 安装完依赖后,重新执行安装命令,即可安装成功

        安装成功.png

      • 检测是否安装成功

        检测是否安装成功.png

代码准备

具体内容根据实际需求进行调整即可

页眉 (header.html)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页眉</title>

    <style>
        .report-title{
            text-align: left;
            font-size: 24px;
            font-weight: bold;
        }
        .report-title img{
            width: 50px;
            height: 25px;
        }
    </style>
</head>
<body>
<div style="padding: 0 0 20px 0;">
    <div class="report-title">
        <!--<img src="./images/header.jpg">-->
        <!--需要注意:此处只能使用网络地址-->
        <img src="http://IP地址:端口号/storage/images/header.jpg">
        <span>这是页眉文本</span>
    </div>
</div>
</body>
</html>
页脚 (footer.html)

其中,subst() 方法是固定写法,自定义页码信息

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>页脚</title>

    <style>
        .page-info {
            float: right;
            text-align: right;
        }
    </style>

    <script>
        function subst() {
            var vars = {};
            var x = document.location.search.substring(1).split('&');
            for (var i in x) {
                var z = x[i].split('=', 2);
                vars[z[0]] = unescape(z[1]);
            }
            var x = ['frompage', 'topage', 'page', 'webpage', 'section', 'subsection', 'subsubsection'];
            for (var i in x) {
                var y = document.getElementsByClassName(x[i]);
                for (var j = 0; j < y.length; ++j) y[j].textContent = vars[x[i]];
            }
        }
    </script>
</head>
<body style="border:0; margin: 0;" onload="subst()">

<div>
    <span>联系地址:XXXXXXXXXXXXXXX</span>
    <div class="page-info">
        Page <span class="page"></span> of <span class="topage"></span>
    </div>
</div>
</body>
</html>
封面 (cover.html)
<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title> 封面 </title>

    <style>
        body {
            text-align: center;
        }
    </style>
</head>
<body>
<div>
    <h1>这是封面页</h1>
</div>
</body>
</html>
目录 (toc.xsl)

xml 格式页面,其中,界面元素信息写法格式固定,class可以自定义,样式可以自定义

<?xml version="1.0" encoding="UTF-8"?>
<xsl:stylesheet version="2.0"
                xmlns:xsl="http://www.w3.org/1999/XSL/Transform"
                xmlns:outline="http://wkhtmltopdf.org/outline"
                xmlns="http://www.w3.org/1999/xhtml">
    <xsl:output doctype-public="-//W3C//DTD XHTML 1.0 Strict//EN"
                doctype-system="http://www.w3.org/TR/xhtml1/DTD/xhtml1-transitional.dtd"
                indent="yes" />
    <xsl:template match="outline:outline">
        <html>
            <head>
                <title>Table of Contents</title>
                <meta http-equiv="Content-Type" content="text/html; charset=utf-8" />
                <style>
                    <!--h1 {
                    text-align: center;
                    font-size: 20px;
                    font-family: arial;
                    }
                    div {
                    border-bottom: 1px  rgb(200,200,200);
                    }
                    span {
                    float: right;
                    }
                    li {
                    list-style: none;
                    }
                    ul {
                    font-size: 20px;
                    font-family: arial;
                    }
                    ul ul {
                    font-size: 80%;
                    }
                    ul {
                    padding-left: 0em;
                    }
                    ul ul {
                    padding-left: 1em;
                    }
                    a {
                    text-decoration:none; color: black;
                    }-->

                    h1 {
                    text-align: center;
                    font-size: 20px;
                    }
                    li {
                    list-style: none;
                    }
                    a {
                    text-decoration:none; color: black;
                    }

                    .toc {
                    background-color: #666;
                    height: 50px;
                    line-height: 50px;
                    width: 20%;
                    border-status:
                    }
                    .toc span {float: left; font-size: 24px;}
                    .toc .en {font-size: 14px; padding: 5px 0 0 8px;}

                    li div{
                    background-color: #666;
                    margin-bottom: 10px;
                    display: flex;
                    }
                    .sub-ul li div{
                    background-color: red;
                    margin-top: 10px;
                    }
                    li a{
                    height: 50px;
                    line-height: 50px;
                    }
                </style>
            </head>
            <body>
                <div class="toc">
                    <span class="zh">目录</span>
                    <span class="en">CONTENTS</span>
                </div>
                <ul><xsl:apply-templates select="outline:item/outline:item"/></ul>
            </body>
        </html>
    </xsl:template>
    <xsl:template match="outline:item">
        <li>
            <xsl:if test="@title!=''">
                <div>
                    <a>
                        <xsl:if test="@link">
                            <xsl:attribute name="href"><xsl:value-of select="@link"/></xsl:attribute>
                        </xsl:if>
                        <xsl:if test="@backLink">
                            <xsl:attribute name="name"><xsl:value-of select="@backLink"/></xsl:attribute>
                        </xsl:if>
                        <xsl:value-of select="@title" />
                    </a>
                    <!--<span> <xsl:value-of select="@page" /> </span>-->
                </div>
            </xsl:if>
            <ul class="sub-ul">
                <xsl:comment>added to prevent self-closing tags in QtXmlPatterns</xsl:comment>
                <xsl:apply-templates select="outline:item"/>
            </ul>
        </li>
    </xsl:template>
</xsl:stylesheet>
内容 (contents.html)

由于鄙人对前端样式不熟悉,就只能写最简单的方式说明使用即可

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <title>Title</title>

    <style>
        p {
            font-size: 18px;
            font-weight: bold;
            background: #ffa854;
        }
        .red {
            color: red;
        }
        .green {
            color: green;
        }
        .blue {
            color: blue;
        }

        table {
            border: #000000 2px solid;
        }
        td {
            border: #d23636 2px solid;
        }
    </style>
</head>
<body>
    <div>
        <p class="red">这里是自定义样式测试文本(红色)</p>
        <p class="green">这里是自定义样式测试文本(绿色)</p>
        <p class="blue">这里是自定义样式测试文本(蓝色)</p>
    </div>

    <div>
        <table>
            <thead>
                <tr>
                    <td>编号</td><td>姓名</td><td>邮箱</td>
                </tr>
            </thead>
            <tbody>
                <tr>
                    <td>1</td><td>张三</td><td>zhangsan@qq.com</td>
                </tr>
                <tr>
                    <td>2</td><td>李四</td><td>lisi@qq.com</td>
                </tr>
                <tr>
                    <td>3</td><td>王麻子</td><td>wangmazi@qq.com</td>
                </tr>
            </tbody>
        </table>
    </div>
</body>
</html>
PHP代码

此处按照最简单的方式,新建一个控制器和方法

本文使用 hyperf 3.0 框架,所以代码格式按照框架格式编写,根据实际情况按照自己的方式编写即可

<?php

namespace App\Controller;

use Hyperf\HttpServer\Annotation\Controller;
use Hyperf\HttpServer\Annotation\GetMapping;
use mikehaertl\wkhtmlto\Pdf;

#[Controller(prefix: "pdf/export")]
class PdfExportController extends AbstractController
{
    #[GetMapping(path: "test")]
    public function test()
    {
        $header_html = BASE_PATH . '/pdf_export/header.html';
        $footer_html = BASE_PATH . '/pdf_export/footer.html';
        $cover_html = BASE_PATH . '/pdf_export/cover.html';
        $contents_html = BASE_PATH . '/pdf_export/contents.html';

        // 实例化
        $pdf = new Pdf([
            'commandOptions' => [
                'useExec' => true,
                'escapeArgs' => false,
                'procOptions' => array(
                    // This will bypass the cmd.exe which seems to be recommended on Windows
                    'bypass_shell' => true,
                    // Also worth a try if you get unexplainable errors
                    'suppress_errors' => true,
                ),
            ],

            // 默认页面选项
            'disable-smart-shrinking',

            'encoding' => 'UTF-8',  // option with argument

            // 页眉设置
            'header-html' => $header_html,
            'header-line',
            'header-spacing' => 5,

            // 设置页脚
            'footer-html' => $footer_html,
            'footer-line',
            'footer-spacing' => 5,
        ]);

        // 设置封面
        $pdf->addCover($cover_html);


        // 设置目录
        $tocPath = BASE_PATH . '/pdf_export/toc.xsl';
        $toc_options = [
            'toc-header-text' => '目录',
            'disable-dotted-lines',
            'disable-toc-links',
            'xsl-style-sheet' => $tocPath,
        ];
        $pdf->addToc($toc_options);

        // 添加内容
        $pdf->addPage('<h1>Hello, 世界!</h1>');
        $pdf->addPage($contents_html);


        // 保存PDF文件
        $pdfPath = BASE_PATH . '/pdf_export/export/测试PDF-' . time() . '.pdf';
        $pdf->saveAs($pdfPath);
    }
}
  • 访问控制器方法进行 PDF 生成

  • 当我们打开生成的PDF文件时,可能会出现中文显示问题。

    乱码.png

    • 原因:一般是 我们的服务器环境缺少相应的中文字体导致的

    • 解决

      • 可以从 windows 环境中找到已安装的字体

        windows字体.png

      • 复制出来,并上传到服务器环境 /usr/share/fonts 位置即可(此处我们复制 宋体 simsun.ttc)

        服务器字体.png

      • 复制成功后,再次执行,即可显示正常

成品展示

成品展示.png