一、背景介绍
这周产品提了一个需求,提供一个空出一些位置的证书图片,将这张图片转为pdf,并将指定的文字(如:学生姓名)打印到证书上的指定位置,做了个demo来实现这个功能。
二、需求分析
实际上需求可以拆解为两个部分
- 一是将一个url链接的图片转为pdf文件
- 二是将文字印到图片的指定位置
三、引入依赖
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.13</version>
</dependency>
<dependency>
<groupId>com.lowagie</groupId>
<artifactId>itext</artifactId>
<version>4.2.1</version>
</dependency>
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
四、具体实现
4.1 将图片url转为byte[]数组
我们有一个url的图片地址,我们需要将它处理成二进制byte[]数组,方便进行后续操作。
import lombok.Data;
import lombok.extern.slf4j.Slf4j;
import org.apache.commons.lang3.tuple.Pair;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.URL;
/**
* @Author: KD
* @Date: 2021/1/6 3:42 下午
*/
@Slf4j
@Data
public class FileUrlUtil {
/**
* 从指定url获取byte[]
*
* @param urlStr
* @return
*/
public static Pair<Boolean, byte[]> downloadFromUrl(String urlStr) {
try {
URL url = new URL(urlStr);
HttpURLConnection conn = (HttpURLConnection)url.openConnection();
conn.setConnectTimeout(3*1000);
InputStream inputStream = conn.getInputStream();
byte[] buffer = new byte[1024];
int len = 0;
ByteArrayOutputStream bos = new ByteArrayOutputStream();
while((len = inputStream.read(buffer)) != -1) {
bos.write(buffer, 0, len);
}
bos.close();
byte[] data = bos.toByteArray();
return Pair.of(true, data);
} catch (IOException e) {
log.error("Get bytes from failure");
return Pair.of(false, null);
}
}
}
4.2 将图片转为pdf文件
这个方法参数是url地址,我们调用上面的转换方法把链接转为二进制数组,之后再把这个字节数组转为pdf文件。
public static ByteArrayOutputStream convert(String urlStr) {
Document document = new Document();
//设置文档页边距
document.setMargins(0,0,0,0);
Pair<Boolean, byte[]> pair = FileUrlUtil.downloadFromUrl(urlStr);
if(!pair.getLeft()){
throw new CodingCloudException("url转换错误");
}
byte[] bytes = pair.getRight();
ByteArrayOutputStream baos = new ByteArrayOutputStream();
try {
PdfWriter.getInstance(document, baos);
//打开文档
document.open();
//获取图片的宽高
Image image = Image.getInstance(bytes);
float imageHeight=image.getScaledHeight();
float imageWidth=image.getScaledWidth();
//设置页面宽高与图片一致
Rectangle rectangle = new Rectangle(imageWidth, imageHeight);
document.setPageSize(rectangle);
//图片居中
image.setAlignment(Image.ALIGN_CENTER);
//新建一页添加图片
document.newPage();
document.add(image);
return baos;
} catch (Exception ioe) {
System.out.println(ioe.getMessage());
} finally {
//关闭文档
document.close();
try {
baos.flush();
baos.close();
} catch (IOException e) {
e.printStackTrace();
}
}
return null;
}
4.3 证书上打印文字
实际上我们是通过控制边距,将文字显示在我们想要的位置。
/**
* @param waterMarkName 你的水印
* @return
*/
public static ByteArrayOutputStream waterMark(byte[] bytes, String waterMarkName) {
try {
ByteArrayOutputStream baos = new ByteArrayOutputStream();
PdfReader reader = new PdfReader(bytes);
PdfStamper stamper = new PdfStamper(reader, baos);
stamper.setEncryption(null, null, PdfWriter.ALLOW_PRINTING, PdfWriter.STANDARD_ENCRYPTION_40);
//这里的字体设置比较关键,这个设置是支持中文的写法
BaseFont base = BaseFont.createFont("STSong-Light",
"UniGB-UCS2-H", BaseFont.NOT_EMBEDDED);
int total = reader.getNumberOfPages() + 1;
log.info("total:[{}]",total);
PdfContentByte under;
Rectangle pageRect = null;
for (int i = 1; i < total; i++) {
pageRect = stamper.getReader().
getPageSizeWithRotation(i);
// 计算水印X,Y坐标
float x = pageRect.getWidth()/2;
float y = (pageRect.getHeight()/3)*2-150;
// 获得PDF最顶层
under = stamper.getOverContent(i);
under.saveState();
// set Transparency
PdfGState gs = new PdfGState();
// 设置透明度为0.2
gs.setFillOpacity(1.f);
under.setGState(gs);
under.restoreState();
under.beginText();
under.setFontAndSize(base, 80);
under.setColorFill(BaseColor.BLACK);
// 水印文字
under.showTextAligned(Element.ALIGN_CENTER
, waterMarkName, x,
y, 0);
// 添加水印文字
under.endText();
under.setLineWidth(1f);
under.stroke();
}
stamper.close();
return baos;
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
4.4 Controller层
/**
* @Author: KD
* @Date: 2021/1/6 3:44 下午
*/
@Api(tags = "证书生成")
@Slf4j
@RestController
@RequestMapping(value = "/certificate")
public class CertificateController {
@ResponseBody
@GetMapping("/test")
public ResponseEntity<Body<Void>> save(
@ApiParam("资源名称") String url,
@ApiParam("资源名称") String name,
HttpServletResponse response) throws IOException {
log.info("输入参数:url=[{}],name=[{}]",url,name);
ByteArrayOutputStream bos = Convert2Pdf.convert(url);
ByteArrayOutputStream res = Convert2Pdf.waterMark(bos.toByteArray(),name);
OutputStream os = response.getOutputStream();
os.write(res.toByteArray());
return ResponseUtil.ok();
}
}
五、测试效果
5.1 访问url
http://localhost:8380/api/competition/certificate/test?url=https://static.xiaohoucode.com/prod-env/images/coding-cloud-competition/module/de0fec795a52320c60b1042f6f784a09.jpeg&name=KD
两个参数分别为url和name,url是图片地址,name是想要打印的学生名字
5.2 效果图
如图所示,KD两个字母就是打印到PDF证书上的内容。