PDFlib Cookbook

cookbook

interactive/markup_annotations

Create various types of markup annotations directly and in a Textflow.

Download PHP Code  Switch to Java Code  Show Output 

<?php
/*
 * Markup annotations:
 * Create various types of markup annotations directly and in a Textflow.
 *
 * Some annotations are placed by explicitly specifying their rectangle
 * coordinates.
 * 
 * The remaining annotations are placed over text fragments in a Textflow.
 * These locations are referenced by named matchboxes in the Textflow.
 * The options "matchbox" and "matchbox end" define the matchboxes in
 * the Textflow. The names are later used for creating the annotations;
 * explicit coordinates are not required in this case.
 * 
 * For the Highlight annotation a corresponding Popup annotation is created.
 * 
 * The following types of Markup annotations are demonstrated:
 * Caret, Circle, FileAttachment, FreeText, Highlight, Line, Square, Squiggly,
 * StrikeOut, Text (=sticky note), Underline
 * 
 * The following types of Markup annotations are not demonstrated here:
 * Ink, Polygon, PolyLine, Stamp (see topic stamp_annotation)
 *
 * Required software: PDFlib/PDFlib+PDI/PPS 9
 * Required data: none
 */

/* This is where the data files are. Adjust as necessary. */
$searchpath = dirname(__FILE__,3)."/input";
$outfile = "";
$title = "Markup annotations";

$exitcode = 0;

try {
    $p = new pdflib();

    $p->set_option("searchpath={" . $searchpath . "} stringformat=utf8");

    /* This means we must check return values of load_font() etc. */
    $p->set_option("errorpolicy=return");

    if ($p->begin_document($outfile, "destination={type=fitwindow}") == 0)
        throw new Exception("Error: " . $p->get_errmsg());

    $p->set_info("Creator", "PDFlib Cookbook");
    $p->set_info("Title", $title);

    /* Create page */
    $p->begin_page_ext(0, 0, "width=a4.width height=a4.height");

    // For simplicity we name the matchboxes according to the
    // corresponding annotation type that they will hold.
    $text =
        "Lorem " .
            "<matchbox={name=caret boxheight={ascender descender}}>caret<matchbox=end> " .
        "ipsum dolor sit amet, consectetur adi&shy;pi&shy;sicing elit, " .
        "sed do eius&shy;mod" .
            "<matchbox={name=circle boxheight={ascender descender}}> circle <matchbox=end>" .
        "tempor incidi&shy;dunt ut labore et dolore magna ali&shy;qua. Ut " .
            "<matchbox={name=highlight boxheight={ascender descender}}>highlight<matchbox=end> " .
        "enim ad minim ve&shy;niam, quis nostrud " .
            "<matchbox={name=line}>line<matchbox=end> " .
        "exer&shy;citation ull&shy;amco la&shy;bo&shy;ris nisi ut " .
            "<matchbox={name=square boxheight={ascender descender}}>square<matchbox=end> " .
        "ali&shy;quip ex ea commodo con&shy;sequat. Duis aute irure dolor in " .
            "<matchbox={name=squiggly boxheight={ascender descender}}>squiggly<matchbox=end> " .
        "repre&shy;henderit in voluptate velit esse cillum dolore eu " .
            "<matchbox={name=strikeout}>strikeout<matchbox=end> " .
        "fugiat nulla pari&shy;atur. Excep&shy;teur sint occae&shy;cat " .
        "cupi&shy;datat non proident, sunt in culpa qui officia dese&shy;runt " .
            "<matchbox={name=underline boxheight={ascender descender}}>underline<matchbox=end> " .
        "mollit anim id est laborum. ";

    $tf = $p->create_textflow($text,
        "fontname=NotoSerif-Regular fontsize=20 encoding=unicode leading=140% charref");
    if ($tf == 0)
        throw new Exception("Error: " . $p->get_errmsg());

    // Place the Textflow on the page
    $result = $p->fit_textflow($tf, 50, 50, 550, 550, "fitmethod=auto");
    if ($result != "_stop")
    {
        /* Check for errors */
    }


    /*
        * $p->info_matchbox() returns box coordinates in user coordinates
        * which we use in calls to create_annotation(). If the user
        * coordinate system has been transformed we need this method to
        * accept user coordinates. Instead of supplying "usercoordinate=true"
        * to individual calls we set it here globally. 
        */
    $p->set_option("usercoordinates=true");
    
    // Create some annotation types with explicit rectangle coordinates
    $p->create_annotation(50, 750, 75, 800, "Text",
        "contents={My little note...} open=false iconname=comment annotcolor=red");

    /* File attachment annotation */
    $asset = $p->load_asset("Attachment", "af1.txt", "description={Simple text file}");
    if ($asset == 0)
        throw new Exception("Error: " . $p->get_errmsg());
    
    $p->create_annotation(50, 700, 75, 725, "FileAttachment",
        "attachment=" . $asset . " contents={My file attachment} iconname=pushpin annotcolor=blue");
    
    /* FreeText annotation with a callout line pointing to the
        * top left corner of the Textflow box.
        */
    $llx = 200;
    $lly = 600;
    $urx = 350;
    $ury = 650;
    $font = $p->load_font("NotoSerif-Regular", "unicode", "embedding");
    $p->create_annotation($llx, $lly, $urx, $ury, "FreeText",
        "contents={FreeText with callout line} calloutline={" .
        "50 550 " .                                     // start point (what it points to)
        ($llx+50)/2 . " " . ($lly + $ury)/2 . " " .     // optional knee point
        $llx . " " . ($lly + $ury)/2 . "} " .           // end point (on the annotation border)
        "endingstyles={closedarrow none} fontsize=12 font=" . $font . " annotcolor=yellow");

    
    /* Create various annotation types in the corresponding matchboxes
     * in the Textflow. Since we use matchboxes as target rectangles
     * we can supply the annotation coordinates as 0, 0, 0, 0.
     */
    $p->create_annotation(0, 0, 0, 0, "Caret", "usematchbox=caret annotcolor=blue");
    
    $p->create_annotation(0, 0, 0, 0, "Circle", "usematchbox=circle linewidth=2 annotcolor=purple");
    
    // Attach Popup to the Highlight annotation via name
    $p->create_annotation(0, 0, 0, 0, "Highlight", "name=my_highlight popup=my_popup title={My highlight} contents={My comment in the Popup...} usematchbox=highlight linewidth=3 annotcolor=red");
    
    /* Create a Popup annotation for the Highlight annotation.
     * Fetch corner coordinates of the parent Highlight annotation so
     * that we can place the corresponding Popup close to it.
     */
    $llx = $p->info_matchbox("highlight", 1, "x1") +  50;
    $lly = $p->info_matchbox("highlight", 1, "y1") + 150;
    $p->create_annotation($llx, $lly, $llx+150, $lly+100, "Popup", "name=my_popup parentname=my_highlight open");
    
    // Fetch corner coordinates so that we can draw a diagonal line
    $llx = $p->info_matchbox("line", 1, "x1");
    $lly = $p->info_matchbox("line", 1, "y1");
    $urx = $p->info_matchbox("line", 1, "x3");
    $ury = $p->info_matchbox("line", 1, "y3");
    $p->create_annotation(0, 0, 0, 0, "Line", "usematchbox=line line={" . $llx . " " . $lly . " " . $urx . " " . $ury . "} linewidth=3 annotcolor=green");

    $p->create_annotation(0, 0, 0, 0, "Square", "usematchbox=square cloudy=1 linewidth=2 annotcolor=magenta");
    
    $p->create_annotation(0, 0, 0, 0, "Squiggly", "usematchbox=squiggly annotcolor=blue");
    
    $p->create_annotation(0, 0, 0, 0, "Strikeout", "usematchbox=strikeout annotcolor=red");
    
    $p->create_annotation(0, 0, 0, 0, "Underline", "usematchbox=underline annotcolor=purple");
    
    $p->end_page_ext("");

    $p->end_document("");

    $buf = $p->get_buffer();
    $len = strlen($buf);

    header("Content-type: application/pdf");
    header("Content-Length: $len");
    header("Content-Disposition: inline; filename=markup_annotations.pdf");
    print $buf;
} catch (PDFlibException $e) {
    echo("PDFlib exception occurred:\n".
        "[" . $e->get_errnum() . "] " . $e->get_apiname() .
        ": " . $e->get_errmsg() . "\n");
    exit(1);
} catch (Throwable $e) {
    echo("PHP exception occurred: " . $e->getMessage() . "\n");
    exit(1);
}

$p = 0;

?>