Wednesday, August 24, 2011

PDFlib & PHP (11) TOC w/ proper dot leaders

This is just the dot-leader example from PDFlib site converted to a working PHP example.



# PHP-PDF-to-browser example of dot_leaders_with_tabs
# [currently in PDFlib Cookbook in Java only]
$file_name = "blog_11.pdf";
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
#
try{
$p = new PDFlib();
$p->set_parameter("logging", "filename {PDFlib.log}");
$p->set_parameter("errorpolicy", "exception");
# set textformat
$p->set_parameter("textformat", "utf8");
#
$optlist = "";
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", "dot-leaders-with-tabs");
# loading font (that most will have)
$font = $p->load_font("Helvetica", "unicode", "");
// if ($p->setfont($font, 12) == 0)
// throw new Exception("Error@setfont: " . $p->get_errmsg());
# add the toc page(s)
$optlist = "fontname=Helvetica fontsize=12 ".
"encoding=unicode leading=160% ruler=100% ".
"hortabmethod=ruler tabalignment=right";
$text = "Introduction".
"\t7".
"Chapter 1".
"\t25".
"Chapter 2".
"\t107".
"Chapter 3".
"\t219".
"Appendix\t240";
$text_flow = $p->create_textflow($text, $optlist);
do{
$p->begin_page_ext($doc_width, $doc_height, "");
$p->setfont($font, 12);
$p->fit_textline("Table of Contents", 50, 740, "");
$p->fit_textflow($text_flow, 50, 600, 500, 700, "");
$p->end_page_ext("");
} while($result == "_boxfull" || $result == "_nextpage");
#
$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());
}

Tuesday, August 16, 2011

PDFlib & PHP - (9) Formatting - Wrap Image

Yo! I need to wrap an image w/ text. Here is how it is done in PHP with PDFlib. Get your own, bigger chunk o' lorem if you want. It was five paragraphs of lorem ipsum, but it was an ugly paste, so I cut a few out. Also, the image is from the web and may not be at that address one day.


Using Matchbox optlist of optlist of... to Wrap Image



# wrap image with text using the "usematchbox" option
# of fit_image() - [available in Java only @Cookbook].
$file_name = "blog_9.pdf";
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
#
$chunk_o_lorem = "Lorem ipsum dolor sit amet, consectetur adipiscing elit. Nam condimentum suscipit arcu in pharetra. Pellentesque ac purus diam. Proin in orci in diam auctor laoreet. Quisque vel mauris urna, ut aliquet nunc. Nullam diam justo, semper in suscipit vitae, rutrum quis ligula. Proin nec lectus purus. Praesent ut consectetur elit. In a nulla vel orci pretium vehicula at quis dui.

Integer consectetur faucibus hendrerit. Ut ac ante arcu, a fringilla orci. Donec vel sapien enim, a facilisis ipsum. Sed malesuada adipiscing eros a porta. Sed luctus elit id urna faucibus quis mollis risus commodo. Mauris sodales diam vitae mi vulputate placerat. Sed sodales nisi quis orci volutpat sit amet gravida lectus congue. In nec sapien ut mi sodales eleifend. Suspendisse ipsum diam, auctor id consequat sed, rutrum suscipit lacus. Vivamus diam nisi, fringilla nec pharetra id, commodo vel nunc. Quisque aliquam porttitor mi id blandit. Vestibulum et iaculis risus. Suspendisse consectetur, eros quis blandit varius, purus purus venenatis mauris, eu ultrices tortor justo at magna. Vestibulum nulla orci, tincidunt a varius at, dictum sit amet ante. Vestibulum ac lectus ante, ac fermentum erat. Donec nec ante quis neque lacinia viverra id quis tellus.

Nunc et sapien in lacus ultrices mollis. Maecenas pharetra magna lobortis nulla euismod sit amet consequat odio dictum. Donec euismod lorem diam. Ut rutrum urna pharetra lacus hendrerit vestibulum. Vivamus fermentum nunc sed nunc accumsan condimentum ac sit amet ante. Cras quis tortor vitae dolor scelerisque commodo. Aliquam quis diam nibh, a consectetur ante.

Duis ultrices, felis et placerat placerat, dui risus lobortis enim, quis commodo turpis massa sed diam. Curabitur rutrum sapien ac tortor ultricies commodo. Vestibulum ante ipsum primis in faucibus orci luctus et ultrices posuere cubilia Curae; Proin eu dolor lacus. Duis placerat, velit nec rhoncus volutpat, eros purus auctor diam, non eleifend velit tortor nec nunc. Cras consectetur eleifend massa nec tempor. Aenean euismod sem quis mauris ultrices feugiat. Sed ultrices tincidunt tincidunt. Pellentesque velit dolor, tristique a varius non, ultrices non nisi. Fusce adipiscing purus arcu, ac sollicitudin ipsum.";
#
try{
$p = new PDFlib();
$p->set_parameter("logging", "filename {PDFlib.log}");
$p->set_parameter("errorpolicy", "exception");
# set textformat
$p->set_parameter("textformat", "utf8");
#
$optlist = "";
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, "");
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
# random pic, big enough to show wrapping...
$an_otter_pic = "http://carinbondar.com/wp-content/uploads/2010/11/seaotter2.jpg";
$image_data = file_get_contents($an_otter_pic);
$p->create_pvf("/pvf/image", $image_data, "");
$image = $p->load_image("auto", "/pvf/image", "");
if($image == 0) die("Failed at load_image");
$p->delete_pvf("/pvf/image");
$image_width = $p->get_value("imagewidth", $image);
$image_height = $p->get_value("imageheight", $image);
$matchbox_name = "_image_matchbox";
#
# THERE IS NO create_matchbox. It is always created as
# a part of the optlist, for various elements, but does
# have its own string optlist - see API section 6.2.
#
# here, because i have the width and height of image
# i will just use that for boxsize and fitmethod auto, at 40%
$fit_image_opts = "matchbox={name=$matchbox_name margin=-7 } ".
"boxsize={".($image_width*.4)." ".($image_height*.4)."} fitmethod=auto showborder=true";
# the auto fitmethod may not serve your wants
# there are several in the API reference
# fitmethod=clip position={left center}";
$p->fit_image($image, $doc_width*.5-100, 600, $fit_image_opts);
$p->close_image($image);
# now, wrap the text(_flow)!
$tf = $p->add_textflow(0, $chunk_o_lorem, "fontname=Helvetica fontsize=10.5 encoding=unicode");
$result = $p->fit_textflow($tf, 50, 50, 550, 800,
"verticalalign=justify linespreadlimit=150% ".
"wrap={usematchboxes={{$matchbox_name}}}");
$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());
}

PDFlib & PHP (8) Combo #1 Large

Finally, I need to have text flow and while I was going to use a tables-only approach it turns out fit_textflow returns _boxfull and knows where to resume the text of a text flow, on the next page (just like fit_table when fitting added cells).

What I did to make my data-set big enough was to go find the lyrics of the favoriteSong listed in the bands.xml and paste those lyrics into a new tag under band called lyrics.



#########
$file_name = "blog_8.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 ###########
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();
// TODO: Add text to the point of run-on to next page...
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 textflow, track pages.
$textflow_optlist = "font=$font fontsize=12";
$textflow = $p->create_textflow($x->lyrics, $textflow_optlist);
# can we just use the same return strings to continue here?
# as with fit_table? we can!
$result = $p->fit_textflow($textflow, 50, 50, 550, 370, "");
if ($result == "_error") {
die("Couldn't place table: " . PDF_get_errmsg($p));
}
# states of result of fit_table...
# I'm not finding this in the documentation, yet.
if($result == "_boxfull"){
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$message = "Page $count";
$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("");
do{
$p->begin_page_ext($doc_width, $doc_height, "group=content");
$count++;
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$result = $p->fit_textflow($textflow, 50, 120, 550, 720, "");
if ($result == "_error") {
die("Couldn't place table: " . PDF_get_errmsg($p));
}
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$message = "Page $count";
$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("");
}while ($result == "_boxfull");
}else{
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$message = "Page $count";
$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("");
}
// $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 )
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());
}

Friday, August 12, 2011

PDFlib & PHP - (7) Combo #1

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());
}

PDFlib & PHP - (6) Tables

Adding a TOC in my last post brought me face-to-face with an issue with alignment/control. It would seem that a table would fix that. Trying to tack-in some table code to the existing document isn't working though, so I figure I'm going to have to start small.

The PDFlib "starter_table" is a bit beyond starter. So, I'm going to make the real starter table. I cut the middle out from the first setfont to the last end_page... and added the code that simply makes a table there. Progressively I want to add options and understand what they are (all about the OPTLIST).


A Really Very Simple Table



$p->begin_page_ext($doc_width, $doc_height, "");
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
###### TABLE TIME ###########
$table=0; $rowmax = 50;
# colmax used in colspan
$colmax = 5;
#llx = lower left x, whereas ury = upper right y
$llx= 50; $lly=50; $urx=550; $ury=800;
$row = 1; $col = 1;
$content = "I AM THE REAL STARTER TABLE!";
###### add_table optlist. add table cell. table no longer is zero.
# not setting the colwidth here results in warning.
$optlist = "fittextline={position=center font=" . $font .
" fontsize=14} colwidth=400 ";
$table = $p->add_table_cell($table, $col, $row, $content, $optlist);
###### fit_table optlist. time to place table.
# add a stroke so the table is visibly defined for us.
$optlist = "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));
}
##############################
$p->end_page_ext("");

I want to make it layout a multiplication table where the cells are all exactly the same size and the numbers are all centered.


Okay. I have learned a few things. First of all, if your cell-text is large enough it doesn't matter if you have set the option colwidth to try and constraint the text, when adding like this. This will die/delete the PDF-in-the-making. Secondly, defining a rowheight is advisable.


Multiplication Table


This would just replace the above "TABLE TIME" commented bit.



###### TABLE TIME ###########
$table=0;
$num_columns = 12;
$num_rows = 12;
#llx = lower left x, whereas ury = upper right y
$llx= 50; $lly=50; $urx=550; $ury=800;
###### add_table optlist.
$optlist = "fittextline={position=center font=" . $font .
" fontsize=14} colwidth=50 rowheight=20";
// colspan=" . $num_columns;
# make table-grid of 12x12
$col_num = 0;
do{
$col_num++;
$row_num = 0;
do{
$row_num++;
# just fun with background color... 0.0 - 1
$r = $row_num/$num_rows;
$g = $col_num/$num_columns;
$b = 0.5;
$optlist .= " matchbox={fillcolor={rgb $r $g $b}}";
# end fun :\
$value = $col_num*$row_num;
$table = $p->add_table_cell($table, $col_num, $row_num, $value, $optlist);
} while($row_num < $num_rows);
} while ($col_num < $num_columns);
###### fit_table optlist. time to place table.
# header says: this is or isn't a header. if set to 1 the table instance
# will expect more row. since this is just to show a table on the page...
$optlist = "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));
}
##############################

Paging the Multiplication Table


This is pastable over same lines from above example. It starts with the first three lines synching with the last three lines of the above. That is this is over/and after that to the end_page_ext. We've started the table already, and "fit" an added cell. NOTE: Comment out the old final end_page_ext as shown.

To see it work:


  1. Change $num_rows to greater than 37.


$result = $p->fit_table($table, $llx, $lly, $urx, $ury, $optlist);
if ($result == "_error") {
die("Couldn't place table: " . PDF_get_errmsg($p));
}
# states of result of fit_table...
# I'm not finding this in the documentation, yet.
$page_num = 1;
if($result == "_boxfull"){
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$message = "Page $page_num";
$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("");
do{
$p->begin_page_ext($doc_width, $doc_height, "");
$page_num++;
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$result = $p->fit_table($table, $llx, $lly, $urx, $ury, $optlist);
if ($result == "_error") {
die("Couldn't place table: " . PDF_get_errmsg($p));
}
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$message = "Page $page_num";
$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("");
}while ($result == "_boxfull");
}else{
$p->end_page_ext("");
}
##############################
// $p->end_page_ext("");

Thursday, August 11, 2011

PDFlib & PHP - (5) Add TOC

Using the code from blog PDFlib & PHP (4) I will eventually add a TOC between the Title page and the Content section. I'm expecting this to go smooth and quick because the group "toc" is already in the begin_document optslist.

The TOC is here! Whoop! But, I used an algorithm to say: you have this much space to fill, take the length of the section string, and the length of the page number string, then write out section, fill with periods or some filler in the amount of the difference of the "this much space"-(length of section+length of page number)... but, that doesn't layout as nice as I thought. I can see it is because periods don't take the width of the other characters... So, while working there is improvement to be done. I think it is time to break out: add_table_cell and see if I can't fit a table that lines up all pretty. That'll be tomorrow.



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);
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
# 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++){
$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 = $section.str_repeat('~', $periods).$page_num;
$message_width = $p->stringwidth($message, $font, 12);
$p->show_xy($message, 36, $doc_height-(24*($i+1))-60);
}
$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=blog_5.pdf");
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());
}

PDFlib & PHP - (4) fit_image from XML/URL

Here I'm going to add to the previous blog-post-code by adding an image tag on the same level as the: name, genre, and favoriteSong (+ image, now). While I'm having difficulty sharing the XML, I think nesting bulleted lists may be a good visual representation.


  • bands [singular parent]
    • band [multiple children]
      • name
      • genre
      • favoriteSong
      • image

There is the best I can do to share the XML. The point of this post is the journey to loading and placing an image in a somewhat templated manner, i.e. I'm going to grab a pic of each of the three bands in my own XML, add the web-location of this picture, and I'm guessing they won't be the same size, but then I'll place them on the page(s) respectively.

NOTE: The crux of the code will be the few lines added to the (3) XML it! post to add the image. This is noted in comments.



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);
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
# 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;
foreach($xml->band as $x){
$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, "");
############### new image stuff ###########
# 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);
}
############### end new image stuff #######
# 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("");
}
#
$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=blog_4.pdf");
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());
}

Wednesday, August 10, 2011

PDFlib & PHP - (3) XML it!

Now I have page groups, so it is time to start bringing the data! :o)


Using PHP's SimpleXML I'll add multiple pages. Hmmm. Well, it seems I cannot paste the XML in a pre tag w/ or w/o prettyprint. So, I'm going to just load it in. The structure is like so: (bands(band(name,genre,favoriteSong))(band(name,genre,favoriteSong))) where bands is the singular parent tag with band children with three tagged attributes: name, genre, and favoriteSong.


I will not need the suspend/resume for this example as I will have the page total for the content group from the count of bands children, band.



#...xml has loaded into string (you will need to do this somehow)
$xml = new SimpleXMLElement($xmlstr);
try{
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
# 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;
foreach($xml->band as $x){
$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 281 U} 1}";
$textflow = $p->create_textflow($band_info, $band_info_font);
$p->fit_textflow($textflow, 50, 120, 550, 720, "");
$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("");
}
#
$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=blog_3.pdf");
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());
}

Tuesday, August 9, 2011

PDFlib & PHP - (2) What is the optlist param in many functions?

The next thing I need to understand is the optlist. This is a parameter (string) that is passed into many of the PDFlib functions. In the HELLO WERLD post, previously, the parameter is passed as a blank string in functions: begin_page_ext AND begin_document.


But what is it?


Looking through the PDFlib.com Cookbook we can see all sorts of usage, but how to make that understandable? Over in one we have a table of contents structured within an optlist structure, but what does it mean? How does it jibe with the optlist in the link-annotations example: "boxsize={50 50} fitmethod=meet matchbox={name=image_matchbox}"; ? We can see that this optlist is passed to fit_image, where as the optlist in the insert-toc, reflecting structure, is passed to begin_page_ext.


I'll explain as best as I can these questions:

  1. What options CAN GO in an optlist for what function/where to find this?
  2. How is optlist used for a suspend page/resume page?


OPTLIST for different functions - answering question one


NOTE: The API listing/real documentation for PDFlib lives here under Reference


"Option lists are a powerful yet easy method for controlling API function calls. Instead of
requiring a multitude of function parameters, many API methods support option lists,
or optlists for short. These are strings which can contain an arbitrary number of options.
Option lists support various data types and composite data like lists. In most language
bindings optlists can easily be constructed by concatenating the required keywords and
values."
"An option may have a list value according to its documentation in this PDFlib Reference.
List values contain one or more elements (which may themselves be lists). They
are separated according to the rules above, with the only difference that the equal sign
is no longer treated as a separator.
Note Option names (i.e. the key) never contain"
- Section 1, Option Lists, PDFlib API Reference

Now that I've found/read-into the available PDFlib API Reference things are starting to become much more clear. Check the Reference for syntax questions, limits, color, etc.


Using the optlist/finding possible values


The aforementioned reference contains a table of contents (known as TOC from now on). Starting there I'll explain how to use the optslist.

  1. At the end of the TOC are sections: A, B, C, and D (as of 8.0.3-API-reference). Peruse section 'C' or the equivalent: List of all Options and Keywords. This is a little overwhelming and not applicable to every function taking an optlist, but we can see there are MANY options.
    For the example we want 'A' or the equivalent: List of All Functions (it is clickable).
  2. Now that we can see the entire list it is easy to pick out what I will make a usable example of:
    • suspend_page
    • resume_page

Suspend and resume are on the same page. Suspend states:

optslist - An option list for future use.
Not extremely helpful if you ask me :)


Resume documentation is a bit more helpful, and these must be paired, i.e. where we have suspended... we must eventually resume. Resume states:

optlist - An option list according to Table 3.8. The following options can be used:

group, pagenumber


PDFlib-8.0.3-API-reference - Table 3.8 Options for PDF_resume_page( )


optiondescription
group(String; required if the document uses page groups, but not allowed otherwise) Name of the page group of the resumed page. The group name must have been defined with the groups option in PDF_begin_document( ).
pagenumber(Integer) If this option is supplied, the page with the specified number within the page group chosen in the group option (or in the document if the document doesn’t use page groups) will be resumed. If this option is missing the last page in the group will be resumed.

It seems my journey to understand how to use suspend/resume took me on a nearly full-day segue to learn about the OPTLIST for begin_document(name, optlist). So, the full example below works AND does a bunch of un-necessary extras for plain vanilla example. The important parts for suspend/resume are: We have some grouping defined in the optlist of begin_document, then we use this to suspend/resume.



try{
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
# create a new instance of PDFlib.
$p = new PDFlib();
# extras here. in cookbook examples these are not mentioned.
# but, for error logging add logging and set errorpolicy to exception.
# (thanks PDFLib support for these setting info)
$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.
# NOTE: the index group is never used in this example and it
# does not cause any break.
$optlist = "groups={title toc content index} ".
"labels={{group=title prefix=title} ".
"{group=toc prefix={toc } start=1 style=r} ".
# style=D says: Decimal, Arabic numerals.
# search in API ref: Suboptions for the labels
"{group=content start=1 style=D} ".
"{group=index prefix={index } start=1 style=r} } ".
# we can exercise the suspend/resume now, the rest of
# theses optlist option are to simply show the usage of
# what I thought were some cool/useful ones...
"destination={ type=fixed zoom=.5 } " .
# direction defaults to l2r. switch to r2l to reverse layout,
# i.e. page 2 will be before page 1.
"viewerpreferences={displaydoctitle=true direction=l2r} ".
# "openmode=bookmarks ". If you add some new options you want/need
# and get an error that that option is wrong,
# first check you have spaces around the key=value pair.
# show thumbnails menu on open.
"openmode=thumbnails ".
# show pages side by side [1][2] odd then even.
"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", "Blog 2");
# loading font (that most will have)
$font = $p->load_font("Helvetica", "unicode", "");
# make/begin page, see:
# http://www.php.net/manual/en/function.pdf-begin-page-ext.php
$p->begin_page_ext($doc_width, $doc_height, "group=title");
# setfont before adding text, see:
# http://www.php.net/manual/en/function.pdf-setfont.php
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
# add line of text, to center of page, approximately :)
# doing a few things here to center the width and then place text.
$message = "Hello Werld!";
$message_width = $p->stringwidth($message, $font, 24);
$p->show_xy($message, ($doc_width*.5)-($message_width*.5), $doc_height*.5);
# suspending page for future access.
$p->suspend_page("");
# the first page has been suspended,
# add a second w/ content, then resume first.
$p->begin_page_ext($doc_width, $doc_height, "group=content");
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$message = "I am page one of content group!";
$message_width = $p->stringwidth($message, $font, 12);
$p->show_xy($message, ($doc_width-$message_width)*.5, $doc_height*.5);
$p->end_page_ext("");
# second page is done, resume first via group
# opt setting and add more content.
# this resume would resume the last suspended page for group
# and would work just fine for this example: $p->resume_page("group=title");
# but, if we want to resume a specific page of multiple suspends...
# we add the pagenumber (which will not break example).
$p->resume_page("group=title pagenumber=1");
# CHECK THIS OUT: if not setting font here the font will display
# NOT in the size of the last setfont, but in the last setfont on
# the resumed page, i.e. size 24.
# Try it: comment out the setfont/throw below.
if ($p->setfont($font, 12) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
$message = "Or is it Hello World!?";
$message_width = $p->stringwidth($message, $font, 12);
# throwing in a little color change.
$spot = $p->makespotcolor("PANTONE 1935 U");
$p->setcolor("fill", "spot", $spot, 1.0, 0, 0);
# put this
$p->show_xy($message, ($doc_width-$message_width)*.5, ($doc_height*.5)-24);
# end page...
$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=blog_2.pdf");
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());
}



PDFlib & PHP - (1) HELLO WERLD

I need to take baby-steps; myself described in the PDFlib manual as a PHP Web Programmer.
NOTE: The API listing/real documentation for PDFlib lives here under Reference

In the example here I sometimes say, "see: ...php.net/some-documentation," and there is a distinct and notable difference between the php.net and usage below. That is, in all "see:" comments it works like this:


  • PHP.NET says:
    float PDF_stringwidth ( resource $p , string $text , int $font , float $fontsize )

  • Where usage below is always going to replace the PDF_ with the PDFlib object $p.

  • ...and the first parameter in the PHP.NET documentaion is never resource, but the next, i.e. for the above mentioned PDF_stringwidth we have:
    $p->stringwidth(string $text, int $font, float $fontsize)

This will be:


  1. Make a PDF delivered to browser that was crafted using PDFlib usage of PHP.

  2. Throw in a (centered-to-doc) line of text, in the PDF.



try{
# number reflect an A4 sized document.
$doc_width = 595;
$doc_height = 842;
# create a new instance of PDFlib.
$p = new PDFlib();
# extras here. in cookbook examples these are not mentioned.
# but, for error logging add logging and set errorpolicy to exception.
# (thanks PDFLib support for these setting info)
$p->set_parameter("logging", "filename {PDFlib.log}");
$p->set_parameter("errorpolicy", "exception");
# set textformat, or try without and watch log:
# [Warning message 2592: "Glyph for Unicode value U+6548 not
# found in font 'Helvetica' (Unicode will be replaced in text)"]
$p->set_parameter("textformat", "utf8");
# begin document, see:
# http://www.php.net/manual/en/function.pdf-begin-document.php
if ($p->begin_document("", "") == 0)
throw new Exception("Error@begin_document: " . $p->get_errmsg());
# loading font (that most will have)
$font = $p->load_font("Helvetica", "unicode", "");
# make/begin page, see:
# http://www.php.net/manual/en/function.pdf-begin-page-ext.php
$p->begin_page_ext($doc_width, $doc_height, "");
# setfont before adding text, see:
# http://www.php.net/manual/en/function.pdf-setfont.php
if ($p->setfont($font, 24) == 0)
throw new Exception("Error@setfont: " . $p->get_errmsg());
# add line of text, to center of page, approximately :)
# doing a few things here to center the width and then place text.
$message = "Hello Werld!";
$message_width = $p->stringwidth($message, $font, 24);
$p->show_xy($message, ($doc_width*.5)-($message_width*.5), $doc_height*.5);
# end page...
$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=blog_1.pdf");
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());
}

Props to this person for the pretty-print on blogger, blog:
Pretty Print Blog by Luka Marinko