Creating PDF Output

Creating PDF Output


5 min read

In the last post we generated a view on all "contemporaries" of a specified person. The data were fetched from the database with help of a Pilog query.

Now we will see how we can write this output to a .txt or .PDF file.

We continue with our family-contemporaries.l file from the last post.


In order to be able to print the PDF output, we need some additional packages.

First of all, make sure that the @lib/svg.l library and svg namespace are declared at the top of the program:

(load "@lib/http.l" "@lib/xhtml.l" "@lib/form.l" "@lib/svg.l")
(symbols 'family 'svg 'pico)

Also, we need an external package to convert SVG files to PDF called librsvg2.bin ("librsvg" on termux for Android). On Debian systems you can install it with

sudo apt-get install librsvg2.bin

Creating a Textfile for Download with tmp

First of all, let's create a new file. For this purpose we can use the tmp function:

(tmp ['any ..]) -> sym

Returns the path name to the packed any arguments in a process-local temporary directory. The directory name consists of the path to ".pil/tmp/" in the user's home directory, followed by the current process ID *Pid. This directory is automatically created if necessary, and removed upon termination of the process (bye).

Let's try in the REPL:

: (tmp "Testfile")
-> "/home/mia/.pil/tmp/7768/Testfile"

Let's name our file Contemporaries.txt and bind its path to the local variable Txt:

'(let Txt (tmp "Contemporaries.txt")
   (out Txt (txt> (chart)))

Then we need to get all properties from our chart as text. This can be done using the txt> method of the PicoLisp GUI class. Then we pass txt> to our curent chart using the function (chart) which returns the current chart and write the output to Txt.

'(let Txt (tmp "Contemporaries.txt")
   (out Txt (txt> (chart)))

Creating the download button

Now let's wrap the above code in a button which triggers the download. When the button is pressed, the above code is executed and the user is redirected to the URL of the file. This automatically triggers the download dialog in the browser.

The final code for the button looks like this:

(gui '(+Rid +Button) "Textfile"
    '(let Txt (tmp "Contemporaries.txt")
       (out Txt (txt> (chart)))
       (url Txt) ) )

Quite short, isn't it? If we add this to our family-contemporaries.l file (still inside of the form function), the result looks like this:

Peek 2021-11-06 13-04.gif

We get a textfile where each line corresponds to a row in the table. The columns are separated by tabs.

Creating the PDF download

Now let's do the same with our PDF. However this time we the output formatted as table, like this:


The first step for the PDF are the same, except that we don't use a simple text file, but the pdf function to create a PDF in landscape format:

(pdf *A4-DY *A4-DX (tmp "Contemporaries.pdf")
   (out (tmp "Contemporaries.txt")
      (txt> (chart)) )

For portrait format, we can write *A4-DX *A4-DY.

Formatting the output

In the next step, let's read the file in again and add some formatting. First, we define the variables

  • Page: 1,
  • Fmt: list of width attributes in "dots" (1/72 inch), and
  • Ttl: the first line that is read in.
(in (tmp "Contemporaries.txt")
   (let (Page 1  Fmt (200 120 50 50 120 120 120)  Ttl (line T))

Then we style the format with help of the following functions:

  • font: takes a number and a font
  • width: stroke width in dots
  • down: margin to the top in dots
  • indent: margin to the left in dots

First we set the general font style:

(font (7 . "Helvetica"))
(width "0.5")

Every new page has a offset to top by 12 and is indented by 40. If there is more than one page, every subsequent page carries the title and page number. After that comes the table head, followed by another space.

      (down 12)
      (indent 40
         (ifn (=1 *Page)
            (ps (pack Ttl ", Page " (inc 'Page)))
            (font 9 (ps Ttl)) )
         (down 12)
         (table Fmt
            "Name" "Occupation" "born" "died" "Father" "Mother" "Partner" )
         (down 6)

Generating the table content

After the table title is created, we loop over the each line of the file and split the line at the tabs ("^I") and put them to a new list L.

Also we need to set the exit condition for loop: either when the file ends (eof) or when there are less than 80 dots distance to the lower bottom of the PDF.

   (let L (mapcar pack (split (line) "^I"))
   (T (eof))
   (T (>= *Pos (- *DY 80)) T) ) )

Within the loop, we can write each table cell using the ps function:

(down 8) 
(table Fmt
   (font "Helvetica-Bold" (ps (car L)))
   (ps (cadr L))
   (ps (caddr L))
   (ps (cadddr L))
   (ps (get L 5))
   (ps (get L 6))
   (ps (get L 7)) )
(down 4) )

Defining the button

Now the last thing we need to do is defining the button and call the url function, which we can simply wrap around the whole PDF generating code:

(gui '(+Rid +Button) "PDF"
      (pdf *A4-DY *A4-DX (tmp "Contemporaries.pdf")
         (out (tmp "Contemporaries.txt")
         (in (tmp "Contemporaries.txt")

If we now restart our script, we get another button labelled "PDF" and can download the displayed content.


You can find the finished program here.