In some business scenarios, you need to generate PDF files or JPG images, and sometimes watermarks. We can define the HTML template with Freemarker and convert the template to A PDF or JPG file.
The freemarker template also supports the definition of variables that can be used to populate specific business data.
1. Maven package guide
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.1.4. The RELEASE</version>
</parent>
<dependencies>
<! -- freemarker -->
<dependency>
<groupId>org.springframework</groupId>
<artifactId>spring-context-support</artifactId>
</dependency>
<dependency>
<groupId>org.freemarker</groupId>
<artifactId>freemarker</artifactId>
</dependency>
<! -- PDF Core Package -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itextpdf</artifactId>
<version>5.5.12</version>
</dependency>
<! -- Adapt Chinese font -->
<dependency>
<groupId>com.itextpdf</groupId>
<artifactId>itext-asian</artifactId>
<version>5.2.0</version>
</dependency>
<! HTML to PDF -->
<dependency>
<groupId>com.itextpdf.tool</groupId>
<artifactId>xmlworker</artifactId>
<version>5.5.12</version>
</dependency>
<! -- PDF to image -->
<dependency>
<groupId>org.apache.pdfbox</groupId>
<artifactId>pdfbox</artifactId>
<version>At 2.0.5</version>
</dependency>
</dependencies>
Copy the code
2. Interface definition
2.1 request,
@Data
public class GeneratePdfReq {
/** * The absolute path to the PDF file */
@notblank (message = "Absolute path to generate PDF file cannot be empty ")
@ the Pattern (regexp = "^. * (PDF | \ \. \ \. JPG) $", message =" must be generated file. PDF or JPG. "at the end)
private String absolutePath;
/** * Use the absolute path of the HTML template */
@notblank (message = "use template path cannot be empty ")
private String templateName;
/** * Render the template's business data */
private Object dataModel;
/** ** Watermark information */
private WaterMarkInfo waterMarkInfo;
/** * Width of PDF file, default A4 */
private float width = 595;
/** * PDF file height, default A4 */
private float height = 842;
}
Copy the code
2.2, the watermark
@Data
public class WaterMarkInfo {
/** * An error occurs when setting watermark for null */
private String waterMark = "";
/** * Watermark transparency, the lower the value, the higher the transparency */
private float opacity = 0.2 F;
/** * watermarking font, if garbled is set to local 宋 font :fonts/simsun.ttc,1 */
private String fontName = "STSong-Light";
/** * Watermark encoding format, if garbled characters are set to basefont. IDENTITY_H */
private String encoding = "UniGB-UCS2-H";
/** * font size */
private float fontSize = 24;
/** * the percentage of the width of the page, with the lower left corner as the origin */
private float x = 50;
/** * the percentage of the vertical coordinate in the height of the page, with the lower left corner as the origin */
private float y = 40;
/** ** Watermark rotation Angle */
private float rotation = 45;
}
Copy the code
2.3, response,
@Data
public class GeneratePdfResp {
/** * The absolute path to generate PDF */
private String absolutePath;
}
Copy the code
3. Application code
3.1, renderingfreemarker
A template forhtml
Web page
@Service("freeMarkerService")
@Slf4j
public class FreeMarkerServiceImpl implements FreeMarkerService {
@Autowired
private FreeMarkerConfigurer freeMarkerConfigurer;
/** * Get the entire page after rendering HTML **@paramTemplatePath templatePath *@paramDataModel Service data, usually in the form of map *@return* /
@Override
public String getHtml(String templatePath, Object dataModel) {
log.info("Start rendering template {} to HTML, business data {}", templatePath, JSONUtil.toJsonPrettyStr(dataModel));
Configuration cfg = freeMarkerConfigurer.getConfiguration();
cfg.setTemplateExceptionHandler(TemplateExceptionHandler.RETHROW_HANDLER); // Freemaker exception is still thrown, unified exception handling
cfg.setClassicCompatible(true);// There is no need to preprocess the null value. Otherwise, an error will be reported if the value exists in the template
StringWriter stringWriter = new StringWriter();
try {
// Set the template directory to an absolute path without typing jar packages
// cfg.setDirectoryForTemplateLoading(new File(templatePath).getParentFile());
// Template temp = cfg.getTemplate(new File(templatePath).getName());
// Relative path sets the directory where the templates are located. The templates are inserted into the JAR package, which is the /templates directory in the resources directory by default.
cfg.setClassForTemplateLoading(this.getClass(), "/templates");
Template temp = cfg.getTemplate(templatePath);
temp.process(dataModel, stringWriter);
} catch (Exception e) {
log.error(PdfErrorCode.PDF_TEMPLATE_RENDER_FAIL.getDesc(), e);
throw new PdfBizException(PdfErrorCode.PDF_TEMPLATE_RENDER_FAIL);
}
returnstringWriter.toString(); }}Copy the code
3.2. Convert HTML web pages to PDF and add watermarks
@Service("pdfService")
@Slf4j
public class PdfServiceImpl implements PdfService {
public static final String FONT_PATH = "fonts/simsun.ttc,1";
@Autowired
private WaterMarkerService waterMarkerService;
/** * Convert HTML page content to PDF, and attach watermarks to each page **@paramHTML HTML page content *@paramWidth Indicates the width of the PDF@paramHeight of PDF *@paramWaterMarkInfo Indicates the watermark *@return* /
@Override
public byte[] html2Pdf(String html, float width, float height, WaterMarkInfo waterMarkInfo) {
log.info("================= Start converting HTML to PDF =================");
ByteArrayOutputStream out = new ByteArrayOutputStream();
this.html2Pdf(html, width, height, out);
byte[] bytes = out.toByteArray();
// Set the watermark
if(waterMarkInfo ! =null) {
bytes = waterMarkerService.addWaterMarker(bytes, waterMarkInfo);
}
return bytes;
}
/** * HTML to PDF **@paramHTML HTML page content *@paramWidth Indicates the width of the PDF@paramHeight of PDF *@paramOut output stream, which is used to output PDF files. Data will not be in the stream until the PDF document is closed
@Override
@SneakyThrows
public void html2Pdf(String html, float width, float height, OutputStream out) {
@Cleanup Document document = new Document(new RectangleReadOnly(width, height)); // Default A4 portrait
// Here we need to close document to get the generated PDF bytes flushed to the output stream
PdfWriter writer = PdfWriter.getInstance(document, out); // Close the PDF that may cause the generated PDF display to be abnormal (Chrome)
document.open();
// set the font to simsun.ttc
XMLWorkerFontProvider asianFontProvider = new XMLWorkerFontProvider() {
@Override
public Font getFont(String fontname, String encoding, boolean embedded, float size, int style, BaseColor color, boolean cached) {
Font font;
try {
font = new Font(BaseFont.createFont(FONT_PATH, BaseFont.IDENTITY_H, BaseFont.EMBEDDED));
} catch (Exception e) {
log.error(PdfErrorCode.SET_PDF_FONT_FAIL.getDesc(), e);
throw new PdfBizException(PdfErrorCode.SET_PDF_FONT_FAIL);
}
font.setStyle(style);
font.setColor(color);
if (size > 0) {
font.setSize(size);
}
returnfont; }};/ / generate PDF
try {
XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8"), asianFontProvider);
// If the system already has simsun. TTC font, there is no need to set the font separately and itext-Asian JAR package is not required
// XMLWorkerHelper.getInstance().parseXHtml(writer, document, new ByteArrayInputStream(html.getBytes("UTF-8")), null, Charset.forName("UTF-8"));
} catch (RuntimeWorkerException e) {
log.error(PdfErrorCode.HTML_CONVERT2PDF_FAIL.getDesc(), e);
throw newPdfBizException(PdfErrorCode.HTML_CONVERT2PDF_FAIL); }}}Copy the code
Add a watermark implementation class
@Service("waterMarkerService")
@Slf4j
public class WaterMarkerServiceImpl implements WaterMarkerService {
/** * Add watermark to each page of PDF file **@paramSource PDF file in byte array form *@paramWaterMarkInfo Indicates the watermark *@return* /
@Override
public byte[] addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo) {
log.info("Start setting watermark data {}", JSONUtil.toJsonPrettyStr(waterMarkInfo));
ByteArrayOutputStream out = new ByteArrayOutputStream();
this.addWaterMarker(source, waterMarkInfo, out);
return out.toByteArray();
}
/** * Add watermark to each page of PDF file **@paramSource PDF file in byte array form *@paramWaterMarkInfo Indicates the watermark *@paramOut output stream, which is used to output PDF files. Data will not be in the stream until the PDF document is closed
@Override
@SneakyThrows
public void addWaterMarker(byte[] source, WaterMarkInfo waterMarkInfo, OutputStream out) {
@Cleanup PdfReader reader = new PdfReader(source);
// You need to turn off PdfStamper to flush the generated PDF bytes to the output stream
@Cleanup PdfStamper pdfStamper = new PdfStamper(reader, out);
BaseFont font = BaseFont.createFont(waterMarkInfo.getFontName(), waterMarkInfo.getEncoding(), BaseFont.EMBEDDED);
PdfGState gs = new PdfGState();
gs.setFillOpacity(waterMarkInfo.getOpacity());
// Create a watermark for each PDF page
for (int i = 1; i <= reader.getNumberOfPages(); i++) {
PdfContentByte waterMarker = pdfStamper.getUnderContent(i);
waterMarker.beginText();
// Set the watermark transparency
waterMarker.setGState(gs);
// Set the watermark font and size
waterMarker.setFontAndSize(font, waterMarkInfo.getFontSize());
// Set the watermark position, content and rotation Angle
float X = reader.getPageSize(i).getWidth() * waterMarkInfo.getX() / 100;
float Y = reader.getPageSize(i).getHeight() * waterMarkInfo.getY() / 100;
waterMarker.showTextAligned(Element.ALIGN_CENTER, waterMarkInfo.getWaterMark(), X, Y, waterMarkInfo.getRotation());
// Set the watermark colorwaterMarker.setColorFill(BaseColor.GRAY); waterMarker.endText(); }}}Copy the code
3.3. Integrated implementation
@Slf4j
@Service("generatePdfService")
public class GeneratePdfServiceImpl implements RestService {
@Autowired
private FreeMarkerService freeMarkerService;
@Autowired
private PdfService pdfService;
@Override
@SneakyThrows
public GeneratePdfResp service(GeneratePdfReq generatePdfReq) {
log.info("Start generating PDF file, request message :{}", JSONUtil.toJsonPrettyStr(generatePdfReq));
/* 1. Populate the business data from the Freemarker template to get the full HTML string */
String html = freeMarkerService.getHtml(generatePdfReq.getTemplateName(), generatePdfReq.getDataModel());
/*
2.生成pdf文件(内存)
*/
byte[] bytes = pdfService.html2Pdf(html, generatePdfReq.getWidth(), generatePdfReq.getHeight(), generatePdfReq.getWaterMarkInfo());
/*
3.本地保存pdf文件
*/
File targetFile = new File(generatePdfReq.getAbsolutePath());
// The parent directory does not exist
if(! targetFile.getParentFile().exists()) { targetFile.getParentFile().mkdirs(); }// Generate corresponding files according to different file name suffixes
if (generatePdfReq.getAbsolutePath().endsWith("pdf")) {
FileUtils.writeByteArrayToFile(targetFile, bytes);
} else {
@Cleanup PDDocument document = PDDocument.load(bytes);
PDFRenderer renderer = new PDFRenderer(document);
BufferedImage bufferedImage = renderer.renderImageWithDPI(0.150);// Just type the first page. The larger the DPI is, the higher the image will be
ByteArrayOutputStream baos = new ByteArrayOutputStream();
ImageIO.write(bufferedImage, "jpg", baos);
FileUtils.writeByteArrayToFile(targetFile, baos.toByteArray());
}
log.info("File saved locally, file path :[{}]", targetFile.getAbsolutePath());
/* 4
GeneratePdfResp generatePdfResp = new GeneratePdfResp();
generatePdfResp.setAbsolutePath(targetFile.getAbsolutePath());
returngeneratePdfResp; }}Copy the code
3.4, the controller
@Slf4j
@RestController
public class PdfController {
@Autowired
private RestService generatePdfService;
@PostMapping(value = "/html2Pdf")
public GeneratePdfResp html2Pdf(@RequestBody @Validated GeneratePdfReq req) {
GeneratePdfResp resp = generatePdfService.service(req);
returnresp; }}Copy the code
4, application
4.1,freemarker
Template (html
Template)
<html>
<head>
<meta http-equiv="Content-Type" content="text/html; charset=utf-8"/>
<meta http-equiv="Content-Style-Type" content="text/css"/>
<style>
body {
font-family: SimSun
}
</style>
<title>HTML template</title>
</head>
<body>
<div>
<p style="margin:0pt; orphans:0; text-align:center; widows:0">
<span style="font-family:SimSun; font-size:16pt">HTML template</span><br/>
</p>
<p>Name: ${name}</p>
<p>CardNo. : ${cardNo}</p>
<p>Date: ${date}</p>
</div>
</body>
</html>
Copy the code
4.2. Interface invocation generates PDF
5, description,
- PDF or JPG files can be generated according to the parameter suffix. The default size of the generated PDF file is A4, or the size can be set by request parameters.
- PDF files are automatically paginated based on the size of the HTML template content.
- If you generate images, multiple pages will not generate multiple images, you can set the height to a larger size, resulting in a growth map.
- The watermark is automatically added to each page.
- In order to improve the reusability and maintainability of code, there are separate interfaces for rendering HTML templates, generating PDF files and adding watermarks in the project.
Refer to the link
- Java uses xmlWorkerHelper to convert HTML to PDF
- An introduction to the use of Freemarker templates
- Add text watermarks to PDF using IText5
The code address
- Github:github.com/senlinmu100…
- Gitee:gitee.com/ppbin/sprin…
Personal website
- Gitee Pages
- Github Pages