当前位置: 首页 > 工具软件 > HTML2PDF > 使用案例 >

itextpdf(html2pdf)有这样的坑?java导出pdf必看

林威
2023-12-01

背景

        公司最近有做做Java导出pdf的需求,因为之前没有做过的缘故,所以从网上找来了itextpdf的包,前期做的还是非常顺利的,本地测试都是非常的顺利,正当我以为导出pdf如此简单的时候,上Linux测试就踩了大坑。

<!-- 字符串转pdf -->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>html2pdf</artifactId>
            <version>2.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>font-asian</artifactId>
            <version>7.1.2</version>
        </dependency>
import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.kernel.colors.Color;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.geom.Rectangle;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfPage;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.kernel.pdf.canvas.PdfCanvas;
import com.itextpdf.kernel.pdf.canvas.draw.ILineDrawer;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.element.LineSeparator;

import java.io.ByteArrayOutputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;

public class PdfUtil {

    /**
     * @param html html页面字符串
     * @return
     * @throws FileNotFoundException
     * @throws IOException
     * @Description 将html转换为pdf文件
     */
    public ByteArrayOutputStream html2Pdf(String html) throws FileNotFoundException, IOException {
//       PdfFont font = PdfFontFactory.createFont("STSongStd-Light", "UniGB-UCS2-H", false);
        ConverterProperties props = new ConverterProperties();
        DefaultFontProvider defaultFontProvider = new DefaultFontProvider(false, false, false);
        defaultFontProvider.addFont(this.getClass().getClassLoader().getResource("../conf/simkai.ttf").getPath());
        defaultFontProvider.addSystemFonts();
        props.setFontProvider(defaultFontProvider);
        ByteArrayOutputStream bao = new ByteArrayOutputStream();
        PdfWriter writer = new PdfWriter(bao);
        PdfDocument pdf = new PdfDocument(writer);
        pdf.setDefaultPageSize(new PageSize(595, 14400));
        Document document = HtmlConverter.convertToDocument(html, pdf, props);
        EndPosition endPosition = new EndPosition();
        LineSeparator separator = new LineSeparator(endPosition);
        document.add(separator);
        document.getRenderer().close();
        PdfPage page = pdf.getPage(1);
        float y = endPosition.getY() - 36;
        page.setMediaBox(new Rectangle(0, y, 595, 14400 - y));
        document.close();
        return bao;
    }

    /**
     * 定义操作区域
     */
    class EndPosition implements ILineDrawer {
        // y坐标
        protected float y;

        /**
         * @return
         * @Description: 获取y坐标
         */
        public float getY() {
            return y;
        }

        /**
         * @param pdfCanvas:操作画布
         * @param rect:操作区域
         * @Description: 操作画布特定区域
         */
        @Override
        public void draw(PdfCanvas pdfCanvas, Rectangle rect) {
            this.y = rect.getY();
        }

        /**
         * @return
         * @Description: 获取行颜色
         */
        @Override
        public Color getColor() {
            return null;
        }

        /**
         * @return
         * @Description: 获取行宽
         */
        @Override
        public float getLineWidth() {
            return 0;
        }

        /**
         * @param color
         * @Description: 设置行颜色
         */
        @Override
        public void setColor(Color color) {
        }

        /**
         * @param lineWidth:宽度
         * @Description: 设置行宽
         */
        @Override
        public void setLineWidth(float lineWidth) {
        }
    }

    public static void main(String args[]) throws IOException {

        String htmlStr = "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\"> 2021年12月6日,工信部发布关于规范旅居车辆《公告》申报要求的通知。</p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif; text-align: center;\"><img style=\"margin: 0px auto; padding: 0px; display: block; border: 0px; max-width: 100%;\" src=\"https://www.chinabuses.com/uploadfile/2021/1207/20211207041655819.jpg\" alt=\"\" /></p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\">  各车辆生产企业及检验检测机构:</p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\">  为进一步规范旅居车辆《公告》产品准入申报工作,经请示工业和信息化部装备工业一司同意,现将旅居车辆产品准入申报相关要求通知如下:</p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\"><span style=\"margin: 0px; padding: 0px; font-weight: bold;\">  一、相关申报要求</span></p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\">  1、申报《公告》产品准入的旅居车辆产品(含旅居挂车)应满足相关国家强制性标准的技术要求。同时,旅居车辆的内部空间应设计合理,能够满足旅居生活功能的要求。至少应具备独立的卫生设施使用空间;车内高度(车厢地板到车厢内顶蓬的距离)不低于1750mm;相关设备设施应在车内固定,方便操作使用。</p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\">  2、旅居车辆(含旅居挂车)申报产品准入时,企业应提供车辆内部专用装置、设施的具体情况说明。包括(但不限于):内部生活设施固定方式;供排水系统布置;车辆配备的报警装置(烟雾、气体等)及应急逃生口位置等。</p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\">  3、旅居车辆(含旅居挂车)申报产品准入时,企业应提供相应的内部装置照片和图纸。包括:车内专用装置设施布置简图、照片;车辆内部空间布置尺寸简图、车门尺寸标注简图。</p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\"><span style=\"margin: 0px; padding: 0px; font-weight: bold;\">  二、实施要求</span></p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\">  1、自2022年1月1日起,申报《公告》(第352批)的新产品应满足以上要求;</p>\n" +
                "<p style=\"margin: 22px 0px 0px; padding: 0px; line-height: 32px; font-family: Arial, 'PingFang SC', 'Hiragino Sans GB', STHeiti, 'Microsoft YaHei', 'WenQuanYi Micro Hei', sans-serif;\">  2、对于已《公告》的旅居车辆产品,应在2022年3月31日前完成相关车辆信息的补充和整改工作。</p>";
        PdfUtil pdfUtil = new PdfUtil();
        ByteArrayOutputStream stream = pdfUtil.html2Pdf(htmlStr);

        FileOutputStream fileOutputStream = new FileOutputStream("D://test.pdf");
        fileOutputStream.write(stream.toByteArray());

    }
}

当Linux服务器上没有字体报错怎么办?

        当我将需求搞定以后,上Linux上测试时老报Font Provider contains zero fonts. At least one font shall be present。遇到这种情况我就很奇怪,Linux上没有字体吗?明明有加itextpdf的font-asian-7.1.2.jar,经过后续的排查发现原来测试服务器上头并没有加任何的字体,于是从网上找到了相关命令yum -y install fontconfig,结果下载一堆乱七八糟的字体,一个简体都没有(该结果导致最后导出来的pdf中一个中文都没有),没办法。通过领导的提示,从win10本地环境下的fonts文件夹中拿了ttc与ttf(ttf是ttc的集合体)楷体字体,后测试发现,加载ttc不好使,于是换ttf。

        原以为到这里就结束了,但是因为我在做手动加载的时候指定的是绝对路径,这样导致的后果就是路径写死,需要改成相对路径。

如何根据相对路径读取字体?

        当我想改成相对路径时才发现,我们当前的项目是利用Jenkins打包,并且是打的压缩包,而不是一个整体的jar,所以这个tar.gz中包含bin,conf和lib。验证的时候堪称泪如雨下,因为我们是利用Jenkins打包,每次都是通过git的版本部署,所以我在验证的时候,改一下,提交一个版本,测一下,再改。到现在为止已经打了几十个标签!!!全都是测试的时候,没法在本地测试,上Linux测试不成,继续改的结果。

defaultFontProvider.addFont(this.getClass().getClassLoader().getResource("../conf/simkai.ttf").getPath());

        在利用resource获取路径时,该路径时bin目录,所以需要返回上一个目录,再找conf中的字体,这个simkai.ttf就是放在resources下的字体文件,打包完毕就放在conf下。这样加载就没有问题了,但是无法在conf文件夹下再创建文件夹,所以根据getPath()可以获取路径,但是无法加载到itextpdf的font提供容器中,我也很奇怪,试了好几次都是,所以没有办法才扔到根目录。

 类似资料: