danfickle / openhtmltopdf

An HTML to PDF library for the JVM. Based on Flying Saucer and Apache PDF-BOX 2. With SVG image support. Now also with accessible PDF support (WCAG, Section 508, PDF/UA)!
https://danfickle.github.io/pdf-templates/index.html
Other
1.93k stars 360 forks source link

Calculated rendered height of html document a bit off when more than one page #924

Open pejmanebrahimi opened 1 year ago

pejmanebrahimi commented 1 year ago

Hello!

Using version 1.0.10, I am trying to get a PDF printout to be printed by thermal printers (with roll paper).

I have not set any CSS width or height values to any of the HTML elements, instead I am setting the PdfRendererBuilder#useDefaultPageSize method with my desired width and height values in MM.

The width is fixed and set to 80mm, but I need to calculate the rendered height of the document to give it to the above-mentioned method, to avoid splitting my printout into several pages, resulting in the printer cutting the paper at the end of every page. In other words I need a long continious one page PDF.

To achieve this I used the suggestions mentioned here, and it works, but not in all cases and as precisely as I expect.

The problem is that only when the content fits in one page (PageBox) (a default page height is internally set, regardless of HTML page having no heights set, resulting in generation of multiple pages when the content is large), the calculated height works correctly, but when it is greater that one page, I get an extra blank space (space between the bottom of my .container HTML element and the bottom of the body element) added to the bottom of the rendered PDF, which grows in height every couple of pages being added (3 to be more precise).

I will share with you a simplified snippet of my HTML and the code, and also a simple visual representation of what I get as the result.

HTML to be rendered (I have added the red body bg to be visible in visual outcome):

<!DOCTYPE html>
<html>

<head>
    <title>Printout</title>
    <meta http-equiv="Content-Type" content="text/html; charset=UTF-8" />

    <style>
        @page {
            margin: 0;
        }

        body {
            background-color: #ff0000;
        }

        .container * {
            page-break-inside: avoid;
        }

        .container {
            padding: 5mm;
            overflow: hidden;
            page-break-inside: avoid;
            background-color: white;
        }
    </style>
</head>

<body>
    <section class="container">

        <header>
            HEADER CONTENT
        </header>

        <section>
            MAIN CONTENT
        </section>

        <footer>
            FOOTER CONTENT
        </footer>

    </section>
</body>

</html>

Calculation of the rendered height and assigning it to the builder:

private static void createPdfForPrinting(OutputStream outputStream, Document document)
throws IOException {

PdfRendererBuilder pdfBuilder = new PdfRendererBuilder();

try (PdfBoxRenderer pdfRenderer = pdfBuilder
    .withW3cDocument(document, "/")
    .useFastMode()
    .toStream(outputStream)
    .buildPdfRenderer()) {

    pdfRenderer.layout();
    Layer mainLayer = pdfRenderer.getRootBox().getContainingLayer();
    float renderedHeight = findMaxLayerY(mainLayer);
    float pageHeight = convertToMillimeters(renderedHeight);

    pdfBuilder.useDefaultPageSize(80, pageHeight, BaseRendererBuilder.PageSizeUnits.MM)
        .run();
  }
}

private static int findMaxLayerY(Layer layer) {
    int maxLayerY = layer.getMaster().getAbsY() + layer.getMaster().getHeight();

    int maxChildY = layer.getChildren()
        .stream()
        .mapToInt(RendererUtil::findMaxLayerY)
        .max()
        .orElse(0);

    return Math.max(maxChildY, maxLayerY);
} 

private static float convertToMillimeters(float pixels) {
    final float PDF_DOTS_PER_PIXEL = 20.0f;
    final float MM_PER_INCH = 25.4f;
    final int PIXELS_PER_INCH = 96;
    return (pixels / PDF_DOTS_PER_PIXEL) * MM_PER_INCH / PIXELS_PER_INCH;
}

Simple visual representation of the problem: printouts