flyingsaucerproject / flyingsaucer

XML/XHTML and CSS 2.1 renderer in pure Java
Other
2.02k stars 564 forks source link

disable print causes nullpointer #375

Open mahmutcanprehcm opened 2 months ago

mahmutcanprehcm commented 2 months ago

i have an html page which can be dynamically grow. i want to get one pdf page.

This code causes NullPointerException:

         ITextRenderer iTextRenderer = new ITextRenderer();
         iTextRenderer.setDocumentFromString(readHtmlFileAsString("foo.html"));
         SharedContext sharedContext = iTextRenderer.getSharedContext();
         sharedContext.setPrint(false);
         iTextRenderer.layout();

ITextRendere.java:

        public void createPDF(OutputStream os, boolean finish, int initialPageNo) throws DocumentException {
        List<PageBox> pages = _root.getLayer().getPages();
        RenderingContext c = newRenderingContext();
        c.setInitialPageNo(initialPageNo);
        PageBox firstPage = pages.get(0);
        ....

it this a bug, or wrong usage?

asolntsev commented 2 months ago

@mahmutcanprehcm Please show the NullPointerException itself with full stacktrace.

mahmutcanprehcm commented 2 months ago
java.lang.NullPointerException
    at org.xhtmlrenderer.layout.Layer.layoutPages(Layer.java:1066)
    at org.xhtmlrenderer.pdf.ITextRenderer.layout(ITextRenderer.java:232)
    at de.printer.service.pdf.DefaultTemplateBuilderService.xhtmlToPdf(DefaultTemplateBuilderService.java:72)
    at de.printer.service.pdf.DefaultTemplateBuilderService.dtoToByte(DefaultTemplateBuilderService.java:33)
mahmutcanprehcm commented 2 months ago

Created by Apache Maven 3.3.9

version=9.1.11 groupId=org.xhtmlrenderer artifactId=flying-saucer-core

asolntsev commented 2 months ago

@mahmutcanprehcm But version 9.1.11 is very old. Please try with the latest version 9.9.1.

mahmutcanprehcm commented 2 months ago

the same with 9.9.1

java.lang.IndexOutOfBoundsException: Index 0 out of bounds for length 0
    at java.base/jdk.internal.util.Preconditions.outOfBounds(Preconditions.java:100)
    at java.base/jdk.internal.util.Preconditions.outOfBoundsCheckIndex(Preconditions.java:106)
    at java.base/jdk.internal.util.Preconditions.checkIndex(Preconditions.java:302)
    at java.base/java.util.Objects.checkIndex(Objects.java:385)
    at java.base/java.util.ArrayList.get(ArrayList.java:427)
    at org.xhtmlrenderer.pdf.ITextRenderer.createPDF(ITextRenderer.java:372)
    at org.xhtmlrenderer.pdf.ITextRenderer.createPDF(ITextRenderer.java:328)
    at com.solutionfactory.HtmlToPdfOpenPDF.main(HtmlToPdfOpenPDF.java:32)

"The problem occurs when you call setPrint(false) on SharedContext."

ITextRenderer.java Line 372

  RenderingContext c = newRenderingContext();
        c.setInitialPageNo(initialPageNo);
        PageBox firstPage = pages.get(0);
        com.lowagie.text.Rectangle firstPageSize = new com.lowagie.text.Rectangle(0, 0, firstPage.getWidth(c) / _dotsPerPoint,
                firstPage.getHeight(c) / _dotsPerPoint);
asolntsev commented 2 months ago

@mahmutcanprehcm I would say, it's rather a wrong usage.

Why do you call setPrint(false)?

When you convert a web page to PDF, it's usually intended for printing. It means setPrint(true) would be reasonable, and it is the default value.

mahmutcanprehcm commented 2 months ago

I need a single page. It is an invoice that goes to a thermal printer and if the customer wants to download a PDF, he should not receive several pages.

asolntsev commented 2 months ago

Ok, but how setPrint(false) is related to single/multiple pages? It dictates "media: print" or "media: screen". It doesn't affect number of pages.

mahmutcanprehcm commented 2 months ago

OK. Unfortunately I couldn't find any documentation about this method.

How can I then ensure that everything only ends up on one page?

pbrant commented 2 months ago

I'm not sure I entirely understand your use case, but one way to effectively disable pagination would be to set a page height of e.g. 500 cm.

On Wed, Sep 4, 2024, 10:11 mahmutcanprehcm @.***> wrote:

OK. Unfortunately I couldn't find any documentation about this method.

How can I then ensure that everything only ends up on one page?

— Reply to this email directly, view it on GitHub https://github.com/flyingsaucerproject/flyingsaucer/issues/375#issuecomment-2328200794, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAF42F4XTEURSDIWJE2ZKTZU26DBAVCNFSM6AAAAABNMN4NTGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDGMRYGIYDANZZGQ . You are receiving this because you are subscribed to this thread.Message ID: @.***>

mahmutcanprehcm commented 2 months ago

Bildschirmfoto vom 2024-09-04 13-34-59

this produces always a large pdf file

pbrant commented 2 months ago

(Urk, sorry the late response here.)

Thanks, that makes sense. The behavior you're looking for (PDF output on a single page with page extents trimmed to the content extent) isn't something that's come up before. FS doesn't support it directly.

One fairly low effort way you could hack around this would be to use ITextRenderer#findPagePositionsByID(). It takes a regex that returns the content area of block-level elements with an id attribute set that match the pattern. With an empty <div id="content-end"></div> element you could use that to figure out where the content stops on the huge page. You could then use that to render the receipt a second time with the corrected page size.

This would have the negative that you'd be laying out the receipt twice (but only rendering the PDF once). FS is fast enough though that that may not matter.

If that is a problem, I can't think of an alternative beyond doing surgery on ITextRenderer itself to provide the option to adjust page sizes post layout.

Edit: Another option would be to use iText or PDFBox to update the crop box of the page after the fact (vs. doing a second layout). I'm not entirely sure which would be faster though.

mahmutcanprehcm commented 4 weeks ago

i am sorry, but do you have an example how do this?

asolntsev commented 4 weeks ago

@mahmutcanprehcm Do I understand correctly that your request is not always possible? You want to fit the html into a single page in PDF, BUT the html can be large enough NOT to fit to a single page. Then your PDF will contain more than 1 page anyway.

When we want a single-page pdf, we usually try to:

  1. use smaller fonts,
  2. fit the content into multi-column tables
  3. remove all unneeded spaces and images
  4. etc.
mahmutcanprehcm commented 3 weeks ago

@asolntsev what I need is simply one page, no matter how big the HTML page becomes. it is not suitable for printing but for downloading.

asolntsev commented 3 weeks ago

@mahmutcanprehcm I don't know how to achieve that. But I would ask, WHY do you need to avoid pages? Do they really break something? what practical problem do you want to solve?

pbrant commented 3 weeks ago

It's mentioned earlier in the issue thread. Mahmut is printing a receipt on a continuous paper spool so the effective paper size should change dynamically based on the amount of content (e.g. the number of items on the receipt).

I don't think I'll get to it today, but I'll throw up an example of how to do this by tomorrow. I don't think it should be particularly involved as long as the layout process is fast enough to be run twice (which it should be except in really resource constrained environments).

On Tue, Oct 29, 2024 at 8:57 AM Andrei Solntsev @.***> wrote:

@mahmutcanprehcm https://github.com/mahmutcanprehcm I don't know how to achieve that. But I would ask, WHY do you need to avoid pages? Do they really break something? what practical problem do you want to solve?

— Reply to this email directly, view it on GitHub https://github.com/flyingsaucerproject/flyingsaucer/issues/375#issuecomment-2443495524, or unsubscribe https://github.com/notifications/unsubscribe-auth/AAAF42HUDLQAEO5KHJTZRGTZ545VHAVCNFSM6AAAAABNMN4NTGVHI2DSMVQWIX3LMV43OSLTON2WKQ3PNVWWK3TUHMZDINBTGQ4TKNJSGQ . You are receiving this because you commented.Message ID: @.***>

asolntsev commented 3 weeks ago

@pbrant Em... No, Mahmut just said in https://github.com/flyingsaucerproject/flyingsaucer/issues/375#issuecomment-2440000072 that

it is not suitable for printing but for downloading

pbrant commented 3 weeks ago

@pbrant Em... No, Mahmut just said in #375 (comment) that

it is not suitable for printing but for downloading

Be that as it may, the desired result is the same. See the sample image above. The idea would be that the PDF is cut at the end of the receipt.

See also from above:

I need a single page. It is an invoice that goes to a thermal printer and if the customer wants to download a PDF, he should not receive several pages.

mahmutcanprehcm commented 3 weeks ago

`

    ITextRenderer iTextRenderer = new ITextRenderer();
    iTextRenderer.setDocumentFromString(html);
    iTextRenderer.layout();

    int width = iTextRenderer.getRootBox().getWidth();
    int height = iTextRenderer.getRootBox().getHeight();

    float x = width / iTextRenderer.getSharedContext().getDPI();
    float y = height / iTextRenderer.getSharedContext().getDPI();

    // width is ok. replace lenght
    html = html.replace("size: 3.183465in;", "size: "+x+"in "+y+"in;");

    iTextRenderer.setDocumentFromString(html);
    iTextRenderer.layout();

    ByteArrayOutputStream os = new ByteArrayOutputStream();
    iTextRenderer.createPDF(os, true);
    os.close();

    return os.toByteArray();`

i wrote this code. the pdf generation is ok. but the printed document is scaled to the middle.

pbrant commented 3 weeks ago

Could you try again with FS 9.10.2? There were some issues with the initial 9.10.0 release.

The above should work though and it's definitely easier than using findPagePositionsByID like I suggested. SSCCE follows:

import java.io.ByteArrayOutputStream;
import java.io.FileOutputStream;

public class Driver {

    private static String generateHtml() {
        return generateHtml(72f);
    }

    private static String generateHtml(float pageHeight) {
        var html = """
                <html>
                  <head>
                    <style>
                      @page {
                        margin: 0.125in;
                        size: 3in %fin;
                      }
                    </style>
                  </head>
                  <body style="margin: 0; padding: 0">
                    <div style="font-size: 20pt; background-color: green">A B C D E F G H I J K L M N O P Q R S T U V W X Y Z</div>
                  </body>
                </html>""";

        return String.format(html, pageHeight);
    }

    public static void main(String[] args) throws Exception {
        ITextRenderer iTextRenderer = new ITextRenderer();
        iTextRenderer.setDocumentFromString(generateHtml());
        iTextRenderer.layout();

        int height = iTextRenderer.getRootBox().getHeight();

        float heightInInches = height / iTextRenderer.getSharedContext().getDPI();

        iTextRenderer.setDocumentFromString(generateHtml(heightInInches +
                // Add back page margins
                0.25f +
                // Make sure last line box doesn't touch bottom of page (which will move it to
                // a second page)
                0.01f));
        iTextRenderer.layout();

        ByteArrayOutputStream os = new ByteArrayOutputStream();
        iTextRenderer.createPDF(os, true);

        try (FileOutputStream fos = new FileOutputStream("/tmp/foo.pdf")) {
            fos.write(os.toByteArray());
        }
    }
}