参考大佬们的代码:(部分)
JAVA利用Graphics2D生成电子签章
修改Jar包中的内容(替换、删除文件)+ aspose-words签名解除 + 配置maven引入
工具类如下:(直接拷贝可用,已根据博主需要做了一下细微调整)
package com.siboo.util;
import java.awt.BasicStroke;
import java.awt.Color;
import java.awt.Font;
import java.awt.Graphics2D;
import java.awt.RenderingHints;
import java.awt.Transparency;
import java.awt.font.FontRenderContext;
import java.awt.geom.AffineTransform;
import java.awt.geom.Ellipse2D;
import java.awt.geom.Rectangle2D;
import java.awt.image.BufferedImage;
import com.newtec.myqdp.server.utils.StringUtils;
public class Graphics2DUtil {
private static final int WIDTH = 450;//图片宽度
private static final int HEIGHT = 450;//图片高度
/**
*
* @Title: splitImage
* @Description: 分割图片
* @param image 图片BufferedImage流
* @param rows 分割行
* @param cols 分割列
* @return BufferedImage[] 返回分割后的图片流
*/
public static BufferedImage[] splitImage(BufferedImage image, int rows, int cols) {
// 分割成4*4(16)个小图
int chunks = rows * cols;
// 计算每个小图的宽度和高度
int chunkWidth = image.getWidth() / cols + 3;// 向右移动3
int chunkHeight = image.getHeight() / rows;
int count = 0;
BufferedImage[] imgs = new BufferedImage[chunks];
for (int x = 0; x < rows; x++) {
for (int y = 0; y < cols; y++) {
//设置小图的大小和类型
imgs[count] = new BufferedImage(chunkWidth, chunkHeight, BufferedImage.TYPE_INT_RGB);
//写入图像内容
Graphics2D gr = imgs[count].createGraphics();
// 增加下面代码使得背景透明
imgs[count] = gr.getDeviceConfiguration().createCompatibleImage(chunkWidth, chunkHeight, Transparency.TRANSLUCENT);
// gr.setBackground(Color.WHITE);// 背景为白色
// // 加上这句才算真正将背景颜色设置为透明色
// gr.clearRect(0, 0,chunkWidth,chunkHeight);
gr.dispose();
gr = imgs[count].createGraphics();
gr.drawImage(image, 0, 0,
chunkWidth, chunkHeight,
chunkWidth * y, chunkHeight * x,
chunkWidth * y + chunkWidth,
chunkHeight * x + chunkHeight, null);
gr.dispose();
count++;
}
}
return imgs;
}
/**
*
* @Title: startGraphics2D
* @Description: 生成公司电子公章
* @param message 公司名称
* @param centerName 公章类型,如:测试章
* @param year 时间
* @return BufferedImage 返回类型
*/
public static BufferedImage startGraphics2D(String message, String centerName, String year){
// 定义图像buffer
BufferedImage buffImg = new BufferedImage(WIDTH, HEIGHT, BufferedImage.TYPE_INT_RGB);
Graphics2D g = buffImg.createGraphics();
// 增加下面代码使得背景透明
buffImg = g.getDeviceConfiguration().createCompatibleImage(WIDTH, HEIGHT, Transparency.TRANSLUCENT);
g.dispose();
g = buffImg.createGraphics();
// 背景透明代码结束
g.setColor(Color.RED);
//设置锯齿圆滑
g.setRenderingHint(RenderingHints.KEY_ANTIALIASING, RenderingHints.VALUE_ANTIALIAS_ON);
//绘制圆
int radius = HEIGHT/3;//周半径
int CENTERX = WIDTH/2;//画图所出位置
int CENTERY = HEIGHT/2;//画图所处位置
Ellipse2D circle = new Ellipse2D.Double();
circle.setFrameFromCenter(CENTERX, CENTERY, CENTERX + radius, CENTERY + radius);
g.setStroke(new BasicStroke(10));//设置圆的宽度
g.draw(circle);
int num = 120;
int num1 = 40;
// num = 90;
num1 = 72;
//绘制中间的五角星
g.setFont(new Font("宋体", Font.BOLD, num));
g.drawString("★", CENTERX-(num/2), CENTERY+(num/3));
//添加姓名
g.setFont(new Font("宋体", Font.LAYOUT_LEFT_TO_RIGHT, 30));// 写入签名
g.drawString(centerName, CENTERX -(num1), CENTERY +(30+50));
//添加年份
g.setFont(new Font("宋体", Font.LAYOUT_LEFT_TO_RIGHT, 20));// 写入签名
g.drawString(year, CENTERX -(66), CENTERY +(30+78));
//根据输入字符串得到字符数组
char[] messages = message.toCharArray();
//输入的字数
int ilength = messages.length;
//设置字体属性
int fontsize = 40;
Font f = new Font("Serif", Font.BOLD, fontsize);
FontRenderContext context = g.getFontRenderContext();
Rectangle2D bounds = f.getStringBounds(message, context);
//字符宽度=字符串长度/字符数
double char_interval = (bounds.getWidth() / ilength);
//上坡度
double ascent = -bounds.getY() + 3;
int first = 0,second = 0;
boolean odd = false;
if (ilength%2 == 1) {
first = (ilength-1)/2;
odd = true;
} else {
first = (ilength)/2-1;
second = (ilength)/2;
odd = false;
}
double radius2 = radius - ascent;
double x0 = CENTERX;
double y0 = CENTERY - radius + ascent;
//旋转角度
double a = 2*Math.asin(char_interval/(2*radius2));
if (odd) {
g.setFont(f);
g.drawString(StringUtils.toString(messages[first]), (float)(x0 - char_interval/2), (float)y0);
//中心点的右边
for (int i=first+1;i<ilength;i++)
{
double aa = (i - first) * a;
double ax = radius2 * Math.sin(aa);
double ay = radius2 - radius2 * Math.cos(aa);
AffineTransform transform = AffineTransform.getRotateInstance(aa);//,x0 + ax, y0 + ay);
Font f2 = f.deriveFont(transform);
g.setFont(f2);
g.drawString(StringUtils.toString(messages[i]), (float)(x0 + ax - char_interval/2* Math.cos(aa)), (float)(y0 + ay - char_interval/2* Math.sin(aa)));
}
//中心点的左边
for (int i=first-1;i>-1;i--)
{
double aa = (first - i) * a;
double ax = radius2 * Math.sin(aa);
double ay = radius2 - radius2 * Math.cos(aa);
AffineTransform transform = AffineTransform.getRotateInstance(-aa);//,x0 + ax, y0 + ay);
Font f2 = f.deriveFont(transform);
g.setFont(f2);
g.drawString(StringUtils.toString(messages[i]), (float)(x0 - ax - char_interval/2* Math.cos(aa)), (float)(y0 + ay + char_interval/2* Math.sin(aa)));
}
} else {
//中心点的右边
for (int i=second;i<ilength;i++)
{
double aa = (i - second + 0.5) * a;
double ax = radius2 * Math.sin(aa);
double ay = radius2 - radius2 * Math.cos(aa);
AffineTransform transform = AffineTransform.getRotateInstance(aa);//,x0 + ax, y0 + ay);
Font f2 = f.deriveFont(transform);
g.setFont(f2);
g.drawString(StringUtils.toString(messages[i]), (float)(x0 + ax - char_interval/2* Math.cos(aa)), (float)(y0 + ay - char_interval/2* Math.sin(aa)));
}
//中心点的左边
for (int i=first;i>-1;i--)
{
double aa = (first - i + 0.5) * a;
double ax = radius2 * Math.sin(aa);
double ay = radius2 - radius2 * Math.cos(aa);
AffineTransform transform = AffineTransform.getRotateInstance(-aa);//,x0 + ax, y0 + ay);
Font f2 = f.deriveFont(transform);
g.setFont(f2);
g.drawString(StringUtils.toString(messages[i]), (float)(x0 - ax - char_interval/2* Math.cos(aa)), (float)(y0 + ay + char_interval/2* Math.sin(aa)));
}
}
return buffImg;
}
}
调用方法如下:
public static void main(String[] args) throws Exception{
Date dNow = new Date( );
SimpleDateFormat ft = new SimpleDateFormat ("yyyy年MM月dd日");
String year = ft.format(dNow);
BufferedImage image = startGraphics2D("广州思博商贸有限公司","合同专用章",year);
String filePath = "";
try {
filePath = "F:\\BaiduNetdiskDownload\\"+new Date().getTime()+".png";
ImageIO.write(image, "png", new File(filePath)); //将其保存在D:\\下,得有这个目录
} catch (Exception ex) {
ex.printStackTrace();
}
}
package com.siboo.util;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import org.apache.poi.xwpf.usermodel.XWPFDocument;
import org.apache.poi.xwpf.usermodel.XWPFParagraph;
import org.apache.poi.xwpf.usermodel.XWPFRun;
import org.apache.poi.xwpf.usermodel.XWPFTable;
import org.apache.poi.xwpf.usermodel.XWPFTableCell;
import org.apache.poi.xwpf.usermodel.XWPFTableRow;
import com.aspose.words.License;
import com.newtec.myqdp.server.utils.StringUtils;
public class WordUtil {
/**
*
* @Title: replaceAndGenerateWord
* @Description: 替换word中需要替换的特殊字符
* 优化后是为了分别减少map遍历,增加性能,前提是表格的替换的数据不一样,所以将两者分离处理(加空间,提性能)
* @param srcPath 需要替换的文档全路径
* @param destPath 替换后文档的保存路径
* @param contentMap {key:将要被替换的内容,value:替换后的内容}
* @param replaceTableMap {key:将要被替换的表格内容,value:替换后的表格内容}
* @return boolean 返回成功状态
*/
public static boolean replaceAndGenerateWord(String srcPath, String exportFile, Map<String, String> contentMap, Map<String, String> replaceTableMap) {
boolean bool = true;
try {
FileInputStream inputStream = new FileInputStream(srcPath);
XWPFDocument document = new XWPFDocument(inputStream);
// 替换段落中的指定文字
Iterator<XWPFParagraph> itPara = document.getParagraphsIterator();
while (itPara.hasNext()) {
XWPFParagraph paragraph = itPara.next();
commonCode(paragraph, contentMap);
}
// 替换表格中的指定文字
Iterator<XWPFTable> itTable = document.getTablesIterator();
while (itTable.hasNext()) {
XWPFTable table = itTable.next();
int rcount = table.getNumberOfRows();
for (int i = 0; i < rcount; i++) {
XWPFTableRow row = table.getRow(i);
List<XWPFTableCell> cells = row.getTableCells();
for (XWPFTableCell cell : cells) {
//单元格中有段落,得做段落处理
List<XWPFParagraph> paragraphs = cell.getParagraphs();
for (XWPFParagraph paragraph : paragraphs) {
commonCode(paragraph, replaceTableMap);
}
}
}
}
FileOutputStream outStream = new FileOutputStream(exportFile);
document.write(outStream);
outStream.close();
inputStream.close();
}catch (Exception e){
bool = false;
e.printStackTrace();
}
return bool;
}
/**
*
* @Title: commonCode
* @Description: 替换内容
* @param paragraph 被替换的文本信息
* @param contentMap {key:将要被替换的内容,value:替换后的内容}
*/
private static void commonCode(XWPFParagraph paragraph,Map<String, String> contentMap){
List<XWPFRun> runs = paragraph.getRuns();
for (XWPFRun run : runs) {
String oneparaString = run.getText(run.getTextPosition());
if (StringUtils.isStrNull(oneparaString)){
continue;
}
for (Map.Entry<String, String> entry : contentMap.entrySet()) {
oneparaString = oneparaString.replace(entry.getKey(), StringUtils.isStrNull(entry.getValue()) ? "--" : entry.getValue());
}
run.setText(oneparaString, 0);
}
}
/**
*
* @Title: getLicense
* @Description:验证license许可凭证
* @return boolean 返回验证License状态
*/
private static boolean getLicense() {
boolean result = true;
try {
// new License().setLicense(new FileInputStream(new File("D:\\develop\\template\\license.xml").getAbsolutePath()));
new License().setLicense(new FileInputStream(new File(FilesUtil.getLicensePath()).getAbsolutePath()));
} catch (Exception e) {
result = false;
e.printStackTrace();
}
return result;
}
/**
*
* @Title: wordConverterToPdf
* @Description: word转pdf(aspose转换)
* @param wordPath word 全路径,包括文件全称
* @param pdfPath pdf 保存路径,包括文件全称
* @return boolean 返回转换状态
*/
public static boolean wordConverterToPdf(String wordPath, String pdfPath) {
System.out.println("===================aspose开始转换=======================");
//开始时间
long start = System.currentTimeMillis();
boolean bool = false;
// 验证License,若不验证则转化出的pdf文档会有水印产生
if (!getLicense()) return bool;
try {
FileOutputStream os = new FileOutputStream(new File(pdfPath));// 新建一个pdf文档输出流
com.aspose.words.Document doc = new com.aspose.words.Document(wordPath);// Address是将要被转化的word文档
doc.save(os, com.aspose.words.SaveFormat.PDF);// 全面支持DOC, DOCX, OOXML, RTF HTML, OpenDocument, PDF, EPUB, XPS, SWF 相互转换
os.close();
bool = true;
} catch (Exception e) {
e.printStackTrace();
} finally {
System.out.println("(aspose)word转换PDF完成,用时"+(System.currentTimeMillis()-start)/1000.0+"秒");
}
return bool;
}
/**
*
* @Title: wordConverterToPdfJacob
* @Description: word转pdf(jacob转换)
* @param wordFile word 全路径,包括文件全称
* @param pdfFile pdf 保存路径,包括文件全称
* @return boolean 返回转换状态
*/
// public static boolean wordConverterToPdfJacob(String wordFile, String pdfFile) {
// System.out.println("===================jacob开始转换=======================");
// //开始时间
// long start = System.currentTimeMillis();
// ActiveXComponent app = new ActiveXComponent("Word.Application");// 创建对象,花了很长时间
// boolean bool = false;
// try {
// // 打开word
// // 获得word中所有打开的文档
// Dispatch documents = app.getProperty("Documents").toDispatch();
System.out.println("打开文件: " + wordFile);
// // 打开文档
// Dispatch document = Dispatch.call(documents, "Open", wordFile, false, true).toDispatch();
// // 如果文件存在的话,不会覆盖,会直接报错,所以我们需要判断文件是否存在
// File target = new File(pdfFile);
// if (target.exists()) {
// target.delete();
// }
System.out.println("另存为: " + pdfFile);
// Dispatch.call(document, "SaveAs", pdfFile, 17);
// // 关闭文档
// Dispatch.call(document, "Close", false);
// bool = true;
// } catch (Exception e) {
// System.out.println("转换失败" + e.getMessage());
// } finally {
// // 关闭office
// app.invoke("Quit", 0);
// System.out.println("生成完成,用时"+(System.currentTimeMillis()-start)/1000.0+"秒");
// }
// return bool;
// }
/**
*
* @Title: insertRow
* @Description: 在word表格中指定位置插入一行,并将某一行的样式复制到新增行
* @param table 需要插入的表格
* @param copyrowIndex 需要复制的行位置
* @param newrowIndex 需要新增一行的位置
* @return void 返回类型
*/
public static void insertRow(XWPFTable table, int copyrowIndex, int newrowIndex) {
// 在表格中指定的位置新增一行
XWPFTableRow targetRow = table.insertNewTableRow(newrowIndex);
// 获取需要复制行对象
XWPFTableRow copyRow = table.getRow(copyrowIndex);
//复制行对象
targetRow.getCtRow().setTrPr(copyRow.getCtRow().getTrPr());
//或许需要复制的行的列
List<XWPFTableCell> copyCells = copyRow.getTableCells();
//复制列对象
XWPFTableCell targetCell = null;
for (int i = 0; i < copyCells.size(); i++) {
XWPFTableCell copyCell = copyCells.get(i);
targetCell = targetRow.addNewTableCell();
targetCell.getCTTc().setTcPr(copyCell.getCTTc().getTcPr());
if (copyCell.getParagraphs() != null && copyCell.getParagraphs().size() > 0) {
targetCell.getParagraphs().get(0).getCTP().setPPr(copyCell.getParagraphs().get(0).getCTP().getPPr());
if (copyCell.getParagraphs().get(0).getRuns() != null
&& copyCell.getParagraphs().get(0).getRuns().size() > 0) {
XWPFRun cellR = targetCell.getParagraphs().get(0).createRun();
cellR.setBold(copyCell.getParagraphs().get(0).getRuns().get(0).isBold());
}
}
}
}
}
String id = "?";// 合同id,根据自己需求
// 合同保存路径
String contractPath = FilesUtil.getContract();
// 电子合同临时保存路径
StringBuilder temporaryPath = new StringBuilder(contractPath)
.append("temporary");
// 判断临时文件夹是否存在,不存在则新建
File file = new File(temporaryPath.toString());
if (!file.exists()) {
file.mkdirs();
}
temporaryPath.append(File.separator).append(id).append(".docx");
// 1、 查询合同信息(该方法省略,具体看自己的需求)
Map<String, String> replaceMap = getContractData(contractService,id, temporaryPath.toString());
String userNum = replaceMap.get("userNum");
StringBuilder destPathDOCX = new StringBuilder(contractPath)
.append(userNum);
// 判断用户文件夹是否存在,不存在则新建
File userNumFile = new File(destPathDOCX.toString());
if (!userNumFile.exists()) {
userNumFile.mkdirs();
}
destPathDOCX.append(File.separator).append(id).append(".docx");
// StringBuilder savePath = new StringBuilder(userNum)
// .append(File.separator).append(id).append(".pdf");
Map<String, String> replaceTableMap = new HashMap<String, String>();
replaceTableMap.put("totalAmount", replaceMap.remove("totalAmount"));
replaceTableMap.put("totalCapitalizeAmount", replaceMap.remove("totalCapitalizeAmount"));
// 2、 替换合同里面的参数
WordUtil.replaceAndGenerateWord(temporaryPath.toString(), destPathDOCX.toString(), replaceMap, replaceTableMap);
StringBuilder pdfFile = new StringBuilder(contractPath)
.append(userNum).append(File.separator).append(id).append(".pdf");
// 3、将word转pdf文件
Boolean bool = WordUtil.wordConverterToPdf(destPathDOCX.toString(), pdfFile.toString());
if (!bool) {
map.put("msg", "合同word转pdf文件失败");
return;
}
// 4.1 删除临时电子合同
File temporaryPathDOCX = new File(temporaryPath.toString());
if (temporaryPathDOCX.exists()) {
temporaryPathDOCX.delete();
}
// 4.2 删除word电子合同
File fileDOCX = new File(destPathDOCX.toString());
if (fileDOCX.exists()) {
fileDOCX.delete();
}
/**
*
* @Title: itextPDFAddPicture
* @Description: 为pdf加图片(电子合同盖公章)
* @param inputStream 电子合同pdf文件流
* @param map {company:公司公章名称,purpose:公章用途,year:日期}
* @param targetPath 保存路径
* @throws Exception 异常参数
*/
public static void itextPDFAddPicture(InputStream inputStream, Map<String, String> map, String targetPath) throws Exception{
// 1.1 读取模板文件
PdfReader reader = new PdfReader(inputStream);
// 1.2 创建文件输出流
FileOutputStream out = new FileOutputStream(targetPath);
// 2、创建PdfStamper对象
PdfStamper stamper = new PdfStamper(reader, out);
// 3、设置公章信息
String company = FilesUtil.getDefaultCompany();// 公司公章名称
String purpose = FilesUtil.getDefaultPurpose();// 公章用途
String year = map.get("year");// 日期
if (StringUtils.isStrNull(year)) {
year = new SimpleDateFormat (FilesUtil.getContractTimeFormat()).format(new Date());
}
// 4、生成公章
BufferedImage bufferedImage = Graphics2DUtil.startGraphics2D(company, purpose, year);// 整个公章图片流
BufferedImage[] imgs = Graphics2DUtil.splitImage(bufferedImage, 1, 2);
BufferedImage leftBufferedImage = imgs[0];// 左边公章图片流
BufferedImage rightBufferedImage = imgs[1];// 右边公章图片流
// 5、读公章图片
Image image = Image.getInstance(imageToBytes(bufferedImage));
Image leftImage = Image.getInstance(imageToBytes(leftBufferedImage));
Image rightImage = Image.getInstance(imageToBytes(rightBufferedImage));
int chunkWidth = 200;// 公章大小,x轴
int chunkHeight = 200;// 公章大小,y轴
// 获取pdf页面的高和宽
Rectangle pageSize = reader.getPageSize(1);
float height = pageSize.getHeight();
float width = pageSize.getWidth();
// 6、为pdf每页加印章
// 设置公章的位置
float xL = width - chunkWidth/2 - 2;
float yL = height/2-chunkHeight/2-25;
float xR = width-chunkHeight/2 + chunkHeight/8 + 4;
float yR = yL;
// 6.1 第一页盖左章
leftImage.scaleToFit(chunkWidth, chunkHeight);// 公章大小
leftImage.setAbsolutePosition(xL, yL);// 公章位置
// 6.2 第二页盖右章
rightImage.scaleToFit(chunkWidth, chunkHeight);// 公章大小
rightImage.setAbsolutePosition(xR, yR);// 公章位置
int pdfPages = reader.getNumberOfPages();// pdf页面页码
// 遍历为每页盖左章或右章
for (int i = 1; i <= pdfPages; i++) {
if (i % 2 == 0) {// 盖右章
stamper.getOverContent(i).addImage(rightImage);
} else {// 盖左章
stamper.getOverContent(i).addImage(leftImage);
}
}
// 6.3 最后一页盖公章
image.scaleToFit(chunkWidth, chunkWidth);
image.setAbsolutePosition(width/2 + 32, height-chunkHeight + 20);
stamper.getOverContent(pdfPages).addImage(image);
// 7、关闭相关流
stamper.close();
out.close();
reader.close();
inputStream.close();
}
<License>
<Data>
<Products>
<Product>Aspose.Total for JavaProduct>
<Product>Aspose.Words for JavaProduct>
Products>
<EditionType>EnterpriseEditionType>
<SubscriptionExpiry>20991231SubscriptionExpiry>
<LicenseExpiry>20991231LicenseExpiry>
<SerialNumber>8bfe198c-7f0c-4ef8-8ff0-acc3237bf0d7SerialNumber>
Data>
<Signature>sNLLKGMUdF0r8O1kKilWAGdgfs2BvJb/2Xp8p5iuDVfZXmhppo+d0Ran1P9TKdjV4ABwAgKXxJ3jcQTqE/2IRfqwnPf8itN8aFZlV3TJPYeD3yWE7IT55Gz6EijUpC7aKeoohTb4w2fpox58wWoF3SNp6sK6jDfiAUGEHYJ9pjU=Signature>
License>
合同模板.docx
所需相关jar包
链接:https://pan.baidu.com/s/12ysaXaJ0XfRj4MUXwaIUXA
提取码:ar8a
此贴是博主第一次写博,还存在很多不足之处,后期会慢慢完善,如果有不理解之处可以联系博主哈,也欢迎各路大神过来一起探讨。