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");
    }

  }
}
 

運行結果圖如下:

 

發表評論
所有評論
還沒有人評論,想成為第一個評論的人麼? 請在上方評論欄輸入並且點擊發布.
相關文章