flying-saucer-pdf终于完美解决了(中文问题,换行问题,分页,页眉页脚,水印),html+css控制pdf样式

集成freemarker+flying-saucer-pdf+itext,通过html模板生成PDF
 折腾了很久,flying-saucer-pdf终于完美解决了(中文问题,换行问题,页眉页脚,水印),html+css控制pdf样式
 
 一共集成到两个类中:Generator & PDFBuilder,具体看详细的代码注释,相关文件路径自行修改
 
 转黄方法入口:Generator.pdfGeneratePlus

 1.引入相关java
 <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf-itext5</artifactId>
            <version>9.1.18</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.5.11</version>
        </dependency>
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.28</version>
        </dependency>


2.上上面提到两个类的实现代码

package com.xxxxx.xxxx.file.config;

import java.io.BufferedWriter;
import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.io.OutputStream;
import java.io.StringWriter;
import java.nio.charset.Charset;
import java.util.Map;

import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;

import org.springframework.util.StringUtils;
import org.xhtmlrenderer.pdf.ITextRenderer;

import com.itextpdf.text.Document;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.Pipeline;
import com.itextpdf.tool.xml.XMLWorker;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import com.itextpdf.tool.xml.html.CssAppliersImpl;
import com.itextpdf.tool.xml.html.Tags;
import com.itextpdf.tool.xml.net.FileRetrieve;
import com.itextpdf.tool.xml.net.ReadingProcessor;
import com.itextpdf.tool.xml.parser.XMLParser;
import com.itextpdf.tool.xml.pipeline.css.CSSResolver;
import com.itextpdf.tool.xml.pipeline.css.CssResolverPipeline;
import com.itextpdf.tool.xml.pipeline.end.PdfWriterPipeline;
import com.itextpdf.tool.xml.pipeline.html.AbstractImageProvider;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipeline;
import com.itextpdf.tool.xml.pipeline.html.HtmlPipelineContext;
import com.itextpdf.tool.xml.pipeline.html.ImageProvider;

import freemarker.template.Configuration;
import freemarker.template.Template;
import lombok.extern.slf4j.Slf4j;

/**
 * 
 * @描述:html/pdf生成器
 *
 * @作者:zhongjy
 * 
 * @时间:2019年7月15日 下午12:31:25
 */
@Slf4j
public class Generator {

  /**
   * 
   * @描述:生成html
   *
   * @返回:String
   *
   * @作者:zhongjy
   *
   * @时间:2019年7月15日 下午12:33:58
   */
  public static String htmlGenerate(String template, Map<String, Object> variables)
      throws Exception {
    Configuration config = FreemarkerConfiguration.getConfiguation();
    Template tp = config.getTemplate(template);
    StringWriter stringWriter = new StringWriter();
    BufferedWriter writer = new BufferedWriter(stringWriter);
    tp.process(variables, writer);
    String htmlStr = stringWriter.toString();
    writer.flush();
    writer.close();
    return htmlStr;
  }

  public static void pdfGenerate(String htmlStr, OutputStream out) throws Exception {
    DocumentBuilder builder = DocumentBuilderFactory.newInstance().newDocumentBuilder();
    org.w3c.dom.Document doc = builder.parse(new ByteArrayInputStream(htmlStr.getBytes()));
    ITextRenderer renderer = new ITextRenderer();
    renderer.setDocument(doc, null);
    renderer.layout();
    renderer.createPDF(out);
    out.close();
  }

  /**
   * 
   * @描述:生成pdf
   *
   * @返回:void
   *
   * @作者:zhongjy
   *
   * @时间:2019年7月16日 上午10:59:11
   */
  public static void pdfGeneratePlus(String htmlTemplate, Map<String, Object> dataMap,
      String targetPdf, Rectangle pageSize, String header, boolean isFooter, File watermark)
      throws Exception {
    /**
     * 根据freemarker模板生成html
     */
    String htmlStr = htmlGenerate(htmlTemplate, dataMap);
    final String charsetName = "UTF-8";
    Document document = new Document(pageSize);
    
    OutputStream out = new FileOutputStream(targetPdf);
    /**
     * 设置边距
     */
    // document.setMargins(30, 30, 30, 30);
    PdfWriter writer = PdfWriter.getInstance(document, out);
    /**
     * 添加页码
     */
    PDFBuilder builder = new PDFBuilder(header, 10, pageSize, watermark, isFooter);
    writer.setPageEvent(builder);

    document.open();

    /**
     * html内容解析
     */
    HtmlPipelineContext htmlContext =
        new HtmlPipelineContext(new CssAppliersImpl(new XMLWorkerFontProvider() {
          @Override
          public Font getFont(String fontname, String encoding, float size, final int style) {
            if (fontname == null) {
              /**
               * 操作系统需要有该字体, 没有则需要安装; 当然也可以将字体放到项目中, 再从项目中读取
               */
              fontname = "STSong-Light";
              encoding = "UniGB-UCS2-H";
            }
            Font font = null;
            try {
              font = new Font(BaseFont.createFont(fontname, encoding, BaseFont.NOT_EMBEDDED), size,
                  style);
            } catch (Exception e) {
              log.error("", e);
            }
            return font;
          }
        })) {
          @Override
          public HtmlPipelineContext clone() throws CloneNotSupportedException {
            HtmlPipelineContext context = super.clone();
            ImageProvider imageProvider = this.getImageProvider();
            context.setImageProvider(imageProvider);
            return context;
          }
        };

    /**
     * 图片解析
     */
    htmlContext.setImageProvider(new AbstractImageProvider() {

      String rootPath = "C:\\Users\\Administrator\\Desktop\\刘亦菲\\";

      @Override
      public String getImageRootPath() {
        return rootPath;
      }

      @Override
      public Image retrieve(String src) {
        if (StringUtils.isEmpty(src)) {
          return null;
        }
        try {
          Image image = Image.getInstance(new File(rootPath, src).toURI().toString());
          /**
           * 图片显示位置
           */
          image.setAbsolutePosition(400, 400);
          if (image != null) {
            store(src, image);
            return image;
          }
        } catch (Exception e) {
          log.error("", e);
        }
        return super.retrieve(src);
      }
    });
    htmlContext.setAcceptUnknown(true).autoBookmark(true)
        .setTagFactory(Tags.getHtmlTagProcessorFactory());

    /**
     * css解析
     */
    CSSResolver cssResolver = XMLWorkerHelper.getInstance().getDefaultCssResolver(true);
    cssResolver.setFileRetrieve(new FileRetrieve() {
      @Override
      public void processFromStream(InputStream in, ReadingProcessor processor) throws IOException {
        try (InputStreamReader reader = new InputStreamReader(in, charsetName)) {
          int i = -1;
          while (-1 != (i = reader.read())) {
            processor.process(i);
          }
        } catch (Throwable e) {
        }
      }

      /**
       * 解析href
       */
      @Override
      public void processFromHref(String href, ReadingProcessor processor) throws IOException {
        InputStream is = new ByteArrayInputStream(href.getBytes());
        try {
          InputStreamReader reader = new InputStreamReader(is, charsetName);
          int i = -1;
          while (-1 != (i = reader.read())) {
            processor.process(i);
          }
        } catch (Exception e) {
          log.error("", e);
        }

      }
    });

    HtmlPipeline htmlPipeline =
        new HtmlPipeline(htmlContext, new PdfWriterPipeline(document, writer));
    Pipeline<?> pipeline = new CssResolverPipeline(cssResolver, htmlPipeline);
    XMLWorker worker = null;
    worker = new XMLWorker(pipeline, true);
    XMLParser parser = new XMLParser(true, worker, Charset.forName(charsetName));
    try (InputStream inputStream = new ByteArrayInputStream(htmlStr.getBytes())) {
      parser.parse(inputStream, Charset.forName(charsetName));
    }
    document.close();
  }
}
 

package com.xxxxx.xxxx.file.config;

import java.io.File;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Image;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import com.xxxxx.xxxx.util.BaseUtil;

import lombok.extern.slf4j.Slf4j;

/**
 * 
 * @描述:PDF生成水印和页眉页脚(页码).
 *
 * @作者:zhongjy
 * 
 * @时间:2019年7月15日 下午8:22:24
 */
@Slf4j
public class PDFBuilder extends PdfPageEventHelper {
  /**
   * 页眉
   */
  public String header = "";

  /**
   * 文档字体大小,页脚页眉最好和文本大小一致
   */
  public int presentFontSize = 10;

  /**
   * 文档页面大小,最好前面传入,否则默认为A4纸张
   */
  public Rectangle pageSize = PageSize.A4;

  /**
   * 模板
   */
  public PdfTemplate total;

  /**
   * 基础字体对象
   */
  public BaseFont bf = null;

  /**
   * 利用基础字体生成的字体对象,一般用于生成中文文字
   */
  public Font fontDetail = null;
  /**
   * 水印文件
   */
  private File watermark = null;
  /**
   * 是否显示页脚页码信息
   */
  private boolean isHeaderFooter = false;

  public PDFBuilder() {

  }

  /**
   * 
   * @param header
   * @param presentFontSize
   * @param pageSize
   * @param watermark
   */
  public PDFBuilder(String header, int presentFontSize, Rectangle pageSize, File watermark,
      boolean isHeaderFooter) {
    this.header = header;
    this.presentFontSize = presentFontSize;
    this.pageSize = pageSize;
    this.watermark = watermark;
    this.isHeaderFooter = isHeaderFooter;
  }

  public void setHeader(String header) {
    this.header = header;
  }

  public void setPresentFontSize(int presentFontSize) {
    this.presentFontSize = presentFontSize;
  }

  /**
   * 文档打开时创建模板
   */
  public void onOpenDocument(PdfWriter writer, Document document) {
    /**
     * 共 页 的矩形的长宽高
     */
    total = writer.getDirectContent().createTemplate(50, 50);
  }

  /**
   * 关闭每页的时候添加页眉页脚和水印
   */
  public void onEndPage(PdfWriter writer, Document document) {
    /**
     * 添加分页
     */
    if (isHeaderFooter) {
      this.addPage(writer, document);
    }
    /**
     * 添加水印
     */
    if (watermark != null) {
      this.addWatermark(writer);
    }
  }

  /**
   * 
   * @描述:分页
   *
   * @返回:void
   *
   * @作者:zhongjy
   *
   * @时间:2019年7月15日 下午9:09:39
   */
  public void addPage(PdfWriter writer, Document document) {
    try {
      if (bf == null) {
        bf = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", false);
      }
      if (fontDetail == null) {
        /**
         * 数据体字体
         */
        fontDetail = new Font(bf, presentFontSize, Font.NORMAL);
        fontDetail.setColor(BaseColor.GRAY);
      }
    } catch (Exception e) {
      log.error("", e);
    }

    /**
     * 写入页眉
     */
    ColumnText.showTextAligned(writer.getDirectContent(), Element.ALIGN_LEFT,
        new Phrase(header, fontDetail), document.left(), document.top() + 20, 0);
    /**
     * 写入页脚(分页信息)
     */
    int pageS = writer.getPageNumber();
    String foot1 = "第 " + pageS + " 页 / 共";
    Phrase footer = new Phrase(foot1, fontDetail);

    /**
     * 计算前半部分的foot1的长度,后面好定位最后一部分的'Y页'这俩字的x轴坐标,字体长度也要计算进去 = len
     */
    float len = bf.getWidthPoint(foot1, presentFontSize);

    /**
     * 拿到当前的PdfContentByte
     */
    PdfContentByte cb = writer.getDirectContent();

    /**
     * 写入页脚1,x轴就是(右margin+左margin + right() -left()- len)/2.0F 再给偏移20F适合人类视觉感受,否则肉眼看上去就太偏左了
     * y轴就是底边界-20,否则就贴边重叠到数据体里了就不是页脚了;注意Y轴是从下往上累加的,最上方的Top值是大于Bottom好几百开外的
     */
    ColumnText.showTextAligned(cb, Element.ALIGN_CENTER, footer,
        (document.rightMargin() + document.right() + document.leftMargin() - document.left() - len)
            / 2.0F + 20F,
        document.bottom() - 25, 0);

    /**
     * 写入页脚2的模板(就是页脚的Y页这俩字)添加到文档中,计算模板的和Y轴,X=(右边界-左边界 - 前半部分的len值)/2.0F + len , y 轴和之前的保持一致,底边界-20
     */
    cb.addTemplate(total,
        (document.rightMargin() + document.right() + document.leftMargin() - document.left()) / 2.0F
            + 20F,
        document.bottom() - 25);
  }

  /**
   * 
   * @描述:添加水印
   *
   * @返回:void
   *
   * @作者:zhongjy
   *
   * @时间:2019年7月15日 下午9:12:40
   */
  public void addWatermark(PdfWriter writer) {
    /**
     * 水印图片
     */
    Image image = null;
    try {
      image = Image.getInstance(BaseUtil.file2byte(watermark));
      PdfContentByte content = writer.getDirectContentUnder();
      content.beginText();
      /**
       * 开始写入水印
       */
      image.setAbsolutePosition(300, 300);
      content.addImage(image);
      content.endText();
    } catch (Exception e) {
      log.error("", e);
    }
  }

  /**
   * 关闭文档时,替换模板,完成整个页眉页脚组件
   */
  public void onCloseDocument(PdfWriter writer, Document document) {
    if (isHeaderFooter) {
      /**
       * 最后一步了,就是关闭文档的时候,将模板替换成实际的 Y 值,至此,page x of y 制作完毕,完美兼容各种文档size
       */
      total.beginText();
      /**
       * 生成的模版的字体、颜色
       */
      total.setFontAndSize(bf, presentFontSize);
      total.setColorFill(BaseColor.GRAY);
      String foot2 = " " + (writer.getPageNumber()) + " 页";
      /**
       * 模版显示的内容
       */
      total.showText(foot2);
      total.endText();
      total.closePath();
    }
  }
}
 

最后附上HTML模板和调用方法

<html>
<head>
<title></title>
<style>
table {
    width: 100%;
    border-collapse: collapse;
    border-style: solid;
    border-width: 0.5px;
    border-color: #000000;
}

table tr td {
    border-width: 0.5px;
    border-style: solid;
    border-color: #000000;
    padding: 9px 9px;
}

table thead tr th {
    border-width: 0.5px;
    border-style: solid;
    border-color: #000000;
    text-align: center;
    padding: 9px 9px;
}
</style>
</head>
<body>
    <table>
        <thead>
            <tr>
                <#list titleList as title>
                  <th>${title}</th>
                </#list>
            </tr>
        </thead>
        <#list dataList as dl>
                <tr>
                    <#list dl as d><td>${d}</td></#list>
                </tr>
        </#list>
        
    </table>
</body>
</html>

 

import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

import com.itextpdf.text.PageSize;
import com.xxxxx.xxxx.file.config.Generator;

public class Test4 {

  public static void main(String[] args) throws Exception {
    Map<String, Object> mp = new HashMap<String, Object>();
    try {
      String outputFile = "C:\\Users\\Administrator\\Desktop\\test123\\abc1.pdf";// 生成后的路径
      Map<String, Object> dataMap = new HashMap<String, Object>();

      List<String> titleList = Arrays.asList("属性1", "属性2", "属性3", "属性4", "属性5", "属性6", "属性7");
      dataMap.put("titleList", titleList);

      List<List<String>> dataList = new ArrayList<List<String>>();
      for (int i = 0; i < 100; i++) {
        dataList
            .add(Arrays.asList("数据1_" + i, "数据2_" + i, "数据3_数据3_数据3_数据3_数据3_数据3_数据3_数据3_数据3_" + i,
                "数据4_" + i, "数据5_" + i, "数据6_" + i, "数据7_" + i));
      }
      dataMap.put("dataList", dataList);

      //File water = new File("C:\\Users\\zhongjy\\Desktop\\test123\\water.png");
      Generator.pdfGeneratePlus("laytable/normal-teble.html", dataMap, outputFile, PageSize.A4, "", true, null);
      mp.put("code", "200");
      mp.put("url", outputFile);
    } catch (Exception ex) {
      ex.printStackTrace();
      mp.put("code", "500");
    }

  }
}
 

运行结果图如下:

 

 

 

补充上面用到的方法

 

 

 

/**
       * 
       * @描述:文件转byte[]
       *
       * @返回:byte[]
       *
       * @作者:zhongjy
       *
       * @时间:2019年7月15日 下午10:19:18
       */
      public static byte[] file2byte(File file) {
        FileInputStream fileInputStream = null;
        byte[] bFile = null;
        try {
          bFile = new byte[(int) file.length()];
          fileInputStream = new FileInputStream(file);
          fileInputStream.read(bFile);
        } catch (Exception e) {
            logger.error("", e);
        } finally {
          if (fileInputStream != null) {
            try {
              fileInputStream.close();
            } catch (Exception e) {
                logger.error("", e);
            }
          }

        }
        return bFile;
      }

 

 

 

 

 

/**
 * 
 * @描述:html报表模板配置
 *
 * @作者:zhongjy
 * 
 * @时间:2019年7月15日 下午12:25:59
 */
public class FreemarkerConfiguration {

  private static Configuration config = null;
  static {
    config = new Configuration(Configuration.DEFAULT_INCOMPATIBLE_IMPROVEMENTS);
    config.setClassForTemplateLoading(FreemarkerConfiguration.class, "/report/");
  }

  public static Configuration getConfiguation() {
    return config;
  }
}

说明:/report/是springboot项目下freemarker的模板路径

 

 

  • 4
    点赞
  • 41
    收藏
    觉得还不错? 一键收藏
  • 打赏
    打赏
  • 11
    评论
Flying Saucer是一个Java库,可以将HTML文档换为PDF格式。它是基于开源的HTMLCSS渲染引擎——WebKit的一个分支。 要使用Flying SaucerHTML换为PDF,您可以按照以下步骤进行操作: 1. 首先,将Flying Saucer添加到您的Java项目中。您可以通过Maven或手动下载JAR文件来完成。Flying Saucer的最新版本可以在其官方网站上找到。 2. 创建一个Java类,并导入所需的Flying Saucer类。 3. 在代码中,使用Flying Saucer提供的API加载和渲染HTML文档。您可以使用`ITextRenderer`类来执行此操作。 4. 设置输出路径和文件名,然后使用`ITextRenderer`类将渲染的内容保存为PDF文件。 下面是一个简单的示例代码,演示了如何使用Flying SaucerHTML换为PDF: ```java import org.xhtmlrenderer.pdf.ITextRenderer; public class HtmlToPdfConverter { public static void main(String[] args) { String inputHtmlPath = "path/to/input.html"; String outputPdfPath = "path/to/output.pdf"; try { // Create an instance of ITextRenderer ITextRenderer renderer = new ITextRenderer(); // Set the input HTML document renderer.setDocument(new File(inputHtmlPath)); // Render the HTML document to PDF renderer.layout(); // Save the rendered PDF to the output path OutputStream outputStream = new FileOutputStream(outputPdfPath); renderer.createPDF(outputStream); outputStream.close(); System.out.println("PDF conversion completed successfully."); } catch (Exception e) { e.printStackTrace(); } } } ``` 您需要将`inputHtmlPath`替换为实际的HTML文件路径,并将`outputPdfPath`替换为要保存的PDF文件路径。 请注意,Flying SaucerCSS的支持相对较好,但不是完全完善的。因此,在将HTML换为PDF之前,最好确保您的HTML文档与Flying Saucer兼容,并且在渲染过程中没有任何问题。 希望这能帮助到您!
评论 11
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

zhong_jianyu

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值