Now to combine what I've learned about PDFlib & PHP thus far:
- group/label in optlist for begin doc to be used with suspend/resume_page.
- tables know when they are full, so we can use that in a sort of template manner and for a well-formatted TOC, as well as in tracking what pages are what section
There are a few changes to the "standard" create PDF file I've been building on since the (1) HELLO WERLD in this code. Nothing too complex. I change the file name of the output PDF to a variable outside the try catch and use it in the header/file= instead of the hard-casted way, and I put the document width/height also outside the try/catch so I can call them global in a function I tagged on to do all the work.
That said, the most important change of the last TOC code (5) is cutting out the building of a TOC by show_xy and replacing with a singular call to: buildTOC. I'll paste the entire thing last in this blog entry.
Change from show_xy to use of buildTOC function
// $total_line_length = 70;
// for($i = 0; $i < count($toc); $i++){
# the layout didn't work out with: $message = $section.str_repeat('.', $periods).$page_num;
# because the '.' character just is not taking up the space of alpha-chars of same size font.
# and if it is font based we have no flexibility (have to stick to Helvetica, 12) so, it looks
# like TABLES are the answer.?
// $section = $toc[$i][0];
// $page_num = $toc[$i][1];
// $periods = $total_line_length-(strlen($section)+strlen($page_num));
// syslog(LOG_ERR, "strlen(section): ".strlen($section));
// syslog(LOG_ERR, "strlen(page_num): ".strlen($page_num));
// syslog(LOG_ERR, "PERIODS: ".$periods);
// $message_width = $p->stringwidth($message, $font, 12);
// $p->show_xy($message, 36, $doc_height-(24*($i+1))-60);
// }
buildTOC($p, $toc, $font);
$p->end_page_ext("");
Function buildTOC()
There was a bunch of work and reading API and even some experimenting. I am obviously trying to use PDFlib, and I think it is powerful, but the optlist vs. documentaion combined with many Java-only examples is just a real time-burner. So, I hope this helps somebody because I did not find one single plain example of how to make a TOC with links to pages IN DOCUMENT. You'd think it would be out there, but I can find how to open and goto a place in ANOTHER DOCUMENT, or open a URL, but f-me if you can find an example of: Here is a clickable linked-to-page example using PDFlib.
On the other hand, I finally got it and that feels pretty good :)
As with all the code I've been pasting into this blog; make sure you read the comments if you want to know what is going on.
$file_name = "blog_7.pdf";
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
function buildTOC($pdf, $table_of_contents, $font){
global $doc_width, $doc_height, $file_name;
$p = $pdf;
$toc = $table_of_contents;
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
###### TABLE TIME ###########
$table=0;
$num_columns = 3;
$num_rows = count($toc);
#llx = lower left x, whereas ury = upper right y
$llx= 30; $lly=50; $urx=550; $ury=760;
# flipping the process to per row, per col.
$row_num = 0;
$widths = array(150, 370, 30);
# keep from ugly via tagging left and right with center, i.e. right center.
$positions = array("left center", "right center", "right center");
do{
$row_num++;
$col_num = 0;
do{
$col_num++;
$position = $positions[$col_num-1];
#addnameddest() may be great for a more complex document, i.e. having sub-sections in the
# middle of a page/section. but, going to the page where a section starts requires laying
# an annotation over each row in the TOC that we have w/o sub-section. _this is how:
# create_action w/ type GoTo, create_annotation w/ type: Link
$total_line_length = 100;
list($section, $page_num) = $toc[$row_num-1];
# using groups we can point at the group's page_num.
$link_optlist = "filename=$file_name destination={group=content page=$page_num type=fitwindow}";
$action = $p->create_action("GoTo", $link_optlist);
# the x positions are all good for these table rows to match the fit_table.
# but, the y positions need to change by 20, each, per row...
$anno_ury = $ury-(20*($row_num-1));
$annotation = $p->create_annotation($llx, $anno_ury-20, $urx, $anno_ury, "Link", "linewidth=0 action {activate $action}");
$periods = str_repeat('.', ($total_line_length-(strlen($section)+strlen($page_num))));
$optlist = "fittextline={position={".$position."} font=" . $font .
" fontsize=14} rowheight=20 colwidth=".($widths[$col_num-1]);//.$cell_border_opt;
$value = (($col_num-1) == 0)?$toc[$row_num-1][0]:((($col_num-1)==1)?$periods:$toc[$row_num-1][1]);
$table = $p->add_table_cell($table, $col_num, $row_num, $value, $optlist);
} while($col_num < $num_columns);
} while ($row_num < $num_rows);
###### fit_table optlist. time to place table.
$optlist = "";
//"stroke={{line=other}} header=0 fill={{area=rowodd fillcolor={gray 0.9}}} stroke={{line=other}} ";
$result = $p->fit_table($table, $llx, $lly, $urx, $ury, $optlist);
if ($result == "_error") {
die("Couldn't place table: " . PDF_get_errmsg($p));
}
}
The Whole Enchillada #1 Combo
Using same XML (bands/band) from other examples.
$file_name = "blog_7.pdf";
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
function buildTOC($pdf, $table_of_contents, $font){
global $doc_width, $doc_height, $file_name;
$p = $pdf;
$toc = $table_of_contents;
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
###### TABLE TIME ###########
$table=0;
$num_columns = 3;
$num_rows = count($toc);
#llx = lower left x, whereas ury = upper right y
$llx= 30; $lly=50; $urx=550; $ury=760;
# flipping the process to per row, per col.
$row_num = 0;
$widths = array(150, 370, 30);
# keep from ugly via tagging left and right with center, i.e. right center.
$positions = array("left center", "right center", "right center");
do{
$row_num++;
$col_num = 0;
do{
$col_num++;
$position = $positions[$col_num-1];
#addnameddest() may be great for a more complex document, i.e. having sub-sections in the
# middle of a page/section. but, going to the page where a section starts requires laying
# an annotation over each row in the TOC that we have w/o sub-section._this is how:
# create_action w/ type GoTo, create_annotation w/ type: Link
$total_line_length = 100;
list($section, $page_num) = $toc[$row_num-1];
# using groups we can point at the group's page_num.
$link_optlist = "filename=$file_name destination={group=content page=$page_num type=fitwindow}";
$action = $p->create_action("GoTo", $link_optlist);
# the x positions are all good for these table rows to match the fit_table.
# but, the y positions need to change by 20, each, per row...
$anno_ury = $ury-(20*($row_num-1));
$annotation = $p->create_annotation($llx, $anno_ury-20, $urx, $anno_ury, "Link", "linewidth=0 action {activate $action}");
$periods = str_repeat('.', ($total_line_length-(strlen($section)+strlen($page_num))));
$optlist = "fittextline={position={".$position."} font=" . $font .
" fontsize=14} rowheight=20 colwidth=".($widths[$col_num-1]);//.$cell_border_opt;
$value = (($col_num-1) == 0)?$toc[$row_num-1][0]:((($col_num-1)==1)?$periods:$toc[$row_num-1][1]);
$table = $p->add_table_cell($table, $col_num, $row_num, $value, $optlist);
} while($col_num < $num_columns);
} while ($row_num < $num_rows);
###### fit_table optlist. time to place table.
$optlist = "";
//"stroke={{line=other}} header=0 fill={{area=rowodd fillcolor={gray 0.9}}} stroke={{line=other}} ";
$result = $p->fit_table($table, $llx, $lly, $urx, $ury, $optlist);
if ($result == "_error") {
die("Couldn't place table: " . PDF_get_errmsg($p));
}
}
###### END FUNC LIST ###########
###### END FUNC LIST ###########
try{
# image tag of xml has url to image like so:
# http://img.karaoke-lyrics.net/img/artists/32670/the-pogues-164114.jpg
$xml = new SimpleXMLElement($xmlstr);
# create a new instance of PDFlib.
$p = new PDFlib();
$p->set_parameter("logging", "filename {PDFlib.log}");
$p->set_parameter("errorpolicy", "exception");
# set textformat
$p->set_parameter("textformat", "utf8");
# SPACES are important here, at end of concatenated lines.
# all we really need for example to work are the groups and labels chunk.
$optlist = "groups={title toc content index} ".
"labels={{group=title prefix=title} ".
"{group=toc prefix={toc } start=1 style=r} ".
"{group=content start=1 style=D} ".
"{group=index prefix={index } start=1 style=r} } ".
"destination={ type=fixed zoom=.5 } " .
"viewerpreferences={displaydoctitle=true direction=l2r} ".
"openmode=thumbnails ".
"pagelayout=twocolumnleft ";
if ($p->begin_document("", $optlist) == 0)
throw new Exception("Error@begin_document: " . $p->get_errmsg());
# add some meta data on file
$p->set_info("Creator", "Neil Lindberg");
$p->set_info("Title", "SVM User Guide (2.1)");
# loading font (that most will have)
$font = $p->load_font("Helvetica", "unicode", "");
$p->begin_page_ext($doc_width, $doc_height, "group=title");
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
# add line of text, to center of page, approximately :)
$message = "Bands XML Breakdown";
$message_width = $p->stringwidth($message, $font, 24);
$p->show_xy($message, ($doc_width*.5)-($message_width*.5), $doc_height*.5);
#
$p->end_page_ext("");
# make page per band... suspend to later add page footer/num.
$num_bands = count($xml->band);
$count = 0;
$toc = array();
foreach($xml->band as $x){
$count++;
# TOC will be a breeze knowing the count reflects the
# page number of the content section, and we only have
# one page per band...
$toc[] = array($x->name, $count);
$p->begin_page_ext($doc_width, $doc_height, "group=content");
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
# put name of band in upper left-aligned corner.
$message = $x->name;
$p->show_xy($message, 24, $doc_height-48);
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
# add band info in a rectangular container, align center.
$band_info = "Genre: $x->genre\nFavorite song: $x->favoriteSong";
# NOTE: using fillcolor rather than ->setcolor does
# not require setting back to black.
$band_info_font = "font=$font fontsize=12 alignment=center ".
"fillcolor={spotname {PANTONE 222 U} 1}";
$textflow = $p->create_textflow($band_info, $band_info_font);
$p->fit_textflow($textflow, 50, 120, 550, 720, "");
# loading image from url is a bit more work. this section is that...
# we will load it and then put it in a temp pvf file
$image_data = file_get_contents($x->image);
$p->create_pvf("/pvf/image", $image_data, "");
# load image. auto == automatically detect type. could be jpeg, etc.
# NOTE: if image were local none of the pvf would be needed.
# instead the "/pvf/image" would be the image local name...
$image = $p->load_image("auto", "/pvf/image", "");
# temp pvf used, clear it for next run.
$p->delete_pvf("/pvf/image");
# finally place image.
$matchbox_name = $count."_image";
# options say: matchbox (for future access), boxsize for w/h, and finally,
# boxsize wouldn't work w/o some fitmethod. meet == scale to boxsize.
$fit_image_opts = "matchbox={name=$matchbox_name} ".
"boxsize={200 200} fitmethod=meet";
$p->fit_image($image, 200, 400, $fit_image_opts);
$p->close_image($image);
# add the url to the bottom of image using matchbox
if($p->info_matchbox($matchbox_name, 1, "exists")){
# x1, y1 = lower left. x2, y2 = lower right, etc.
$lower_left_x = $p->info_matchbox($matchbox_name, 1, "x1");
$lower_left_y = $p->info_matchbox($matchbox_name, 1, "y1");
$image_nm = explode("/", $x->image);
$p->show_xy($image_nm[count($image_nm)-1],
$lower_left_x,
$lower_left_y-12);
}
# add page n of E.
$message = "Page $count of $num_bands";
$message_width = $p->stringwidth($message, $font, 12);
# cause to right-align in bottom corner.
$page_num_x = ($doc_width-$message_width)-24;
$page_num_y = $doc_height-($doc_height-24);
$p->show_xy($message,
$page_num_x,
$page_num_y);
$p->end_page_ext("");
}
# TOC information is ready, add page and contents.
$p->begin_page_ext($doc_width, $doc_height, "group=toc");
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
# put TOC label in upper left-aligned corner.
$message = "Table of Contents";
$p->show_xy($message, 24, $doc_height-48);
#
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
# we will make it pretty with these two:
#string str_repeat ( string $input , int $multiplier )
#int strlen ( string $string )
// $total_line_length = 70;
// for($i = 0; $i < count($toc); $i++){
# the layout didn't work out with: $message = $section.str_repeat('.', $periods).$page_num;
# because the '.' character just is not taking up the space of alpha-chars of same size font.
# and if it is font based we have no flexibility (have to stick to Helvetica, 12) so, it looks
# like TABLES are the answer.?
// $section = $toc[$i][0];
// $page_num = $toc[$i][1];
// $periods = $total_line_length-(strlen($section)+strlen($page_num));
// syslog(LOG_ERR, "strlen(section): ".strlen($section));
// syslog(LOG_ERR, "strlen(page_num): ".strlen($page_num));
// syslog(LOG_ERR, "PERIODS: ".$periods);
// $message_width = $p->stringwidth($message, $font, 12);
// $p->show_xy($message, 36, $doc_height-(24*($i+1))-60);
// }
buildTOC($p, $toc, $font);
$p->end_page_ext("");
#
$p->end_document("");
# get and send buffer
$buf = $p->get_buffer();
$len = strlen($buf);
header("Content-type: application/pdf");
header("Content-Length: $len");
header("Content-Disposition: inline; filename=$file_name");
print $buf;
} catch (PDFlibException $e) {
die("PDFlib exception occurred:\n".
"[" . $e->get_errnum() . "] " . $e->get_apiname() .
": " . $e->get_errmsg() . "\n");
} catch (Exception $e) {
die($e->getMessage());
}