skip to Main Content

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?

2

Answers


  1. 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

    who said that? This statement is a bit too general for now. Also, your content is not accessible in any way, because you achieve this in the PDF only through Tagged PDF.

    PDFlib can be used to create PDF/UA (i.e. Tagged PDF) which can be used to create accessible PDFs.

    The easiest way would be to follow the PDFlib 10 Coobkook example https://www.pdflib.com/pdflib-cookbook/pdfua/table_pdfua1/php/ and fill one table cell after the other with a textflow.

    Then PDFlib 10 will generate the table tagging for you, and you will have a nice accessible PDF. (See also PDFlib 10 Tutorial, Chapter 11.2.1 "Automatic Table Tagging"). The tutorial is included in the PDFlib 10 package, as well available on the download page.

    For PDF/UA please also refer to Chapter 11.4.1 "The PDF/UA-1 Standard" in the PDFlib 10 Tutorial. Your code fragment already shows that you must at least change the font, because the font must be embedded. In your case you are using the PDF Latin Core font Helvetica which is not embedded. Therefore I recommend to use a font that you have and like. As an example you could of course use the NotoSerif-Regular used in the PDFlib examples.

    if you want to stick with your solution, you could first determine the length for each key entry. You can do this via info_textline() with the same font options as when placing the text with fit_textline(). You could use this to determine the maximum length and then adjust the llx position for the textflows accordingly.
    I would not use leftindent, but pass different X values for fit_textline() and fit_texfflow().

    Also, I would get the end position with info_textflow(), not with get_option("texty").

    I think you are always better off with a PDFlib table. And if you really want to create Tagged PDF, that is also possible with PDFlib 10.

    One comment about your used options:

    $label_optlist = "fontname={Helvetica} fontsize=7 encoding=utf8 fakebold=true leftindent=0%";
    

    encoding=utf8 is not valid. The correct option value is "encoding=unicode". You can set $pdf->set_optin("stringformat=utf8"); when your input content is UTF-8. In PDFlib 10 stringformat=utf8 and encoding=unicode is default and can be omitted.

    Login or Signup to reply.
  2. 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.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search