_y_offset = $y_offset; $this->_x_scale = $x_scale; $this->_y_scale = $y_scale; } function apply(&$x, &$y) { $x = floor($x * $this->_x_scale); $y = floor($this->_y_offset - $y * $this->_y_scale); } } class OutputDriverPNG extends OutputDriverGeneric { var $_image; var $_clipping; var $_media; var $_heightPixels; var $_widthPixels; var $_color; var $_font; var $_path; /** * This variable contains an array of clipping contexts. Clipping * context describes the "active area" and "base" image (image which * will take the changes drawn in clipped area). * * As GD does not support clipping natively, when new clipping area * is defined, we create new image. When clipping context is * terminated (i.e. by establishing new clipping context, by call to * 'restore' or by finishing the image output), only area bounded by * clipping region is copied to the "base" image. Note that This * will increase the memory consumption, as we'll need to keep * several active images at once. */ var $_clip; function _restoreColor() { imagecolordeallocate($this->_image, $this->_color[0]); array_shift($this->_color); } function _restoreClip() { /** * As clipping context images have the same size/scale, we may use * the simplest/fastest image copying function */ $clip = $this->_clipping[0]; imagecopy($clip['image'], $this->_image, $clip['box']->ll->x, $clip['box']->ll->y, $clip['box']->ll->x, $clip['box']->ll->y, $clip['box']->getWidth(), $clip['box']->getHeight()); /** * Now we should free image allocated for the clipping context to avoid memory leaks */ imagedestroy($this->_image); $this->_image = $clip['image']; /** * Remove clipping context from the stack */ array_shift($this->_clipping); } function _saveColor($rgb) { $color = imagecolorallocate($this->_image, $rgb[0], $rgb[1], $rgb[2]); array_unshift($this->_color, array('rgb' => $rgb, 'object' => $color)); } function _saveClip($box) { /** * Initialize clipping context record and add it to the clipping * stack */ $clip = array('image' => $this->_image, 'box' => $box); array_unshift($this->_clipping, $clip); /** * Create a copy of current image for the clipping context */ $width = imagesx($clip['image']); $height = imagesy($clip['image']); $this->_image = imagecreatetruecolor($width, $height); imagecopy($this->_image, $clip['image'], 0,0, 0,0, $width, $height); } function _getCurrentColor() { return $this->_color[0]['object']; } function _setColor($color) { imagecolordeallocate($this->_image, $this->_color[0]['object']); $this->_color[0] = $color; } function _setFont($typeface, $encoding, $size) { global $g_font_resolver_pdf; $fontfile = $g_font_resolver_pdf->ttf_mappings[$typeface]; $font = $this->_font_factory->getTrueType($typeface, $encoding); $ascender = $font->ascender() / 1000; $this->_font[0] = array('font' => $typeface, 'encoding' => $encoding, 'size' => $size, 'ascender' => $ascender); } function _getFont() { return $this->_font[0]; } function _drawLine($x1, $y1, $x2, $y2) { imageline($this->_image, $x1, $y1, $x2, $y2, $this->_color[0]['object']); } /** * Note that "paper space" have Y coordinate axis directed to the bottom, * while images have Y coordinate axis directory to the top */ function _fixCoords(&$x, &$y) { $x = $this->_fixCoordX($x); $y = $this->_fixCoordY($y); } function _fixCoordX($source_x) { $x = $source_x; $dummy = 0; $this->_transform->apply($x, $dummy); return $x; } function _fixCoordY($source_y) { $y = $source_y; $dummy = 0; $this->_transform->apply($dummy, $y); return $y; } function _fixSizes(&$x, &$y) { $x = $this->_fixSizeX($x); $y = $this->_fixSizeY($y); } function _fixSizeX($x) { static $scale = null; if (is_null($scale)) { $scale = $this->_widthPixels / mm2pt($this->media->width()); }; return ceil($x * $scale); } function _fixSizeY($y) { static $scale = null; if (is_null($scale)) { $scale = $this->_heightPixels / mm2pt($this->media->height()); }; return ceil($y * $scale); } function OutputDriverPNG() { $this->OutputDriverGeneric(); $this->_color = array(); $this->_font = array(); $this->_path = new Path; $this->_clipping = array(); $this->_font_factory = new FontFactory(); } function reset(&$media) { parent::reset($media); $this->update_media($media); } function update_media($media) { parent::update_media($media); /** * Here we use a small hack; media height and width (in millimetres) match * the size of screenshot (in pixels), so we take them as-is */ $this->_heightPixels = $media->height(); $this->_widthPixels = $media->width(); $this->_image = imagecreatetruecolor($this->_widthPixels, $this->_heightPixels); /** * Render white background */ $white = imagecolorallocate($this->_image, 255,255,255); imagefill($this->_image, 0,0,$white); imagecolordeallocate($this->_image, $white); $this->_color[0] = array('rgb' => array(0,0,0), 'object' => imagecolorallocate($this->_image, 0,0,0)); /** * Setup initial clipping region */ $this->_clipping = array(); $this->_saveClip(new Rectangle(new Point(0, 0), new Point($this->_widthPixels-1, $this->_heightPixels-1))); $this->_transform = new AffineTransform($this->_heightPixels, $this->_widthPixels / mm2pt($this->media->width()), $this->_heightPixels / mm2pt($this->media->height())); } function add_link($x, $y, $w, $h, $target) { /* N/A */ } function add_local_link($left, $top, $width, $height, $anchor) { /* N/A */ } function circle($x, $y, $r) { $this->_path = new PathCircle(); $this->_path->set_r($r); $this->_path->set_x($x); $this->_path->set_y($y); } function clip() { /** * Only rectangular clipping areas are supported; we'll use * bounding box of current path for clipping. If current path is * an rectangle, bounding box will match the path itself. */ $box = $this->_path->getBbox(); /** * Convert bounding from media coordinates * to output device coordinates */ $this->_fixCoords($box->ll->x, $box->ll->y); $this->_fixCoords($box->ur->x, $box->ur->y); $box->normalize(); /** * Add a clipping context information */ $this->_restoreClip(); $this->_saveClip($box); /** * Reset path after clipping have been applied */ $this->_path = new Path; } function close() { /** * A small hack; as clipping context is save every time 'save' is * called, we may deterine the number of graphic contexts saved by * the size of clipping context stack */ while (count($this->_clipping) > 0) { $this->restore(); }; imagepng($this->_image, $this->get_filename()); imagedestroy($this->_image); } function closepath() { $this->_path->close(); } function content_type() { return ContentType::png(); } function dash($x, $y) { } function decoration($underline, $overline, $strikeout) { } function error_message() { return "OutputDriverPNG: generic error"; } function field_multiline_text($x, $y, $w, $h, $value, $field_name) { /* N/A */ } function field_text($x, $y, $w, $h, $value, $field_name) { /* N/A */ } function field_password($x, $y, $w, $h, $value, $field_name) { /* N/A */ } function field_pushbutton($x, $y, $w, $h) { /* N/A */ } function field_pushbuttonimage($x, $y, $w, $h, $field_name, $value, $actionURL) { /* N/A */ } function field_pushbuttonreset($x, $y, $w, $h) { /* N/A */ } function field_pushbuttonsubmit($x, $y, $w, $h, $field_name, $value, $actionURL) { /* N/A */ } function field_checkbox($x, $y, $w, $h, $name, $value) { /* N/A */ } function field_radio($x, $y, $w, $h, $groupname, $value, $checked) { /* N/A */ } function field_select($x, $y, $w, $h, $name, $value, $options) { /* N/A */ } function fill() { $this->_path->fill($this->_transform, $this->_image, $this->_getCurrentColor()); $this->_path = new Path; } function font_ascender($name, $encoding) { $font = $this->_font_factory->getTrueType($name, $encoding); return $font->ascender() / 1000; } function font_descender($name, $encoding) { $font = $this->_font_factory->getTrueType($name, $encoding); return -$font->descender() / 1000; } function get_bottom() {} /** * Image output always contains only one page */ function get_expected_pages() { return 1; } function image($image, $x, $y, $scale) { $this->image_scaled($image, $x, $y, $scale, $scale); } function image_scaled($image, $x, $y, $scale_x, $scale_y) { $this->_fixCoords($x, $y); $sx = $image->sx(); $sy = $image->sy(); /** * Get image size in device coordinates */ $dx = $sx*$scale_x; $dy = $sy*$scale_y; $this->_fixSizes($dx, $dy); imagecopyresampled($this->_image, $image->get_handle(), $x, $y-$dy, 0, 0, $dx, $dy, $sx, $sy); } function image_ry($image, $x, $y, $height, $bottom, $ox, $oy, $scale) { $base_y = floor($this->_fixCoordY($bottom)); $this->_fixCoords($x, $y); $dest_height = floor($this->_fixSizeY($height)); $start_y = $y - $dest_height; $sx = $image->sx(); $sy = $image->sy(); $dx = $this->_fixSizeX($sx * $scale); $dy = $this->_fixSizeY($sy * $scale); $cx = $x; $cy = $start_y - ceil($this->_fixSizeY($oy) / $dest_height) * $dest_height; while ($cy < $base_y) { imagecopyresampled($this->_image, $image->get_handle(), $cx, $cy, 0, 0, $dx, $dy, $sx, $sy); $cy += $dest_height; }; } function image_rx($image, $x, $y, $width, $right, $ox, $oy, $scale) { $base_x = floor($this->_fixCoordX($right)); $this->_fixCoords($x, $y); $dest_width = floor($this->_fixSizeX($width)); $start_x = $x - $dest_width; $sx = $image->sx(); $sy = $image->sy(); $dx = $this->_fixSizeX($sx * $scale); $dy = $this->_fixSizeY($sy * $scale); $cx = $start_x - ceil($this->_fixSizeX($oy) / $dest_width) * $dest_width; $cy = $y - $dy; while ($cx < $base_x) { imagecopyresampled($this->_image, $image->get_handle(), $cx, $cy, 0, 0, $dx, $dy, $sx, $sy); $cx += $dest_width; }; } function image_rx_ry($image, $x, $y, $width, $height, $right, $bottom, $ox, $oy, $scale) { $base_x = floor($this->_fixCoordX($right)); $base_y = floor($this->_fixCoordY($bottom)); $this->_fixCoords($x, $y); $dest_width = floor($this->_fixSizeX($width)); $dest_height = floor($this->_fixSizeY($height)); $start_x = $x - $dest_width; $start_y = $y - $dest_height; $sx = $image->sx(); $sy = $image->sy(); $dx = $this->_fixSizeX($sx * $scale); $dy = $this->_fixSizeY($sy * $scale); $cx = $start_x - ceil($this->_fixSizeX($ox) / $dest_width) * $dest_width; $cy = $start_y - ceil($this->_fixSizeY($oy) / $dest_height) * $dest_height; while ($cy < $base_y) { while ($cx < $base_x) { imagecopyresampled($this->_image, $image->get_handle(), $cx, $cy, 0, 0, $dx, $dy, $sx, $sy); $cx += $dest_width; }; $cx = $start_x - ceil($this->_fixSizeX($ox) / $dest_width) * $dest_width; $cy += $dest_height; }; } function lineto($x, $y) { $this->_path->addPoint(new Point($x, $y)); } function moveto($x, $y) { $this->_path->clear(); $this->_path->addPoint(new Point($x, $y)); } function new_form($name) { /* N/A */ } function next_page() { /* N/A */ } function release() { } /** * Note: _restoreClip will change current image object, so we must * release all image-dependent objects before call to _restoreClip * to ensure resources are released correctly */ function restore() { $this->_restoreColor(); $this->_restoreClip(); } /** * Note: _saveClip will change current image object, so we must * create all image-dependent objects after call to _saveClip to * ensure resources are created correctly */ function save() { $this->_saveClip($this->_clipping[0]['box']); $this->_saveColor($this->_color[0]['rgb']); } function setfont($name, $encoding, $size) { $this->_setFont($name, $encoding, $size); return true; } function setlinewidth($x) { $dummy = 0; $this->_fixSizes($x, $dummy); imagesetthickness($this->_image, $x); } function setrgbcolor($r, $g, $b) { $color = array('rgb' => array($r, $g, $b), 'object' => imagecolorallocate($this->_image, $r*255, $g*255, $b*255)); $this->_setColor($color); } function set_watermark($text) { } function show_xy($text, $x, $y) { $this->_fixCoords($x, $y); $font = $this->_getFont(); $converter = Converter::create(); global $g_font_resolver_pdf; $fontFile = $g_font_resolver_pdf->ttf_mappings[$font['font']]; $fontSize = $font['size']; $dummy = 0; $this->_fixSizes($dummy, $fontSize); $utf8_string = $converter->to_utf8($text, $font['encoding']); imagefttext($this->_image, $fontSize * $font['ascender'], 0, $x, $y, $this->_getCurrentColor(), TTF_FONTS_REPOSITORY.$fontFile, $utf8_string); } /** * Note: the koefficient is just a magic number; I'll need to examine the * imagefttext behavior more closely */ function stringwidth($string, $name, $encoding, $size) { $font = $this->_font_factory->getTrueType($name, $encoding); return Font::points($size, $font->stringwidth($string))*1.25; } function stroke() { $this->_path->stroke($this->_transform, $this->_image, $this->_getCurrentColor()); $this->_path = new Path; } } ?>