java生成PDF的几种方法

问题场景

总结一下用java生成PDF的方法:

A、itext-PdfStamper pdfStamper(俗称抠模板)

B、itext-Document document(正常代码撰写)

C、wkhtmltopdf(使用工具)

比较分析

方法 优点 缺点
A 代码简单 模板要先提供,且字段长度固定、不灵活
B 模板可根据代码调整、但样式不如C灵活 要维护的后台代码较多
C 模板样式可根据前端随意调整 要维护的前台代码较多

用到的资源

A/B: itext-pdfa-5.5.6.jar、itext-xtra-5.5.6.jar、itext-5.5.6.jar、itext-asian.jar

C: wkhtmltopdf-0.12.4、python2.7.14、pdfkit

举例:

A:

1、用PDF编辑器编辑好模板,留出空白等待程序填充。

2、程序生成与下载。

/**
     * 抠模板
     * @throws Exception
     */
    public void createAllPdf() throws Exception {
        //填充创建pdf
        PdfReader reader = null;
        PdfStamper stamp = null;
        try {
            reader = new PdfReader("E:/module.pdf");
            SimpleDateFormat simp = new SimpleDateFormat("yyyy-MM-dd");
            String times = simp.format(new Date()).trim();
            //创建生成报告名称
            String root = ServletActionContext.getRequest().getRealPath("/upload") + File.separator;
            if (!new File(root).exists())
                new File(root).mkdirs();
            File deskFile = new File(root, times + ".pdf");
            stamp = new PdfStamper(reader, new FileOutputStream(deskFile));
            //取出报表模板中的所有字段
            AcroFields form = stamp.getAcroFields();
            // 填充数据
            form.setField("name", "zhangsan");
            form.setField("sex", "男");
            form.setField("age", "15");

            //报告生成日期
            SimpleDateFormat dateformat = new SimpleDateFormat("yyyy-MM-dd");
            String generationdate = dateformat.format(new Date());
            form.setField("generationdate", generationdate);
            stamp.setFormFlattening(true);
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            if (stamp != null) {
                stamp.close();
            }
            if (reader != null) {
                reader.close();
            }
        }
    }

B: 常用功能推荐列表

ITEXT PDF文件的拆分与合并

ITEXT 表格的指定列合并-升级版

ITEXT 表格的指定列合并

ITEXT 实现背景色交替的三线表

ITEXT 页眉页脚页码三件套

ITEXT 目录生成的第三种方法(同时带有书签功能)

ITEXT-定位PDF中图片的坐标与页码

ITEXT-PDF彩色字体显示-支持中文

ITEXT-插入水印(itext-pdfa-5.5.6.jar)

直接上代码

package itext;

import com.itextpdf.text.*;
import com.itextpdf.text.pdf.*;

import java.io.FileNotFoundException;
import java.io.FileOutputStream;

/**
 * Created on 2017/5/16
 * Author: youxingyang.
 */
public class TableAndTitle {
    /**
     * @param args
     */
    public static void main(String[] args) throws Exception {
        String fileName = "tableAndTitle.pdf";
        TableAndTitle.test(fileName);
    }

    private static void test(String fileName) {
        Document document = new Document();
        try {
            PdfWriter.getInstance(document, new FileOutputStream(fileName));
            document.open();

            PdfPTable table = new PdfPTable(1);
            table.setKeepTogether(true);
            table.setSplitLate(false);

            PdfPTable table1 = new PdfPTable(1);
            PdfPCell cell0 = new PdfPCell();
            Paragraph p = new Paragraph("table title sample");
            p.setAlignment(1);
            p.setSpacingBefore(15f);
            cell0.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
            cell0.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);//然并卵
            cell0.setPaddingTop(-2f);//把字垂直居中
            cell0.setPaddingBottom(8f);//把字垂直居中
            cell0.addElement(p);
            cell0.setBorder(0);
            table1.addCell(cell0);

            PdfPTable pTable = new PdfPTable(table1);
            document.add(pTable);

            PdfPTable table2 = new PdfPTable(2);
            float border = 1.5f;
            for (int a = 0; a < 20; a++) {
                PdfPCell cell = new PdfPCell();
                Paragraph pp;
                if (a == 0 || a == 1) {
                    pp = str2ParaByTwoFont("tableTitle" + (a + 1), 9f, BaseColor.BLACK, Font.BOLD); //小五 加粗
                    cell.setBorderWidthBottom(border);
                    cell.setBorderWidthTop(border);
                } else {
                    if (a == 18 || a == 19) {
                        cell.setBorderWidthTop(0);
                        cell.setBorderWidthBottom(border);
                    } else {
                        cell.setBorderWidthBottom(0);
                        cell.setBorderWidthTop(0);
                    }
                    pp = str2ParaByTwoFont("tableContent" + (a - 1), 9f, BaseColor.BLACK); //小五
                }
                //设置间隔的背景色
                if ((a + 1) % 2 == 0) {
                    if (((a + 1) / 2) % 2 == 1) {
                        cell.setBackgroundColor(new BaseColor(128, 128, 255));
                    } else {
                        cell.setBackgroundColor(new BaseColor(128, 255, 255));
                    }
                } else {
                    if (((a + 1) / 2) % 2 == 1) {
                        cell.setBackgroundColor(new BaseColor(128, 255, 255));
                    } else {
                        cell.setBackgroundColor(new BaseColor(128, 128, 255));
                    }
                }
                pp.setAlignment(1);
                cell.setBorderWidthLeft(0);
                cell.setBorderWidthRight(0);
                cell.setHorizontalAlignment(PdfPCell.ALIGN_CENTER);
                cell.setVerticalAlignment(PdfPCell.ALIGN_MIDDLE);//然并卵
                cell.setPaddingTop(-2f);//把字垂直居中
                cell.setPaddingBottom(8f);//把字垂直居中
                cell.addElement(pp);
                table2.addCell(cell);
            }

            PdfPCell c1 = new PdfPCell();
            c1.setBorder(0);
            c1.addElement(table1);

            PdfPCell c2 = new PdfPCell();
            c2.setBorder(0);
            c2.addElement(table2);

            table.addCell(c1);
            table.addCell(c2);

            document.add(table);
            document.close();
        } catch (DocumentException | FileNotFoundException e) {
            e.printStackTrace();
        }
    }

    /**
     * 两种字体显示文字
     *
     * @param cont
     * @param size
     * @param color
     * @return
     */
    private static Paragraph str2ParaByTwoFont(String cont, float size, BaseColor color) {
        Paragraph res = new Paragraph();
        FontSelector selector = new FontSelector();
        //非汉字字体颜色
        Font f1 = FontFactory.getFont(FontFactory.TIMES_ROMAN, size);
        f1.setColor(color);
        //汉字字体颜色
        Font f2 = FontFactory.getFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, size);
        f2.setColor(color);

        selector.addFont(f1);
        selector.addFont(f2);
        Phrase ph = selector.process(cont);
        res.add(ph);
        return res;
    }

    /**
     * 两种字体显示文字
     *
     * @param cont
     * @param size
     * @param color
     * @param bold
     * @return
     */
    private static Paragraph str2ParaByTwoFont(String cont, float size, BaseColor color, int bold) {

        Paragraph res = new Paragraph();
        FontSelector selector = new FontSelector();
        //非汉字字体颜色
        Font f1 = FontFactory.getFont(FontFactory.TIMES_ROMAN, size);
        f1.setColor(color);
        f1.setStyle(bold);
        //汉字字体颜色
        Font f2 = FontFactory.getFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED, size);
        f2.setColor(color);
        f2.setStyle(bold);

        selector.addFont(f1);
        selector.addFont(f2);
        Phrase ph = selector.process(cont);
        res.add(ph);
        return res;
    }
}

C: 总思路:java调用python脚本,python下多线程调用了pdfkit(pdfkit 是 wkhtmltopdf 的Python封装包)

CODE: 里面有的简单方法未提供源码,自己写

1、java代码

/**
     * 多线程生成报告
     * @param map           样本及公司对应集合
     * @param productMap    样本及产品对应集合
     * @param sampleList    样本列表集合
     * @param storeDir      报告存储路径
     * @param url           源转换的页面url前缀
     * @param pyPre         python脚本存放的位置
     * @param uuid          本地任务唯一标识
     * @param storePrefix   url参数文件前缀
     * @return
     */
    private static int createMul(Map<String, String> map, Map<String, String> productMap,
                                 List<String> sampleList, String storeDir, String url, String pyPre, String uuid, String storePrefix) {
        String date = DateUtil.date2Str(new Date());
        StringBuilder pathTemp = new StringBuilder("");
        String companyId;
        String productCode;
        String cmd;
        int sum = 0;
        Map<String, String> sampleMap = new LinkedHashMap<>(sampleList.size());
        String paraFileName;
        try {
            String path;
            for (String sampleCode : sampleList) {
                companyId = map.get(sampleCode);
                productCode = productMap.get(sampleCode);
                pathTemp.append(storeDir).append(date).append("-").append(uuid).append(File.separator).append(companyId).append(File.separator).append(productCode);
                path = pathTemp.toString();
                pathTemp.setLength(0);
                File file = new File(path);
                if (!file.exists()) {
                    file.mkdirs();
                }
                path += File.separator + sampleCode + "-" + productCode + ".pdf";
                path = path.replace("\\", "/");
                sampleMap.put(sampleCode, path);
            }
            paraFileName = storePrefix + DateUtil.date2Str(new Date()) + "-" + EncryUtil.getUUID() + ".txt";
            boolean success = writeMapFile(sampleMap, paraFileName);
            if (success) {
                log.info("多线程生成报告参数: url: {}, paraFileName: {}", url, paraFileName);
                cmd = "python " + pyPre + "mul_queue.py -u " + url + " -f " + paraFileName;
                Process pr = Runtime.getRuntime().exec(cmd);
                BufferedReader in = new BufferedReader(new InputStreamReader(pr.getInputStream()));
                String result = null;
                String line;
                while ((line = in.readLine()) != null) {
                    result = line;
                    System.out.println("creating: " + result);
                    log.info("creating: {}", result);
                }
                if (result != null && result.contains("completed:")) {
                    sum = Integer.parseInt(result.split(":")[1]);
                }
                in.close();
                pr.waitFor();
            }
        } catch (Exception e) {
            e.printStackTrace();
            log.info("多线程生成报告出错: {} ", e.getMessage());
        }
        return sum;
    }

    /**
     * map写进文件里
     * // a = {'a': 'hangge', 'b': 'man', 'school': 'wust'}
     * @param sampleMap
     * @param paraFileName
     * @return
     */
    public static boolean writeMapFile(Map<String, String> sampleMap, String paraFileName) {
        boolean res = false;
        BufferedWriter bw = null;
        try {
            File file = new File(paraFileName);
            if (!file.exists()) {
                CommonUtil.createFile(paraFileName);
            }
            bw = new BufferedWriter(new OutputStreamWriter(new FileOutputStream(paraFileName)));
            if (sampleMap.size() > 0) {
                bw.write('{');
                int index = 0;
                for (String key : sampleMap.keySet()) {
                    bw.write('\'');
                    bw.write(key);
                    bw.write('\'');
                    bw.write(':');
                    bw.write('\'');
                    bw.write(sampleMap.get(key));
                    bw.write('\'');
                    if (index < sampleMap.size() - 1) {
                        bw.write(',');
                    }
                    index++;
                }
                bw.write('}');
                res = true;
            }
        } catch (Exception e) {
            e.printStackTrace();
        }try {
            if (bw != null) {
                bw.close();
            }
        } catch (IOException e) {
            e.printStackTrace();
        }
        return res;
    }

2、python脚本

import threading
import ast
from Queue import Queue

import pdfkit
import sys
import getopt
import codecs

class MyThread(threading.Thread):
    def __init__(self, q):
        super(MyThread, self).__init__()
        self.q = q

    def run(self):
        while True:
            url_path = self.q.get()
            url_in = url_path[0]
            path = url_path[1]
            createpdf(url=url_in, path=path)
            self.q.task_done()

def createpdf(url, path):
    options = {
        'margin-top': '0in',
        'margin-right': '0in',
        'margin-bottom': '0in',
        'margin-left': '0in',
        'encoding': "UTF-8",
        'javascript-delay': '200000',
    }
    num = 0
    compete = pdfkit.from_url(url, path, options=options)
    if compete:
        num = 1

    return num

if __name__ == '__main__':
    parameterList = sys.argv[1:]
    url = ''
    file_name = ''
    opts, args = getopt.getopt(parameterList, "u:f:", ['url=', 'file_name='])
    for opt, arg in opts:
        if opt in ("-u", "--url"):
            url = arg
        elif opt in ("-f", "--file_name"):
            file_name = arg

    # print('url:', url)
    # print('file_name:', file_name)

    sample_map = {}
    f = codecs.open(filename=file_name, mode="r+", encoding='utf-8')
    lines = f.readlines()
    sample_map_string = ''
    for line in lines:
        sample_map_string = line
        break

    sample_map = ast.literal_eval(sample_map_string)

    queue = Queue()
    size = len(sample_map)
    stable_num = 5
    if size > stable_num:
        size = stable_num

    for x in range(size):
        worker = MyThread(queue)
        worker.daemon = True
        worker.start()

    for i in sample_map.keys():
        url_path_list = [url + '?sample_sn=%s' % i, sample_map.get(i)]
        queue.put(url_path_list)

    queue.join()

    print "completed:" + bytes(len(sample_map))
©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 215,794评论 6 498
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,050评论 3 391
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 161,587评论 0 351
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 57,861评论 1 290
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 66,901评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 50,898评论 1 295
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 39,832评论 3 416
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,617评论 0 271
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,077评论 1 308
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,349评论 2 331
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,483评论 1 345
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,199评论 5 341
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 40,824评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,442评论 0 21
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,632评论 1 268
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,474评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,393评论 2 352

推荐阅读更多精彩内容