py-pdf / fpdf2

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

New feature: implement Pattern #790

Open Lucas-C opened 1 year ago

Lucas-C commented 1 year ago

Quoting the 1.7 PDF spec:

When operators such as S (stroke), f (fill), and Tj (show text) paint an area of the page with the current color, they ordinarily apply a single color that covers the area uniformly. However, it is also possible to apply “paint” that consists of a re- peating graphical figure or a smoothly varying color gradient instead of a simple color. Such a repeating figure or smooth gradient is called a pattern. Patterns come in two varieties: • Tiling patterns consist of a small graphical figure (called a pattern cell) that is replicated at fixed horizontal and vertical intervals to fill the area to be painted. • Shading patterns define a gradient fill that produces a smooth transition between colors across the area.

fpdf2 could support patterns to fill shapes.

tiling_patterns pdf

The developper who wishes to implement this feature can start by defining the end user API that fpdf2 users will use to define and use those PDF Patterns, in Python, to style shapes.

A starting point could be to extend the RenderStyle enum to support Patterns: https://pyfpdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.RenderStyle

Some reference documents:


By implementing this feature you, as a benevolent FLOSS developper, will provide access to the large community of fpdf2 users to a useful PDF functionality. As a contributor you will get review feedbacks from maintainers & other contributors, and learn about the lifecycle & structure of a Python library on the way. You will also be added into the contributors list & map.

This issue can count as part of hacktoberfest

elrobitaille commented 1 year ago

Will try to help with this feature

Lucas-C commented 1 year ago

Will try to help with this feature

Great! Do you have any questions? 😊

boushrabnd commented 11 months ago

Hello,

I was wondering if I could be assigned to this issue with my teammates to work on for the next three weeks for a university software engineering course (CMU17313) project. Is that possible?

Also, this link you provided gives a 404 error: https://pyfpdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.RenderStyle

Thank you.

Lucas-C commented 11 months ago

I was wondering if I could be assigned to this issue with my teammates to work on for the next three weeks for a university software engineering course (CMU17313) project. Is that possible?

Sure! I just assigned it to you 🙂

Please ask all the questions you need on this subject and the process of contributing to fpdf2, and I'll be happy to answer you!

Also, this link you provided gives a 404 error: https://pyfpdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.RenderStyle

This page has moved there: https://py-pdf.github.io/fpdf2/fpdf/enums.html#fpdf.enums.RenderStyle

boushrabnd commented 11 months ago

Thank you!

I have a quick question, I have been looking through the codebase, looking at the documentation and testing out py-pdf to try to understand how things work. I took a deep dive into understanding how pdfs are represented at a very low level and I understood that they are structured as series of objects like dictionaries, arrays and streams and that shapes specifically are defined using paths. Would you mind telling me which function or process takes care of inserting this path into the pdf? are you using a library (I am assuming the point py-pdf is to be that library lol ) or is this done manually?

I am trying to have a full grasp of understanding the process from beginning to end so that we can produce the best possible result when implementing patterns.

I am currently in the process of understanding the set_fill_color() function as I think it is the closest to what we are trying to do.

Lucas-C commented 11 months ago

I have been looking through the codebase, looking at the documentation and testing out py-pdf to try to understand how things work. I took a deep dive into understanding how pdfs are represented at a very low level and I understood that they are structured as series of objects like dictionaries, arrays and streams and that shapes specifically are defined using paths.

Great! Good job investigating the code base 😊 Just a minor note: this project is named fpdf2. It's part of the py-pdf GitHub organization, that also includes the pypdf project. I know, it can be confusing 😅

Would you mind telling me which function or process takes care of inserting this path into the pdf?

Sure, I'll try. So first, the fpdf.drawing module contains all the vector drawing logic. Then, the .new_path() or .draw_path() methods can be used to render vector graphics in a PDF document.

For example, the rendering of SVG images is performed in FPDF._vector_image(): https://github.com/py-pdf/fpdf2/blob/2.7.6/fpdf/fpdf.py#L4007 - There are basically 3 steps:

  1. Parse the SVG document into a SVGObject instance object: https://github.com/py-pdf/fpdf2/blob/2.7.6/fpdf/fpdf.py#L4018
  2. "Project" this SVGObject onto the PDF document viewport to get a drawing.GraphicsContext: https://github.com/py-pdf/fpdf2/blob/2.7.6/fpdf/fpdf.py#L4065C26-L4065C52
  3. Call .draw_path() to render this path: https://github.com/py-pdf/fpdf2/blob/2.7.6/fpdf/fpdf.py#L4077

I am currently in the process of understanding the set_fill_color() function as I think it is the closest to what we are trying to do.

Regarding this, there is currently a PR open about this feature, and how it behaves with tables: https://github.com/py-pdf/fpdf2/pull/934 You can have a look at it and even give some feedback or ask questions if you want!

By the way, I'll be busy until Monday, but you can expect more answers from me in a couple of days 😊

boushrabnd commented 11 months ago

Thank you so much for the detailed answer, this is very helpful! I will do my best.

Good luck on your work!

boushrabnd commented 11 months ago

Hello again,

I have been trying to integrate example 2 provided in the 1.7 PDF spec (Page 184 of the pdf) into a blank pdf generated by fpdf to see how the end product would look like so that I can implement it in the same manner. However, it refuses to show up. Could this be because of the fact that the specification is old? Or is there something in fpdf preventing this from showing up?

Lucas-C commented 11 months ago

However, it refuses to show up. Could this be because of the fact that the specification is old? Or is there something in fpdf preventing this from showing up?

You can check your PDF file using a PDF checker. We use 3 different ones in our GitHub Actions pipeline: https://github.com/py-pdf/fpdf2/blob/master/.github/workflows/continuous-integration-workflow.yml#L45

qpdf is especially simple to use: qpdf --check $file.pdf

You can also share the PDF document you generated in this thread, and I'll have a look at it.

boushrabnd commented 11 months ago

Hello Lucas,

I have made some good progress in trying to implement the Pattern Feature and now my teammates are going to complete it. I tried to make a pull request with my changes so that my teammates could work on that branch however I was denied permission. How do you think I should go about this?

Also, some feature findings:

I know these findings might be trivial but it took me a while to grasp how pdfs were represented (so I studied multiple pdfs generated by fpdf) but I put in my best and I am required to let my teammates continue the work as a part of our project.

Please let me know, thank you!

Lucas-C commented 11 months ago

I tried to make a pull request with my changes so that my teammates could work on that branch however I was denied permission. How do you think I should go about this?

I cannot help you much without details on the error you faced 😅 However, one thing is certain : you cannot push directly a branch to this git repository. You will have to create a fork of this repository, then push a branch on this forked repository and finally submit a PR.

Regarding your other comments, I agree that the FPDF.set_fill_color() & FPDF.set_font() methods are relatively "basic" and do not cover all the features of the PDF format. There are 2 main reasons for this:

Regarding the Pattern feature that you are working on, it simply means that some thought should be given on how to "expose" this functionality to fpdf2 users in the end. Maybe you could share in comments your ideas about this? 😊 Do you suggest to add new methods, add new optional parameters to existing methods, or even another approach?

boushrabnd commented 11 months ago

I have made a PR with the subtasks we suggested. We are suggesting a new method using the same structure set_font() uses. On extra thing that tilling patterns require is specifying the cell to be repeated in the stream so we'll have to work on that and see is we want to have set options or allow that much customization from the user.

Lucas-C commented 2 weeks ago

@boushrabnd started some work on this in PR https://github.com/py-pdf/fpdf2/pull/1041

However he was not able to finish it, so this issue is up-for-grabs! 🙂

Anyone is welcome to implement this.