The incredibly long example of code below has been one of my favorite and most recent projects with Zetec. I have generalized the functions and have probably left out some constants but the point here is to provide samples
. I created this in about 2 days (after mulling and dreading over it for a while). There is wrapper file that it uses to actually create the final graphic but that’s a little too specific to post publicly. The code below is an example of object oriented PHP, parsing HTML style rules, image creation and manipulation including transparency, text and external images. I opted out of using PHP’s built in DOM API since I really was just dealing with style rules
<?php
class html2image {
function parse ($content) {
//Setting up some repeated regular expressions
$regx->rgb = '/rgb\(\s?([0-9]+)\s?,\s?([0-9]+)\s?,\s?([0-9]+)\s?\)/i';
$regx->px = '/([-.0-9]+)px/i';
$regx->pt = '/([-0-9]+)pt/i';
$regx->num = '/([0-9]+)/i';
$html = explode("\r\n",$content);
foreach ($html as $val) {
//Now, let's parse the request's HTML
if (preg_match('/style="([^"]+)"/i',$val,$matches)){
$styles = explode(";",$matches[1]);
$obj = '';
foreach ($styles as $style) {
list($stname,$stval) = explode(":",$style);
$stval = trim($stval);
$stname = trim($stname);
$n = explode("-",$stname);
switch ($n[0]) {
case 'border':
$b = '';
if (preg_match($regx->px,$stval,$tmp)) $b->thick = $tmp[1];
if (preg_match('/(solid|dotted|dashed)/i',$stval,$tmp)) $b->style = $tmp[1];
if (preg_match($regx->rgb,$stval,$tmp)) $b->color = array($tmp[1],$tmp[2],$tmp[3]);
if ($b) $obj->$stname = $b;
break;
case 'background':
if (preg_match($regx->rgb,$stval,$tmp)) $obj->bgcolor = array($tmp[1],$tmp[2],$tmp[3]);
//using $style below instead of $stval because if value in url() is fully qualified path then the explode ":" will break a piece of that value off
if (preg_match('/url\(([^)]+)\)/i',$style,$tmp)) $obj->bgimage = str_replace('../images/','images/',$tmp[1]);
break;
case 'font':
$f = '';
if (preg_match($regx->pt,$stval,$tmp)) $obj->font->size = $tmp[1];
if ($stname == 'font-size'&&preg_match($regx->pt,$stval,$tmp)) {
$obj->font->size = $tmp[1];
if (!$obj->height) $obj->height = $tmp[1]; //Added just in case the object completes parsing and no height was found
}
if ($stname == 'font-family') $obj->font->family = $stval;
break;
case 'opacity':
if (preg_match('/0?\.?([0-9]+)/i',$stval,$tmp)) $obj->opacity = $tmp[1];
break;
case 'height':
if (preg_match($regx->px,$stval,$tmp)) $obj->height = $tmp[1];
break;
case 'width':
if (preg_match($regx->px,$stval,$tmp)) $obj->width = $tmp[1];
break;
case 'left':
if (preg_match($regx->px,$stval,$tmp)) $obj->left = $tmp[1];
break;
case 'top':
if (preg_match($regx->px,$stval,$tmp)) $obj->top = $tmp[1];
break;
case 'z':
if ($stname=='z-index'&&preg_match($regx->num,$stval,$tmp)) $obj->zindex = $tmp[1];
break;
}
}
$obj->raw = $matches[1];
$objects[] = $obj;
}
//Grab any text within the div elements
if (preg_match('/^<[^>]*>(.+)?<\/div>$/i',$val,$tmp)&&trim($tmp[1])) $obj->innerHTML = trim($tmp[1]);
if ($debug) {
echo '<pre>';
print_r($matches[1]);
print_r($obj);
}
}
function cmp($a, $b) {
if($a->zindex == $b->zindex){ return 0;}
return ($a->zindex < $b->zindex) ? -1 : 1;
}
usort($objects,'cmp');
//echo '<pre>';
//die(print_r($objects));
return $objects;
}
function imagettftextalign($image, $size, $angle, $x, $y, $color, $font, $text, $alignment='L') {
//I got this somewhere on the php.net manual site in the comments section
//check width of the text
$bbox = imagettfbbox ($size, $angle, $font, $text);
$textWidth = $bbox[2] - $bbox[0];
$textHeight = $bbox[1] - $bbox[5];
switch ($alignment) {
case "R":
$x -= $textWidth;
break;
case "C":
$x -= $textWidth / 2;
break;
}
$y += ($textHeight/2)+$size;
//write text
imagettftext ($image, $size, $angle, $x, $y, $color, $font, $text);
}
function min_max(&$objects) {
// Go over the object we've created and determine some mins and maxes
$mm->l_offset = 0;
$mm->left = 0;
$mm->top = 0;
foreach ($objects as $obj) {
if ($obj->left < $mm->l_offset) $mm->l_offset = $obj->left;
if ($obj->left+$obj->width > $mm->left) $mm->left = $obj->left+$obj->width;
if ($obj->top+$obj->height > $mm->top) $mm->top = $obj->top+$obj->height;
}
//Adjust the max left value to compensate for the offset from elements placed in negative left positions
$mm->left += abs($mm->l_offset);
define('ZOOM',(DF_WIDTH-(DF_SIDEPAD*2))/$mm->left);
//Zoom Factor
foreach ($mm as $name => $val) {
$mm->$name = floor($mm->$name*ZOOM);
}
foreach ($objects as $obj) {
foreach ($obj as $name => $val) {
if (is_numeric($val)) $obj->$name = floor($obj->$name*ZOOM);
if (is_object($val)) {
foreach ($val as $n => $v) {
if (is_numeric($v)) $val->$n = floor($val->$n*ZOOM);
}
}
}
}
//echo '<pre>';
//die(print_r($objects));
//Let's go over the parsed HTML object again and adjust the top and left values to factor in the padding and/or the left offset
if ($mm->l_offset < 0||SD_PAD > 0) {
foreach ($objects as $obj) {
if ($mm->l_offset < 0&&isset($obj->left)) $obj->left = floor(abs($mm->l_offset)+$obj->left); //Just in case $obj->left is a float
if (SD_PAD > 0) {
if (isset($obj->left)) $obj->left = floor(SD_PAD+$obj->left);//Just in case $obj->left is a float
if (isset($obj->top)) $obj->top = floor(SD_PAD+$obj->top);//Just in case $obj->top is a float
}
}
}
return $mm;
}
function create_image(&$objects) {
//The standard image, made as a separate image because we may have to resize it later to fit on the drawing template
$mm = $this->min_max($objects);
$im2 = imagecreatetruecolor($mm->left+$st_img_pad,$mm->top+$st_img_pad);
$white = imagecolorallocate($im2, 255, 255, 255);
imagefill($im2,0,0,$white);
//Start drawing the standard image
foreach ($objects as $obj) {
/*
We're going to go over the $obj structure a few times here in separate loops
because we don't necessarily know the order of the attributes so we first
have to draw any background style rules then we have to draw any foreground
rules and lastly we need to draw borders if any
*/
//Draw background rules
foreach ($obj as $name => $val) {
$n = explode('-',$name);
switch ($n[0]) {
case 'bgcolor':
if ($obj->opacity&&$obj->opacity < 95) $color = imagecolorallocatealpha($im2,$obj->bgcolor[0],$obj->bgcolor[1],$obj->bgcolor[2],floor((100-$obj->opacity)*1.27));
else $color = imagecolorallocate($im2,$obj->bgcolor[0],$obj->bgcolor[1],$obj->bgcolor[2]);
imagefilledrectangle($im2,$obj->left,$obj->top,$obj->width+$obj->left,$obj->height+$obj->top,$color);
imagecolordeallocate($im2,$color);
break;
case 'bgimage';
list($width, $height, $img_system) = getimagesize($val);
switch ($img_system) {
case IMAGETYPE_GIF: $bimg = imagecreatefromgif($val);break;
case IMAGETYPE_JPEG: $bimg = imagecreatefromjpeg($val);break;
case IMAGETYPE_PNG: $bimg = imagecreatefrompng($val);break;
case IMAGETYPE_BMP: $bimg = imagecreatefrombmp($val);break;
}
if (floor(imagesx($bimg)*ZOOM)==$obj->width) {
imagecopyresampled($im2,$bimg,$obj->left,$obj->top,0,0,$obj->width,$obj->height,imagesx($bimg),imagesy($bimg));
}
else {
$tmp = imagecreatetruecolor($obj->width,$obj->height);
imagesettile($tmp,$bimg);
imagefill($tmp,0,0,IMG_COLOR_TILED);
imagecopy($im2,$tmp,$obj->left,$obj->top,0,0,$obj->width,$obj->height);
imagedestroy($tmp);
}
imagedestroy($bimg);
break;
}
}
//Draw any foreground rules
foreach ($obj as $name => $val) {
$n = explode('-',$name);
switch ($n[0]) {
case 'innerHTML':
if ($obj->color) $color = imagecolorallocate($im2,$obj->color[0],$obj->color[1],$obj->color[2]);
else $color = imagecolorallocate($im2,0,0,0);
$val = str_replace('<br>',"\r\n",$val);
if (strpos($val,'<center>')) $align = 'C';
else $align = 'L';
$val = html_entity_decode($val);
$val = strip_tags($val);
$val = wordwrap($val,ceil($obj->width*.1),"\r\n");
$font = explode(',',$obj->font->family);
$font = strtolower('fonts/'.$font[0].'.ttf');
$this->imagettftextalign($im2,$obj->font->size,0,$obj->left,$obj->top,$color,$font,$val,$align);
break;
}
}
//Finally, draw borders
foreach ($obj as $name => $val) {
$n = explode('-',$name);
switch ($n[0]) {
case 'border':
if ($val->color) $color = imagecolorallocate($im2,$val->color[0],$val->color[1],$val->color[2]);
if ($val->style == 'dashed') {
imagesetstyle($im2,array($color,$color,$color,$color,IMG_COLOR_TRANSPARENT,IMG_COLOR_TRANSPARENT,IMG_COLOR_TRANSPARENT));
$color = IMG_COLOR_STYLED;
}
if ($val->style == 'dotted') {
imagesetstyle($im2,array($color,IMG_COLOR_TRANSPARENT));
$color = IMG_COLOR_STYLED;
}
if ($name == 'border-top') imageline($im2, $obj->left, $obj->top, $obj->width+$obj->left, $obj->top,$color);
if ($name == 'border-left') imageline($im2, $obj->left, $obj->top, $obj->left, $obj->top+$obj->height,$color);
if ($name == 'border-right') imageline($im2, $obj->left+$obj->width, $obj->top, $obj->width+$obj->left, $obj->top+$obj->height,$color);
if ($name == 'border') {
imagerectangle($im2,$obj->left,$obj->top,$obj->width+$obj->left,$obj->height+$obj->top,$color);
if ($obj->bgcolor) {
$color2 = imagecolorallocate($im2,$obj->bgcolor[0],$obj->bgcolor[1],$obj->bgcolor[2]);
imagefilltoborder($im2,$obj->left,$obj->top,$color,$color2);
imagecolordeallocate($im2,$color2);
}
}
if ($color) imagecolordeallocate($im2,$color);
break;
}
}
if ($_GET['text']) {
echo '<pre>';
print_r($obj);
}
$inc++;
if ($inc == 0) {
if (!$_GET['text']) {
header('Content-type: image/png');
imagepng($im2);
}
die();
}
}
//Create a temporary file, destroy the original and create it again... not sure why I have to do this to get transparency working in the final image but it is.
$tfile = md5(time());
imagepng($im2,"$tfile.png");
imagedestroy($im2);
$im2 = imagecreatefrompng("$tfile.png");
unlink("$tfile.png");
$white = imagecolorallocate($im2, 255, 255, 255);
imagecolortransparent($im2,$white);
return $im2;
}
}
?>