生成电子发票的简单示例

思路

方法一:使用word制作一个表格(如下图),然后使用Adobe Acrobat DC操作此文件,编辑相关的表单项,设置各个表单的名称(百度DC的使用方法)

方法二: 直接制作一个相应尺寸大小的图片,(如下图),然后使用Adobe Acrobat DC操作此文件,编辑相关的表单项,,设置各个表单的名称。保存后会生成相应的pdf文件,然后使用下面提供的代码,即可向里面填充你需要的内容了。

db编辑表单

工具类

所需要的依赖

<dependency>
    <groupId>commons-io</groupId>
    <artifactId>commons-io</artifactId>
    <version>2.4</version>
</dependency>

<dependency>
    <groupId>commons-codec</groupId>
    <artifactId>commons-codec</artifactId>
    <version>1.10</version>
</dependency>
<dependency>
    <groupId>junit</groupId>
    <artifactId>junit</artifactId>
    <scope>test</scope>
</dependency>

<!-- itextpdf 依赖包start -->
<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itextpdf</artifactId>
    <version>5.5.10</version>
</dependency>

<dependency>
    <groupId>com.itextpdf</groupId>
    <artifactId>itext-asian</artifactId>
    <version>5.2.0</version>
</dependency>
<!-- itextpdf 依赖包end -->

<!--pdf 转图片 start -->
<dependency>
    <groupId>org.apache.pdfbox</groupId>
    <artifactId>pdfbox</artifactId>
    <version>2.0.21</version>
</dependency>

<dependency>
    <groupId>net.coobird</groupId>
    <artifactId>thumbnailator</artifactId>
    <version>0.4.12</version>
</dependency>
<!--pdf 转图片 end -->
<dependency>
    <groupId>cn.hutool</groupId>
    <artifactId>hutool-all</artifactId>
    <version>5.3.8</version>
</dependency>

代码

import cn.hutool.core.codec.Base64;
import com.itextpdf.text.Document;
import com.itextpdf.text.pdf.*;
import org.apache.commons.io.FileUtils;
import org.apache.commons.io.IOUtils;
import org.apache.pdfbox.pdmodel.PDDocument;
import org.apache.pdfbox.rendering.PDFRenderer;

import javax.imageio.ImageIO;
import java.awt.*;
import java.awt.image.BufferedImage;
import java.io.*;
import java.util.List;
import java.util.*;

/**
 * https://www.cnblogs.com/lookupthesky/p/9897373.html
 */
public class CoalTicketPDFUtil {


    public static void main(String[] args) throws Exception {
        HashMap map = new HashMap<String, String>();
        map.put("year","2020");
        map.put("month","08");
        map.put("day","08");
        map.put("numbercode","123456789");
        map.put("companyName", "鄂尔多斯市永恒华煤炭运销有限公司前进煤矿");
        map.put("carNumber", "晋B65821");
        map.put("coalTypeName", "褐煤");
        map.put("netWeight", "3111");
        map.put("coalVarietiesName", "原煤");
        map.put("univalent", "200");
        map.put("totalPrice", "6333");
        map.put("destination", "内蒙古鄂尔多斯市伊旗转龙湾装站内蒙古鄂尔多斯市伊旗转龙湾装站内蒙古鄂尔多斯市伊旗转龙湾装站内蒙古鄂尔多斯市伊尔多斯市伊旗转龙湾装站alllllll");
        map.put("teller", "开票员张三");
        map.put("roadChecker", "路检员李四");
        map.put("datetime", "时间时间时间时间时间时间");
        map.put("logoImg", Base64.encode(new File("d:/qrcode.jpg")));
        map.put("barCodeImg", map.get("logoImg"));
        map.put("qrImg", map.get("logoImg"));
        //生成条形码
        map.put("barCodeImg", BarcodeUtil.getImageBase64("6952069500011"));
        map.put("sealImg", Base64.encode(new File("d:/sealImg.gif")));


        String sourceFile = "d:/coal-ticket-template.pdf";
        String targetFile = "d:/coal-ticket-finish.pdf";
        String imageFilePath = "d:/coal-ticket-finish.png";

        generateAsPDFFile(map, FileUtils.openInputStream(new File(sourceFile)), targetFile);
//        System.out.println("获取pdf表单中的fieldNames:"+getTemplateFileFieldNames(sourceFile));
//        System.out.println("读取文件数组:"+fileBuff(sourceFile));
//        System.out.println("pdf转图片:"+pdf2Img(new File(targetFile),imageFilePath));
//      System.out.println(generateAsPDFImg(map,FileUtils.openInputStream(new File(sourceFile))));
    }


    /**
     * 生成的是pdf文件
     * @param fieldValueMap map中的参数填充pdf,map中的key和pdf表单中的field对应
     * @param templateFileInputStream  模板文件的输入流
     * @param fileName  生成文件的路径+名称
     */
    public static void generateAsPDFFile(Map<String, String> fieldValueMap, InputStream templateFileInputStream, String fileName){
        ByteArrayOutputStream fos = (ByteArrayOutputStream) fillParam(fieldValueMap, templateFileInputStream);
        try {
            FileUtils.writeByteArrayToFile(new File(fileName), fos.toByteArray());
        } catch (IOException e) {
            e.printStackTrace();
        }finally {
            IOUtils.closeQuietly(fos);
        }
    }
    /**
     * 生成pdf文件的 base64编码的字符串
     * @param fieldValueMap map中的参数填充pdf,map中的key和pdf表单中的field对应
     * @param templateFileInputStream 模板文件的输入流
     * @return
     */
    public static String generateAsPDFBase64(Map<String, String> fieldValueMap, InputStream templateFileInputStream) throws Exception{
        try (ByteArrayOutputStream fos = (ByteArrayOutputStream) fillParam(fieldValueMap, templateFileInputStream)){
            return Base64.encode(fos.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }

    /**
     * 生成pdf的图片格式,把图片转换成base64编码的字符串
     * @param fieldValueMap
     * @param templateFileInputStream
     * @return
     * @throws Exception
     */
    public static String generateAsPDFImg(Map<String, String> fieldValueMap, InputStream templateFileInputStream) throws Exception{
        try (ByteArrayOutputStream fos = (ByteArrayOutputStream) fillParam(fieldValueMap, templateFileInputStream)){
            ByteArrayOutputStream outputStream = (ByteArrayOutputStream)pdf2ImgOutputStream(fos.toByteArray());
            return Base64.encode(outputStream.toByteArray());
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }
    }
    /**
     * Description:使用map中的参数填充pdf,map中的key和pdf表单中的field对应 <br>
     * @param fieldValueMap  键值对
     * @param templateFileInputStream 模板文件的输入流
     * @return
     */
    private static OutputStream fillParam(Map<String, String> fieldValueMap, InputStream templateFileInputStream) {
        ByteArrayOutputStream fos = new ByteArrayOutputStream();
        try {
            PdfReader reader = null;
            PdfStamper stamper = null;
            BaseFont base = null;
            try {
                reader = new PdfReader(templateFileInputStream);
                stamper = new PdfStamper(reader, fos);
                stamper.setFormFlattening(true);
                //获取系统字体的路径
                String prefixFont = "";
                //获取系统类型
                String os = System.getProperties().getProperty("os.name");
                if (os.startsWith("win") || os.startsWith("Win")) {
                    //win下获取字体的路径
                    prefixFont = "C:\\Windows\\Fonts" + File.separator + "STSONG.TTF";
                } else {
                    //linux下获取字体的路径,注意该目录下如果没有需额外安装,如我用的是STSONG字体
                    prefixFont = "/usr/share/fonts/win_font" + File.separator + "STSONG.TTF";
                 }
                //base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                try{
                    base = BaseFont.createFont(prefixFont, BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED);
                }catch (Exception e){
                    base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
                }
                AcroFields acroFields = stamper.getAcroFields();
                for (String key : acroFields.getFields().keySet()) {
                    acroFields.setFieldProperty(key, "textfont", base, null);
                    acroFields.setFieldProperty(key, "textsize", new Float(35), null);
                }
                if (fieldValueMap != null) {
                    for (String fieldName : fieldValueMap.keySet()) {
                        acroFields.setField(fieldName, fieldValueMap.get(fieldName));
                    }
                }
            } catch (Exception e) {
                e.printStackTrace();
            } finally {
                if (stamper != null) {
                    try {
                        stamper.close();
                    } catch (Exception e) {
                        e.printStackTrace();
                    }
                }
                if (reader != null) {
                    reader.close();
                }
            }
            return fos;
        } catch (Exception e) {
            System.out.println("填充参数异常");
            throw e;
        }
    }

    /**
     * Description: 获取pdf表单中的fieldNames<br>
     * @author mk
     * @Date 2018-11-2 15:21 <br>
     * @Param
     * @return
     */
    public static Set<String> getTemplateFileFieldNames(String pdfFileName) {
        Set<String> fieldNames = new TreeSet<String>();
        PdfReader reader = null;
        try {
            reader = new PdfReader(pdfFileName);
            Set<String> keys = reader.getAcroFields().getFields().keySet();
            for (String key : keys) {
                int lastIndexOf = key.lastIndexOf(".");
                int lastIndexOf2 = key.lastIndexOf("[");
                fieldNames.add(key.substring(lastIndexOf != -1 ? lastIndexOf + 1 : 0, lastIndexOf2 != -1 ? lastIndexOf2 : key.length()));
            }
        } catch (IOException e) {
            e.printStackTrace();
        } finally {
            if (reader != null) {
                reader.close();
            }
        }

        return fieldNames;
    }


    /**
     * Description: 读取文件数组<br>
     * @author mk
     * @Date 2018-11-2 15:21 <br>
     * @Param
     * @return
     */
    public static byte[] fileBuff(String filePath) throws IOException {
        File file = new File(filePath);
        long fileSize = file.length();
        if (fileSize > Integer.MAX_VALUE) {
            //System.out.println("file too big...");
            return null;
        }
        FileInputStream fi = new FileInputStream(file);
        byte[] file_buff = new byte[(int) fileSize];
        int offset = 0;
        int numRead = 0;
        while (offset < file_buff.length && (numRead = fi.read(file_buff, offset, file_buff.length - offset)) >= 0) {
            offset += numRead;
        }
        // 确保所有数据均被读取
        if (offset != file_buff.length) {
            throw new IOException("Could not completely read file " + file.getName());
        }
        fi.close();
        return file_buff;
    }

    /**
     * Description: 合并pdf <br>
     * @author mk
     * @Date 2018-11-2 15:21 <br>
     * @Param
     * @return
     */
    public static void mergePdfFiles(String[] files, String savepath) {
        Document document = null;
        try {
            document = new Document(); //默认A4大小
            PdfCopy copy = new PdfCopy(document, new FileOutputStream(savepath));
            document.open();
            for (int i = 0; i < files.length; i++) {
                PdfReader reader = null;
                try {
                    reader = new PdfReader(files[i]);
                    int n = reader.getNumberOfPages();
                    for (int j = 1; j <= n; j++) {
                        document.newPage();
                        PdfImportedPage page = copy.getImportedPage(reader, j);
                        copy.addPage(page);
                    }
                } finally {
                    if (reader != null) {
                        reader.close();
                    }
                }
            }
        } catch (Exception e) {
            e.printStackTrace();
        } finally {
            //关闭PDF文档流,OutputStream文件输出流也将在PDF文档流关闭方法内部关闭
            if (document != null) {
                document.close();
            }

        }
    }



    /**
     * pdf转图片
     * @param file pdf
     * @return
     */
    public static boolean pdf2Img(File file,String imageFilePath) {
        try {
            //生成图片保存
            byte[] data = pdfToPic(PDDocument.load(file));
            File imageFile = new File(imageFilePath);
            ImageThumbUtils.thumbImage(data, 1, imageFilePath); //按比例压缩图片
            System.out.println("pdf转图片文件地址:" + imageFilePath);
            return true;
        } catch (Exception e) {
            System.out.println("pdf转图片异常:");
            e.printStackTrace();
        }

        return false;
    }

    /**
     * pdf转图片
     * @param originalImgData pdf的源文件
     * @return
     */
    public static OutputStream pdf2ImgOutputStream(byte[] originalImgData) throws Exception{
        try {
            byte[] data = pdfToPic(PDDocument.load(originalImgData));
            //按比例压缩图片
            OutputStream outputStream = ImageThumbUtils.thumbImage(data, 1);
            return outputStream;
        } catch (Exception e) {
            e.printStackTrace();
            throw e;
        }

    }

    /**
     * pdf转图片
     */
    private static byte[] pdfToPic(PDDocument pdDocument) {
        ByteArrayOutputStream baos = new ByteArrayOutputStream();
        List<BufferedImage> piclist = new ArrayList<BufferedImage>();
        try {
            PDFRenderer renderer = new PDFRenderer(pdDocument);
            for (int i = 0; i < pdDocument.getNumberOfPages(); i++) {//
                // 0 表示第一页,300 表示转换 dpi,越大转换后越清晰,相对转换速度越慢
                BufferedImage image = renderer.renderImageWithDPI(i, 108);
                piclist.add(image);
            }
            // 总高度 总宽度 临时的高度 , 或保存偏移高度 临时的高度,主要保存每个高度
            int height = 0, width = 0, _height = 0, __height = 0,
                    // 图片的数量
                    picNum = piclist.size();
            // 保存每个文件的高度
            int[] heightArray = new int[picNum];
            // 保存图片流
            BufferedImage buffer = null;
            // 保存所有的图片的RGB
            List<int[]> imgRGB = new ArrayList<int[]>();
            // 保存一张图片中的RGB数据
            int[] _imgRGB;
            for (int i = 0; i < picNum; i++) {
                buffer = piclist.get(i);
                heightArray[i] = _height = buffer.getHeight();// 图片高度
                if (i == 0) {
                    // 图片宽度
                    width = buffer.getWidth();
                }
                // 获取总高度
                height += _height;
                // 从图片中读取RGB
                _imgRGB = new int[width * _height];
                _imgRGB = buffer.getRGB(0, 0, width, _height, _imgRGB, 0, width);
                imgRGB.add(_imgRGB);
            }

            // 设置偏移高度为0
            _height = 0;
            // 生成新图片
            BufferedImage imageResult = new BufferedImage(width, height, BufferedImage.TYPE_INT_RGB);
            int[] lineRGB = new int[8 * width];
            int c = new Color(128, 128, 128).getRGB();
            for (int i = 0; i < lineRGB.length; i++) {
                lineRGB[i] = c;
            }
            for (int i = 0; i < picNum; i++) {
                __height = heightArray[i];
                // 计算偏移高度
                if (i != 0)
                    _height += __height;
                imageResult.setRGB(0, _height, width, __height, imgRGB.get(i), 0, width); // 写入流中

                // 模拟页分隔
                if (i > 0) {
                    imageResult.setRGB(0, _height + 2, width, 8, lineRGB, 0, width);
                }
            }
            // 写流
            ImageIO.write(imageResult, "jpg", baos);
        } catch (Exception e) {
            System.out.println("pdf转图片异常:");
            e.printStackTrace();
        } finally {
            IOUtils.closeQuietly(baos);
            try {
                pdDocument.close();
            } catch (Exception ignore) {
            }
        }

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