Search code examples
phppdfpdf-generationpdflib

PDFLib how to print name value pairs?


I'm using PDFLib (this library https://www.pdflib.com/). I'm on PHP but this library exists also for other languages, so the question is not specific for PHP.

I would like to print on the PDF name value pairs. Something like this: expected output

I know the easiest solution would be to use a table, but I can't because the PDF has to be accessible and they told me that, on PDF, a table to show name-value paris would not be accessible, so I have to find another solution instead of table.

Currently I tried with Textflow:

<?php
$upperX = 525;
$upperY = 780;
$lowerX = 70;
$lowerY = 50;

$y = $upperY;
$x = 70;

$pdf = new \PDFlib();
$pdf->begin_document('', '');
$pdf->begin_page_ext(0, 0, 'width=a4.width height=a4.height');

// Write "Name-Value paris:"
$optlist = "fontname={Helvetica} fontsize=8 encoding=utf8 alignment=center fakebold=true";
$tf = 0;
$tf = $pdf->add_textflow($tf, "Name-Value paris:", $optlist);
$pdf->fit_textflow($tf, $x, $lowerY, $upperX, $y, '');
$pdf->delete_textflow($tf);

$y -= 10;

// Write the pairs
$label_optlist = "fontname={Helvetica} fontsize=7 encoding=utf8 fakebold=true leftindent=0%";
$value_optlist = "fontname={Helvetica} fontsize=7 encoding=utf8 fakebold=false leftindent=22%";

$tf = 0;
$tf = $pdf->add_textflow($tf, "Name:", $label_optlist);
$tf = $pdf->add_textflow($tf, "John", $value_optlist);
$pdf->fit_textflow($tf, $x, $lowerY, $upperX, $y, '');
$pdf->delete_textflow($tf);
$y = $pdf->get_option('texty', ''); // Get Y where the above textflow ends

$tf = 0;
$tf = $pdf->add_textflow($tf, "Surname:", $label_optlist);
$tf = $pdf->add_textflow($tf, "Doe", $value_optlist);
$pdf->fit_textflow($tf, $x, $lowerY, $upperX, $y, '');
$pdf->delete_textflow($tf);
$y = $pdf->get_option('texty', '');

$tf = 0;
$tf = $pdf->add_textflow($tf, "Date of birth:", $label_optlist);
$tf = $pdf->add_textflow($tf, "2022/11/08", $value_optlist);
$pdf->fit_textflow($tf, $x, $lowerY, $upperX, $y, '');
$pdf->delete_textflow($tf);
$y = $pdf->get_option('texty', '');

$tf = 0;
$tf = $pdf->add_textflow($tf, "A key that has a long value:", $label_optlist);
$tf = $pdf->add_textflow($tf, "A very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very very long value", $value_optlist);
$pdf->fit_textflow($tf, $x, $lowerY, $upperX, $y, '');
$pdf->delete_textflow($tf);


$pdf->end_page_ext('');
$pdf->end_document('');
return $pdf->get_buffer();

It is working, but as you can see, in optlist, I put leftindent=0% and leftindent=22%

The problem is that if a key would be longer, I will have to increase the "leftindent" manually, otherwise it will not align with other pairs. Furthermore, what if the keys would be dynamic so I don't know their length? I wouldn't know how much "leftindent".

Is there a cleaner and better way to print name value paris using PDFLib?


Solution

  • I would like to answer your questions about the above answer in a new one. There you can format better.

    Instead of leftindent you can also simply move your x position of the fit_textflow(). Hopefully that makes your code easier to understand.

    About using encoding=utf8: I always get an error message then. Which PDFlib version are you using? (you can see in the phpinfo() output for example) Generally utf8 is not a valid keyword unless you have crafted and provided an encoding yourself. But that would not be recommended.

    From the PDFlib 10 API Reference, chapter 4.1, table 4.1: enter image description here

    About embedding: you must provide the font files in the SearchPath, because PDFlib needs the font data at runtime. Please refer to the PDFlib 10 Tutorial, Chapter 3.1.4, and Chapter 6.3.4 "Searching for Fonts". In the supplied PDFlib examples the SearchPath is set to "../data", where you can find the resources needed in the examples. You might want to crib there.

    Back to the table and accessibiltiy:

    In general it is already possible to create a table without a header, but this will be flagged in accessibility checks. Depending on how important the topic is, you should implement the table accordingly. On the other hand, your output is currently not accessible either, so you will have a much easier time if you put everything in a table. I have attached a very simple PDF/UA table which is created with the following code. Maybe you can identify your problem more precisely.

    PDF/UA Table created with PDFlib 10

    <?php
    /*
     *
     * Demonstrate automatic table tagging
     *
     * required software: PDFlib/PDFlib+PDI/PPS 10
     * required data: image file (dummy text created within the program)
     */
    
    /* This is where the data files are. Adjust as necessary. */
    $searchpath = dirname(__FILE__,3)."/input";
    $title = "table_pdfua1";
    
    $p = null;
    
    try {
        $p = new pdflib();
    
        $tf = 0;
        $tbl = 0;
        $rowmax = 5;
        $colmax = 5;
    
        $llx = 50; $lly = 50; $urx = 550; $ury = 700;
    
        /* Dummy text for filling a cell with multi-line Textflow */
        $tf_text =
            "Sample text created with the Textflow feature in order to " .
            "create multiline content within a table cell.";
    
        /*
         * Set the search path for fonts and images etc.
         */
        $p->set_option(
            "errorpolicy=exception SearchPath={" . $searchpath . "}");
        
    
        if ($p->begin_document("", 
                "pdfua=PDF/UA-1 lang=en tag={tagname=Document}") == 0)
            throw new Exception("Error: " . $p->get_errmsg());
    
        $p->set_info("Creator", "PDFlib Cookbook");
        $p->set_info("Title", $title);
        
        /* Automatically create spaces between chunks of text */
        $p->set_option("autospace=true charref");
    
        /* -------------------- Add table cells -------------------- */
        $row = 1;
        $col = 1;
       
        for ($row; $row <= $rowmax; $row++) {
        /* ----- Simple text cell */
        $col = 1;
    
        $optlist = "colwidth = 100 fittextline={fontname=NotoSerif-Bold fontsize=10 position={left top}} margin=5";
    
        $tbl = $p->add_table_cell($tbl, $col, $row, "text " . $row, $optlist);
    
        /* ----- Multi-line text with Textflow */
        $col++;
    
        $optlist = "fontname=NotoSerif-Regular fontsize=10";
    
        $tf = $p->add_textflow(0, $tf_text, $optlist);
    
        $optlist = "colwidth=300 rowheight=8 margin=5 textflow=" . $tf . " fittextflow={firstlinedist=capheight}";
    
        $tbl = $p->add_table_cell($tbl, $col, $row, "", $optlist);
        }
    
        /* ---------- Place the table on one or more pages ---------- */
    
        /*
         * Loop until all of the table is placed; create new pages as long
         * as more table instances need to be placed.
         */
        do {
            $p->begin_page_ext(0, 0, "width=a4.width height=a4.height");
            
            $p->create_bookmark("Tagged table demo", "");
            
            $p->fit_textline("Name-Value pairs", 50, 750,
                "fontname=NotoSerif-Regular " .
                "fontsize=16 tag={tagname=H1}");
            
            /*
             * Shade every other $row; draw lines for all table cells. Add
             * "showcells showborder" to visualize cell borders
             */
            $optlist = "tag={tagname=Table Summary={key value tablese}} ";
            /* Place the table instance */
            $result = $p->fit_table($tbl, $llx, $lly, $urx, $ury, $optlist);
    
            if ($result == "_error")
                throw new Exception("Couldn't place table : "
                        . $p->get_errmsg());
    
            $p->end_page_ext("");
        }
        while ($result == "_boxfull");
    
        /* Check the $result; "_stop" means all is ok. */
        if (!$result == "_stop") {
            if ($result == "_error") {
                throw new Exception("Error when placing table: " 
                                            . $p->get_errmsg());
            }
            else {
                /*
                 * Any other return value is a user exit caused by the
                 * "return" option; this requires dedicated code to deal
                 * with.
                 */
                throw new Exception("User return found in Textflow");
            }
        }
    
        /* This will also delete Textflow handles used in the table */
        $p->delete_table($tbl, "");
    
        $p->end_document("");
        $buf = $p->get_buffer();
        $len = strlen($buf);
    
        header("Content-type: application/pdf");
        header("Content-Length: $len");
        header("Content-Disposition: inline; filename=" . $title . ".pdf");
        print $buf;
    }
    catch (PDFlibException $e) {
        echo("PDFlib exception occurred in" . $title . "sample:\n" .
            "[" . $e->get_errnum() . "] " . $e->get_apiname() . ": " .
            $e->get_errmsg() . "\n");
        exit(1);
    }
    catch (Throwable $e) {
        echo($e);
        exit(1);
    }
    
    $p = 0;
    ?>
            
    

    this sample is adapted from the PDFlib Cookbook "pdfua/table_pdfua1".

    However, I suspect that the accessibility issue is not a real problem for you, so you could simply implement the whole thing with a PDFlib table. Just ask the person what the table and accessibility concerns are. Because if the real Tagged PDF (PDF/UA) is not an issue, many things become easier.