Now that we can draw in our PDF, we want to add images. There are two kinds of images, bitmapped and vector. In PDF, images are called XObject
s (The X stands for external, meaning defined outside the page). Vector images are easier, since they are just packages of PDF drawing commands, a sort of macro. PDF calls them Form
s, since they were originally used to draw the boxes on a printed form.
Archive for the ‘PHP’ Category
Now that we can create blank PDF's, it's time to add some stuff. Vector drawing commands (lines and shapes) are simple; you just add the commands to the page content stream. In terms of the original class that would be:
$this->pages[count($this->pages)-1]->contents .= "the command\n";
// we just need some whitespace at the end, but the newline makes it easier to read the resulting PDF
But to make things easier, we can keep track of the last page:
function newpage(){
parent::newpage();
$this->currentPage = $this->pages[count($this->pages)-1];
}
// and now adding commands is:
$this->currentPage->contents .= "the command\n";
// this also has the advantage that we can manipulate currentPage to add commands to other content streams
There are lots of commands, all of which are postfix (parameters come before operators). There are no math operators or stack manipulation operators; any calculation has to be done before generating the PDF and numbers inserted directly.
Continue reading ‘Creating PDFs with PHP, part 3: Drawing’ »Continuing my attempt to dissect FDPF to understand PDF's, we'll create the simplest PDF: a blank page.
We need a couple of objects:
- Catalog
- This serves as the root object and describes the data structures in the document, which for our purposes is just the collection of printed pages. Other things, like the data for interactive forms, Javascript routines and metadata (author, subject, keywords) would go here.
1 0 obj << /Type /Catalog /Pages 0 2 R % reference to object number 2 >> endobj
- One useful optional entry in the catalog is the
/OpenAction
that can be used to set the zoom level and opening page./OpenAction [3 0 R /Fit]
starts at the page described in object 3 and zooms in to fit the page on the screen.
I wanted to allow my webservices to create PDF files, and I figured it couldn't be too hard—after all, it's just a bunch of graphics commands in a text file, right? Foolish me. The reference manual is 756 pages long, not including the javascript reference, another 769 pages. The place to start is fPDF, which is open source and pretty easy to understand, and its derivative tFPDF that lets you use and embed True Type fonts (it's the 21st century; who uses anything but True Type fonts?). Using it is simple:
define('_SYSTEM_TTFONTS', '/path/to/your/truetype/fonts/'); // Took a bit of experimenting to find the right values for these
define('FPDF_FONTPATH', _SYSTEM_TTFONTS);
putenv('GDFONTPATH='._SYSTEM_TTFONTS); // so we can use GD images as well
$pdf=new tFPDF();
$pdf->AddPage();
$pdf->SetFont('Arial','B',16);
$pdf->Cell(40,10,'Hello World!');
$pdf->Output();
One gotcha is that you need to create the unifont
directory within the fonts folder, and copy tFPDF's ttfonts.php
file into that.
The result is here.
Continue reading ‘Creating PDFs with PHP: Syntax’ »I've been playing with search engines and for the search API I'm going with Bing; Google limits their free API to 100 queries a day and requires creating a custom search engine. Bing requires signing up and getting an "AppID" but from there it's unlimited. Documentation is, well, Microsoftian: impossible to find and hard to use when you get it, but there's a PDF (!) that explains things pretty well. I decided to do everything on the server rather than being fancy with AJAX; the user has to wait either way and usually wants to leave the current page anyway (that's why he's searching!). Continue reading ‘A Search Box for the Website’ »
Sitemeter works by inserting an image and iframe into your page, so that when the browser retrieves it, sitemeter can look at the referer and know what page was visited. But you can't include image links in an XML or GIF file, so I can't tell which of the bililite webservices are being visited. But behind the scenes, all the files are PHP files, so I can use cURL to get the image file as long as I can spoof the parameters so sitemeter thinks it knows where the request came from. A little experimentation came up with this:
function getSitemeterImage(){
// the sitemeter image parameters; obtained by inspection
$opts = array(
'site' => 's48bililite',
'refer' => urlencode ($_SERVER['HTTP_REFERER']),
'pg' => urlencode('http://'.$_SERVER['HTTP_HOST'].$_SERVER['REQUEST_URI']),
'rnd' => rand()/getrandmax()
);
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, 'http://s48.sitemeter.com/meter.asp?'.http_build_query($opts));
curl_setopt($ch, CURLOPT_REFERER, $_SERVER['HTTP_REFERER']);
curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
// be nice to the server (http://inanimatt.com/php-curl.php)
curl_setopt($ch, CURLOPT_USERAGENT, 'Bililite Webservices (http://bililite.com/webservices)');
curl_exec($ch); // get the image to tell sitemeter that we were here
}
We don't actually use the image; all we want is to let sitemeter know that we want it.
I just started using Sitemeter for tracking page visits (everyone seems to use Google Analytics, but I've used Sitemeter before and am satisfied with what I've got). Sort of depressing seeing a "1" for the visit count, but it's new as of today. Creating a widget to put in the sidebar was straightforward, since I've already created one widget:
/*
Plugin Name: Sitemeter Widget
Plugin URI: http://bililite.nfshost.com/blog/2010/12/26/simple-sitemeter-widget/
Description: Display the sitemeter.com badge in the sidebar
Author: Daniel Wachsstock
Version: 1.0
Author URI: http://bililite.nfshost.com/blog
*/
// WordPress 2.8 Widget code based on http://jessealtman.com/2009/06/08/tutorial-wordpress-28-widget-api/
// and http://justcoded.com/article/wordpress-28-multi-widgets/
class Sitemeter_Widget extends WP_Widget{
function Sitemeter_Widget(){
$this->WP_Widget ('sitemeter', __('Sitemeter'), array(
'description' => 'Display the Sitemeter Badge'
));
} // constructor
function widget ($args, $instance){
extract ($args, EXTR_SKIP);
extract ($instance, EXTR_SKIP);
echo $before_widget.$before_title.$title.$after_title;
echo "<!-- Site Meter -->
<script type=\"text/javascript\" src=\"http://$server.sitemeter.com/js/counter.js?site=$account\">
</script>
<noscript>
<a href=\"http://$server.sitemeter.com/stats.asp?site=$account\" target=\"_top\">
<img src=\"http://$server.sitemeter.com/meter.asp?site=$account\" alt=\"Site Meter\" border=\"0\"/></a>
</noscript>
<!-- Copyright (c)2009 Site Meter -->";
echo $after_widget;
} // widget display code
function update ($new, $old){
return array_merge ($old, $new);
} // options update
function form ($instance){
extract ($instance, EXTR_SKIP);
$this->textElement ('title', 'Title', $title);
$this->textElement ('server', 'Server', $server);
$this->textElement ('account', 'Account', $account);
} // control form
function textElement ($index, $text, $value){
$id = $this->get_field_id($index);
$name = $this->get_field_name ($index);
$text = __($text); // localize
echo "<p><label for=\"$id\">$text <input name=\"$name\" class=\"widefat\" value=\"$value\" id=\"$id\" /></label></p>";
} // textElement
} // class Sitemeter_Widget
add_action ('widgets_init', create_function('', 'return register_widget("Sitemeter_Widget");'));
Short and sweet
This post is obsolete. See the updated post.
Since AVH Amazon plugin no longer works, as Amazon in its infinite wisdom removed the ListLookup API call, I had to create my own. Without an API, I resorted to a hacker's best friend: screen scraping. The wishlist page has a simple structure, and all links to Amazon products have as part of the URL "dp/{ASIN}
", where {ASIN}
is the Amazon ID number. If you view a wishlist in compact form, then the only links are to wishlist items and all the items (as far as I can tell) will be on a single page. Other advertisements for Amazon products that you see on the page are added with Javascript, so they won't show up when we grab the page with PHP.
So it's easy to get a list of ASIN's of the products on my wishlist:
function wishlist($listname){
$url = "http://www.amazon.com/registry/wishlist/$listname?layout=compact";
$contents = file_get_contents($url);
preg_match_all ('|/dp/(\w+)/|', $contents, $ASINs, PREG_PATTERN_ORDER);
return $ASINs[1]; // just get the numbers
}
This returns an array of ASIN's, that you can now pass the the Amazon ItemLookup API, which still works fine (Ulrich Mierendorff's AWS Signed Query makes that easy).
If your web hosting service disables file_get_contents, as mine does, you can use cURL instead.
As you can see from my sidebar, it seems to work well. Obviously, Amazon can change the layout and content of their pages without notice, but as we have seen, they can change the API as well.
I've been using Peter van der Does's AVH Amazon plugin to display my Amazon wish list (more as an "about my interests" than a "buy things and make me money") and it's worked well, allowing a random selection of books I would like to be shown in the sidebar. Now it's dead, killed by Amazon's deprecating the API to get the wish list items. It seems short sighted, especially since Amazon's wish list widget is so limited and poorly formatted (it's on the sidebar now). I may look into creating my own, if I ever have free time again.
I wanted the webservices to be as RESTful as possible, so they should use the Accept:
header rather than file name extensions to determine the type. Continue reading ‘Parsing the HTTP Accept: header’ »