Java之生成条形码、PDF、HTML
关于Java生成HTML,可参考我的这篇文章:FreeMarker之根据模型生成HTML代码
当然了,该篇文章也会给你很多启发,比如,根据html生成html,大家不要小看这个,著名的WordPress博客文章,本质上就是这个机制,每发表一篇文章相当于新生成的一个HTML,内容不一样,样式基本是一致的。
下面进入正题:
一、导入Maven依赖
<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.foresee</groupId> <artifactId>mypdf</artifactId> <version>0.0.1-SNAPSHOT</version> <dependencies> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itext-asian</artifactId> <version>5.2.0</version> </dependency> <dependency> <groupId>com.itextpdf</groupId> <artifactId>itextpdf</artifactId> <version>5.4.2</version> </dependency> <dependency> <groupId>org.xhtmlrenderer</groupId> <artifactId>core-renderer</artifactId> <version>R8</version> </dependency> <dependency> <groupId>org.freemarker</groupId> <artifactId>freemarker</artifactId> <version>2.3.23</version> </dependency> <dependency> <groupId>net.sf.barcode4j</groupId> <artifactId>barcode4j-light</artifactId> <version>2.0</version> </dependency> </dependencies> </project>
二、编写Java代码
package com.beck.util; import java.awt.image.BufferedImage; import java.io.ByteArrayOutputStream; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStream; import org.krysalis.barcode4j.HumanReadablePlacement; import org.krysalis.barcode4j.impl.code39.Code39Bean; import org.krysalis.barcode4j.output.bitmap.BitmapCanvasProvider; import org.krysalis.barcode4j.tools.UnitConv; import sun.misc.BASE64Encoder; public class BarcodeUtil { public static final String IMG_TYPE_PNG="image/png"; public static final String IMG_TYPE_GIF="image/gif"; public static final String IMG_TYPE_JPEG="image/jpeg"; public static String generateToBase64(String msg,String imgType) throws IOException { ByteArrayOutputStream ous = new ByteArrayOutputStream(); generateToStream(msg,imgType, ous); BASE64Encoder encoder = new BASE64Encoder(); return encoder.encode(ous.toByteArray()); } public static File generateToFile(String msg,String imgType, File file) throws IOException { FileOutputStream out = new FileOutputStream(file); try { generateToStream(msg,imgType, out); } finally { if (out != null) { out.close(); } } return file; } public static byte[] generateToByte(String msg,String imgType) throws IOException { ByteArrayOutputStream out = new ByteArrayOutputStream(); try { generateToStream(msg,imgType, out); return out.toByteArray(); } finally { if (out != null) { out.close(); } } } public static void generateToStream(String msg, String imgType,OutputStream out) throws IOException { if (msg == null || out == null) { return; } Code39Bean bean = new Code39Bean(); // 精细度 int dpi = 150; // module宽度 double moduleWidth = UnitConv.in2mm(1.0f / dpi); bean.setModuleWidth(moduleWidth); // 不显示文字 bean.setMsgPosition(HumanReadablePlacement.HRP_NONE); bean.setHeight(5); bean.setWideFactor(3); bean.doQuietZone(false); // 输出到流 BitmapCanvasProvider canvas = new BitmapCanvasProvider(out, imgType, dpi, BufferedImage.TYPE_BYTE_BINARY, false, 0); // 生成条形码 bean.generateBarcode(canvas, msg); // 结束绘制 canvas.finish(); } }
package com.beck.util; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.OutputStreamWriter; import java.io.StringWriter; import java.io.Writer; import java.util.HashMap; import java.util.Locale; import java.util.Map; import freemarker.template.Configuration; import freemarker.template.Template; public class FreemarkerTemplate { private final Configuration configuration = new Configuration( Configuration.VERSION_2_3_23); private String charset; public FreemarkerTemplate(String charset) { this.charset = charset; configuration.setEncoding(Locale.CHINA, charset); configuration.setClassicCompatible(true);//处理空值为空字符串 } public void setTemplateClassPath(Class resourceLoaderClass, String basePackagePath) { configuration.setClassForTemplateLoading(resourceLoaderClass, basePackagePath); } public void setTemplateDirectoryPath(String templatePath) throws IOException { configuration.setDirectoryForTemplateLoading(new File(templatePath)); } public void processToStream(String templateFileName, Map<String, Object> dataMap, Writer writer) throws Throwable { Template template = configuration.getTemplate(templateFileName); template.process(dataMap, writer); } public void processToFile(String templateFileName, Map<String, Object> dataMap, File outFile) throws Throwable { Writer writer = new OutputStreamWriter(new FileOutputStream(outFile), charset); try { processToStream(templateFileName, dataMap, writer); } catch (Throwable e) { e.printStackTrace(); } finally { if (writer != null) { writer.close(); } } } public String processToString(String templateFileName, Map<String, Object> dataMap) throws Throwable { Writer writer = new StringWriter(2048); try { processToStream(templateFileName, dataMap, writer); return writer.toString(); } finally { if (writer != null) { writer.close(); } } } }
package com.beck.util; import java.io.File; import java.io.FileOutputStream; import java.io.OutputStream; import org.xhtmlrenderer.pdf.ITextFontResolver; import org.xhtmlrenderer.pdf.ITextRenderer; import com.itextpdf.text.pdf.BaseFont; public class PDFUtils { private static String fontBasePath=PDFUtils.class.getResource("/").getPath()+"font/"; public static void htmlFileToPDFStream(File htmlFile, OutputStream output, File imageBaseURL) throws Throwable { if (htmlFile == null) { throw new RuntimeException("htmlFile is null"); } if (output == null) { throw new RuntimeException("output is null"); } // 生成ITextRenderer实例 ITextRenderer renderer = new ITextRenderer(); // 关联html页面 renderer.setDocument(htmlFile.toURI().toURL().toString()); // 设置获取图片的基本路径 renderer.getSharedContext().setBaseURL( imageBaseURL.toURI().toURL().toString()); // 设置字体路径,必须和html设置一致 ITextFontResolver fontResolver = renderer.getFontResolver(); fontResolver.addFont(fontBasePath+"msyh.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); fontResolver.addFont(fontBasePath+"msyhl.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); fontResolver.addFont(fontBasePath+"msyhbd.ttc", BaseFont.IDENTITY_H, BaseFont.NOT_EMBEDDED); renderer.layout(); renderer.createPDF(output); } public static void htmlFileToPDFFile(File htmlFile, File pdfFile, File imageBaseURL) throws Throwable { OutputStream output = new FileOutputStream(pdfFile); try { htmlFileToPDFStream(htmlFile, output, imageBaseURL); } finally { if (output != null) { output.close(); } } } }
package com.beck.util; public class Receipt { private String receiptType; private double amount; public String getReceiptType() { return receiptType; } public void setReceiptType(String receiptType) { this.receiptType = receiptType; } public double getAmount() { return amount; } public void setAmount(double amount) { this.amount = amount; } }
三、在src/main/resources建立HTML页面和相关的文件夹
建立font、images、templates文件夹
文件夹如图所示:
html代码:
<?xml version="1.0" encoding="UTF-8" ?> <!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" /> <title>报销单</title> <style type="text/css" rel="stylesheet"> * { margin: 0; padding: 0; } body { font-family: "Microsoft YaHei UI"; font-size: 12px; max-width: 555px; margin: 0 auto; padding: 20px; } h1 { text-align: center; font-size: 18px; color: #000; font-weight: bold; } .comptitle { height: 66px; } .comptitle h2 { float: left; height: 66px; line-height: 66px; font-size: 14px; color: #000; font-weight: bold; } .barcode { width: 162px; float: right; padding: 10px 0; text-align: center; } .barcode img { width: 162px; height: 30px; display: table-cell; vertical-align: middle; margin-bottom: 5px; } .barcode h3 { font-size: 12px; color: #000; } .stafftbl, .detailstbl, .approverrd { border-collapse: collapse; border: 1px solid #f3f3f3; } .stafftbl th, .stafftbl td, .detailstbl th, .detailstbl td, .approverrd th, .approverrd td { height: 20px; line-height: 20px; border: 1px solid #f3f3f3; } .stafftbl th { font-weight: normal; background: #f8f9fd; text-align: right; color: #777; padding-right: 12px; } .stafftbl td { padding-left: 10px; } .wrap p { color: #000; margin: 12px 0 10px; } .detailstbl th, .approverrd th { font-weight: normal; color: #777; background: #f8f9fd; border: none; } .detailstbl td, .approverrd td { text-align: center; } .sum { background: #f8f9fd; color: #777; } .detailstbl td.sumdata { text-align: right; padding-right: 20px; font-weight: bold; } </style> </head> <body> <div class="wrap"> <h1>费用报销单</h1> <div class="comptitle"> <h2>*****有限公司</h2> <div class="barcode"><img src="${barcodeImg}" alt=""></img> <h3>${barcode}</h3> </div> </div> <table width="100%" border="0" cellspacing="0" cellpadding="0" class="stafftbl"> <tbody> <tr> <th width="18%">报销人</th> <td width="32%">${userName}</td> <th width="18%">填写日期</th> <td>${submitDate}</td> </tr> <tr> <th>费用所属部门</th> <td colspan="3">${deptName}</td> </tr> <tr> <th>所属项目</th> <td colspan="3">运营平台2.0</td> </tr> <tr> <th>报销事由</th> <td colspan="3">${reimReason}</td> </tr> </tbody> </table> <p>费用明细</p> <table width="100%" border="0" cellspacing="0" cellpadding="0" class="detailstbl"> <tbody> <tr> <th width="8%">序号</th> <th>发票类型</th> <th width="10%">发票张数</th> <th width="12%">发票金额</th> <th width="12%">报销金额</th> <th>专票税额</th> <th width="28%">用途</th> </tr> <#list receiptList as item> <tr> <td >${list_index+1}</td> <td>${item.receiptType}</td> <td>1</td> <td>¥${item.amount}</td> <td>¥${item.amount}</td> <td> </td> <td> </td> </tr> </#list> <tr> <td colspan="2" class="sum">总报销金额(小写)</td> <td colspan="5" class="sumdata" >¥259.46</td> </tr> <tr> <td colspan="2" class="sum">总报销金额(大写)</td> <td colspan="5" class="sumdata" >贰佰伍拾玖元肆角陆分</td> </tr> </tbody> </table> <p>审批记录</p> <table width="100%" border="0" cellspacing="0" cellpadding="0" class="approverrd"> <tbody> <tr> <th width="8%">序号</th> <th>节点名称</th> <th>电子签名</th> <th>签名日期</th> <th width="8%">操作</th> <th width="25%">备注</th> </tr> <tr> <td>1</td> <td>提交</td> <td>张三/平台支撑部</td> <td>2018-09-18 16:18:41</td> <td>提交</td> <td> </td> </tr> </tbody> </table> </div> </body> </html>
图片要求必须符合png、jpg等格式,只要是这些格式的都行。
四、编写测试类
package com.beck.util; import java.io.File; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; public class Test { public static void main(String[] args) throws Throwable { String barcode = "12345678910"; String uuid=UUID.randomUUID().toString(); String htmlBasePath="E://Demo//workspace//mypdf//src//main//resources//template";//html的根目录 String tempathBasePath=Test.class.getResource("/").getPath()+"template/";//模板路径 String pdfBasePath="E://Demo//workspace//mypdf//src//main//resources//template";//生成的pdf目录 String barCodePath="E://Demo//workspace//mypdf//src//main//resources//images//"+uuid+".png"; String htmlPath=htmlBasePath+uuid+".html"; String pdfPath=pdfBasePath+uuid+".pdf"; //生成条形码 File barCodeFile=new File(barCodePath); BarcodeUtil.generateToFile(barcode,BarcodeUtil.IMG_TYPE_PNG,barCodeFile); System.out.println("生成条形码完成!"); //生成html FreemarkerTemplate tp=new FreemarkerTemplate("UTF-8"); tp.setTemplateDirectoryPath(tempathBasePath); //封装数据 start ,必须是Map Map<String, Object> dataMap = new HashMap<String, Object>(); dataMap.put("barcode",barcode); dataMap.put("barcodeImg",barCodePath); dataMap.put("userName", "游总"); dataMap.put("submitDate", "2018-09-18"); dataMap.put("deptName", "研发平台"); //费用明细 List<Receipt> receiptList=new ArrayList<Receipt>();//Receipt 必须是public类型的类 Receipt r1=new Receipt(); r1.setReceiptType("普通发票"); r1.setAmount(100); Receipt r2=new Receipt(); r2.setReceiptType("普通发票"); r2.setAmount(100); receiptList.add(r1); receiptList.add(r2); dataMap.put("receiptList", receiptList); //封装数据 end File htmlFile=new File(htmlPath); tp.processToFile("expense.html", dataMap,htmlFile); System.out.println("生成html完成!"); //生成pdf PDFUtils.htmlFileToPDFFile(htmlFile, new File(pdfPath), new File(htmlBasePath)); System.out.println("生成pdf完成!"); } }
运行结果如图所示(控制台打印这些就表示没有问题,如果控制台报错,常见错误就是文件找不到异常,改下路径就可以了,一般情况是没有其他错误):
pdf显示如图:
html显示如图:
条形码显示如图:
源码地址:https://github.com/youcong1996/study_simple_demo.git
如果实在上述代码,不能运行,大家可以将源码移植过来,运行效果是没有问题的。
【推荐】国内首个AI IDE,深度理解中文开发场景,立即下载体验Trae
【推荐】编程新体验,更懂你的AI,立即体验豆包MarsCode编程助手
【推荐】抖音旗下AI助手豆包,你的智能百科全书,全免费不限次数
【推荐】轻量又高性能的 SSH 工具 IShell:AI 加持,快人一步
· 如何编写易于单元测试的代码
· 10年+ .NET Coder 心语,封装的思维:从隐藏、稳定开始理解其本质意义
· .NET Core 中如何实现缓存的预热?
· 从 HTTP 原因短语缺失研究 HTTP/2 和 HTTP/3 的设计差异
· AI与.NET技术实操系列:向量存储与相似性搜索在 .NET 中的实现
· 周边上新:园子的第一款马克杯温暖上架
· Open-Sora 2.0 重磅开源!
· 分享 3 个 .NET 开源的文件压缩处理库,助力快速实现文件压缩解压功能!
· Ollama——大语言模型本地部署的极速利器
· [AI/GPT/综述] AI Agent的设计模式综述