Converting wkhtmltopdf from HTML to PDF files causes table pagination to fail, solution and Java implementation

This requirement requires that the space where the table is insufficient be displayed on another page.

After dynamically splicing HTML statements based on data, use wkhtmltopdf to convert HTML to PDF files.

It was found that it was successful locally but failed when deployed to the test environment.

After investigation, it is because the version on the server is version 12.4 and the local version is 12.6. After many attempts, it was found that there is a bug in version 12.4. Versions 12.5.X and above can be displayed in paging normally.

As shown in Figure 12.4 version:

As shown in Figure 12.5 version:

Code part:

 @Override
    public void exprot(String supervisionLogId) throws BussinesException {
        
        String htmlString = "Splice HTML code here";
        WkhtmltopdfUtil.start(htmlString);
    }
 public static void start(String content) {

        final String path = CreateHtmlFileUtil.getHtmlPath(content, null);
        final File file = new File(path);
        final String pdfPath = file.getParentFile().toString() + File.separator + System.currentTimeMillis() + ".pdf";
        final String commandPath = "";
        WkhtmltopdfUtil.convert(null, path, pdfPath, commandPath);
        file.getAbsoluteFile().delete();
    }
 public static boolean convert(final String headerHtmlPath, final String htmlPath, String pdfPath, String commandPath) {

        if (System.getProperty("os.name").contains("Windows")) {
            // commandPath fill in the exe address of wkhtmltopdf
            commandPath = "D:\wkhtmltopdf\bin\wkhtmltopdf.exe";
            // pdfPath fill in the file address and name where you need to store the exported pdf
            pdfPath = "D:\htmlDir\test.pdf";
        }
        return convert2pdf(commandPath, headerHtmlPath, htmlPath, pdfPath);
    }
/**
     * Convert HTML files to PDF files
     *
     * @param commandPath wkhtmltopdf command complete directory
     * @param headerHtmlPath header html file (the source code must be an html string starting with <!DOCTYPE html>)
     * @param htmlPath html file path (for example: /app/ifs/contract.html, which can be a local or network full path, local files must include the file name and suffix)
     * @param pdfPath pdf storage path (for example: /app/ifs/contract.pdf, full path including file name and suffix)
     * @return conversion success or failure
     */
    public static boolean convert2pdf(final String commandPath, final String headerHtmlPath, final String htmlPath, final String pdfPath) {
        //The PDF storage directory does not exist, create a new one
        final File parent = new File(pdfPath).getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        //Assemble command
        final StringBuilder cmd = new StringBuilder();
        cmd.append(commandPath);
        cmd.append(" --footer-center [page]/total [topage] pages");
        cmd.append(" --margin-top 30mm");
        cmd.append(" --margin-bottom 20mm");
        cmd.append(" --footer-spacing 5");
        cmd.append(" --header-spacing 5");
        if (StringUtils.isNotBlank(headerHtmlPath)) {
            cmd.append(" --header-html ").append(headerHtmlPath);
        }
        final process process;
        try {
            // Execute the command (depending on whether the source HTML is a network path or a local path, determine whether to set the working directory)
            final String workPath = FilenameUtils.getFullPath(htmlPath);
            if (!htmlPath.startsWith("/") || StringUtils.isBlank(workPath)) {
                cmd.append(" ").append(htmlPath);
                cmd.append(" ").append(pdfPath);
                process = Runtime.getRuntime().exec(cmd.toString());
            } else {
                cmd.append(" ").append(FilenameUtils.getName(htmlPath));
                cmd.append(" ").append(pdfPath);
                process = Runtime.getRuntime().exec(cmd.toString(), null, new File(workPath));
            }
            final ExecutorService threadPool = Executors.newCachedThreadPool();
            threadPool.execute(new ClearBufferThread(process.getErrorStream()));
            threadPool.execute(new ClearBufferThread(process.getInputStream()));
            process.waitFor();
            threadPool.shutdown();
        } catch (final Exception e) {
            // LogUtil.getLogger().error("HTML to PDF exception", e);
            return false;
        }
        return true;
    }

Complete WkhtmltopdfUtil file

package yeshen.dto.SupervisionLog;

import com.itextpdf.text.BaseColor;
import com.itextpdf.text.Element;
import com.itextpdf.text.Rectangle;
import com.itextpdf.text.pdf.BaseFont;
import com.itextpdf.text.pdf.PdfContentByte;
import com.itextpdf.text.pdf.PdfGState;
import com.itextpdf.text.pdf.PdfReader;
import com.itextpdf.text.pdf.PdfStamper;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import javax.swing.*;
import java.awt.*;
import java.io.BufferedReader;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.util.Calendar;
import java.util.Date;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;

/**
 * @Author: YeShen
 * @Date: 2023/9/26 9:46
 * @Description: wkhtmltopdf tool class
*/
@SuppressWarnings({"PMD.CyclomaticComplexity", "PMD.ExcessiveClassLength"})
public class WkhtmltopdfUtil {

    private static final Logger logger = LoggerFactory.getLogger(CreateHtmlFileUtil.class);

    public static boolean convert(final String headerHtmlPath, final String htmlPath, String pdfPath, String commandPath) {

        if (System.getProperty("os.name").contains("Windows")) {
            commandPath = "D:\wkhtmltopdf\bin\wkhtmltopdf.exe";
            pdfPath = "D:\htmlDir\test.pdf";
        }
        return convert2pdf(commandPath, headerHtmlPath, htmlPath, pdfPath);
    }

    /**
     * Convert HTML files to PDF files
     *
     * @param commandPath wkhtmltopdf command complete directory
     * @param headerHtmlPath header html file (the source code must be an html string starting with <!DOCTYPE html>)
     * @param htmlPath html file path (for example: /app/ifs/contract.html, which can be a local or network full path, local files must include the file name and suffix)
     * @param pdfPath pdf storage path (for example: /app/ifs/contract.pdf, full path including file name and suffix)
     * @return conversion success or failure
     */
    public static boolean convert2pdf(final String commandPath, final String headerHtmlPath, final String htmlPath, final String pdfPath) {
        //The PDF storage directory does not exist, create a new one
        final File parent = new File(pdfPath).getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        //Assemble command
        final StringBuilder cmd = new StringBuilder();
        cmd.append(commandPath);
        cmd.append(" --footer-center [page]/total [topage] pages");
        cmd.append(" --margin-top 30mm");
        cmd.append(" --margin-bottom 20mm");
        cmd.append(" --footer-spacing 5");
        cmd.append(" --header-spacing 5");
        if (StringUtils.isNotBlank(headerHtmlPath)) {
            cmd.append(" --header-html ").append(headerHtmlPath);
        }
        final process process;
        try {
            // Execute the command (depending on whether the source HTML is a network path or a local path, determine whether to set the working directory)
            final String workPath = FilenameUtils.getFullPath(htmlPath);
            if (!htmlPath.startsWith("/") || StringUtils.isBlank(workPath)) {
                cmd.append(" ").append(htmlPath);
                cmd.append(" ").append(pdfPath);
                process = Runtime.getRuntime().exec(cmd.toString());
            } else {
                cmd.append(" ").append(FilenameUtils.getName(htmlPath));
                cmd.append(" ").append(pdfPath);
                process = Runtime.getRuntime().exec(cmd.toString(), null, new File(workPath));
            }
            final ExecutorService threadPool = Executors.newCachedThreadPool();
            threadPool.execute(new ClearBufferThread(process.getErrorStream()));
            threadPool.execute(new ClearBufferThread(process.getInputStream()));
            process.waitFor();
            threadPool.shutdown();
        } catch (final Exception e) {
            // LogUtil.getLogger().error("HTML to PDF exception", e);
            return false;
        }
        return true;
    }

    public static boolean convert2pdf2(final String commandPath, final String headerHtmlPath, final String htmlPath, final String pdfPath) {
        //The PDF storage directory does not exist, create a new one
        final File parent = new File(pdfPath).getParentFile();
        if (!parent.exists()) {
            parent.mkdirs();
        }
        //Assemble command
        final StringBuilder cmd = new StringBuilder();
        if (StringUtils.isNotBlank(headerHtmlPath)) {
            cmd.append(" --header-html ").append(headerHtmlPath);
        }
        final process process;
        try {
            // Execute the command (depending on whether the source HTML is a network path or a local path, determine whether to set the working directory)
            final String workPath = FilenameUtils.getFullPath(htmlPath);
            if (!htmlPath.startsWith("/") || StringUtils.isBlank(workPath)) {
                cmd.append(" ").append(htmlPath);
                cmd.append(" ").append(pdfPath);
                process = Runtime.getRuntime().exec(cmd.toString());
            } else {
                cmd.append(" ").append(FilenameUtils.getName(htmlPath));
                cmd.append(" ").append(pdfPath);
                process = Runtime.getRuntime().exec(cmd.toString(), null, new File(workPath));
            }
            final ExecutorService threadPool = Executors.newCachedThreadPool();
            threadPool.execute(new ClearBufferThread(process.getErrorStream()));
            threadPool.execute(new ClearBufferThread(process.getInputStream()));
            process.waitFor();
            threadPool.shutdown();
        } catch (final Exception e) {
            logger.info(e.getMessage() + "html to pdf failed");
            return false;
        }
        return true;
    }

    /**
     * Thread to clean input stream cache---------------------------------------------- ----------------------------------
     * jdk-1.6: process.waitFor() cannot be executed normally and will block. 1.7 does not have this problem. Therefore, 1.6 needs to create another thread when receiving Process input and error information, otherwise the current thread will keep waiting.
     */
    static class ClearBufferThread implements Runnable {

        private final InputStream is;

        ClearBufferThread(final InputStream is) {

            this.is = is;
        }

        @Override
        public void run() {

            try {
                final BufferedReader br = new BufferedReader(new InputStreamReader(this.is));
                for (String line; (line = br.readLine()) != null; ) {
                    logger.info("message =========== {}", line);
                }
            } catch (final Exception e) {
                logger.info(e.getMessage());
            }
        }
    }

    public static void start(String content) {

        final String path = CreateHtmlFileUtil.getHtmlPath(content, null);
        final File file = new File(path);
        final String pdfPath = file.getParentFile().toString() + File.separator + System.currentTimeMillis() + ".pdf";
        final String commandPath = "";
        WkhtmltopdfUtil.convert(null, path, pdfPath, commandPath);
        file.getAbsoluteFile().delete();
    }

    public ByteArrayOutputStream addWaterMark(final InputStream inputFile, final String waterMarkName,
                                              final float opacity, final int fontsize, final int angle, final int heightdensity, final int widthdensity, final String userId, final String date, final String phone, final String createdName, final String companyName) {

        PdfReader reader = null;
        PdfStamper stamper = null;
        final ByteArrayOutputStream baos = new ByteArrayOutputStream();
// final LoginAuthDto loginAuthDto = LoginAuthUtil.getLoginAuthDto();
        try {
            //int interval = -5;
            reader = new PdfReader(inputFile);
            stamper = new PdfStamper(reader, baos);
            final BaseFont base = BaseFont.createFont("STSong-Light", "UniGB-UCS2-H", BaseFont.EMBEDDED);
            Rectangle pageRect = null;
            final PdfGState gs = new PdfGState();
            //Here is the transparency setting
            gs.setFillOpacity(opacity);
            //Here is the stripe opacity
            gs.setStrokeOpacity(0.2f);
            final int total = reader.getNumberOfPages() + 1;
            //System.out.println("Number of contract pages:" + reader.getNumberOfPages());
            final JLabel label = new JLabel();
            final FontMetrics metrics;
            int textH = 0;
            int textW = 0;
            label.setText(waterMarkName);
            metrics = label.getFontMetrics(label.getFont());
            textH = metrics.getHeight(); //The height of the string is only related to the font
            textW = metrics.stringWidth(label.getText()); //The width of the string
            PdfContentByte under;
            //This loop is to ensure that each PDF is watermarked
            for (int i = 1; i < total; i + + ) {
                pageRect = reader.getPageSizeWithRotation(i);
                under = stamper.getOverContent(i); //Add watermark above the content
                //under = stamper.getUnderContent(i); //Add watermark below the content
                under.saveState();
                under.setGState(gs);
                under.beginText();
                under.setColorFill(new BaseColor(4, 0, 0)); //Add text color. It cannot be changed dynamically. Abandon use.
                under.setFontAndSize(base, fontsize); //Here is the watermark font size
                int time = 1;
                for (float height = pageRect.getHeight() - textH * 4; height > textH; height = height - textH * heightdensity) {
                    int time2 = 1;
                    for (int width = textW; width < pageRect.getWidth() * 1.5 + textW; width = width + textW * widthdensity) {
                        if (time % 2 == 1 & amp; & amp; time2 % 2 == 1) {
                            under.showTextAligned(Element.ALIGN_CENTER, "China Mobile Planning and Construction Management System", width - 15, height + 35, 30);
                            under.showTextAligned(Element.ALIGN_CENTER, this.dataToUpper(new Date()), width - 10, height + 20, 30);
                            under.showTextAligned(Element.ALIGN_CENTER, companyName, width - 5, height + 5, 30);
                            under.showTextAligned(Element.ALIGN_CENTER, createdName + " " + phone, width + 5, height - 10, 30);
                        } else if (time % 2 == 0 & amp; & amp; time2 % 2 == 0) {
                            under.showTextAligned(Element.ALIGN_CENTER, "China Mobile Planning and Construction Management System", width - 15, height + 35, 30);
                            under.showTextAligned(Element.ALIGN_CENTER, this.dataToUpper(new Date()), width - 10, height + 20, 30);
                            under.showTextAligned(Element.ALIGN_CENTER, companyName, width - 5, height + 5, 30);
                            under.showTextAligned(Element.ALIGN_CENTER, createdName + " " + phone, width + 5, height - 10, 30);
                        }
                        // rotation: tilt angle
                        time2 + + ;
                    }
                    time + + ;
                }
                under.setFontAndSize(base, 20);
                under.showTextAligned(Element.ALIGN_LEFT, userId + " " + date, 0, textH, 0);
                //Add watermark text
                under.endText();
            }
// System.out.println("Watermark added successfully");
            logger.info("Watermark added successfully");
            return baos;
        } catch (final Exception e) {
            return null;
        } finally {
            //Close the stream
            if (stamper != null) {
                try {
                    stamper.close();
                } catch (final Exception e) {
                    logger.info(e.getMessage());
                }
            }
            if (reader != null) {
                reader.close();
            }
        }
    }

    //Convert date to uppercase and lowercase
    public String dataToUpper(final Date date) {

        final Calendar ca = Calendar.getInstance();
        ca.setTime(date);
        final int year = ca.get(Calendar.YEAR);
        final int month = ca.get(Calendar.MONTH) + 1;
        final int day = ca.get(Calendar.DAY_OF_MONTH);
        return this.numToUpper(year) + "year" + this.monthToUppder(month) + "month" + this.dayToUppder(day) + "day";
    }

    // Convert numbers to uppercase
    public String numToUpper(final int num) {
        //String u[] = {"zero","one","two","three","four","five","Lu", "旒","八","九"};
        final String[] u = {"〇", "一", "二", "三", "四", "五", "六", \ "seven", "eight", "nine"};
        final char[] str = String.valueOf(num).toCharArray();
        String rstr = "";
        for (int i = 0; i < str.length; i + + ) {
            rstr = rstr + u[Integer.parseInt(str[i] + "")];
        }
        return rstr;
    }

    // Convert month to uppercase
    public String monthToUppder(final int month) {

        if (month < 10) {
            return this.numToUpper(month);
        } else if (month == 10) {
            return "十";
        } else {
            return "十" + this.numToUpper(month - 10);
        }
    }

    //Convert day to uppercase
    public String dayToUppder(final int day) {

        if (day < 20) {
            return this.monthToUppder(day);
        } else {
            final char[] str = String.valueOf(day).toCharArray();
            if (str[1] == '0') {
                return this.numToUpper(Integer.parseInt(str[0] + "")) + "十";
            } else {
                return this.numToUpper(Integer.parseInt(str[0] + "")) + "十" + this.numToUpper(Integer.parseInt(str[1] + ""));
            }
        }
    }
}