freemarker转PDF,支持分页,增加页眉页脚

参考 https://github.com/superad/pdf-kit.git

先看效果(不能上传PDF文档。。。)

POM.XML

<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
         xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
         xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
    <modelVersion>4.0.0</modelVersion>

    <groupId>com.github.superad.pdf.kit</groupId>
    <artifactId>pdf-kit</artifactId>
    <packaging>jar</packaging>
    <name>${project.artifactId}</name>
    <version>1.1-RELEASE</version>

    <properties>
        <java.src.version>1.7</java.src.version>
        <java.target.version>1.7</java.target.version>
        <project.encoding>UTF-8</project.encoding>
    </properties>

    <parent>
        <groupId>org.sonatype.oss</groupId>
        <artifactId>oss-parent</artifactId>
        <version>7</version>
    </parent>

    <dependencies>
        <!--pdf生成工具类-->
        <!--pdf生成 itext-->
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itextpdf</artifactId>
            <version>5.4.2</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf.tool</groupId>
            <artifactId>xmlworker</artifactId>
            <version>5.4.1</version>
        </dependency>
        <dependency>
            <groupId>com.itextpdf</groupId>
            <artifactId>itext-asian</artifactId>
            <version>5.2.0</version>
        </dependency>
        <dependency>
            <groupId>org.xhtmlrenderer</groupId>
            <artifactId>flying-saucer-pdf</artifactId>
            <version>9.0.3</version>
        </dependency>
        <!--freemarker-->
        <dependency>
            <groupId>org.freemarker</groupId>
            <artifactId>freemarker</artifactId>
            <version>2.3.26-incubating</version>
        </dependency>
        <!--jfreechart-->
        <dependency>
            <groupId>jfree</groupId>
            <artifactId>jfreechart</artifactId>
            <version>1.0.2</version>
        </dependency>

        <!--log-->
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-core</artifactId>
            <version>1.0.13</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-classic</artifactId>
            <version>1.0.13</version>
        </dependency>
        <dependency>
            <groupId>ch.qos.logback</groupId>
            <artifactId>logback-access</artifactId>
            <version>1.0.13</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>slf4j-api</artifactId>
            <version>1.7.5</version>
        </dependency>
        <dependency>
            <groupId>org.slf4j</groupId>
            <artifactId>log4j-over-slf4j</artifactId>
            <version>1.7.21</version>
        </dependency>

        <!--util-->
        <dependency>
            <groupId>com.google.guava</groupId>
            <artifactId>guava</artifactId>
            <version>20.0</version>
        </dependency>
        <dependency>
            <groupId>org.projectlombok</groupId>
            <artifactId>lombok</artifactId>
            <version>1.14.8</version>
        </dependency>
        <dependency>
            <groupId>org.apache.commons</groupId>
            <artifactId>commons-io</artifactId>
            <version>1.3.2</version>
        </dependency>
        <dependency>
            <groupId>commons-lang</groupId>
            <artifactId>commons-lang</artifactId>
            <version>2.6</version>
        </dependency>
        <!--servlet-->
        <dependency>
            <groupId>javax.servlet</groupId>
            <artifactId>servlet-api</artifactId>
            <version>2.5</version>
            <scope>provided</scope>
        </dependency>
    </dependencies>


        <!--增加的配置,过滤ttf文件的匹配-->
    <profiles>
        <profile>
            <id>release</id>
            <build>
                <resources>
                    <resource>
                        <directory>src/main/resources</directory>
                        <filtering>true</filtering>
                    </resource>
                </resources>
                <plugins>
                    <!--compile-->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-compiler-plugin</artifactId>
                        <version>3.5.1</version>
                        <configuration>
                            <source>${java.src.version}</source>
                            <target>${java.target.version}</target>
                            <encoding>${project.encoding}</encoding>
                        </configuration>
                    </plugin>
                    <!-- Source -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-source-plugin</artifactId>
                        <version>2.2.1</version>
                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>jar-no-fork</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <!-- Javadoc -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-javadoc-plugin</artifactId>
                        <version>2.9.1</version>
                        <executions>
                            <execution>
                                <phase>package</phase>
                                <goals>
                                    <goal>jar</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                    <!-- GPG -->
                    <plugin>
                        <groupId>org.apache.maven.plugins</groupId>
                        <artifactId>maven-gpg-plugin</artifactId>
                        <version>1.5</version>
                        <executions>
                            <execution>
                                <phase>verify</phase>
                                <goals>
                                    <goal>sign</goal>
                                </goals>
                            </execution>
                        </executions>
                    </plugin>
                </plugins>
            </build>
            <distributionManagement>
                <snapshotRepository>
                    <id>oss</id>
                    <url>https://oss.sonatype.org/content/repositories/snapshots/</url>
                </snapshotRepository>
                <repository>
                    <id>oss</id>
                    <url>https://oss.sonatype.org/service/local/staging/deploy/maven2</url>
                </repository>
            </distributionManagement>
        </profile>
    </profiles>


    <licenses>
        <license>
            <name>The Apache Software License, Version 2.0</name>
            <url>http://www.apache.org/licenses/LICENSE-2.0.txt</url>
            <distribution>repo</distribution>
        </license>
    </licenses>
    <scm>
        <tag>alpha</tag>
        <url>https://github.com/superad/pdf-kit.git</url>
        <connection>scm:git:https://github.com/superad/pdf-kit.git</connection>
        <developerConnection>scm:git:https://github.com/superad/pdf-kit.git</developerConnection>
    </scm>
    
</project>

 hello.ftl

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta http-equiv="Content-Style-Type" content="text/css"/>
    <title></title>
    <style type="text/css">
        body {
            font-family: pingfang sc light;
        }
        .center{
            text-align: center;
            width: 100%;
        }
    </style>
</head>
<body>
<!--第一页开始-->
<div class="page" >
    <div class="center"><p>${templateName}</p></div>
    <div><p>iText官网:${ITEXTUrl}</p></div>
    <div><p>FreeMarker官网:${freeMarkerUrl}</p></div>
    <div><p>JFreeChart教程:${JFreeChartUrl}</p></div>
    <!--外部链接-->
    <p>静态logo图</p>
    <div>
        <img src="${imageUrl}" alt="美团点评" width="110" height="20.8"/>
    </div>
    <!--动态生成的图片-->
    <p>气温变化对比图</p>
    <div>
        <img src="${picUrl}" alt="我的图片" width="500" height="270"/>
    </div>
    <!--jfreechart 散点图-->
    <p>发展建议分布图</p>
    <div>
        <img src="${scatterUrl}" alt="散点图" width="700" height="600"/>
    </div>
</div>
<!--第一页结束-->
<!---分页标记-->
<span style="page-break-after:always;"></span>
<!--第二页开始-->
<div class="page">
    <div>第二页开始了</div>
    <div>列表值:</div>
    <div>
    <#list scores as item>
        <div><p>${item}</p></div>
    </#list>
    </div>

</div>


<!--第二页结束-->
</body>
</html>

report_template.ftl

<!DOCTYPE html PUBLIC "-//W3C//DTD XHTML 1.0 Strict//EN"  "http://www.w3.org/TR/xhtml1/DTD/xhtml1-strict.dtd">
<html xmlns="http://www.w3.org/1999/xhtml">
<head>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8"/>
    <meta http-equiv="Content-Style-Type" content="text/css"/>
    <title></title>
    <style type="text/css">
        body {
            font-family: pingfang sc light;
        }
    </style>
</head>
<body>
<!--第一页开始-->
<div class="page" >
    <div>report_template</div>
    <div>姓名:${name}</div>
    <div>年龄:${age}</div>
    <div>成绩:</div>
    <#list scores as item>
        <div>${item}</div>
    </#list>

</div>
<!--第一页结束-->
<!---分页标记-->
<span style="page-break-after:always;"></span>
<!--第二页开始-->
<div class="page">
    <div>第二页开始了</div>
    <img src="${imageUrl}" alt="百度图标" width="270" height="129"/>
</div>


<!--第二页结束-->
</body>
</html>

TemplateBO

@Data
public class TemplateBO {

    private String templateName;

    private String freeMarkerUrl;

    private String ITEXTUrl;

    private String JFreeChartUrl;

    private List<String> scores;

    private String imageUrl;

    private String picUrl;

    private String scatterUrl;
}

reportKit360.java

package pdf.kit;

import com.google.common.collect.Lists;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import pdf.kit.component.PDFHeaderFooter;
import pdf.kit.component.PDFKit;
import pdf.kit.component.chart.ScatterPlotChart;
import pdf.kit.component.chart.model.XYLine;
import pdf.kit.component.chart.impl.DefaultLineChart;

import java.util.ArrayList;
import java.util.List;

/**
 * Created by fgm on 2017/4/17.
 * 360报告
 *
 */
@Slf4j
public class ReportKit360 {

    public static List<XYLine> getTemperatureLineList() {
        List<XYLine> list= Lists.newArrayList();
        for(int i=1;i<=7;i++){
            XYLine line=new XYLine();
            float random=Math.round(Math.random()*10);
            line.setXValue("星期"+i);
            line.setYValue(20+random);
            line.setGroupName("下周");
            list.add(line);
        }
        for(int i=1;i<=7;i++){
            XYLine line=new XYLine();
            float random=Math.round(Math.random()*10);
            line.setXValue("星期"+i);
            line.setYValue(20+random);
            line.setGroupName("这周");
            list.add(line);
        }
        return list;
    }

    public  String createPDF(String templatePath,Object data, String fileName){
        //pdf保存路径
        try {
            //设置自定义PDF页眉页脚工具类
            PDFHeaderFooter headerFooter=new PDFHeaderFooter();
            PDFKit kit=new PDFKit();
            kit.setHeaderFooterBuilder(headerFooter);
            //设置输出路径
            kit.setSaveFilePath("D:\\gitee\\pdf-kit\\hello.pdf");

            String saveFilePath=kit.exportToFile(fileName,data);
            return  saveFilePath;
        } catch (Exception e) {
            log.error("PDF生成失败{}", ExceptionUtils.getFullStackTrace(e));
            return null;
        }

    }

    public static void main(String[] args) {

        ReportKit360 kit=new ReportKit360();
        TemplateBO templateBO=new TemplateBO();
        templateBO.setTemplateName("Hello iText! Hello freemarker! Hello jFreeChart!");
        templateBO.setFreeMarkerUrl("http://www.zheng-hang.com/chm/freemarker2_3_24/ref_directive_if.html");
        templateBO.setITEXTUrl("http://developers.itextpdf.com/examples-itext5");
        templateBO.setJFreeChartUrl("http://www.yiibai.com/jfreechart/jfreechart_referenced_apis.html");
        templateBO.setImageUrl("http://mss.vip.sankuai.com/v1/mss_74e5b6ab17f44f799a524fa86b6faebf/360report/logo_1.png");
        List<String> scores=new ArrayList<String>();
        scores.add("90");
        scores.add("95");
        scores.add("98");
        templateBO.setScores(scores);
        //折线图
        List<XYLine> lineList=getTemperatureLineList();
        DefaultLineChart lineChart=new DefaultLineChart();
        lineChart.setHeight(500);
        lineChart.setWidth(300);
        String picUrl=lineChart.draw(lineList,0);
        templateBO.setPicUrl(picUrl);

        //散点图
        String scatterUrl=ScatterPlotChart.draw(ScatterPlotChartTest.getData(),1,"他评得分(%)","自评得分(%)");
        templateBO.setScatterUrl(scatterUrl);
        String templatePath="D:\\gitee\\pdf-kit\\src\\test\\resources\\templates";
        String path = kit.createPDF(templatePath, templateBO, "hello.pdf");
        System.out.println(path);



    }






}

ScatterPlotChartTest.java

package pdf.kit;

import com.google.common.collect.Lists;
import pdf.kit.component.chart.ScatterPlotChart;
import pdf.kit.component.chart.model.XYScatter;

import java.util.List;

/**
 * Created by fgm on 2017/7/9.
 */
public class ScatterPlotChartTest {
    
    public static void main(String[] args) {
        List<XYScatter> list=getData();
        String path= ScatterPlotChart.draw(list,-1,"他评得分(%)","自评得分(%)");
        System.out.println(path);
    }

    public static List<XYScatter> getData() {
        List<XYScatter> list= Lists.newArrayList();
        XYScatter xy=new XYScatter();
        xy.setX(45);
        xy.setY(55);
        xy.setLabel("主见");
        list.add(xy);
        XYScatter xy1=new XYScatter();
        xy1.setX(75);
        xy1.setY(80);
        xy1.setLabel("定战略");
        list.add(xy1);
        XYScatter xy2=new XYScatter();
        xy2.setX(80);
        xy2.setY(80);
        xy2.setLabel("拿结果");
        list.add(xy2);
        XYScatter xy3=new XYScatter();
        xy3.setX(90);
        xy3.setY(25);
        xy3.setLabel("带团队");
        list.add(xy3);

        return list;
    }
}
interface HeaderFooterBuilder
package pdf.kit.component.builder;

import com.itextpdf.text.Document;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;

/**
 * Created by fgm on 2017/4/15.
 */
public interface HeaderFooterBuilder {


    /**
     * @description 写页眉
     */
    void writeHeader(PdfWriter writer, Document document, Object data, Font font, PdfTemplate template);
    /**
     * @description 写页脚
     */
    void writeFooter(PdfWriter writer, Document document, Object data, Font font, PdfTemplate template);

    /**
     * @description 关闭文档前,获取替换页眉页脚处设置模板的文本
     */
    String getReplaceOfTemplate(PdfWriter writer, Document document, Object data);

}
class PDFBuilder
package pdf.kit.component.builder;

import com.itextpdf.text.Document;
import com.itextpdf.text.DocumentException;
import com.itextpdf.text.Font;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfPageEventHelper;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import org.apache.commons.lang.StringUtils;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import pdf.kit.exception.PDFException;

import java.io.IOException;

/**
 * 设置页面附加属性
 *
 */
public class PDFBuilder extends PdfPageEventHelper {

   private static Logger log= LoggerFactory.getLogger(PDFBuilder.class);

    //字体文件名
    private String fontFileName;
    // 基础字体对象
    private  BaseFont bf;
    // 利用基础字体生成的字体对象,一般用于生成中文文字
    private  Font fontDetail;
    //文档字体大小
    private  int fontSize=12;
    //模板
    private  PdfTemplate template;
    //数据实体
    private  Object data;
    //页眉页脚定制接口
    private HeaderFooterBuilder headerFooterBuilder;

    //不允许空的构造方法
    private PDFBuilder() {

    }
    public PDFBuilder(HeaderFooterBuilder headerFooterBuilder) {
        this(headerFooterBuilder,null);
    }

    public PDFBuilder(HeaderFooterBuilder headerFooterBuilder, Object data) {
        this(headerFooterBuilder, data, "ping_fang_light.ttf");
    }
    public PDFBuilder(HeaderFooterBuilder headerFooterBuilder, Object data, String fontFileName) {
        this(headerFooterBuilder, data, fontFileName, 12);
    }

    public PDFBuilder(HeaderFooterBuilder headerFooterBuilder, Object data, String fontFileName, int fontSize) {
        this.headerFooterBuilder = headerFooterBuilder;
        this.data=data;
        this.fontFileName=fontFileName;
        this.fontSize=fontSize;
    }


    public void onOpenDocument(PdfWriter writer, Document document) {
        template = writer.getDirectContent().createTemplate(50, 50);
    }
    /**
     *
     * 关闭每页的时候,写入页眉,页脚等
     *
     */
    public void onEndPage(PdfWriter writer, Document document) {
        this.addPage(writer, document);
    }

    //加分页
    private void addPage(PdfWriter writer, Document document){
        if(headerFooterBuilder !=null){
            //1.初始化字体
            initFront();
            //2.写入页眉
            headerFooterBuilder.writeHeader(writer,document,data,fontDetail,template);
            //3.写入前半部分页脚
            headerFooterBuilder.writeFooter(writer,document,data,fontDetail,template);
        }
    }
    /**
     *
     * 关闭文档时,替换模板,完成整个页眉页脚组件
     *
     */
    public void onCloseDocument(PdfWriter writer, Document document) {
        if(headerFooterBuilder !=null){
            template.beginText();
            template.setFontAndSize(bf,fontSize);
            String replace= headerFooterBuilder.getReplaceOfTemplate(writer,document,data);
            template.showText(replace);
            template.endText();
            template.closePath();
        }
    }

    /**
     * @description 初始化字体
     */
    private void initFront(){
        if(StringUtils.isEmpty(fontFileName)){
            throw new PDFException("PDF文档字体未设置!");
        }
        try {
            if (bf == null) {
                //添加字体,以支持中文
                String classpath=PDFBuilder.class.getClassLoader().getResource("").getPath();
                String fontPath =classpath+"fonts/"+fontFileName;
                //创建基础字体
                bf = BaseFont.createFont(fontPath,BaseFont.IDENTITY_H,BaseFont.EMBEDDED);
            }
            if (fontDetail == null) {
                fontDetail = new Font(bf, fontSize, Font.NORMAL);// 数据体字体
                log.info("PDF文档字体初始化完成!");
            }
        } catch (DocumentException e) {
            log.error("字体初始化失败{}", ExceptionUtils.getFullStackTrace(e));
            throw new PDFException("字体初始化失败",e);
        } catch (IOException e) {
            log.error("字体初始化失败{}", ExceptionUtils.getFullStackTrace(e));
            throw new PDFException("字体初始化失败",e);
        }
    }

    public int getPresentFontSize() {
        return fontSize;
    }

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

    public String getFontFileName() {
        return fontFileName;
    }

    public void setFontFileName(String fontFileName) {
        this.fontFileName = fontFileName;
    }
}
class DefaultLineChart 
package pdf.kit.component.chart.impl;

import lombok.extern.slf4j.Slf4j;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.labels.ItemLabelAnchor;
import org.jfree.chart.labels.ItemLabelPosition;
import org.jfree.chart.labels.StandardCategoryItemLabelGenerator;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.renderer.category.LineAndShapeRenderer;
import org.jfree.data.category.DefaultCategoryDataset;
import org.jfree.ui.TextAnchor;
import pdf.kit.component.chart.LineChart;

/**
 * Created by fgm on 2017/5/15.
 * 设置默认的画图工具
 *
 */
@Slf4j
public class DefaultLineChart extends LineChart {
    private int width;
    private int height;
    protected void initPlot(JFreeChart chart, DefaultCategoryDataset dataSet) {

        CategoryPlot plot = chart.getCategoryPlot();
        super.initDefaultXYPlot(plot);
        //设置节点的值显示
        LineAndShapeRenderer lineRender = new LineAndShapeRenderer();
        lineRender.setBaseItemLabelGenerator(new StandardCategoryItemLabelGenerator());
        lineRender.setBaseItemLabelsVisible(true);
        lineRender.setBasePositiveItemLabelPosition(
                new ItemLabelPosition(ItemLabelAnchor.OUTSIDE12, TextAnchor.BASELINE_CENTER));
        plot.setRenderer(lineRender);

    }
    @Override
    public int getWidth() {
        if(width==0){
            return super.getWidth();
        }
        return width;
    }

    @Override
    public void setWidth(int width) {
        this.width = width;
    }

    @Override
    public int getHeight() {
        if(height==0){
            return super.getHeight();
        }
        return height;
    }

    @Override
    public void setHeight(int height) {
        this.height = height;
    }
}
class XYLine
package pdf.kit.component.chart.model;

import lombok.Data;

/**
 * Created by fgm on 2017/4/7.
 */
@Data
public class XYLine {
    private double yValue;
    private String  xValue;
    private String groupName;
    public XYLine(){

    }
    public XYLine(double yValue, String xValue, String groupName){
        this.yValue=yValue;
        this.xValue=xValue;
        this.groupName=groupName;
    }



}
 class XYScatter 
package pdf.kit.component.chart.model;

import lombok.Data;

/**
 * Created by fgm on 2017/7/9.
 */
@Data
public class XYScatter {
    private double x;
    private double y;
    private String label;

}
abstract class LineChart
package pdf.kit.component.chart;

import lombok.extern.slf4j.Slf4j;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.axis.CategoryAxis;
import org.jfree.chart.axis.CategoryLabelPositions;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.plot.CategoryPlot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.data.category.DefaultCategoryDataset;
import pdf.kit.component.chart.model.XYLine;
import pdf.kit.util.FontUtil;

import java.awt.*;
import java.io.File;
import java.io.IOException;
import java.util.List;

/**
 * Created by fgm on 2017/4/7.
 */
@Slf4j
public abstract class LineChart {


    private  int width;

    private  int height;

    private static int   defaultWidth=500;
    private static int  defaultHeight=220;

    private String fileName;


    public String draw(List<XYLine> lineList, int picId){
        return draw("","","",lineList,picId);
    }

    public  String draw(String title, String xLabel, String yLabel,
                        List<XYLine> lineList, int picId){

        if(lineList==null || lineList.size()==0){
            return "";
        }
        DefaultCategoryDataset dataSet=new DefaultCategoryDataset();
        for(XYLine line:lineList){
            dataSet.addValue(line.getYValue(),line.getGroupName(),line.getXValue());
        }
        try {
            return  drawLineChar(title,xLabel,yLabel,dataSet,picId);
        }catch (Exception ex){
            log.error("画图异常{}",ex);
            return "";
        }

    }

    /**
     * @description 设置自定义的线条和背景色
     */
    protected  abstract void initPlot(JFreeChart  chart,DefaultCategoryDataset dataSet);


    protected void initDefaultXYPlot(CategoryPlot plot){
        // 设置X轴
        CategoryAxis domainAxis = plot.getDomainAxis();
        domainAxis.setLabelFont(FontUtil.getFont(Font.PLAIN,13)); // 设置横轴字体
        domainAxis.setTickLabelFont(FontUtil.getFont(Font.PLAIN,13));// 设置坐标轴标尺值字体
        domainAxis.setLowerMargin(0.01);// 左边距 边框距离
        domainAxis.setUpperMargin(0.06);// 右边距 边框距离,防止最后边的一个数据靠近了坐标轴。
        domainAxis.setMaximumCategoryLabelLines(10);
        domainAxis.setCategoryLabelPositions(CategoryLabelPositions.DOWN_45);// 横轴 lable 的位置 横轴上的 Lable 45度倾斜 DOWN_45
        // 设置Y轴
        NumberAxis rangeAxis = (NumberAxis) plot.getRangeAxis();
        rangeAxis.setLabelFont(FontUtil.getFont(Font.PLAIN, 13));
        rangeAxis.setAutoRangeMinimumSize(1);   //最小跨度
        rangeAxis.setUpperMargin(0.18);//上边距,防止最大的一个数据靠近了坐标轴。
        rangeAxis.setLowerBound(18);   //最小值显示18
        rangeAxis.setUpperBound(33);  //最大值显示33
        rangeAxis.setAutoRange(false); //不自动分配Y轴数据
        rangeAxis.setTickMarkStroke(new BasicStroke(1.6f)); // 设置坐标标记大小
        rangeAxis.setTickMarkPaint(Color.BLACK);     // 设置坐标标记颜色
        rangeAxis.setTickUnit(new NumberTickUnit(1));//每1个刻度显示一个刻度值


    }

    /**
     * @description 画出折线图
     * @return 图片地址
     */
    private  String   drawLineChar(String title, String xLabel, String yLabel, DefaultCategoryDataset dataSet, int picId)
            throws IOException {
        JFreeChart lineChartObject= ChartFactory.createLineChart(
                title,
                xLabel, // 横轴
                yLabel, // 纵轴
                dataSet, // 获得数据集
                PlotOrientation.VERTICAL,// 图标方向垂直
                true, // 显示图例
                false, // 不用生成工具
                false // 不用生成URL地址
        );
        String path=this.getClass().getClassLoader().getResource("").getPath();
        String filePath=path+"/images/"+picId+"/"+getFileName();
        File lineChart = new File(filePath);
        if(!lineChart.getParentFile().exists()){
            lineChart.getParentFile().mkdirs();
        }
        //初始化表格样式
        initDefaultPlot(lineChartObject,dataSet);

        ChartUtilities.saveChartAsJPEG(lineChart ,lineChartObject, getWidth() ,getHeight());

        return lineChart.getAbsolutePath();

    }


    private  void  initDefaultPlot(JFreeChart  chart,DefaultCategoryDataset dataSet){
        //设置公共颜色
        chart.getTitle().setFont(FontUtil.getFont(Font.PLAIN, 15)); // 设置标题字体
        chart.getLegend().setItemFont(FontUtil.getFont(Font.PLAIN, 13));// 设置图例类别字体
        chart.setBackgroundPaint(Color.white);// 设置背景色
        CategoryPlot plot = chart.getCategoryPlot();
        plot.setNoDataMessage("无对应的数据。");
        plot.setNoDataMessageFont(FontUtil.getFont(Font.PLAIN, 13));//字体的大小
        plot.setNoDataMessagePaint(Color.RED);//字体颜色
        //设置自定义颜色
        initPlot(chart,dataSet);

    }



    public int getWidth() {
        if(width==0){
            return defaultWidth;
        }
        return width;
    }

    public void setWidth(int width) {
        this.width = width;
    }

    public int getHeight() {
        if(height==0){
            return defaultHeight;
        }
        return height;
    }

    public void setHeight(int height) {
        this.height = height;
    }

    public String getFileName() {
        return fileName;
    }

    public void setFileName(String fileName) {
        this.fileName = fileName;
    }
}
class ScatterPlotChart extends ChartFactory
package pdf.kit.component.chart;

import com.google.common.collect.Lists;
import freemarker.template.utility.CollectionUtils;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;
import org.jfree.chart.ChartFactory;
import org.jfree.chart.ChartUtilities;
import org.jfree.chart.JFreeChart;
import org.jfree.chart.annotations.XYBoxAnnotation;
import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.axis.NumberAxis;
import org.jfree.chart.axis.NumberTickUnit;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.labels.StandardXYToolTipGenerator;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.XYPlot;
import org.jfree.chart.renderer.xy.StandardXYItemRenderer;
import org.jfree.chart.renderer.xy.XYItemRenderer;
import org.jfree.chart.renderer.xy.XYLineAndShapeRenderer;
import org.jfree.chart.urls.StandardXYURLGenerator;
import org.jfree.data.xy.DefaultXYDataset;
import org.jfree.data.xy.XYDataset;
import pdf.kit.component.chart.model.XYScatter;
import pdf.kit.util.FontUtil;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.geom.Ellipse2D;
import java.awt.image.BufferedImage;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.List;

/**
 * Created by fgm on 2017/7/4.
 * 散点图生成
 */
@Slf4j
public class ScatterPlotChart extends ChartFactory {

    private static BufferedImage backgroundImage;

    private static String fileName="scatterChart.png";

    private static int width=700;
    private static int height=600;

    //上方垂直
    private  static double turnTop=4.712389;


    public static String draw(List<XYScatter> list,int picId,String xLabel,String yLabel){
        return draw(list,picId,"",xLabel,yLabel);
    }

    public static  String draw(List<XYScatter>  list,int picId,String title,String xLabel,String yLabel){

        XYDataset xyDataset= ScatterPlotChart.createXYDataSet(list,"");
        String path=LineChart.class.getClassLoader().getResource("").getPath();
        String filePath=path+"/images/"+picId+"/"+fileName;
        File scatterChartFile = new File(filePath);
        if(!scatterChartFile.getParentFile().exists()){
            scatterChartFile.getParentFile().mkdirs();
        }
        try {
            JFreeChart jFreeChart = createChart(xyDataset,list,title,xLabel,yLabel);

            ChartUtilities.saveChartAsJPEG(scatterChartFile ,jFreeChart, width ,height);
        } catch (IOException ex) {
            log.error("散点图生成异常:{}", ExceptionUtils.getFullStackTrace(ex));
            return "";
        }
        return scatterChartFile.getAbsolutePath();
    }



    public static JFreeChart createScatterPlot(String title, String xAxisLabel, String yAxisLabel, XYDataset dataset, PlotOrientation orientation, boolean legend, boolean tooltips, boolean urls) {
        if(orientation == null) {
            throw new IllegalArgumentException("Null \'orientation\' argument.");
        } else {
            NumberAxis xAxis = new NumberAxis(xAxisLabel);
            xAxis.setAutoRangeIncludesZero(false);
            NumberAxis yAxis = new NumberAxis(yAxisLabel);
            yAxis.setAutoRangeIncludesZero(false);
            XYItemRenderer render=new StandardXYItemRenderer();
            render.setBaseItemLabelsVisible(false);
            XYPlot plot = new XYPlot(dataset, xAxis, yAxis, render);

            StandardXYToolTipGenerator toolTipGenerator = null;
            if(tooltips) {
                toolTipGenerator = new StandardXYToolTipGenerator();
            }

            StandardXYURLGenerator urlGenerator = null;
            if(urls) {
                urlGenerator = new StandardXYURLGenerator();
            }

            XYLineAndShapeRenderer renderer = new XYLineAndShapeRenderer(false, true);
            renderer.setSeriesItemLabelFont(0,FontUtil.getFont(Font.PLAIN,14));
            renderer.setBaseItemLabelsVisible(false);
            renderer.setBaseToolTipGenerator(toolTipGenerator);
            renderer.setURLGenerator(urlGenerator);
            plot.setRenderer(renderer);
            plot.setOrientation(orientation);
            JFreeChart chart = new JFreeChart(title, FontUtil.getFont(Font.PLAIN,18), plot, legend);
            return chart;
        }
    }



    public static XYDataset createXYDataSet(List<XYScatter> dataList,
                                            String dataName) {
        DefaultXYDataset dataSet = new DefaultXYDataset();
        if(dataList==null ||dataList.size()==0){
            return dataSet;
        }
        int size = dataList.size();
        double[][] data = new double[2][size];
        for (int i = 0; i < size; i++) {
            XYScatter behaviorBO = dataList.get(i);
            double x = behaviorBO.getX();
            double y = behaviorBO.getY();
            data[0][i] = x;
            data[1][i] = y;
        }
        dataSet.addSeries(dataName,data);
        return dataSet;
    }
    public static JFreeChart createChart(XYDataset xydataset, List<XYScatter> dataList,
                                         String title,String xLabel,String yLabel) throws IOException {


        JFreeChart jfreechart = createScatterPlot(title,xLabel,yLabel,
                xydataset, PlotOrientation.VERTICAL, true, true, false);

        jfreechart.getLegend().setItemFont(FontUtil.getFont(Font.PLAIN,14));
        //去掉图例
        jfreechart.getLegend().setBorder(0,0,0,0);

        //画每个坐标点
        Shape shape =  new Ellipse2D.Double(-4,-4,8,8);
        XYPlot xyPlot = (XYPlot) jfreechart.getPlot();
        XYItemRenderer renderer = xyPlot.getRenderer();
        renderer.setItemLabelFont(FontUtil.getFont(Font.PLAIN, 14));
        for(int i=0;i<xydataset.getSeriesCount();i++){
            renderer.setSeriesShape(i, shape);
            renderer.setSeriesPaint(i, Color.WHITE);
        }
        //初始化背景色和坐标轴
        initBackGroundAndAxis(jfreechart);

        //区间划分
        initRegionPartition(jfreechart);

        //数据标签
        initDataLabels(jfreechart,dataList);

        return jfreechart;
    }

    /**
     * @description 初始化数据标签
     */
    private static void initDataLabels(JFreeChart jfreechart, List<XYScatter> dataList) {
        //数据录入

        if(dataList==null ||dataList.size()==0){
            return;
        }
        XYPlot xyplot = (XYPlot) jfreechart.getPlot();
        for(XYScatter xyScatter:dataList){
            XYPointerFrameAnnotation pointerAnnotation=new XYPointerFrameAnnotation(
                    xyScatter.getLabel(),
                    xyScatter.getX(),
                    xyScatter.getY(),
                    turnTop
                    );
            pointerAnnotation.setFont(FontUtil.getFont(Font.PLAIN, 13));
            xyplot.addAnnotation(pointerAnnotation);

        }

    }



    /**
     * @description 初始化背景色
     */
    private static void initBackGroundAndAxis(JFreeChart jfreechart) throws IOException {
        jfreechart.setBackgroundPaint(Color.white);
        jfreechart.setBorderPaint(Color.black);
        jfreechart.setBorderStroke(new BasicStroke(1.5f));
        XYPlot xyplot = (XYPlot) jfreechart.getPlot();
        //无数据显示
        xyplot.setNoDataMessage("没有数据");
        xyplot.setNoDataMessageFont(FontUtil.getFont(Font.PLAIN, 18));
        //背景色
        if(backgroundImage==null){
            String classpath= ScatterPlotChart.class.getClassLoader().getResource("").getPath();
            String backgroundImg=classpath+"/background/back.png";
            backgroundImage = ImageIO.read(new FileInputStream(backgroundImg));

        }
        xyplot.setBackgroundImage(backgroundImage);
        xyplot.setBackgroundImageAlpha(1.0F);

        xyplot.setOutlineStroke(new BasicStroke(1.5f)); // 边框粗细
        ValueAxis vaaxis = xyplot.getDomainAxis();
        vaaxis.setAxisLineStroke(new BasicStroke(1.5f));
        vaaxis.setLabelFont(FontUtil.getFont(Font.PLAIN,14));

        //坐标轴和背景色
        ValueAxis va = xyplot.getDomainAxis(0);
        va.setAxisLineStroke(new BasicStroke(1.5f));
        va.setAxisLineStroke(new BasicStroke(1.5f)); // 坐标轴粗细
        va.setAxisLinePaint(Color.black); // 坐标轴颜色
        va.setLabelPaint(Color.black); // 坐标轴标题颜色
        va.setTickLabelPaint(Color.black); // 坐标轴标尺值颜色
        va.setAxisLineVisible(false);
        va.setLabelFont(FontUtil.getFont(Font.PLAIN, 14));

        ValueAxis axis = xyplot.getRangeAxis();
        axis.setAxisLineStroke(new BasicStroke(1.5f));
        XYLineAndShapeRenderer xylineandshaperenderer = (XYLineAndShapeRenderer) xyplot
                .getRenderer();
        xylineandshaperenderer.setSeriesOutlinePaint(0, Color.WHITE);
        xylineandshaperenderer.setUseOutlinePaint(false);
        //x 轴
        NumberAxis xAxis = (NumberAxis) xyplot.getDomainAxis();
        xAxis.setAutoRangeIncludesZero(false);
        xAxis.setAutoRangeStickyZero(false);
        xAxis.setTickMarkInsideLength(2.0F);
        xAxis.setTickMarkOutsideLength(0.0F);
        xAxis.setAxisLineStroke(new BasicStroke(1.5f));
        xAxis.setTickUnit(new NumberTickUnit(25));
        xAxis.setLabelFont(FontUtil.getFont(Font.PLAIN, 14));

        xAxis.setUpperBound(101);//最大值
        xAxis.setLowerBound(0);//最小值

        //y 轴
        NumberAxis yAxis = (NumberAxis) xyplot.getRangeAxis();
        yAxis.setAutoRangeIncludesZero(false);
        yAxis.setAutoRangeStickyZero(false);
        yAxis.setTickMarkInsideLength(2.0F);
        yAxis.setTickMarkOutsideLength(0.0F);
        yAxis.setTickUnit(new NumberTickUnit(25));
        yAxis.setAxisLineStroke(new BasicStroke(1.5f));
        yAxis.setLabelFont(FontUtil.getFont(Font.PLAIN, 14));

        yAxis.setUpperBound(101);//最大值
        yAxis.setLowerBound(0);//最小值

    }



    /**
     * @description 初始化区域划分
     * | 00 | 10  | 20 |
     * | 01 | 11  | 21 |
     * | 02 | 21  | 22 |
     *
     *
     */
    private static void initRegionPartition(JFreeChart jfreechart) {
        Stroke stroke=new BasicStroke(0.5F);
        Paint outlinePaint=Color.WHITE;
        //划分区间
        XYBoxAnnotation box00 = new XYBoxAnnotation(0, 0, 50, 50,stroke,outlinePaint);
        XYBoxAnnotation box01 = new XYBoxAnnotation(0, 50, 50, 75,stroke,outlinePaint);
        XYBoxAnnotation box02 = new XYBoxAnnotation(0, 75, 50, 101,stroke,outlinePaint);

        XYBoxAnnotation box10 = new XYBoxAnnotation(50, 0, 75, 50,stroke,outlinePaint);
        XYBoxAnnotation box11 = new XYBoxAnnotation(50, 50, 75, 75,stroke,outlinePaint);
        XYBoxAnnotation box12 = new XYBoxAnnotation(50, 75, 75, 101,stroke,outlinePaint);

        XYBoxAnnotation box20 = new XYBoxAnnotation(75, 0, 101, 50,stroke,outlinePaint);
        XYBoxAnnotation box21 = new XYBoxAnnotation(75, 50, 101, 75,stroke,outlinePaint);
        XYBoxAnnotation box22 = new XYBoxAnnotation(75, 75, 101, 101,stroke,outlinePaint);

        XYPlot xyplot = (XYPlot) jfreechart.getPlot();

        xyplot.addAnnotation(box00);
        xyplot.addAnnotation(box01);
        xyplot.addAnnotation(box02);

        xyplot.addAnnotation(box10);
        xyplot.addAnnotation(box11);
        xyplot.addAnnotation(box12);

        xyplot.addAnnotation(box20);
        xyplot.addAnnotation(box21);
        xyplot.addAnnotation(box22);

        //添加区间标识
        XYTextAnnotation text1 = new XYTextAnnotation("盲区", 4, 98);
        text1.setFont(FontUtil.getFont(Font.PLAIN, 18));
        text1.setPaint(new Color(255,165,0));
        XYTextAnnotation text2 = new XYTextAnnotation("待发展共识区", 10, 3);
        text2.setFont(FontUtil.getFont(Font.PLAIN, 18));
        text2.setPaint(new Color(253, 88, 72));
        XYTextAnnotation text3 = new XYTextAnnotation("潜能区", 96, 3);
        text3.setFont(FontUtil.getFont(Font.PLAIN, 18));
        text3.setPaint(new Color(45, 139, 251));
        XYTextAnnotation text4 = new XYTextAnnotation("优势共识区", 93, 98);
        text4.setFont(FontUtil.getFont(Font.PLAIN, 18));
        text4.setPaint(new Color(20, 149, 134));

        xyplot.addAnnotation(text1);
        xyplot.addAnnotation(text2);
        xyplot.addAnnotation(text3);
        xyplot.addAnnotation(text4);





    }
}
 class XYPointerFrameAnnotation extends XYTextAnnotation implements Cloneable, PublicCloneable, Serializable
package pdf.kit.component.chart;

import org.jfree.chart.annotations.XYTextAnnotation;
import org.jfree.chart.axis.ValueAxis;
import org.jfree.chart.plot.Plot;
import org.jfree.chart.plot.PlotOrientation;
import org.jfree.chart.plot.PlotRenderingInfo;
import org.jfree.chart.plot.XYPlot;
import org.jfree.io.SerialUtilities;
import org.jfree.text.TextUtilities;
import org.jfree.ui.RectangleEdge;
import org.jfree.ui.TextAnchor;
import org.jfree.util.ObjectUtilities;
import org.jfree.util.PublicCloneable;

import java.awt.*;
import java.awt.geom.Line2D;
import java.awt.geom.Rectangle2D;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;

/**
 * Created by fgm on 2017/7/4.
 */
public class XYPointerFrameAnnotation extends XYTextAnnotation implements Cloneable, PublicCloneable, Serializable{

        private static final long serialVersionUID = -4031161445009858499L;
        public static final double DEFAULT_TIP_RADIUS = 10.0D;
        public static final double DEFAULT_BASE_RADIUS = 40.0D;
        public static final double DEFAULT_LABEL_OFFSET = 8.0D;
        public static final double DEFAULT_ARROW_LENGTH = 10.0D;
        public static final double DEFAULT_ARROW_WIDTH = 3.0D;
        private double angle;
        private double tipRadius;
        private double baseRadius;
        private double arrowLength;
        private double arrowWidth;
        private transient Stroke arrowStroke;
        private transient Paint arrowPaint;
        private double labelOffset;
        private transient Paint linePaint;



        public XYPointerFrameAnnotation(String label, double x, double y, double angle) {
            super(label, x, y);
            this.angle = angle;
            this.tipRadius = 1;//连接线到点的距离
            this.baseRadius =50;//连接线的半径
            this.arrowLength =0;//箭头长度 不需要
            this.arrowWidth = 0;//箭头宽度 不需要
            this.labelOffset = 10;//label字体和竖线的距离
            this.arrowStroke = new BasicStroke(1.0F);
            this.arrowPaint = Color.WHITE;
            this.linePaint=Color.WHITE;
        }

        public double getAngle() {
            return this.angle;
        }

        public void setAngle(double angle) {
            this.angle = angle;
        }

        public double getTipRadius() {
            return this.tipRadius;
        }

        public void setTipRadius(double radius) {
            this.tipRadius = radius;
        }

        public double getBaseRadius() {
            return this.baseRadius;
        }

        public void setBaseRadius(double radius) {
            this.baseRadius = radius;
        }

        public double getLabelOffset() {
            return this.labelOffset;
        }

        public void setLabelOffset(double offset) {
            this.labelOffset = offset;
        }

        public double getArrowLength() {
            return this.arrowLength;
        }

        public void setArrowLength(double length) {
            this.arrowLength = length;
        }

        public double getArrowWidth() {
            return this.arrowWidth;
        }

        public void setArrowWidth(double width) {
            this.arrowWidth = width;
        }

        public Stroke getArrowStroke() {
            return this.arrowStroke;
        }

        public Paint getLinePaint() {
            return linePaint;
        }

        public void setLinePaint(Paint linePaint) {
            this.linePaint = linePaint;
        }

        public void setArrowStroke(Stroke stroke) {
            if(stroke == null) {
                throw new IllegalArgumentException("Null \'stroke\' not permitted.");
            } else {
                this.arrowStroke = stroke;
            }
        }

        public Paint getArrowPaint() {
            return this.arrowPaint;
        }

        public void setArrowPaint(Paint paint) {
            this.arrowPaint = paint;
        }

        public void draw(Graphics2D g2, XYPlot plot, Rectangle2D dataArea, ValueAxis domainAxis, ValueAxis rangeAxis, int rendererIndex, PlotRenderingInfo info) {
            PlotOrientation orientation = plot.getOrientation();
            RectangleEdge domainEdge = Plot.resolveDomainAxisLocation(plot.getDomainAxisLocation(), orientation);
            RectangleEdge rangeEdge = Plot.resolveRangeAxisLocation(plot.getRangeAxisLocation(), orientation);
            double j2DX = domainAxis.valueToJava2D(this.getX(), dataArea, domainEdge);
            double j2DY = rangeAxis.valueToJava2D(this.getY(), dataArea, rangeEdge);
            double startX;
            if(orientation == PlotOrientation.HORIZONTAL) {
                startX = j2DX;
                j2DX = j2DY;
                j2DY = startX;
            }
            startX = j2DX + Math.cos(this.angle) * this.baseRadius;
            double startY = j2DY + Math.sin(this.angle) * this.baseRadius;
            double endX = j2DX + Math.cos(this.angle) * this.tipRadius;
            double endY = j2DY + Math.sin(this.angle) * this.tipRadius;
            g2.setStroke(this.arrowStroke);
            g2.setPaint(this.arrowPaint);
            Line2D.Double line = new Line2D.Double(startX, startY, endX, endY);

            g2.draw(line);
            g2.setFont(this.getFont());
            g2.setPaint(this.getPaint());
            double labelX = j2DX + Math.cos(this.angle) * (this.baseRadius + this.labelOffset);
            double labelY = j2DY + Math.sin(this.angle) * (this.baseRadius + this.labelOffset);
            Rectangle2D rectangle2D=TextUtilities.drawAlignedString(this.getText(), g2,(float)labelX, (float)labelY, TextAnchor.BASELINE_CENTER);
            //添加边框
            g2.setColor(Color.WHITE);
            g2.setStroke(new BasicStroke(1.0F));
            rectangle2D.setFrame(rectangle2D.getX()-3,rectangle2D.getY()-2,rectangle2D.getWidth()+5,rectangle2D.getHeight()+2);
            g2.draw(rectangle2D);



        }

        public boolean equals(Object obj) {
            if(obj == null) {
                return false;
            } else if(obj == this) {
                return true;
            } else if(!(obj instanceof XYPointerFrameAnnotation)) {
                return false;
            } else if(!super.equals(obj)) {
                return false;
            } else {
                XYPointerFrameAnnotation that = (XYPointerFrameAnnotation)obj;
                return this.angle != that.angle?false:(this.tipRadius != that.tipRadius?false:(this.baseRadius != that.baseRadius?false:(this.arrowLength != that.arrowLength?false:(this.arrowWidth != that.arrowWidth?false:(!this.arrowPaint.equals(that.arrowPaint)?false:(!ObjectUtilities.equal(this.arrowStroke, that.arrowStroke)?false:this.labelOffset == that.labelOffset))))));
            }
        }

        public Object clone() throws CloneNotSupportedException {
            return super.clone();
        }

        private void writeObject(ObjectOutputStream stream) throws IOException {
            stream.defaultWriteObject();
            SerialUtilities.writePaint(this.arrowPaint, stream);
            SerialUtilities.writeStroke(this.arrowStroke, stream);
        }

        private void readObject(ObjectInputStream stream) throws IOException, ClassNotFoundException {
            stream.defaultReadObject();
            this.arrowPaint = SerialUtilities.readPaint(stream);
            this.arrowStroke = SerialUtilities.readStroke(stream);
        }



}
class PDFHeaderFooter implements HeaderFooterBuilder 
package pdf.kit.component;

import com.itextpdf.text.Document;
import com.itextpdf.text.Element;
import com.itextpdf.text.Font;
import com.itextpdf.text.Phrase;
import com.itextpdf.text.pdf.ColumnText;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfTemplate;
import com.itextpdf.text.pdf.PdfWriter;
import pdf.kit.component.builder.HeaderFooterBuilder;

/**
 * Created by fgm on 2017/4/22.
 * 页眉页脚定制工具
 */
public class PDFHeaderFooter implements HeaderFooterBuilder {
    /**
     * @param writer   PDF编写类
     * @param document PDF文档对象
     * @param data     业务数据
     * @param font     字体设置
     * @param template PDF模板
     * @description PDF页脚设置类
     */
    public void writeFooter(PdfWriter writer,
                            Document document,
                            Object data,
                            Font font,
                            PdfTemplate template) {
        if (data == null) {
            return;
        }
        int pageS = writer.getPageNumber();
        int currentPage = pageS - 1;
        if (currentPage <= 0) {
            return;
        }
        Phrase footer1 = new Phrase("页脚一", font);
        Phrase footer2 = new Phrase("页脚二" + "    " + currentPage + "/", font);

        PdfContentByte cb = writer.getDirectContent();
        ColumnText.showTextAligned(
                cb,
                Element.ALIGN_LEFT,
                footer1,
                (document.left() + 10),
                document.bottom() - 20,
                0);
        ColumnText.showTextAligned(
                cb,
                Element.ALIGN_RIGHT,
                footer2,
                (document.right() - 30),
                document.bottom() - 20, 0);

        //设置模板位置
        cb.addTemplate(template, document.right() - 30, document.bottom() - 20);

    }

    /**
     * @param writer   PDF编写类
     * @param document PDF文档对象
     * @param data     业务数据
     * @param font     字体设置
     * @param template PDF模板
     * @description PDF页头设置类
     */
    public void writeHeader(PdfWriter writer,
                            Document document,
                            Object data,
                            Font font,
                            PdfTemplate template) {

        ColumnText.showTextAligned(
                writer.getDirectContent(),
                Element.ALIGN_LEFT,
                new Phrase("我是页眉", font),
                document.left(),
                document.top() + 20, 0);


    }


    /**
     * @param writer   PDF编写类
     * @param document PDF文档对象
     * @param data     业务数据
     * @description 页头、页眉设置的模板替换类
     */
    public String getReplaceOfTemplate(PdfWriter writer, Document document, Object data) {
        int total = writer.getPageNumber() - 2;
        return total + "";
    }

}
PDFKit
package pdf.kit.component;

import com.itextpdf.text.Document;
import com.itextpdf.text.PageSize;
import com.itextpdf.text.pdf.PdfWriter;
import com.itextpdf.tool.xml.XMLWorkerFontProvider;
import com.itextpdf.tool.xml.XMLWorkerHelper;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.io.IOUtils;
import org.apache.commons.lang.StringUtils;
import org.xhtmlrenderer.pdf.ITextRenderer;
import pdf.kit.component.builder.HeaderFooterBuilder;
import pdf.kit.component.builder.PDFBuilder;
import pdf.kit.exception.PDFException;
import pdf.kit.util.FreeMarkerUtil;

import javax.servlet.http.HttpServletResponse;
import java.io.*;
import java.nio.charset.Charset;

@Slf4j
public class PDFKit {

    //PDF页眉、页脚定制工具
    private HeaderFooterBuilder headerFooterBuilder;
    private String saveFilePath;

    /**
     * @description     导出pdf到文件
     * @param fileName  输出PDF文件名
     * @param data      模板所需要的数据
     *
     */
    public String exportToFile(String fileName,Object data){

        String htmlData= FreeMarkerUtil.getContent(fileName, data);
        if(StringUtils.isEmpty(saveFilePath)){
            saveFilePath=getDefaultSavePath(fileName);
        }
        File file=new File(saveFilePath);
        if(!file.getParentFile().exists()){
            file.getParentFile().mkdirs();
        }
        FileOutputStream outputStream=null;
        try{
            //设置输出路径
            outputStream=new FileOutputStream(saveFilePath);
            //设置文档大小
            Document document = new Document(PageSize.A4);
            PdfWriter writer = PdfWriter.getInstance(document, outputStream);

            //设置页眉页脚
            PDFBuilder builder = new PDFBuilder(headerFooterBuilder,data);
            builder.setPresentFontSize(10);
            writer.setPageEvent(builder);

            //输出为PDF文件
            convertToPDF(writer,document,htmlData);
        }catch(Exception ex){
            throw new PDFException("PDF export to File fail",ex);
        }finally{
            IOUtils.closeQuietly(outputStream);
        }
        return saveFilePath;

    }




    /**
     * 生成PDF到输出流中(ServletOutputStream用于下载PDF)
     * @param ftlPath ftl模板文件的路径(不含文件名)
     * @param data 输入到FTL中的数据
     * @param response HttpServletResponse
     * @return
     */
    public  OutputStream exportToResponse(String ftlPath,Object data,
                                                     HttpServletResponse response){

        String html= FreeMarkerUtil.getContent(ftlPath,data);

        try{
            OutputStream out = null;
            ITextRenderer render = null;
            out = response.getOutputStream();
            //设置文档大小
            Document document = new Document(PageSize.A4);
            PdfWriter writer = PdfWriter.getInstance(document, out);
            //设置页眉页脚
            PDFBuilder builder = new PDFBuilder(headerFooterBuilder,data);
            writer.setPageEvent(builder);
            //输出为PDF文件
            convertToPDF(writer,document,html);
            return out;
        }catch (Exception ex){
            throw  new PDFException("PDF export to response fail",ex);
        }

    }

    /**
     * @description PDF文件生成
     */
    private  void convertToPDF(PdfWriter writer,Document document,String htmlString){
        //获取字体路径
        String fontPath=getFontPath();
        document.open();
        try {
            XMLWorkerHelper.getInstance().parseXHtml(writer,document,
                    new ByteArrayInputStream(htmlString.getBytes()),
                    XMLWorkerHelper.class.getResourceAsStream("/default.css"),
                    Charset.forName("UTF-8"),new XMLWorkerFontProvider(fontPath));
        } catch (IOException e) {
            e.printStackTrace();
            throw new PDFException("PDF文件生成异常",e);
        }finally {
            document.close();
        }

    }
    /**
     * @description 创建默认保存路径
     */
    private  String  getDefaultSavePath(String fileName){
        String classpath=PDFKit.class.getClassLoader().getResource("").getPath();
        String saveFilePath=classpath+"pdf/"+fileName;
        File f=new File(saveFilePath);
        if(!f.getParentFile().exists()){
            f.mkdirs();
        }
        return saveFilePath;
    }

    /**
     * @description 获取字体设置路径
     */
    public static String getFontPath() {
        String classpath=PDFKit.class.getClassLoader().getResource("").getPath();
        String fontpath=classpath+"fonts";
        return fontpath;
    }

    public HeaderFooterBuilder getHeaderFooterBuilder() {
        return headerFooterBuilder;
    }

    public void setHeaderFooterBuilder(HeaderFooterBuilder headerFooterBuilder) {
        this.headerFooterBuilder = headerFooterBuilder;
    }
    public String getSaveFilePath() {
        return saveFilePath;
    }

    public void setSaveFilePath(String saveFilePath) {
        this.saveFilePath = saveFilePath;
    }




}
BaseException
package pdf.kit.exception;

/**
 * Created by fgm on 2017/4/22.
 */
public class BaseException extends RuntimeException {
    public int errorCode;
    public String errorMsg;

    public BaseException(){
        super("运行时异常");
    }
    public BaseException(int errorCode, String errorMsg){
        super(errorMsg);
        this.errorCode=errorCode;
        this.errorMsg=errorMsg;
    }
    public BaseException(String errorMsg){
        super(errorMsg);
        this.errorCode=500;
        this.errorMsg=errorMsg;
    }
    public BaseException(String errorMsg, Exception e){
        super(errorMsg,e);
        this.errorCode=500;
        this.errorMsg=errorMsg;
    }


}
FreeMarkerException
package pdf.kit.exception;

/**
 * Created by fgm on 2017/4/22.
 */
public class FreeMarkerException extends BaseException {
    public FreeMarkerException(){
        super("FreeMarker异常");
    }

    public FreeMarkerException(int errorCode, String errorMsg){
        super(errorMsg);
        this.errorCode=errorCode;
        this.errorMsg=errorMsg;
    }
    public FreeMarkerException(String errorMsg){
        super(errorMsg);
        this.errorCode=500;
        this.errorMsg=errorMsg;
    }
    public FreeMarkerException(String errorMsg, Exception e){
        super(errorMsg,e);
        this.errorCode=500;
        this.errorMsg=errorMsg;
    }
}
PDFException
package pdf.kit.exception;

/**
 * Created by fgm on 2017/4/22.
 */
public class PDFException extends BaseException {

    public PDFException(){
        super("PDF异常");
    }

    public PDFException(int errorCode, String errorMsg){
        super(errorMsg);
        this.errorCode=errorCode;
        this.errorMsg=errorMsg;
    }
    public PDFException(String errorMsg){
        super(errorMsg);
        this.errorCode=500;
        this.errorMsg=errorMsg;
    }
    public PDFException(String errorMsg, Exception e){
        super(errorMsg,e);
        this.errorCode=500;
        this.errorMsg=errorMsg;
    }


}
FontUtil
package pdf.kit.util;

import com.google.common.collect.Maps;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang.exception.ExceptionUtils;

import java.awt.*;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.util.Map;

/**
 * Created by fgm on 2017/4/22.
 */
@Slf4j
public class FontUtil {

    private static  final Map<String,Font> fontCache= Maps.newConcurrentMap();

    public static String getFontPath(String fontName){
        String classPath=FontUtil.class.getClassLoader().getResource("").getPath();
        return classPath+"/fonts/"+fontName;
    }

    /**
     * @description 加载自定义字体
     */
    public static Font loadFont(String fontFileName, int style, float fontSize) throws IOException {
        FileInputStream fis=null;
        String key=fontFileName+"|"+style;
        Font dynamicFont=fontCache.get(key);
        if(dynamicFont==null){
            try{
                File file = new File(fontFileName);
                fis= new FileInputStream(file);
                dynamicFont = Font.createFont(style, fis);
                fontCache.put(key,dynamicFont);

            }catch(Exception e){
                return new Font("宋体", Font.PLAIN, 14);
            }finally{
                fis.close();
            }
        }
        Font dynamicFontPt = dynamicFont.deriveFont(fontSize);

        return dynamicFontPt;


    }
    public static Font getFont(int style,float fontSize){
        try {
            String fontPath = FontUtil.getFontPath("SIMLI.TTF");
            Font font = loadFont(fontPath,style,fontSize);
            return font;
        } catch (IOException e) {
            log.error("字体加载异常{}", ExceptionUtils.getFullStackTrace(e));
        }
        return null;
    }


}
FreeMarkerUtil
package pdf.kit.util;

import com.google.common.collect.Maps;
import freemarker.cache.FileTemplateLoader;
import freemarker.template.Configuration;
import freemarker.template.Template;
import freemarker.template.TemplateExceptionHandler;
import org.apache.commons.lang.StringUtils;
import pdf.kit.component.PDFKit;
import pdf.kit.exception.FreeMarkerException;
import pdf.kit.exception.PDFException;

import java.io.File;
import java.io.IOException;
import java.io.StringWriter;
import java.util.Map;

/**
 * Created by fgm on 2017/4/22.
 * FREEMARKER 模板工具类
 */
public class FreeMarkerUtil {

    private static final String WINDOWS_SPLIT = "\\";

    private static final String UTF_8="UTF-8";

    private static Map<String,FileTemplateLoader> fileTemplateLoaderCache=Maps.newConcurrentMap();

    private static  Map<String,Configuration> configurationCache= Maps.newConcurrentMap();

    public static Configuration getConfiguration(String templateFilePath){
        if(null!=configurationCache.get(templateFilePath)){
            return configurationCache.get(templateFilePath);
        }
        Configuration config = new Configuration(Configuration.VERSION_2_3_25);
        config.setDefaultEncoding(UTF_8);
        config.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER);
        config.setLogTemplateExceptions(false);
        FileTemplateLoader fileTemplateLoader=null;
        if(null!=fileTemplateLoaderCache.get(templateFilePath)){
            fileTemplateLoader=fileTemplateLoaderCache.get(templateFilePath);
        }
        try {
            fileTemplateLoader=new FileTemplateLoader(new File(templateFilePath));
            fileTemplateLoaderCache.put(templateFilePath,fileTemplateLoader);
        } catch (IOException e) {
            throw new FreeMarkerException("fileTemplateLoader init error!",e);
        }
        config.setTemplateLoader(fileTemplateLoader);
        configurationCache.put(templateFilePath,config);
        return config;

    }


    /**
     * @description 获取模板
     */
    public static String getContent(String fileName, Object data) {
        String templatePath = getPDFTemplatePath(fileName);
        String templateFileName = getTemplateName(templatePath);
        String templateFilePath = getTemplatePath(templatePath);
        if (StringUtils.isEmpty(templatePath)) {
            throw new FreeMarkerException("templatePath can not be empty!");
        }
        try {
            Template template=getConfiguration(templateFilePath).getTemplate(templateFileName);
            StringWriter writer = new StringWriter();
            template.process(data, writer);
            writer.flush();
            return  writer.toString();
        } catch (Exception ex) {
            throw new FreeMarkerException("FreeMarkerUtil process fail", ex);
        }
    }





    private static String getTemplatePath(String templatePath) {
        if (StringUtils.isEmpty(templatePath)) {
            return "";
        }
        if (templatePath.contains(WINDOWS_SPLIT)) {
            return templatePath.substring(0, templatePath.lastIndexOf(WINDOWS_SPLIT));
        }
        return templatePath.substring(0, templatePath.lastIndexOf("/"));
    }

    private static String getTemplateName(String templatePath) {
        if (StringUtils.isEmpty(templatePath)) {
            return "";
        }
        if (templatePath.contains(WINDOWS_SPLIT)) {
            return templatePath.substring(templatePath.lastIndexOf(WINDOWS_SPLIT) + 1);
        }
        return templatePath.substring(templatePath.lastIndexOf("/") + 1);
    }

    /**
     * @param fileName PDF文件名    (hello.pdf)
     * @return 匹配到的模板名
     * @description 获取PDF的模板路径,
     * 默认按照PDF文件名匹对应模板
     */
    public static String getPDFTemplatePath(String fileName) {
        String classpath = PDFKit.class.getClassLoader().getResource("").getPath();
        String templatePath = classpath + "/templates";
        File file = new File(templatePath);
        if (!file.isDirectory()) {
            throw new PDFException("PDF模板文件不存在,请检查templates文件夹!");
        }
        String pdfFileName = fileName.substring(0, fileName.lastIndexOf("."));
        File defaultTemplate = null;
        File matchTemplate = null;
        for (File f : file.listFiles()) {
            if (!f.isFile()) {
                continue;
            }
            String templateName = f.getName();
            if (templateName.lastIndexOf(".ftl") == -1) {
                continue;
            }
            if (defaultTemplate == null) {
                defaultTemplate = f;
            }
            if (StringUtils.isEmpty(fileName) && defaultTemplate != null) {
                break;
            }
            templateName = templateName.substring(0, templateName.lastIndexOf("."));
            if (templateName.toLowerCase().equals(pdfFileName.toLowerCase())) {
                matchTemplate = f;
                break;
            }
        }
        if (matchTemplate != null) {
            return matchTemplate.getAbsolutePath();
        }
        if (defaultTemplate != null) {
            return defaultTemplate.getAbsolutePath();
        }

        return null;

    }


}
default.css
pre, tt, code, kbd, samp {
	font-family: Courier;
	font-size: 9pt;
	line-height: 12pt;
}
dt {
	margin: 0;
}
/* styles below are taken from Mozilla Defaults */
body {
    margin: 10%;
    font-size: 12pt;
}
p, dl, multicol {
    margin: 1em 0;
}
dd {
    margin-left: 40px;
    margin-bottom: 0;
    margin-right: 0;
    margin-top: 0;
}
blockquote, figure {
    margin: 1em 40px;
}
center {
    display: block;
    text-align: center;
}
blockquote[type="cite"] {
    border: 3px solid;
    padding-left: 1em;
    border-color: blue;
    border-width: thin;
    margin: 1em 0;
}
h1 {
    font-size: 2em;
    font-weight: bold;
    margin: 0.67em 0;
}
h2 {
    font-size: 1.5em;
    font-weight: bold;
    margin: 0.83em 0;
}
h3 {
    font-size: 1.17em;
    font-weight: bold;
    margin: 1em 0;
}
h4{
    font-size: 1em;
    font-weight: bold;
    margin: 1.33em 0;
}
h5 {
    font-size: 0.83em;
    font-weight: bold;
    margin: 1.67em 0;
}
h6 {
    font-size: 0.67em;
    font-weight: bold;
    margin: 2.33em 0;
}
listing {
    font-size: medium;
    margin: 1em 0;
    white-space: pre;
}
xmp, pre, plaintext {
    margin: 1em 0;
    white-space: pre;
}
table {
    margin-bottom: 0;
    margin-top: 0;
    margin-left: 0;
    margin-right: 0;
    text-indent: 0;
}
caption {
    text-align: center;
}
tr {
    vertical-align: inherit;
}
tbody {
    vertical-align: middle;
}
thead {
    vertical-align: middle;
}
tfoot {
    vertical-align: middle;
}
table > tr {
    vertical-align: middle;
}
td {
    padding: 1px;
    text-align: inherit;
    vertical-align: inherit;
}
th {
    display: table-cell;
    font-weight: bold;
    padding: 1px;
    vertical-align: inherit;
}
sub {
    font-size: smaller;
    vertical-align: sub;
}
sup {
    font-size: smaller;
    vertical-align: super;
}

nobr {
    white-space: nowrap;
}
mark {
    background: none repeat scroll 0 0 yellow;
    color: black;
}
abbr[title], acronym[title] {
    border-bottom: 1px dotted;
}
ul, menu, dir {
    list-style-type: disc;
}

ul li ul {
    list-style-type: circle;
}

ol {
    list-style-type: decimal;
}
hr {
    color: gray;
    height: 2px;
}

  • 0
    点赞
  • 8
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
通过使用Freemarker,可以将XML文件换为PDF。首先,我们需要将Word模板另存为XML格式,并将文件后缀名改为.ftl。然后,在.ftl文件中,使用占位符将相关变量填充进去。接下来,通过赋值给这些变量,可以使用Freemarker将这些值填充到对应的位置上。最后,将此XML文件换为流,并使用Aspose将流换为PDF文件即可。 需要注意的是,对于不包含宏、函数等的Word或Excel文件,也可以使用Freemarker以模板的方式动态生成这些文件。对于Word文件,可以将其另存为XML格式,然后使用Freemarker进行动态编辑;对于Excel文件,暂时不支持直接使用模板动态生成xlsx文件。 具体操作步骤如下: 1. 将Word文档另存为XML文档,并使用文本编辑器(如Notepad)打开XML文件。 2. 将需要修改的内容改为变量,并保存此文档。 3. 将XML文档的后缀名改为.ftl,即将其换为Freemarker模板文档。 4. 添加Freemarker依赖并进行配置。 5. 将.ftl模板文件换为流。 6. 使用Aspose将流换为PDF文件。 通过以上步骤,我们可以成功地将XML文件换为PDF文件,实现Freemarker XMLPDF的功能。<span class="em">1</span><span class="em">2</span><span class="em">3</span> #### 引用[.reference_title] - *1* *3* [利用aspose把freemarker模板换为PDF](https://blog.csdn.net/weixin_44117635/article/details/122605083)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] - *2* [JAVA项目代码绘制PDF和通过freemarker动态生成PDF](https://download.csdn.net/download/hhao0503/87824768)[target="_blank" data-report-click={"spm":"1018.2226.3001.9630","extra":{"utm_source":"vip_chatgpt_common_search_pc_result","utm_medium":"distribute.pc_search_result.none-task-cask-2~all~insert_cask~default-1-null.142^v93^chatsearchT3_1"}}] [.reference_item style="max-width: 50%"] [ .reference_list ]

“相关推荐”对你有帮助么?

  • 非常没帮助
  • 没帮助
  • 一般
  • 有帮助
  • 非常有帮助
提交
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

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

余额充值