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
 
            org.xhtmlrenderer
            flying-saucer-pdf-itext5
            9.1.18
        

        
            com.itextpdf
            itext-asian
            5.2.0
        

        
            com.itextpdf.tool
            xmlworker
            5.5.11
        

        
            org.freemarker
            freemarker
            2.3.28
        


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 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 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模板和调用方法







    


        
            
                <#list titleList as title>
                  
                
            
        
        <#list dataList as dl>
                
                    <#list dl as d>
                
        
        
    
${title}
${d}


 

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 mp = new HashMap();
    try {
      String outputFile = "C:\\Users\\Administrator\\Desktop\\test123\\abc1.pdf";// 生成后的路径
      Map dataMap = new HashMap();

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

      List> dataList = new ArrayList>();
      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");
    }

  }
}
 

运行结果图如下:

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

 

 

 

补充上面用到的方法

 

 

 

/**
       * 
       * @描述:文件转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的模板路径

 

 

你可能感兴趣的:(Java,开发工具,html转pdf,报表)