Using Pango for PHP: a taster
The PECL/Cairo library is pretty good at drawing vector graphics (in our opinion, as the developers, at least!), but one thing it's not able to do by itself is draw text with mildly advanced layout. It has the CairoContext::showText() function, but that doesn't really let you do anything GD can't. That's because the developers of the Cairo library decided to let another more specialised library handle the job of text layout. Much of the time, the library that gets used for this is Pango. It has several bindings already for most popular languages on Unix-like platforms. It's quite capable, and able to lay out text while taking care of things such as paragraph alignment, line breaking, bold/italic text, justification, and various other features. In this post, I intend to give a little tour of some of its features.
Quite a while ago now, I wrote an extension to wrap Pango for PHP. It's available on its Github repository. It's not available on PECL right now, but you can check it out from there and install it using the normal method. (Sorry Windows users, I've not had time to test it out on there, and likely won't any time soon. Patches are welcome, though...). It requires that the Pango headers are installed, and that PECL/Cairo is already installed into PHP.
Once installed, you can use it with Cairo. The best way to show it off is probably some example code:
/* Make a 300x300px image surface */
$s = new CairoImageSurface(CairoFormat::ARGB32, 300, 300);
$c = new CairoContext($s);
/* Set the background to white */
$c-setSourceRGB(1, 1, 1);
/* Let's draw using black 'ink' */
$c-setSourceRGB(0, 0, 0);
/* Make a Pango layout, set the font, then set the layout size */
$l = new PangoLayout($c);
$desc = new PangoFontDescription("Bitstream Charter 28");
$l-setWidth(250 * PANGO_SCALE);
/* Here, we use Pango markup to make part of the text bold */
$l-setMarkup("Hello world! Here is a rather long paragraph which should get wrapped");
/* Draw the layout on the surface */
/* Output the PNG to the browser */
If all goes to plan, you should see a PNG in your browser with the above text.
Going through each step, firstly we set up the Cairo surface to draw on, and the context we use to draw with. Once we have done that, we can create a PangoLayout object which lets us draw the text we require. The PangoLayout is passed a context, so it can invoke the drawing methods itself to draw the text. We pass the PangoLayout a PangoFontDescription object, which lets us choose from the fonts already installed on the machine. This means that we don't need to concern ourselves with the paths to actual TrueType font files, or similar - the fonts are resolved by Fontconfig or whatever system is available on your machine.
Next, we set the width of the layout. We can also set a height, but I haven't bothered on this occasion. This lets Pango know where to wrap the text - if we don't set this, it won't bother, which may result in text falling off the edge of the image. You may note that the width is multiplied by the PANGO_SCALE - this is because Pango deals in units which are a tiny fraction (1/1024, in fact) of a pixel, in order to handle antialiasing properly. Because we're using a CairoImageSurface, this means that they layout will be 250 pixels wide.
Next up, we set the text to be drawn. There are two methods available to do this; PangoLayout::setText() is used when we just want to render text with no formatting instructions. In this case, I've opted for the PangoLayout::setMarkup() method, which lets me use Pango's markup language to make part of the text bold. Many other attributes can be changed using this markup, which has shortcuts that somewhat resemble html.
Finally, the call to showLayout() renders the layout onto the surface. You can also render just a path, using layoutPath(), which sets the path on the surface so you can then use more advanced effects with Cairo. The layout is drawn using whatever the current source is on the Cairo context, so you can render using flat colours, gradients, other source images, or whatever takes your fancy. Additionally, this means that any transformations you have set on the Cairo context also take effect, allowing you to rotate, scale, shear and otherwise distort the text.
It probably looks a little complex just to write some text on an image, but it is rather flexible. This flexibility is handy when you consider that Cairo can render more than just PNG images; PDFs, PostScript and SVG are also easy to create. I hope that this post may inspire someone else to give it a try. Feedback and reports of issues are always welcome.