Outils pour utilisateurs

Outils du site


issue160:python

Ceci est une ancienne révision du document !


Welcome back. I truly hope that everyone is healthy. Here in central Texas, it’s been hot. Air temperature of 104°F (40°C) with a heat index well over 107 for days, and it’s expected to continue for at least the next 7 days.

Last month, I started showing you the pyFPDF library. This month (FCM #160), we will be continuing to look at this library and examining the Templating portion of the library.

My assumption was that the Templating feature was originally designed to handle things like invoices and labels. Furthermore, I thought that this could easily be used to produce things like recipe printouts or anything else that you wanted to have certain data show up at some preset X/Y position on a page and rendered into a PDF file. I was partially right, but it seems that pyFPDF is not completely up to the task for my needs without a tonne of work.

I’ll start with a sample of a recipe from my cookbook database that represents a “normal” single page recipe that would or could be printed. In its current “state”, the cookbook program creates an html page on the fly based on the recipe that needs to be printed, then sends it to the default browser for printing. It looks something like the image bottom left.

Not the prettiest, I have to admit, but it does the job and handles situations where a user needs a paper copy of the recipe (like if a friend who just can’t live without the recipe) as well as recipes that have lots of ingredients and/or lots of instructions, and flows into two or three pages.

Back to the pyFPDF library; at first glance, the documentation is fairly sparse when it comes to some of the options. Most of it made sense and seemed to be reasonably easy to translate into what I was hoping to do. Well, I was really wrong. It IS sparse and some of the information is confusing. I’ll address this as we go.

Since I almost always provide the code and most everything you need to run the demos I create for these articles, I didn’t want to attempt to provide the entire database. There are over 300 recipes and images (in a separate folder), and I really don’t think that pastebin will let me upload a sql database and that many images (or images at all). Therefore, I created a simple program to extract 3 “normal” single-page recipes and hard coded them into a series of “records” which are then dumped into lists for each table. Then, with a printout of the recipes on my clipboard and my ruler in hand, I started to dissect the XY coordinates of where I wanted to put each of the elements.

I decided to use the hardcode method of defining the elements rather than the CSV method, since I wasn’t sure how everything would work and fit together. If it worked, then I could always create a CSV directly later on. I’m really glad I didn’t. You’ll understand later.

Due to the use of f-strings, you will need to use Python 3.7.4 or greater.

I started with the header and recipe title. That went pretty well and I was able to get those two elements defined. With one of the sample programs, I started to copy and paste from their sample into my code.

Shown right is a shortened example of the element structure…

As you can see, it’s a list of dictionaries. We’ll break one of them down… • Name is the name you want to reference it by • Type is the type of element this is defining. The documented options are: 'T': texts, 'L': lines, 'I': images, 'B': boxes, 'BC': barcodes (more on this in a moment). • X1, Y1, X2, Y2 are the xy positions of an imaginary box. Upper left to lower right. • Next comes the font specifications, foreground/background definitions, alignment, text (if any) and the priority for the Z-order of the element. The text can be overridden when calling the element. • There is also an optional multiline attribute, which, if used, will be the last in the dictionary.

Now in the documentation, the Type specification shows only these 5 options. However, by digging into the code of the library, there is one other. It is “W” for write. It was added to allow (url) links in templates (using write method), but can be used in other situations. The good news for this option is that it works similar to the function that we created last month called chapter_body. It allows for multiline paragraph type text to be rendered correctly in a “flowing” manner.

From what I could see from my limited testing, the priority (Z-Order) doesn’t make a difference what value you use. I didn’t spend a tremendous amount of time on that part of the testing.

I was somewhat concerned about the image type, since the images I use are not a standard size. Since most are scraped from the web, some will be in landscape mode and some in portrait mode. Some are rather large and others fairly small. Luckily, the rendering image resizes the image to fit within the bounds set in the XY positioning.

Now, let’s take a look at the Ingredients section of the PDF. I really wanted to have the header and the text of the list to align as left-justified, similar to the printout image at the top of the article. For the list of ingredients (and the instructions) I ended up having to use the “W” write method. The normal “T” text method, with multiline set to True (it uses the multi_cell method internally), didn’t work correctly. The only drawback is a visual one which forces an indent for the first line of the “paragraph”. While this is nice for a chapter paragraph, it looks (at least to me) wrong, and is somewhat distracting.

Shown top right the element definitions for the ingredientshead and ingredientitems, which is very similar to the instructions section…

Notice that the y1 position for the static text “Ingredients” is at 220 while the ingredient items block is set at 115. To me, this makes no sense. The same thing occurs with the instructions static text and the flowing text of the instructions.

Now that we have our data defined and the elements dictionary set up (see the full code for all the elements definition), all that’s left to do is to make the function that will step through all the parts of the recipe document and render the document (below).

So the function definition accepts a parameter called “which”. This will be the index of the recipe within the “database”. We pull the recipe title and the recipe id into separate variables, not only for use now, but also later on. Then we instantiate the template object, defining the title of the document, the format of the printout, and the elements. You can also set properties for the document (author, subject, etc) here as well. Finally, we add a page with the add_page() method. Again, fairly close to what we did last month.

So, to set the various elements in the document, we call the elements that we want to include with, in some cases, a string – one at a time. The format is:

Object[element] = optional string

The code for that is on the next page, top right.

Since there is no straightforward way to handle a list, I stepped through the list of ingredients and created a string, which is then set using the ingredientitems element (bottom left).

The instructions section is handled the same way.

Finally (bottom right), we render the page pretty much as we did last month, but this time, we use the recipe title as the filename for the PDF. After that, we notify the user that the rendering process is complete.

Lastly, I needed a way for the program to prompt the user to select which recipe to use. I created a simple CLI based menu (right).

And a routine that loops and does the menu until the user uses “0” to quit (middle right).

Well, that’s it. I believe that, as a quick PDF generator that can be controlled easily from Python, pyFPDF is a good tool. However, as a template engine, I’m not convinced that it will work in the real world. Given the fact that the latest code changes were done 3 years ago, I don’t hold out much hope that the author will address the many issues surrounding the template engine. There is a big part of me that wants to fork the project and modify the code myself, but life and time and work (as always) prevent me from doing it right now. Just one more item on the todo list.

Normally, I would post the project code, when the project is more than working directly within the Python shell. This month, I am going to break tradition and not post it on Pastebin.com. The reason for this is that I don’t believe that pastebin will allow me to post the image files for the recipes. This is also one of the reasons that I didn’t just directly work from the recipe database, which at the moment, holds over 300 recipes, and for each recipe there is an image. So, I’ve created a repository on Github to hold this month’s project. By doing it this way, all you have to do is follow the link to the repository, download the project as a zip file (you could clone it, but that is, in my mind, a lot of wasted effort on your part), unzip the folder somewhere convenient for you, and run the python program, called template1.py. The link to the repository is https://github.com/gregwa1953/FCM160. One other benefit is that if something happens and I lose a domain (as has happened in the past), this will last forever or until github dies. Given the amount of code on github, I REALLY doubt that will happen.

One more bit of news before I leave you for this month. As of Wednesday, August 5, I have signed a contract to write another book. This one will be about learning to use PAGE to create GUIs for Python. I’ve written a few articles about PAGE in the past, and I’m sure that I will in the future as well. The working title, at the moment, is ‘Learning Page A GUI Designer for Python’ and I’m estimating that it will be published sometime in January 2021.

So as always, until next time; stay safe, healthy, positive and creative!

issue160/python.1598718762.txt.gz · Dernière modification : 2020/08/29 18:32 de auntiee