Skip to content

Using imagettftext with Off-site Font URLs

I'd like to keep my font files on Amazon S3 and save the cost of storing them on my web server. imagettftext doesn't say whether I can use a URL for the font file, but imagettfbbox unambiguously says:

fontfile The name of the TrueType font file (can be a URL).
Unfortunately, they are lying, for both functions. The font file needs to reside in the local filesystem.

But I pay for storage and I want to have plenty of open-source fonts available for the webservices and that adds up. So I have to pull the fonts from Amazon S3 into a temporary file and pass that to imagettftext/imagettfbbox. But then I need to remember to delete the temporary file as soon as I'm done with it, to minimize the storage cost. The only way to guarantee that (that I know about) is with the old C++ techinque Resource Acquisition Is Initialization—create an object that I know will be destroyed and make deleting the file part of that object's destructor. (We usually don't have to worry about that sort of thing in PHP since the most common resource we use is memory, and the garbage collector takes care of deleting unused memory resources).

I also want to keep track of which fonts have already been downloaded, since I can use the same temporary file.

So the following class uses my CDN class for access to the files, and requires a writeable directory for the cache for the temporary files. $fontdir is the local directory where the class looks for the font files initially, pulling then off the S3 server if not available locally.

class Fonts{
	private $fontfiles;
	private $fontdir;
	private $cachedir
	private $cdn;
	

	function __construct ($fontdir, $cachedir, $cdn){
		$this->fontdir = $cdn->slash($fontdir);
		$this->cachedir = realpath($cachedir);
		$this->cdn = $cdn;
		$this->fontfiles = array();
	}
	
	function fontfile($fontname){
		// if we already have a cached file, use it.
		if (isset($this->fontfiles[$fontname])) return $this->fontfiles[$fontname];
		$fontfile = $this->$cdn->realpath($this->fontdir.$fontname.'.ttf');
		if (!$this->$cdn->isCDN($fontfile)) return $fontfile; // if it's available locally, use it 
		$tempFont = tempnam ($this->cachedir, 'font');
		file_put_contents($tempFont, file_get_contents($fontfile)); // copy doesn't seem to work
		$this->fontfiles[$fontname] = $tempFont;
		return $tempFont;
	}
	
	function __destruct(){
		foreach ($this->fontfiles as $fontfile) unlink($fontfile);
	}
}

And use it as

$fontserver = new Fonts ('/fonts/', '/cache/', new S3('http://bililite.s3.amazonaws.com'));
$fontfile = $fontserver->fontfile('DejaVuSans');
imagefttext($image, $size, $x, $y, $color, $fontfile, 'Hello, World');

As an aside, this post reinforced the value of blogging: I had originally written this class as a Singleton, effectively with a global font list. When I wrote (and researched links for) this, I discovered how bad Singletons are, and rewrote it as a simple class.

Post a Comment

Your email is never published nor shared. Required fields are marked *