1<?php 2// $Header: /cvsroot/html2ps/output.png.class.php,v 1.7 2007/05/07 13:12:07 Konstantin Exp $ 3 4require_once(HTML2PS_DIR.'ot.class.php'); 5require_once(HTML2PS_DIR.'path.php'); 6require_once(HTML2PS_DIR.'font_factory.class.php'); 7 8/** 9 * TODO: of course, it is not 'real' affine transformation; 10 * it is just a compatibility hack 11 */ 12class AffineTransform { 13 var $_y_offset; 14 var $_x_scale; 15 var $_y_scale; 16 17 function AffineTransform($y_offset, $x_scale, $y_scale) { 18 $this->_y_offset = $y_offset; 19 $this->_x_scale = $x_scale; 20 $this->_y_scale = $y_scale; 21 } 22 23 function apply(&$x, &$y) { 24 $x = floor($x * $this->_x_scale); 25 $y = floor($this->_y_offset - $y * $this->_y_scale); 26 } 27} 28 29class OutputDriverPNG extends OutputDriverGeneric { 30 var $_image; 31 32 var $_clipping; 33 34 var $_media; 35 var $_heightPixels; 36 var $_widthPixels; 37 var $_color; 38 var $_font; 39 var $_path; 40 41 /** 42 * This variable contains an array of clipping contexts. Clipping 43 * context describes the "active area" and "base" image (image which 44 * will take the changes drawn in clipped area). 45 * 46 * As GD does not support clipping natively, when new clipping area 47 * is defined, we create new image. When clipping context is 48 * terminated (i.e. by establishing new clipping context, by call to 49 * 'restore' or by finishing the image output), only area bounded by 50 * clipping region is copied to the "base" image. Note that This 51 * will increase the memory consumption, as we'll need to keep 52 * several active images at once. 53 */ 54 var $_clip; 55 56 function _restoreColor() { 57 imagecolordeallocate($this->_image, $this->_color[0]); 58 array_shift($this->_color); 59 } 60 61 function _restoreClip() { 62 /** 63 * As clipping context images have the same size/scale, we may use 64 * the simplest/fastest image copying function 65 */ 66 $clip = $this->_clipping[0]; 67 imagecopy($clip['image'], 68 $this->_image, 69 $clip['box']->ll->x, 70 $clip['box']->ll->y, 71 $clip['box']->ll->x, 72 $clip['box']->ll->y, 73 $clip['box']->getWidth(), 74 $clip['box']->getHeight()); 75 76 /** 77 * Now we should free image allocated for the clipping context to avoid memory leaks 78 */ 79 imagedestroy($this->_image); 80 $this->_image = $clip['image']; 81 82 /** 83 * Remove clipping context from the stack 84 */ 85 array_shift($this->_clipping); 86 } 87 88 function _saveColor($rgb) { 89 $color = imagecolorallocate($this->_image, $rgb[0], $rgb[1], $rgb[2]); 90 array_unshift($this->_color, array('rgb' => $rgb, 91 'object' => $color)); 92 } 93 94 function _saveClip($box) { 95 /** 96 * Initialize clipping context record and add it to the clipping 97 * stack 98 */ 99 $clip = array('image' => $this->_image, 100 'box' => $box); 101 array_unshift($this->_clipping, $clip); 102 103 /** 104 * Create a copy of current image for the clipping context 105 */ 106 $width = imagesx($clip['image']); 107 $height = imagesy($clip['image']); 108 $this->_image = imagecreatetruecolor($width, 109 $height); 110 imagecopy($this->_image, 111 $clip['image'], 112 0,0, 113 0,0, 114 $width, $height); 115 } 116 117 function _getCurrentColor() { 118 return $this->_color[0]['object']; 119 } 120 121 function _setColor($color) { 122 imagecolordeallocate($this->_image, $this->_color[0]['object']); 123 $this->_color[0] = $color; 124 } 125 126 function _setFont($typeface, $encoding, $size) { 127 global $g_font_resolver_pdf; 128 $fontfile = $g_font_resolver_pdf->ttf_mappings[$typeface]; 129 130 $font = $this->_font_factory->getTrueType($typeface, $encoding); 131 $ascender = $font->ascender() / 1000; 132 133 $this->_font[0] = array('font' => $typeface, 134 'encoding' => $encoding, 135 'size' => $size, 136 'ascender' => $ascender); 137 } 138 139 function _getFont() { 140 return $this->_font[0]; 141 } 142 143 function _drawLine($x1, $y1, $x2, $y2) { 144 imageline($this->_image, $x1, $y1, $x2, $y2, $this->_color[0]['object']); 145 } 146 147 /** 148 * Note that "paper space" have Y coordinate axis directed to the bottom, 149 * while images have Y coordinate axis directory to the top 150 */ 151 function _fixCoords(&$x, &$y) { 152 $x = $this->_fixCoordX($x); 153 $y = $this->_fixCoordY($y); 154 } 155 156 function _fixCoordX($source_x) { 157 $x = $source_x; 158 $dummy = 0; 159 $this->_transform->apply($x, $dummy); 160 return $x; 161 } 162 163 function _fixCoordY($source_y) { 164 $y = $source_y; 165 $dummy = 0; 166 $this->_transform->apply($dummy, $y); 167 return $y; 168 } 169 170 function _fixSizes(&$x, &$y) { 171 $x = $this->_fixSizeX($x); 172 $y = $this->_fixSizeY($y); 173 } 174 175 function _fixSizeX($x) { 176 static $scale = null; 177 if (is_null($scale)) { $scale = $this->_widthPixels / mm2pt($this->media->width()); }; 178 return ceil($x * $scale); 179 } 180 181 function _fixSizeY($y) { 182 static $scale = null; 183 if (is_null($scale)) { $scale = $this->_heightPixels / mm2pt($this->media->height()); }; 184 return ceil($y * $scale); 185 } 186 187 function OutputDriverPNG() { 188 $this->OutputDriverGeneric(); 189 190 $this->_color = array(); 191 $this->_font = array(); 192 $this->_path = new Path; 193 $this->_clipping = array(); 194 195 $this->_font_factory = new FontFactory(); 196 } 197 198 function reset(&$media) { 199 parent::reset($media); 200 201 $this->update_media($media); 202 } 203 204 function update_media($media) { 205 parent::update_media($media); 206 207 /** 208 * Here we use a small hack; media height and width (in millimetres) match 209 * the size of screenshot (in pixels), so we take them as-is 210 */ 211 $this->_heightPixels = $media->height(); 212 $this->_widthPixels = $media->width(); 213 214 $this->_image = imagecreatetruecolor($this->_widthPixels, 215 $this->_heightPixels); 216 /** 217 * Render white background 218 */ 219 $white = imagecolorallocate($this->_image, 255,255,255); 220 imagefill($this->_image, 0,0,$white); 221 imagecolordeallocate($this->_image, $white); 222 223 $this->_color[0] = array('rgb' => array(0,0,0), 224 'object' => imagecolorallocate($this->_image, 0,0,0)); 225 226 /** 227 * Setup initial clipping region 228 */ 229 $this->_clipping = array(); 230 $this->_saveClip(new Rectangle(new Point(0, 231 0), 232 new Point($this->_widthPixels-1, 233 $this->_heightPixels-1))); 234 235 $this->_transform = new AffineTransform($this->_heightPixels, 236 $this->_widthPixels / mm2pt($this->media->width()), 237 $this->_heightPixels / mm2pt($this->media->height())); 238 } 239 240 function add_link($x, $y, $w, $h, $target) { /* N/A */ } 241 function add_local_link($left, $top, $width, $height, $anchor) { /* N/A */ } 242 243 function circle($x, $y, $r) { 244 $this->_path = new PathCircle(); 245 $this->_path->set_r($r); 246 $this->_path->set_x($x); 247 $this->_path->set_y($y); 248 } 249 250 function clip() { 251 /** 252 * Only rectangular clipping areas are supported; we'll use 253 * bounding box of current path for clipping. If current path is 254 * an rectangle, bounding box will match the path itself. 255 */ 256 $box = $this->_path->getBbox(); 257 258 /** 259 * Convert bounding from media coordinates 260 * to output device coordinates 261 */ 262 $this->_fixCoords($box->ll->x, $box->ll->y); 263 $this->_fixCoords($box->ur->x, $box->ur->y); 264 $box->normalize(); 265 266 /** 267 * Add a clipping context information 268 */ 269 $this->_restoreClip(); 270 $this->_saveClip($box); 271 272 /** 273 * Reset path after clipping have been applied 274 */ 275 $this->_path = new Path; 276 } 277 278 function close() { 279 /** 280 * A small hack; as clipping context is save every time 'save' is 281 * called, we may deterine the number of graphic contexts saved by 282 * the size of clipping context stack 283 */ 284 while (count($this->_clipping) > 0) { 285 $this->restore(); 286 }; 287 288 imagepng($this->_image, $this->get_filename()); 289 imagedestroy($this->_image); 290 } 291 292 function closepath() { 293 $this->_path->close(); 294 } 295 296 function content_type() { 297 return ContentType::png(); 298 } 299 300 function dash($x, $y) { } 301 function decoration($underline, $overline, $strikeout) { } 302 303 function error_message() { 304 return "OutputDriverPNG: generic error"; 305 } 306 307 function field_multiline_text($x, $y, $w, $h, $value, $field_name) { /* N/A */ } 308 function field_text($x, $y, $w, $h, $value, $field_name) { /* N/A */ } 309 function field_password($x, $y, $w, $h, $value, $field_name) { /* N/A */ } 310 function field_pushbutton($x, $y, $w, $h) { /* N/A */ } 311 function field_pushbuttonimage($x, $y, $w, $h, $field_name, $value, $actionURL) { /* N/A */ } 312 function field_pushbuttonreset($x, $y, $w, $h) { /* N/A */ } 313 function field_pushbuttonsubmit($x, $y, $w, $h, $field_name, $value, $actionURL) { /* N/A */ } 314 function field_checkbox($x, $y, $w, $h, $name, $value) { /* N/A */ } 315 function field_radio($x, $y, $w, $h, $groupname, $value, $checked) { /* N/A */ } 316 function field_select($x, $y, $w, $h, $name, $value, $options) { /* N/A */ } 317 318 function fill() { 319 $this->_path->fill($this->_transform, $this->_image, $this->_getCurrentColor()); 320 $this->_path = new Path; 321 } 322 323 function font_ascender($name, $encoding) { 324 $font = $this->_font_factory->getTrueType($name, $encoding); 325 return $font->ascender() / 1000; 326 } 327 328 function font_descender($name, $encoding) { 329 $font = $this->_font_factory->getTrueType($name, $encoding); 330 return -$font->descender() / 1000; 331 } 332 333 function get_bottom() {} 334 335 /** 336 * Image output always contains only one page 337 */ 338 function get_expected_pages() { 339 return 1; 340 } 341 342 function image($image, $x, $y, $scale) { 343 $this->image_scaled($image, $x, $y, $scale, $scale); 344 } 345 346 function image_scaled($image, $x, $y, $scale_x, $scale_y) { 347 $this->_fixCoords($x, $y); 348 349 $sx = $image->sx(); 350 $sy = $image->sy(); 351 352 /** 353 * Get image size in device coordinates 354 */ 355 $dx = $sx*$scale_x; 356 $dy = $sy*$scale_y; 357 $this->_fixSizes($dx, $dy); 358 359 imagecopyresampled($this->_image, $image->get_handle(), 360 $x, $y-$dy, 361 0, 0, 362 $dx, $dy, 363 $sx, $sy); 364 } 365 366 function image_ry($image, $x, $y, $height, $bottom, $ox, $oy, $scale) { 367 $base_y = floor($this->_fixCoordY($bottom)); 368 $this->_fixCoords($x, $y); 369 $dest_height = floor($this->_fixSizeY($height)); 370 $start_y = $y - $dest_height; 371 372 $sx = $image->sx(); 373 $sy = $image->sy(); 374 $dx = $this->_fixSizeX($sx * $scale); 375 $dy = $this->_fixSizeY($sy * $scale); 376 377 $cx = $x; 378 $cy = $start_y - ceil($this->_fixSizeY($oy) / $dest_height) * $dest_height; 379 while ($cy < $base_y) { 380 imagecopyresampled($this->_image, $image->get_handle(), 381 $cx, $cy, 382 0, 0, 383 $dx, $dy, 384 $sx, $sy); 385 $cy += $dest_height; 386 }; 387 } 388 389 function image_rx($image, $x, $y, $width, $right, $ox, $oy, $scale) { 390 $base_x = floor($this->_fixCoordX($right)); 391 $this->_fixCoords($x, $y); 392 $dest_width = floor($this->_fixSizeX($width)); 393 $start_x = $x - $dest_width; 394 395 $sx = $image->sx(); 396 $sy = $image->sy(); 397 $dx = $this->_fixSizeX($sx * $scale); 398 $dy = $this->_fixSizeY($sy * $scale); 399 400 $cx = $start_x - ceil($this->_fixSizeX($oy) / $dest_width) * $dest_width; 401 402 $cy = $y - $dy; 403 404 while ($cx < $base_x) { 405 imagecopyresampled($this->_image, $image->get_handle(), 406 $cx, $cy, 407 0, 0, 408 $dx, $dy, 409 $sx, $sy); 410 $cx += $dest_width; 411 }; 412 } 413 414 function image_rx_ry($image, $x, $y, $width, $height, $right, $bottom, $ox, $oy, $scale) { 415 $base_x = floor($this->_fixCoordX($right)); 416 $base_y = floor($this->_fixCoordY($bottom)); 417 $this->_fixCoords($x, $y); 418 $dest_width = floor($this->_fixSizeX($width)); 419 $dest_height = floor($this->_fixSizeY($height)); 420 $start_x = $x - $dest_width; 421 $start_y = $y - $dest_height; 422 423 $sx = $image->sx(); 424 $sy = $image->sy(); 425 $dx = $this->_fixSizeX($sx * $scale); 426 $dy = $this->_fixSizeY($sy * $scale); 427 428 $cx = $start_x - ceil($this->_fixSizeX($ox) / $dest_width) * $dest_width; 429 $cy = $start_y - ceil($this->_fixSizeY($oy) / $dest_height) * $dest_height; 430 431 while ($cy < $base_y) { 432 while ($cx < $base_x) { 433 imagecopyresampled($this->_image, 434 $image->get_handle(), 435 $cx, $cy, 436 0, 0, 437 $dx, $dy, 438 $sx, $sy); 439 $cx += $dest_width; 440 }; 441 $cx = $start_x - ceil($this->_fixSizeX($ox) / $dest_width) * $dest_width; 442 $cy += $dest_height; 443 }; 444 } 445 446 function lineto($x, $y) { 447 $this->_path->addPoint(new Point($x, $y)); 448 } 449 450 function moveto($x, $y) { 451 $this->_path->clear(); 452 $this->_path->addPoint(new Point($x, $y)); 453 } 454 455 function new_form($name) { /* N/A */ } 456 function next_page() { /* N/A */ } 457 function release() { } 458 459 /** 460 * Note: _restoreClip will change current image object, so we must 461 * release all image-dependent objects before call to _restoreClip 462 * to ensure resources are released correctly 463 */ 464 function restore() { 465 $this->_restoreColor(); 466 $this->_restoreClip(); 467 } 468 469 /** 470 * Note: _saveClip will change current image object, so we must 471 * create all image-dependent objects after call to _saveClip to 472 * ensure resources are created correctly 473 */ 474 function save() { 475 $this->_saveClip($this->_clipping[0]['box']); 476 $this->_saveColor($this->_color[0]['rgb']); 477 } 478 479 function setfont($name, $encoding, $size) { 480 $this->_setFont($name, $encoding, $size); 481 return true; 482 } 483 484 function setlinewidth($x) { 485 $dummy = 0; 486 $this->_fixSizes($x, $dummy); 487 imagesetthickness($this->_image, $x); 488 } 489 490 function setrgbcolor($r, $g, $b) { 491 $color = array('rgb' => array($r, $g, $b), 492 'object' => imagecolorallocate($this->_image, $r*255, $g*255, $b*255)); 493 $this->_setColor($color); 494 } 495 496 function set_watermark($text) { } 497 498 function show_xy($text, $x, $y) { 499 $this->_fixCoords($x, $y); 500 501 $font = $this->_getFont(); 502 $converter = Converter::create(); 503 504 global $g_font_resolver_pdf; 505 $fontFile = $g_font_resolver_pdf->ttf_mappings[$font['font']]; 506 507 $fontSize = $font['size']; 508 509 $dummy = 0; 510 $this->_fixSizes($dummy, $fontSize); 511 512 $utf8_string = $converter->to_utf8($text, $font['encoding']); 513 514 imagefttext($this->_image, 515 $fontSize * $font['ascender'], 516 0, 517 $x, 518 $y, 519 $this->_getCurrentColor(), 520 TTF_FONTS_REPOSITORY.$fontFile, 521 $utf8_string); 522 } 523 524 /** 525 * Note: the koefficient is just a magic number; I'll need to examine the 526 * imagefttext behavior more closely 527 */ 528 function stringwidth($string, $name, $encoding, $size) { 529 $font = $this->_font_factory->getTrueType($name, $encoding); 530 return Font::points($size, $font->stringwidth($string))*1.25; 531 } 532 533 function stroke() { 534 $this->_path->stroke($this->_transform, $this->_image, $this->_getCurrentColor()); 535 $this->_path = new Path; 536 } 537} 538?>