py-pdf / fpdf2

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

Simple section borders #1115

Closed hyperstown closed 8 months ago

hyperstown commented 9 months ago

Hi, first of all, thanks for this great package. I've been using it for a few days and it's working great but I noticed it's really hard to make a border for a certain section of pdf. Something like this for example:

image

I could use table if it was just a text but if I want to use mix of text images or even a table it becomes really difficult.

Example solution At first I created border like this using FPDF.line() method but it's quite a pain to keep track of line coordinates. That's why I thought of simpler way of doing just that with a code like this:

pdf.cell(0, 10, "Example border:", align='C')
pdf.ln(10)

with pdf.border(width=0.8) as border:
    pdf.cell(0, None, "This text will be displayed in border")
    pdf.ln(4)
    pdf.cell(0, None, "And this text as well")
    pdf.ln(4)

It works similar to PDF.table(). I implemented this functionality with this code:

class Point:
    def __init__(self, x: int, y: int):
        self.x = x
        self.y = y

    def __str__(self):
        return "x=%d, y=%d" % (self.x, self.y)

    def __dict__(self):
        return {"x": self.x, "y": self.y}

    def __iter__(self):
        return iter([self.x, self.y])

class Border:

    top_left = None
    top_right = None

    def __init__(self, pdf, width=0.8):
        self.pdf = pdf
        self.width = width
        self.pdf_margin = pdf.l_margin

    @property
    def start_x(cls):
        return (cls.pdf.w - (cls.pdf.w * cls.width)) / 2

    @property
    def end_x(cls):
        return cls.start_x + (cls.pdf.w * cls.width)

    def __enter__(self):
        self.pdf.set_left_margin(self.start_x)
        self.top_left = Point(self.start_x, self.pdf.y)
        self.top_right = Point(self.end_x, self.pdf.y)
        self.pdf.line(*self.top_left, *self.top_right)
        return self

    def __exit__(self, exc_type, exc_value, traceback):
        self.pdf.line(self.start_x, self.pdf.y, *self.top_left)
        self.pdf.line(self.start_x, self.pdf.y, self.end_x, self.pdf.y)
        self.pdf.line(self.end_x, self.pdf.y, *self.top_right)
        self.pdf.set_left_margin(self.pdf_margin)
        return False

It works by creating one horizontal line at the start of the block, another one at the end of the block and two vertical lines that connect the whole.

Additional context If that's something that could be added to this package I can make a PR.

Lucas-C commented 8 months ago

Hi @hyperstown

Welcomen and thank you for taking the time to describe your need and suggesting a new feature! πŸ‘

First off, I'd like to make sure that fpdf2 is not already able to do what you want 😊 Doesn't this code snippet reproduce your target PDF?

from fpdf import FPDF

pdf = FPDF()
pdf.add_page()
pdf.set_line_width(1)

pdf.set_font("Helvetica", "B", size=30)
pdf.cell(text="BORDER EXAMPLE", center=True)
pdf.ln(16)

pdf.set_font(style="B", size=20)
pdf.cell(text="Example border:", center=True)
pdf.ln(12)

pdf.multi_cell(text="This text will be displayed in border\nAnd this text as well", center=True, w=160, border=True)
pdf.ln(20)

pdf.set_font("Times", size=16)
pdf.cell(text="This is outside the border")

pdf.output("issue_1115.pdf")
gmischler commented 8 months ago

Doesn't this code snippet reproduce your target PDF?

The specific example given, yes. But the requirements were to also include images and other stuff (and possibly text with varying formatting).

Those features can be most easily realized with text_columns(). combined with the suggested approach.

Currently, the text columns don't support a border of their own, since those might potentially conflict with other planned features (such as adding and subtracting text flow areas from each other). But especially for the columns, border lines might actually make sense, and may eventually be added. Given that, and the fact that the suggested code works for the specific use case, but is not very adaptable, it should probably not get added to the code base for now. It will still serve its purpose in a subclass, of course.

hyperstown commented 8 months ago

@Lucas-C sorry for late reply. Yes as @gmischler said requirement is to handle not only text but also images, tables and overall just keep full functionality of plain FPDF object inside border. In my example here I used text cells since I wanted to keep it as simple as possible. It's also true that code I provided here is not suitable for actual code base but it's purpose is to show an example implementation of requested feature. I believe making borders using python's with statement is very intuitive and that's why I created this issue.

Lucas-C commented 8 months ago

I believe making borders using python's with statement is very intuitive and that's why I created this issue.

I agree that your approach is quite elegant, I like it πŸ™‚

There are a few points that comes to my mind:

Overall, I think it could be a handy feature for fpdf2 users. We already mention some companion libraries in our "Related" section in the docs, like for example digidigital/Extensions-and-Scripts-for-pyFPDF-fpdf2: https://py-pdf.github.io/fpdf2/#related If you publish your class a GitHub gist (or anywhere else that has a unique URL), I'll be happy to add a reference to it in our docs! Or you could also submit a PR to digidigital/Extensions-and-Scripts-for-pyFPDF-fpdf2 😊

What do yout hink about this @hyperstown?

hyperstown commented 8 months ago

I see, there are a lot of things to consider in order to implement such feature. For now I can provide a GitHub gist as you suggested with example usage and list of caveats. @Lucas-C, please let me know what do you think. As for other features, links and background colors sounds awesome! Though I'd have to study source code a little bit more to just think of a way to implement this πŸ˜‰

Lucas-C commented 8 months ago

Great!

I opened PR #1125 to add a mention of this gist.

Could you review it and tell me if that seems fine to you? 😊

Lucas-C commented 8 months ago

@allcontributors please add @hyperstown for content

allcontributors[bot] commented 8 months ago

@Lucas-C

I've put up a pull request to add @hyperstown! :tada:

hyperstown commented 8 months ago

Could you review it and tell me if that seems fine to you? 😊

Looks great, thank you! 😊

Lucas-C commented 8 months ago

Merged! Thank you @hyperstown πŸ‘ Closing this thread now