本文衔接上篇 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>
Logo

北京人形旗下天工造物具身智能开源社区,聚焦具身天工与慧思开物两大平台

更多推荐