java 引入wkhtmltopdf
·
本文衔接上篇 html转PDF之wkhtmltopdf,提供了一套java的使用demo
package com.zzx.util;
import cn.hutool.core.io.resource.ResourceUtil;
import java.io.*;
import java.util.ArrayList;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
/**
* @Description wkhtmltopdf工具
*/
public class WkHtmlToPdfUtil {
private static final String OS = System.getProperty("os.name").toLowerCase();
/**
* 将 HTML 内容转换为 PDF 文件
* @param htmlContent HTML 内容
* @param outputPdfPath 输出 PDF 文件的路径
* @param params 额外的 wkhtmltopdf 参数列表
* @throws IOException
* @throws InterruptedException
*/
public static void htmlToPdf(String htmlContent, String outputPdfPath, List<String> params) throws IOException, InterruptedException {
File tempHtmlFile = createTempHtmlFile(htmlContent);
List<String> command = buildCommand("wkhtmltopdf", tempHtmlFile.getAbsolutePath(), outputPdfPath, params);
executeCommand(command);
tempHtmlFile.delete();
}
/**
* 将 HTML 内容转换为图片文件
* @param htmlContent HTML 内容
* @param outputImagePath 输出图片文件的路径
* @param params 额外的 wkhtmltoimage 参数列表
* @throws IOException
* @throws InterruptedException
*/
public static void htmlToImage(String htmlContent, String outputImagePath, List<String> params) throws IOException, InterruptedException {
File tempHtmlFile = createTempHtmlFile(htmlContent);
List<String> command = buildCommand("wkhtmltoimage", tempHtmlFile.getAbsolutePath(), outputImagePath, params);
executeCommand(command);
tempHtmlFile.delete();
}
/**
* 创建临时 HTML 文件并写入内容
* @param htmlContent HTML 内容
* @return 临时 HTML 文件
* @throws IOException
*/
private static File createTempHtmlFile(String htmlContent) throws IOException {
File tempHtmlFile = File.createTempFile(UUID.randomUUID().toString().replaceAll("-", ""), ".html");
try (FileWriter writer = new FileWriter(tempHtmlFile)) {
writer.write(htmlContent);
}
return tempHtmlFile;
}
/**
* 构建命令列表
* @param baseCommand 基础命令(wkhtmltopdf 或 wkhtmltoimage)
* @param inputPath 输入文件路径
* @param outputPath 输出文件路径
* @param params 额外的参数列表
* @return 完整的命令列表
*/
private static List<String> buildCommand(String baseCommand, String inputPath, String outputPath, List<String> params) {
List<String> command = new ArrayList<>();
if (OS.contains("win")) {
command.add(baseCommand + ".exe");
} else {
command.add(baseCommand);
}
if (params != null) {
command.addAll(params);
}
command.add(inputPath);
command.add(outputPath);
return command;
}
/**
* 执行系统命令
* @param command 命令列表
* @throws IOException
* @throws InterruptedException
*/
private static void executeCommand(List<String> command) throws IOException, InterruptedException {
System.out.println(command);
// Create a ProcessBuilder to run the command
ProcessBuilder processBuilder = new ProcessBuilder(command);
processBuilder.redirectErrorStream(true); // Redirect error stream to standard output
// Start the process
Process process = processBuilder.start();
// Read the output from the command (including errors)
try (BufferedReader reader = new BufferedReader(new InputStreamReader(process.getInputStream()))) {
String line;
while ((line = reader.readLine()) != null) {
System.out.println(line);
}
}
// Wait for the process to complete and check the exit value
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new RuntimeException("命令执行失败,退出码: " + exitCode);
}
}
public static void main(String[] args) {
String content = ResourceUtil.readUtf8Str("html/analysisUserReport.html");
ConcurrentHashMap<String, Object> dataMap = new ConcurrentHashMap<>();
dataMap.put("title", "测试报告12312312");
dataMap.put("content", "内容4861213");
long time = System.currentTimeMillis();
String pdfPath = "e:\\www\\" + time + ".pdf";
String imagePath = "e:\\www\\" + time + ".png";
// 设置 wkhtmltopdf 参数,例如设置 DPI 为 300
List<String> pdfParams = new ArrayList<>();
pdfParams.add("--dpi");
pdfParams.add("300");
// 设置 wkhtmltoimage 参数,例如设置图片宽度为 800
List<String> imageParams = new ArrayList<>();
imageParams.add("--width");
imageParams.add("500");
imageParams.add("--quality");
imageParams.add("50");
try {
// 生成 PDF
htmlToPdf(htmlContent, pdfPath, pdfParams);
System.out.println("PDF 生成成功:" + pdfPath);
// 生成图片
htmlToImage(htmlContent, imagePath, imageParams);
System.out.println("图片生成成功:" + imagePath);
} catch (IOException | InterruptedException e) {
e.printStackTrace();
}
}
}
analysisUserReport.html
<!DOCTYPE html>
<html>
<head>
<title>检测统计</title>
<meta http-equiv="content-type" content="text/html;charset=utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.0, user-scalable=no">
<style>
body {
margin: 0;
padding: 0;
height: 100%;
/* padding: 15px; */
/* background: linear-gradient(#ffeed2 20%, #fff 80%); */
background: #fff;
font-family: SimSun;
}
/* 确保内容没有额外边距 */
div {
margin: 0;
padding: 0;
}
.userInfo {
box-sizing: border-box;
padding: 15px;
background: #fff;
font-size: 14px;
margin: 0 20px 9px 20px;
width: 90%;
height: 139px;
border-radius: 6px;
position: relative;
}
.userInfo .user .name {
color: #f5ac37;
font-size: 23px;
}
.userInfo .user .info {
display: flex;
color: #f5ac37;
margin-top: 10px;
font-size: 14px;
}
.userInfo .detectionTime {
/* display: flex;
align-items: center; */
margin: 10px 0 5px 0;
font-size: 14px;
}
.userInfo .avatar {
object-fit: contain;
width: 67px;
height: 67px;
opacity: 1;
border-radius: 36px;
position: absolute;
top: 12px;
right: 28px;
background-size: 100% 100%;
}
.detectionItem {
width: 100%;
height: 100%;
box-sizing: border-box;
padding: 15px;
margin-top: 9px;
background: #fff;
}
.detectionItem .table {
border-collapse: collapse;
width: 100%;
margin-bottom: 20px;
font-size: 14px;
min-height: 25px;
}
.detectionItem .table .td {}
.detectionItem .title {
width: 100%;
height: 29px;
background: #e99d42;
color: #fff;
font-size: 20px;
text-align: center;
line-height: 29px;
}
.detectionItem .total {
font-size: 18px;
margin: 15px 0;
font-weight: bold;
}
.detectionItem .total .spanFirst {
width: 100%;
display: inline-block;
}
.detectionItem .content {
width: 100%;
}
.detectionItem .content .tb_title {
/* display: flex;
align-items: center;
justify-content: space-between; */
text-align: left;
margin-bottom: 5px;
font-size: 12px;
color: #545454;
display: flex;
position: relative;
}
.detectionItem .content .tb_title .spanFirst {
font-size: 15px;
color: #000000;
width: 80%;
display: inline-block;
margin-left: 10px
}
.detectionItem .content .tb_title .line {
display: block;
width: 4px;
height: 16px;
opacity: 1;
border-radius: 2px;
margin-right: 5px;
position: absolute;
left: 0;
bottom: 0;
}
</style>
</head>
<body>
<!-- 用户信息区域 -->
<div class="userInfo">
<div class="user">
<div class="name">${name!}</div>
<div class="info">
<span style="margin-right: 20px;">性别:${sex!}</span>
<span>年龄:${age!}</span>
</div>
</div>
<div class="detectionTime">
<span class="title">检测时间段:</span>
<span>${startTime!}至${endTime!}</span>
</div>
<div>总检测项:${testItemsNumber!}项</div>
<div>${dataTypeNames!}</div>
<!--
<img class="avatar" src="https://img.js.design/assets/img/67ad9c00429d6bf489fad93c.png#b9f2342ad0cfa088914f2dc26f2ab0c8">
-->
</div>
<#if dataTypes?has_content && dataTypes?seq_contains(19)>
<div class="detectionItem">
<div class="title" style="background: #eeaeee;">xxx</div>
<div class="total" style="color: #eeaeee;">总次数:${(data19.testTotal)!}次</div>
<div class="content">
<div class="tb_title">
<span class="line" style="background:#eeaeee;"></span>
<span class="spanFirst">平均统计:</span>
<span></span>
</div>
<table border="1" class="table">
<tbody>
<tr style="background: #eeaeee;color: #fff;">
<td> 指标</td>
<td> 平均</td>
<td> 结果</td>
<td> 参考范围</td>
</tr>
<#if data19?? && data19.avgStatistics?has_content>
<#list data19.avgStatistics as item>
<tr>
<td>${(item.type)!}</td>
<td>${(item.value)!}
<#if item.ratio??>
<br/>(${(item.ratio)!})
</#if>
</td>
<td>${(item.typeLevel)!}</td>
<td>${(item.range)?html!}</td>
</tr>
</#list>
</#if>
</tbody>
</table>
<div class="tb_title">
<span class="line" style="background:#eeaeee;"></span>
<span class="spanFirst">最高/低统计:</span>
</div>
<table border="1" class="table">
<tbody>
<tr style="background: #eeaeee;color: #fff;">
<td> 指标</td>
<td> 结果</td>
<td> 测量值</td>
<td> 测量时间</td>
</tr>
<#if data19?? && data19.highestAndLowest?has_content>
<#list mergeColumn(data19.highestAndLowest,"type") as item>
<tr>
<#if item.rowspan?? && item.rowspan?number gt 1>
<td rowspan="${item.rowspan}">${(item.type)!}</td>
</#if>
<td>${(item.typeLevel)!}</td>
<td>${(item.value)!}</td>
<td>${(item.testTime)!}</td>
</tr>
</#list>
</#if>
</tbody>
</table>
<div class="tb_title">
<span class="line" style="background:#eeaeee;"></span>
<span class="spanFirst">结果分布:</span>
</div>
<table border="1" class="table">
<tbody>
<tr style="background: #eeaeee;color: #fff;">
<td> 结果</td>
<td> 次数</td>
<td> 占比</td>
</tr>
<#if data19?? && data19.distribution?has_content>
<#list data19.distribution as item>
<tr>
<td>${(item.typeLevel)!}</td>
<td>${(item.total)!}</td>
<td>${(item.ratio)!}</td>
</tr>
</#list>
</#if>
</tbody>
</table>
</div>
</div>
</#if>
</body>
</html>
更多推荐
所有评论(0)