py-pdf / fpdf2

Simple PDF generation for Python
https://py-pdf.github.io/fpdf2/
GNU Lesser General Public License v3.0
1.06k stars 242 forks source link

[QUESTION] How to generate a single page PDF with dynamic height? #1154

Open FilipeMarch opened 4 months ago

FilipeMarch commented 4 months ago

Hello, I'm trying to print some pdfs using a thermal printer (58mm) on my mobile app.

from fpdf import FPDF

pdf = FPDF("P", "mm", (135, "What do I put here?"))

The issue is that the pdf page is generated dynamically from the data of each user. We generate an image that I don't know beforehand what's gonna be the final height.

So the page can have a final height of 100, 300, 1200 pixels, I simply do not know.

I want to generate a single page pdf that will grow until it fits all the content. The main issue for me is making sure that all the content is going to fit in a single page.

I know that 135 is the correct width, but because the height is dynamic, how can I deal with that situation?

I would appreciate any help or guidance on how to approach this, thanks!

gmischler commented 4 months ago

This is essentially the same scenario as described in #678. The general approach I suggested there is likely to work for you as well.

FilipeMarch commented 4 months ago

This is essentially the same scenario as described in #678. The general approach I suggested there is likely to work for you as well.

I don't see how your example at https://github.com/py-pdf/fpdf2/discussions/678#discussioncomment-5087430 can solve this issue. You created three pages, changed the dimension of the second and third page, and rotated the third page.

How does that relate to dynamically changing the height of a single page based on content?

Look at this very simple example:

from fpdf import FPDF

pdf = FPDF()
pdf.set_font('Courier', '', 11)
pdf.add_page()

x = 200
for i in range(x):
    pdf.cell(txt=f'{i:02}: a'*5, new_x="LEFT", new_y="NEXT")

doc = pdf.output('pagesizetest.pdf')

Would you be able to change that code above such that it will result in a pdf containing only one single page with the whole text and no space left? This is the output I'm expecting:

image

pagesizetest.pdf

I want the result to always fit in one single page. The width of the page needs to be 135, the height depends on the size of the content, which I can't predict. The content may have text and images.

andersonhc commented 4 months ago

The code below is taking your example and applying the approach taken on #678 to adjust the page size:

from fpdf import FPDF

pdf = FPDF("P", "mm", (135, 999999))
pdf.set_font('Courier', '', 11)
pdf.add_page()
with pdf.rotation(180, x=pdf.w/2, y=pdf.h/2):
    x = 500
    for i in range(x):
        pdf.cell(text=f'{i:02}: a'*5, new_x="LEFT", new_y="NEXT")

pdf.pages[1].set_dimensions(pdf.w_pt, (pdf.y + pdf.t_margin)*pdf.k)

doc = pdf.output('pagesizetest.pdf')

The PDF standard interprets the vertical coordinates from bottom to top, so when you re-dimension your page you lose the content at the top - that's why rotating the content will make your life a lot easier. As your use case is to print the result I believe it shouldn't be an issue.

FilipeMarch commented 4 months ago

Woooow, it works! That's awesome. Thanks @andersonhc and @gmischler, I'm really glad this workaround exists, that was quite unexpected.

Later I will see if I can rotate the pdf 180 degrees again after the whole process, because we are printing all other receipts in the "normal" orientation, it would be strange if only this receipt had a different behavior.

gmischler commented 4 months ago

Later I will see if I can rotate the pdf 180 degrees again after the whole process,

I thought about that quite a bit, and couldn't figure out a really good way yet. Given how fpdf2 is organized internally, you need to know the pivot point of a rotation in advance.

There might be a (very hacky) way to do two nested rotations around the original center and later replace the pivot coordinates of the outer one in the output data stream.

But the easiest way will be to do some postprocessing with another tool. Right next door in the same organisation for example we have pypdf, which has no problem at all to rotate a page.

Lucas-C commented 3 months ago

Do you have other questions regarding this @FilipeMarch? Or can we close this issue?

@gmischler & @andersonhc: do you think that we should document this workaround in our documentation?

FilipeMarch commented 3 months ago

Do you have other questions regarding this @FilipeMarch? Or can we close this issue?

@gmischler & @andersonhc: do you think that we should document this workaround in our documentation?

Hello, I ended up finding a solution to print the image directly on the thermal printer, without having to create a pdf first containing the image, which made the whole process much faster.

I really would like this library to support dynamic height without the side-effect of ending up with the content rotated, and needing another library to fix it. The whole concept is strange, and on the other hand I believe creating a pdf with dynamic height is very common in general.

Thanks for the help and support, this issue can be closed.

Lucas-C commented 3 months ago

Thank you for the feedback @FilipeMarch! 👍

I have to say that I am a bit hesitant about this: do we want to support this feature in fpdf2? What do you think about this @andersonhc & @gmischler?