itext,java,使用html2pdf的一些注意,以及多字体匹配的坑

【首先强烈推荐大家去看itext官方文档,里边有很多问题的回答   https://kb.itextpdf.com/itext/installation-guidelines】

一、前提

1,做一个能动态改变样式的pdf,并且将文本内容填充进去,那么使用PdfRender就做不到了,e签宝的模板接口也做不到动态改变字体的颜色等。百度查到可以使用itext的html2pdf,可是却没想到在使用过程中有那么多坑,而且很多教程都不贴html,所说html规范严格也没说到底咋严格,最终还是跟源码也解决问题。

2,最终需要的pdf样式如下:

一些信息我马赛克了,可以看到标题是优设标题黑,正文是微软雅黑,部分字体要根据所填内容不同变换颜色

 

二、代码

<dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>kernel</artifactId>
            <version>8.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>io</artifactId>
            <version>8.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>layout</artifactId>
            <version>8.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>forms</artifactId>
            <version>8.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>pdfa</artifactId>
            <version>8.0.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>font-asian</artifactId>
            <version>8.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-log4j12</artifactId>
            <version>1.7.18</version>
        </dependency>

        <!--itext7 html转pdf用到的包-->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>html2pdf</artifactId>
            <version>5.0.2</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.31</version>
        </dependency>
maven引入
package com.cfam.util;

import com.itextpdf.html2pdf.ConverterProperties;
import com.itextpdf.html2pdf.HtmlConverter;
import com.itextpdf.html2pdf.css.apply.impl.DefaultCssApplierFactory;
import com.itextpdf.html2pdf.resolver.font.DefaultFontProvider;
import com.itextpdf.io.font.FontProgramFactory;
import com.itextpdf.io.font.PdfEncodings;
import com.itextpdf.kernel.font.PdfFont;
import com.itextpdf.kernel.font.PdfFontFactory;
import com.itextpdf.kernel.geom.PageSize;
import com.itextpdf.kernel.pdf.PdfDocument;
import com.itextpdf.kernel.pdf.PdfWriter;
import com.itextpdf.layout.Document;
import com.itextpdf.layout.font.FontProvider;
import com.itextpdf.styledxmlparser.css.media.MediaDeviceDescription;
import com.itextpdf.styledxmlparser.css.media.MediaType;
import com.itextpdf.text.DocumentException;
import freemarker.cache.FileTemplateLoader;
import freemarker.cache.TemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;

import java.io.*;
import java.util.HashMap;
import java.util.Map;

/**
 * html 填充数据渲染 转 pdf
 */
public class PdfGeneratorTest {

    /**
     * 使用Freemarker引擎加载HTML模板文件并填充变量值,并将HTML字符串转换为PDF文件
     *
     * @param data             模板要填充的数据
     * @throws Exception
     * @return
     */
    public static String generatePDF(Map<String, Object> data,  String templateDir, String templateName, String pdfPath, String fileName) throws Exception {
        // 使用Freemarker引擎加载HTML模板文件并填充变量值
        TemplateLoader templateLoader = new FileTemplateLoader(new File(templateDir));
        Configuration cfg = new Configuration(Configuration.getVersion());
        cfg.setTemplateLoader(templateLoader);
        Template template = cfg.getTemplate(templateName,"UTF-8");
        StringWriter out = new StringWriter();
        template.process(data, out);
        out.flush();
        String htmlContent = out.toString();
        return convertHtmlToPdf(htmlContent, pdfPath, fileName);
    }

    /**
     * 使用iText 7将HTML字符串转换为PDF文件,并返回PDF文件的二进制数据
     *
     * @param htmlString    待转换的HTML字符串
     * @return              返回生成的PDF文件内容
     * @throws IOException
     */
    private static String convertHtmlToPdf(String htmlString, String path, String fileName) throws IOException, DocumentException {
        File compressedImageFile = new File(path, fileName);
        OutputStream os = new FileOutputStream(compressedImageFile);
        ByteArrayOutputStream outputStream = new ByteArrayOutputStream();
        PdfWriter writer = new PdfWriter(outputStream);
        PdfDocument pdf = new PdfDocument(writer);
        Document document = new Document(pdf, new PageSize(600.0F, 1150.0F));
        // 设置左、右、上、下四个边距的值,以点(pt)为单位
        document.setMargins(0, 0, 0, 0);
        // 设置中文字体
        FontProvider fontProvider = new DefaultFontProvider(false, false, false);
        //添加自定义字体,例如微软雅黑
        PdfFont microsoft = PdfFontFactory.createFont(FontProgramFactory.createFont("C:\\Users\\carcredit\\Desktop\\youshebiaotihei.ttf"));
        fontProvider.addFont(microsoft.getFontProgram(), PdfEncodings.IDENTITY_H);
        PdfFont microsoft1 = PdfFontFactory.createFont(FontProgramFactory.createFont("C:\\Users\\carcredit\\Desktop\\microsoftyahei.ttf"));
        fontProvider.addFont(microsoft1.getFontProgram(), PdfEncodings.IDENTITY_H);

        ConverterProperties converterProps = new ConverterProperties();
        converterProps.setFontProvider(fontProvider);
        // 调用HtmlConverter类的convertToPdf函数,将HTML字符串转换为PDF文件
        converterProps.setMediaDeviceDescription(new MediaDeviceDescription(MediaType.PRINT));
        converterProps.setCssApplierFactory(new DefaultCssApplierFactory());
        HtmlConverter.convertToPdf(htmlString, pdf, converterProps);
        pdf.close();
        // 将PDF文件转换为字节数组并返回
        outputStream.writeTo(os);
        return compressedImageFile.getPath();
    }

    public static void main(String[] args) throws Exception {
        Map map = new HashMap();
        map.put("name", "太白金星");
        map.put("gender", "男");
        map.put("nationality", "仙族");
        map.put("address", "天庭");
        map.put("td_3_month_loan_dw","0");
        map.put("bairong_network_time_dw","[24,+)");
        map.put("fahai_ajlc_count","0");
        map.put("fahai_fygg_count","0");
        map.put("fahai_ktgg_count","0");
        map.put("fahai_zxgg_count","0");
        map.put("fahai_cpws_count","0");
        map.put("pboc_edu_info_dw","武神");
        map.put("arp_overdue_055_interval","(3,5]");
        map.put("arp_account_028_interval","0");
        map.put("arp_account_018_interval","--");
        map.put("arp_account_012_interval","(0,30000]");
        map.put("arp_overdue_105_interval",">20");
        map.put("arp_base_006","2024-01-17 17:18");
        map.put("apply_con_approve_status_name_dw","建议拒绝");
        map.put("pre_pboc_overdue_max_month_dw","(5,7]");
        map.put("pboc_overdue_max_amount_dw","(20000,40000]");
        map.put("apply_con_reject_reason_sales_dw","拒绝:");
        map.put("pboc_partner_name","占几个");
        map.put("apply_con_result_hf","--");
        map.put("score_level_up","B");
        map.put("pre_apply_con_cert_name","李长庚");
        map.put("pre_pboc_belonger_marital_status","已婚");
        map.put("pre_cert_no","156956265498563654");
        map.put("pboc_phone_no_lastest","17765571433");
        map.put("pboc_partner_phone","15965395626");
        map.put("pboc_partner_cert_no","163956956485236542");
        map.put("mobile_phone_operator","移动");
        map.put("pboc_house_loan_exists","否");
        map.put("pboc_phone_compare","一致");
        map.put("pboc_monthly_pboc_repayments_interval","(30000,50000]");
        map.put("pre_phone","13890786915");
        map.put("bairong_bankfour_res","一致");
        map.put("pboc_car_loan_exists","是");
        map.put("pboc_total_credit_line_credit_card_interval","(0,30000]");
        map.put("pboc_utilization_rate_used_quota_interval","0");
        map.put("pboc_query_count_1_month_interval","0");
        map.put("pboc_query_count_3_months_interval","0");
        map.put("create_time","2024/1/18 15:12");
        map.put("update_time","12:58.3");
        map.put("create_user","0");
        map.put("update_user","0");
        map.put("sync_time","2024/1/18 15:12");
        generatePDF(map,  "C:\\Users\\carcredit\\Desktop", "boke.html", "C:\\Users\\carcredit\\Desktop", "测试程序生成的.pdf");
    }

}
PdfGeneratorTest.java
<!DOCTYPE html>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width,initial-scale=1.0">

<head>
  <title>
  </title>
</head>
<style type="text/css">

  p {
    margin: 0px;
  }

  .report {
    width: 679px;
    margin: 0 auto;
    font-family: 'microsoft yahei';
    color: #333333;
    font-size: 16px;
  }

  .head {
    height: 80px;
    line-height: 80px;
    background: #4384FE;
    color: #fff;
    /* text-align: center; */
    overflow: hidden;
    box-sizing: border-box;
  }

  .head .main_head {
    font-family: "youshebiaotihei";
    margin-top: -10px;
    font-size: 39px;
  }

  .head span {
    display: inline-block;
  }

  .head .report_time {
    height: 100%;
    padding-top: 20px;
    padding-right: 15px;
    box-sizing: border-box;
    font-size: 12px;
  }

  .small_head {
    margin-top: 18px;
    height: 20px;
    line-height: 14px;
  }

  .small_head .treetop {
    display: block;
    font-style: normal;
    width: 3px;
    height: 16px;
    background: #4384FE;
    margin-right: 5px;
    float: left;
  }

  .small_head span {
    display: block;
    height: 16px;
  }

  .bg {
    background: #F8FAFC;
    margin-top: 9px;
  }

  .custom_content {
    height: 102px;
  }

  .custom_content .first_storey {
    height: 100%;
    padding-top: 14px;
    padding-left: 9px;
    box-sizing: border-box;
  }

  .custom_content .second_storey {
    height: 100%;
    padding-top: 14px;
  }

  .custom_content .second_storey img {
    width: 155px;
    height: 41px;
    margin-right: -4px;
  }

  .examine_content {
    height: 87px;
  }

  .examine_content div {
    width: 50%;
  }

  .examine_content .left_01 {
    margin-top: 14px;
    padding-left: 9px;
    box-sizing: border-box;
  }

  .examine_content .left_01 span {
    display: block;
  }

  .examine_content .left_01 span:nth-child(2) {
    margin-top: 24px;
  }

  .examine_content .right_01 {
    line-height: 87px;
  }

  .examine_content .right_01 span {
    display: inline-block;
    color: #08CA85;
    font-size: 25px;
  }

  .unified-format {
    height: 40px;
  }

  .unified-format .left_01 {
    margin-top: 16px;
    padding-left: 10px;
    box-sizing: border-box;
  }

  .unified-format .left_01 span {
    display: block;
    width: 33%;
    height: 40px;
    line-height: 40px;
  }

  .judicial-content {
    height: 70px;
    margin: 0 auto;
    margin-top: 9px;
    padding-top: 8px;
    padding-left: 8px;
    box-sizing: border-box;
  }

  .judicial-content div {
    float: left;
  }

  .judicial-content div span:nth-child(1) {
    display: block;
    width: 130px;
    height: 26px;
    line-height: 26px;
    border-radius: 2px;
    color: #fff;
    background: linear-gradient(180deg, #7FA1FF 0%, #4384FE 100%);
    margin-right: 4px;
    text-align: center;
  }


  .judicial-content div span:nth-child(2) {
    display: block;
    width: 100%;
    text-align: center;
    margin: 9px 0px;
  }

  .phone-content {
    height: 70px;
    margin: 0 auto;
    margin-top: 9px;
    padding-top: 8px;
    padding-left: 8px;
    box-sizing: border-box;
  }

  .phone-content div {
    float: left;
  }

  .phone-content div span:nth-child(1) {
    display: block;
    width: 164px;
    height: 26px;
    line-height: 26px;
    border-radius: 2px;
    color: #fff;
    background: linear-gradient(180deg, #7FA1FF 0%, #4384FE 100%);
    margin-right: 3px;
    text-align: center;
  }

  .phone-content div span:nth-child(2) {
    display: block;
    width: 100%;
    text-align: center;
    margin: 9px 0px;
  }

  .bull-content {
    height: 44px;
    padding: 9px;
    margin: 0 auto;
    margin-top: 9px;
    box-sizing: border-box;
  }

  .bull-content div span {
    display: block;
    width: 330px;
    height: 26px;
    line-height: 26px;
    background: linear-gradient(180deg, #7FA1FF 0%, #4384FE 100%);
    border-radius: 2px;
    text-align: center;
    color: #fff;
  }

  .bull-content div span:nth-child(2) {
    background: #DEE9FF;
    color: #333333;
  }

  .info-content {
    height: 71px;
    padding: 9px;
    box-sizing: border-box;
  }

  .info-content div {
    width: 50%;
    height: 100%;
  }

  .info-content div span {
    display: block;
  }

  .info-content div span:nth-child(1) {
    width: 330px;
    height: 26px;
    line-height: 26px;
    background: linear-gradient(180deg, #7FA1FF 0%, #4384FE 100%);
    border-radius: 2px;
    color: #fff;
    text-align: center;
  }

  .info-content div span:nth-child(2) {
    text-align: center;
    margin-top: 10px;
  }

  .loan-content {
    height: 265px;
    overflow: hidden;
    box-sizing: border-box;
  }

  .loan-content .loan_storey {
    padding-top: 9px;
    padding-left: 9px;
  }

  .loan-content .content {
    width: 50%;
  }

  .loan-content .content p:nth-child(1) {
    width: 330px;
    height: 26px;
    line-height: 26px;
    background: linear-gradient(180deg, #7FA1FF 0%, #4384FE 100%);
    border-radius: 2px;
    color: #fff;
    text-align: center;
  }

  .loan-content .content p:nth-child(2) {
    height: 58px;
    line-height: 58px;
    text-align: center;
    /* margin-top: 18px;
    margin-bottom: 21px; */
  }

  .query-content {
    height: 71px;
    padding: 9px;
    box-sizing: border-box;
  }

  .query-content div {
    width: 50%;
    height: 100%;
  }

  .query-content div span {
    display: block;
  }

  .query-content div span:nth-child(1) {
    width: 330px;
    height: 26px;
    line-height: 26px;
    background: linear-gradient(180deg, #7FA1FF 0%, #4384FE 100%);
    border-radius: 2px;
    color: #fff;
    text-align: center;
  }

  .query-content div span:nth-child(2) {
    text-align: center;
    margin-top: 9px;
  }

  .m24 {
    margin-bottom: 21px;
  }
</style>

<body>
  <div class="report">
    <div class="container">
      <div class="head">
        <span class="main_head" style="padding-left: 120px;float: left;">个人综合风险评估报告</span>
        <!-- <span class="report_time"
          style="float:left;">报告时间:${arp_base_006}</span> -->
        <#if arp_base_006?has_content>
          <span class="report_time" style="float:left;">报告时间:${arp_base_006}</span>
          <#else>
            <span class="report_time" style="float:left;">报告时间:--</span>
        </#if>
      </div>
      <p class="small_head"><span class="treetop"></span><span>客户信息</span></p>
      <div class="custom_content bg">
        <div class="first_storey" style="float:left; width: 50%; box-sizing: border-box;">
          <p>姓名:${pre_apply_con_cert_name}</p>
          <p style="margin-top: 26px;">身份证号 :${pre_cert_no} </p>
        </div>
        <div class="second_storey" style="float:right; width: 50%; box-sizing: border-box;">
          <p>客户风险评级 :</p>
          <div style="height: 41px; line-height: 41px; margin-top:  14px;">
            <p style="float:left;">
              <#if (score_level_up=="A" )>
                <img src="https://www.carcredit.com.cn/riskreporttemplate/A.png" alt="A">
                <#elseif (score_level_up=="B" )>
                  <img src="https://www.carcredit.com.cn/riskreporttemplate/B.png" alt="B">
                  <#elseif (score_level_up=="C" )>
                    <img src="https://www.carcredit.com.cn/riskreporttemplate/C.png" alt="C">
                    <#elseif (score_level_up=="D" )>
                      <img src="https://www.carcredit.com.cn/riskreporttemplate/D.png" alt="D">
              </#if>
            </p>
            <p style="float:left; margin-left: 14px;">
              <span>信用评级为:</span>
              <#if (score_level_up=="A" )>
                <span style="color: #03AA3E;">${score_level_up}</span>
                <#elseif (score_level_up=="B" )>
                  <span style="color: #77D33D;">${score_level_up}</span>
                  <#elseif (score_level_up=="C" )>
                    <span style="color: #FFB84C;">${score_level_up}</span>
                    <#elseif (score_level_up=="D" )>
                      <span style="color: #FE3A41;">${score_level_up}</span>
                      <#else>
                        <span>--</span>
              </#if>
            </p>
          </div>
        </div>
      </div>
      <p class="small_head"><span class="treetop"></span><span>审核结果</span></p>
      <div class="examine_content bg">
        <div class="left_01" style="float: left;">
          <span>通过资方:${apply_con_result_hf} </span>
          <span>拒绝原因:${apply_con_reject_reason_sales_dw} </span>
        </div>
        <div class="right_01" style="float:right;">
          <#if (apply_con_approve_status_name_dw=="建议通过" )>
            <span style="color: #08CA85;">${apply_con_approve_status_name_dw}</span>
            <#elseif (apply_con_approve_status_name_dw=="建议拒绝" )>
              <span style="color: #E72B49;">${apply_con_approve_status_name_dw}</span>
              <#else>
                <span>--</span>
          </#if>
        </div>
      </div>
      <p class="small_head"><span class="treetop"></span><span>身份信息</span></p>
      <div class="unified-format bg">
        <div class="left_01">
          <span style="float: left;">婚姻状况:${pre_pboc_belonger_marital_status} </span>
          <span style="float: left; text-align: center;">学历:${pboc_edu_info_dw}</span>
          <span style="float: left; text-align: right;">手机号:${pboc_phone_no_lastest}</span>
        </div>
      </div>
      <p class="small_head"><span class="treetop"></span><span>司法信息</span></p>
      <div class="judicial-content bg">
        <div>
          <span>裁判文书</span>
          <span>${fahai_cpws_count}</span>
        </div>
        <div>
          <span>执行公告</span>
          <span>${fahai_zxgg_count}</span>
        </div>
        <div>
          <span>开庭公告</span>
          <span>${fahai_ktgg_count}</span>
        </div>
        <div>
          <span>法院公告</span>
          <span>${fahai_fygg_count}</span>
        </div>
        <div>
          <span>案件流程</span>
          <span>${fahai_ajlc_count}</span>
        </div>
      </div>
      <p class="small_head"><span class="treetop"></span><span>手机号验证</span></p>
      <div class="phone-content bg">
        <div>
          <span>手机号码</span>
          <span>${pre_phone}</span>
        </div>
        <div>
          <span>运营商名称</span>
          <#if (mobile_phone_operator="移动" )>
            <span style="color: #3062EC;">${mobile_phone_operator}</span>
            <#elseif (mobile_phone_operator="联通" )>
              <span style="color: #FFA922;">${mobile_phone_operator}</span>
              <#elseif (mobile_phone_operator="电信" )>
                <span style="color: #E40C2F;">${mobile_phone_operator}</span>
                <#else>
                  <span>--</span>
          </#if>
          <!-- <span>${mobile_phone_operator}</span> -->
        </div>
        <div>
          <span>验证结果</span>
          <span>${bairong_bankfour_res}</span>
        </div>
        <div>
          <span>在网时长</span>
          <span>${bairong_network_time_dw}</span>
        </div>

      </div>
      <p class="small_head"><span class="treetop"></span><span>多头查询</span></p>
      <div class="bull-content bg">
        <div>
          <span style="float: left;">3个月内申请人在多个平台申请借款</span>
          <span style="float: right;">${td_3_month_loan_dw}</span>
        </div>
      </div>
      <p class="small_head"><span class="treetop"></span><span>信息提示</span></p>
      <div class="info-content bg">
        <div style="float: left;">
          <span>月负债</span>
          <span>${pboc_monthly_pboc_repayments_interval}</span>
        </div>
        <div style="float: right;">
          <span>手机号比对不一致</span>
          <span>${pboc_phone_compare}</span>
        </div>
      </div>
      <p class="small_head"><span class="treetop"></span><span>贷款信息汇总</span></p>
      <div class="loan-content bg">
        <div class="loan_storey">
          <div class="content" style="float: left;">
            <p>贷款账户数</p>
            <p>${arp_overdue_105_interval}</p>
          </div>
          <div class="content" style="float: left;">
            <p>未结清贷款余额</p>
            <p>${arp_account_012_interval}</p>
          </div>
        </div>
        <div class="loan_storey">
          <div class="content" style="float: left;">
            <p>历史是否有房贷</p>
            <p>${pboc_house_loan_exists}</p>
          </div>
          <div class="content" style="float: left;">
            <p>未结清房贷贷款总额</p>
            <p>${arp_account_018_interval}</p>
          </div>
        </div>
        <div class="loan_storey">
          <div class="content" style="float: left;">
            <p>历史是否有车贷</p>
            <p>${pboc_car_loan_exists}</p>
          </div>
          <div class="content" style="float: left;">
            <p>未结清个人汽车贷款总额</p>
            <p>${arp_account_028_interval}</p>
          </div>
        </div>
      </div>
      <p class="small_head"><span class="treetop"></span><span>查询信息汇总</span></p>
      <div class="query-content bg">
        <div style="float: left;">
          <span>客户近1个月查询次数 </span>
          <span>${pboc_query_count_1_month_interval}</span>
        </div>
        <div style="float: right;">
          <span>客户近3个月查询次数</span>
          <span>${pboc_query_count_3_months_interval}</span>
        </div>
      </div>
      <p class="small_head"><span class="treetop"></span><span>配偶信息</span></p>
      <div class="unified-format bg m24">
        <div class="left_01">
          <span style="float:left;">姓名:${pboc_partner_name}</span>
          <span style="float:left; text-align: center;">证件号码:${pboc_partner_phone}</span>
          <span style="float: left; text-align: right;">联系电话:${pboc_partner_phone}</span>
        </div>
      </div>
    </div>
  </div>
</body>

</html>
boke.html

用到的字体放在百度网盘了:

链接:https://pan.baidu.com/s/1O7sqPnWOHTd1ftruB-pYnQ
提取码:azxq

 此文档参考的官方文档为:itext官网文档   官方文档2

三、一些注意事项

1,加载html模板的方式

① 加载本地模板(不是放在程序resources下,而是服务器的一个位置)

 ② 加载url模板

 

2,html中的css格式不生效

①首先html的格式一定要标准,标签结尾一定要有,比如<div>开头,必须有</div>

②检查引入的itext是否是最新版本,我一开始引入的是5.0.2,结果css的渐变背景(linear-gradient)标签就没有生效,查看maven仓库发现最新的到8.0.2了,html2pdf升到5.0.2,freeMaker到2.3.31 

③建议使用Visual Studio Code格式化一下html,方便检查格式

 

3,html填充内容的velocity语法

 如果用过idea的easyCode插件那一定对velocity不陌生,关于velocity的语法可以参考以下文档:Java中Velocity ,freeMaker使用的稍微有点区别:模板语言参考 - FreeMarker 中文官方参考手册 (foofun.cn)

① 一般填充使用 ${}的方式,如

② if else

一般在easyCode模板中是这样使用的

 但在这里的html中,应该这样使用

 例如:

 或者

 其余用法请参考上面文档

 

4,字体不生效 font-family的问题

首先不用系统自带的字体,会浪费资源

也不使用网上下载的乱七八糟的漂亮字体(有些会乱码),所以这里我将使用标准字体,系统字体都设置为false(注意,即使三个都是false,但是在pdf搜索不到可用字体时,也会使用内置的14种字体比如Times)

官网对使用字体的解释:第 6 章:在 pdfHTML 中使用字体

有两种方式加载字体, 第一种方式,在html中指定@font-face 从网络下载字体,第二种方式,在后台指定字体,两种方式都需要在html样式中指定“font-family”(注:font-family后一定要加单引号);

① html中指定font-face,如下图

 此时后台就可以不加字体或加一个默认的字体。(注意,此时两条线的两端连接处设置的名称一定要一致,下面会说匹配字体的事儿,这里设置 @font-face中的font-family的名字等于给此字体设置了一个别名alias)

(我在测试时发现使用这个html生成的pdf有些文字字体不对,没有使用指定的,我一直以为是font-family没有生效,跟源码才发现是这个woff不支持某些字符,所以一定要先验证这字体好不好用啊,真坑,下面有验证方法。)

② 后台指定字体

 由于使用前端下载字体的方式特别慢,所以我选择在后台指定,html中去掉@font-face, 只写font-family与后台加载的字体对应,比如

 那么html中的font-family和后台加载的字体是如何对应的呢?  由于之前生成的字体都是混乱的,网上也找不到答案,贴点儿边的就说ttf的名字要是英文的,最后无可奈何跟了一下源码,才发现在layout包的字体选择器fontSelector中,是这样判断的

 所以与ttf是否是英文也没关系,html中的font-family要与后台加载ttf的 familyNameLowerCase 相同,html中也不用写@font-face了,那么如何知道ttf的familyNameLowerCase呢?如下:

或者这样创建字体(设置一个别名,要与html中的font-family对应):

匹配familyNameLowerCase的过程其实是将所有的字体排序的过程,将匹配到的放在第一位,接下来还会继续循环排好序的字体集合逐字校验是否支持,如下:

 所以这里要保证,加载的字体一定要支持设置了font-family标签的文字

如何验证是否支持某字呢?使用fontTools如下:

from fontTools.ttLib import TTFont

ch = ''

font_path = 'C:\\Users\\carcredit\\Desktop\\ef6d508.woff'
font = TTFont(font_path)
glyph_name = None
for table in font['cmap'].tables:
    glyph_name = table.cmap.get(ord(ch))
    if glyph_name is not None:
        break
if glyph_name is not None:
    glyf = font['glyf']
    found = glyf.has_key(glyph_name) and glyf[glyph_name].numberOfContours > 0
else:
    found = False

print(found)
校验字体是否支持某个字

 可以看到截图中的woff文件,支持“姓”,却不支持“长”。

总结:如果html中没有指定font-family,那么最终会使用后台addFont的第一个字体;如果指定了font-family,但是名称与后台字体的familyNameLowerCase或alias不同,那么最终会使用后台addFont的第一个字体(或者使用系统自带的默认字体或乱码);

但是要注意,后台加载的ttf越多,生成pdf的时间越慢,所以最好不要加载系统字体,即设置:

FontProvider fontProvider = new DefaultFontProvider(false, false, false)

由于fontSelector会将font-family跟每一个ttf作对比,所以如果想更快的生成pdf,应调整ttf的加载顺序,将pdf中使用次数较多的字体放在第一个,这样能确保第一次循环就匹配到;

出现字体混乱,第一时间验证字体文件是否支持。

 

 5,生成的pdf出现偏移、缺失、分页

一开始设置的pdf纸张大小是A4,与html中定义的大小不同,那就会出现一部分被截断,或分页的情况(我的需求要不分页),所以要根据html的大小进行调试,直到合适

 

6,生成的pdf文件太大

有可能是嵌入字体的问题,由于我这里使用的是自定义字体(中文),并且pdf内部会嵌入字体否则在其他电脑可能看不到,对于这个问题官方解释是这样的:为什么即使我指定不嵌入,iText 也会嵌入字体?

由于中文字符一般使用Identity-H的编码,所以这时如果设置为 FORCE_NOT_EMBEDDED 会报错:Cannot create Type0 font with true type font program without embedding it.

不设置 EmbeddingStrategy 的话,默认是  PREFER_EMBEDDED (趋向于嵌入),所以我这里设置为 PREFER_NOT_EMBEDDED

生成的pdf大小会小很多,我猜测(趋向于不嵌入)仅仅嵌入了使用字体的子字符集(即pdf中的文字),搜索了官网回答,对于嵌入字体的解释,官方回答如下: 如何仅部分嵌入字体?

查看文档属性也确实如此

也就是说,使用了Identity-H,就永远不会嵌入完整字体,但是我测试的使用PREFER_NOT_EMBEDDED和PREFER_EMBEDDED生成的文件大小相同,但是PREFER_EMBEDDED生成的时间却慢很多,这里可以判定使用PREFER_NOT_EMBEDDED最佳。

 然后我又在网上找了另一种微软雅黑的字体,测试发现用这个字体生成的pdf大小比原来的小一倍,当使用思源宋体的时候pdf更小,所以在不嵌入整个字体的情况下,与引用的三方字体也有关系。

另外:在css中设置加粗、倾斜是不生效的,如果想使用加粗字体,应该使用单独的加粗ttf文件。

 

 

 

 

 


 

posted @ 2024-01-25 11:39  hsql  阅读(2113)  评论(1编辑  收藏  举报