1<?php
2
3/* vim: set expandtab tabstop=4 shiftwidth=4 softtabstop=4: */
4
5/**
6 * Class for handling output in Postscript format.
7 *
8 * Requires PHP extension pslib
9 *
10 * PHP versions 4 and 5
11 *
12 * LICENSE: This library is free software; you can redistribute it and/or modify
13 * it under the terms of the GNU Lesser General Public License as published by
14 * the Free Software Foundation; either version 2.1 of the License, or (at your
15 * option) any later version. This library is distributed in the hope that it
16 * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
17 * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
18 * General Public License for more details. You should have received a copy of
19 * the GNU Lesser General Public License along with this library; if not, write
20 * to the Free Software Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA
21 * 02111-1307 USA
22 *
23 * @category   Images
24 * @package    Image_Canvas
25 * @author     Jesper Veggerby <pear.nosey@veggerby.dk>
26 * @copyright  Copyright (C) 2003, 2004 Jesper Veggerby Hansen
27 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL License 2.1
28 * @version    CVS: $Id: PS.php 287471 2009-08-18 23:12:01Z clockwerx $
29 * @link       http://pear.php.net/package/Image_Canvas
30 */
31
32/**
33 * Include file Image/Canvas.php
34 */
35require_once 'Image/Canvas.php';
36
37/**
38 * Include file Image/Canvas/Color.php
39 */
40require_once 'Image/Canvas/Color.php';
41
42/**
43 * PostScript Canvas class.
44 *
45 * @category   Images
46 * @package    Image_Canvas
47 * @author     Jesper Veggerby <pear.nosey@veggerby.dk>
48 * @copyright  Copyright (C) 2003, 2004 Jesper Veggerby Hansen
49 * @license    http://www.gnu.org/copyleft/lesser.html  LGPL License 2.1
50 * @version    Release: @package_version@
51 * @link       http://pear.php.net/package/Image_Canvas
52 */
53class Image_Canvas_PS extends Image_Canvas
54{
55
56    /**
57     * The PostScript document
58     * @var resource
59     * @access private
60     */
61    var $_ps;
62
63    /**
64     * The major version of pslib
65     * @var int
66     * @access private
67     */
68    var $_pslib;
69
70    /**
71     * The font
72     * @var mixed
73     * @access private
74     */
75    var $_psFont = false;
76
77    /**
78     * The width of the page
79     * @var int
80     * @access private
81     */
82    var $_pageWidth;
83
84    /**
85     * The height of the page
86     * @var int
87     * @access private
88     */
89    var $_pageHeight;
90
91    /**
92     * Create the PostScript canvas.
93     *
94     * Parameters available:
95     *
96     * 'page' Specify the page/paper format for the graph's page, available
97     * formats are: A0, A1, A2, A3, A4, A5, A6, B5, letter, legal, ledger,
98     * 11x17, cd_front, inlay, inlay_nosides
99     *
100     * 'align' Alignment of the graph on the page, available options are:
101     * topleft, topcenter, topright, leftcenter, center, rightcenter,
102     * leftbottom, centerbottom, rightbottom
103     *
104     * 'orientation' Specifies the paper orientation, default is 'portrait' and
105     * 'landscape' is also supported.
106     *
107     * 'creator' The creator tag of the PostScript/graph
108     *
109     * 'author' The author tag of the PostScript/graph
110     *
111     * 'title' The title tag of the PostScript/graph
112     *
113     * 'width' The width of the graph on the page
114     *
115     * 'height' The height of the graph on the page
116     *
117     * 'left' The left offset of the graph on the page
118     *
119     * 'top' The top offset of the graph on the page
120     *
121     * 'filename' The PostScript file to open/add page to, using 'filename' requires
122     * 'ps' An existing pslib PostScript document to add the page to
123     *
124     * 'add_page' (true/false) Used together with 'ps', to specify whether the
125     * canvas should add a new graph page (true) or create the graph on the
126     * current page (false), default is 'true'
127     *
128     * The 'page' and 'width' & 'height' can be mutually omitted, if 'page' is
129     * omitted the page is created using dimensions of width x height, and if
130     * width and height are omitted the page dimensions are used for the graph.
131     *
132     * If 'ps' is specified, 'filename', 'creator', 'author' and 'title' has no
133     * effect.
134     *
135     * 'left' and 'top' are overridden by 'align'
136     *
137     * It is required either to specify 'width' & 'height' or 'page'.
138     *
139     * The PostScript format/pslib has some limitations on the capabilities,
140     * which means some functionality available using other canvass (fx. alpha
141     * blending and gradient fills) are not supported with PostScript
142		 * (see Canvas.txt in the docs/ folder for further details)
143     *
144     * @param array $param Parameter array
145     */
146    function Image_Canvas_PS($param)
147    {
148        if (isset($param['page'])) {
149            switch (strtoupper($param['page'])) {
150            case 'A0':
151                $this->_pageWidth = 2380;
152                $this->_pageHeight = 3368;
153                break;
154
155            case 'A1':
156                $this->_pageWidth = 1684;
157                $this->_pageHeight = 2380;
158                break;
159
160            case 'A2':
161                $this->_pageWidth = 1190;
162                $this->_pageHeight = 1684;
163                break;
164
165            case 'A3':
166                $this->_pageWidth = 842;
167                $this->_pageHeight = 1190;
168                break;
169
170            case 'A4':
171                $this->_pageWidth = 595;
172                $this->_pageHeight = 842;
173                break;
174
175            case 'A5':
176                $this->_pageWidth = 421;
177                $this->_pageHeight = 595;
178                break;
179
180            case 'A6':
181                $this->_pageWidth = 297;
182                $this->_pageHeight = 421;
183                break;
184
185            case 'B5':
186                $this->_pageWidth = 501;
187                $this->_pageHeight = 709;
188                break;
189
190            case 'LETTER':
191                $this->_pageWidth = 612;
192                $this->_pageHeight = 792;
193                break;
194
195            case 'LEGAL':
196                $this->_pageWidth = 612;
197                $this->_pageHeight = 1008;
198                break;
199
200            case 'LEDGER':
201                $this->_pageWidth = 1224;
202                $this->_pageHeight = 792;
203                break;
204
205            case '11X17':
206                $this->_pageWidth = 792;
207                $this->_pageHeight = 1224;
208                break;
209
210            case 'CD_FRONT':
211                $this->_pageWidth = 337;
212                $this->_pageHeight = 337;
213                break;
214
215            case 'INLAY':
216                $this->_pageWidth = 425;
217                $this->_pageHeight = 332;
218                break;
219
220            case 'INLAY_NOSIDES':
221                $this->_pageWidth = 390;
222                $this->_pageHeight = 332;
223                break;
224            }
225        }
226
227				$this->setDefaultFont(array('name' => 'Helvetica', 'color' => 'black', 'size' => 9));
228
229        if ((isset($param['orientation'])) && (strtoupper($param['orientation']) == 'LANDSCAPE')) {
230            $w = $this->_pageWidth;
231            $this->_pageWidth = $this->_pageHeight;
232            $this->_pageHeight = $w;
233        }
234
235        parent::Image_Canvas($param);
236
237        if (!$this->_pageWidth) {
238            $this->_pageWidth = $this->_width;
239        } elseif (!$this->_width) {
240            $this->_width = $this->_pageWidth;
241        }
242
243        if (!$this->_pageHeight) {
244            $this->_pageHeight = $this->_height;
245        } elseif (!$this->_height) {
246            $this->_height = $this->_pageHeight;
247        }
248
249        $this->_width = min($this->_width, $this->_pageWidth);
250        $this->_height = min($this->_height, $this->_pageHeight);
251
252        if ((isset($param['align'])) &&
253            (($this->_width != $this->_pageWidth) || ($this->_height != $this->_pageHeight))
254        ) {
255            switch (strtoupper($param['align'])) {
256            case 'TOPLEFT':
257                $this->_top = 0;
258                $this->_left = 0;
259                break;
260
261            case 'TOPCENTER':
262                $this->_top = 0;
263                $this->_left = ($this->_pageWidth - $this->_width) / 2;
264                break;
265
266            case 'TOPRIGHT':
267                $this->_top = 0;
268                $this->_left = $this->_pageWidth - $this->_width;
269                break;
270
271            case 'LEFTCENTER':
272                $this->_top = ($this->_pageHeight - $this->_height) / 2;
273                $this->_left = 0;
274                break;
275
276            case 'CENTER':
277                $this->_top = ($this->_pageHeight - $this->_height) / 2;
278                $this->_left = ($this->_pageWidth - $this->_width) / 2;
279                break;
280
281            case 'RIGHTCENTER':
282                $this->_top = ($this->_pageHeight - $this->_height) / 2;
283                $this->_left = $this->_pageWidth - $this->_width;
284                break;
285
286            case 'LEFTBOTTOM':
287                $this->_top = $this->_pageHeight - $this->_height;
288                $this->_left = 0;
289                break;
290
291            case 'CENTERBOTTOM':
292                $this->_top = $this->_pageHeight - $this->_height;
293                $this->_left = ($this->_pageWidth - $this->_width) / 2;
294                break;
295
296            case 'RIGHTBOTTOM':
297                $this->_top = $this->_pageHeight - $this->_height;
298                $this->_left = $this->_pageWidth - $this->_width;
299                break;
300            }
301        }
302
303        $addPage = true;
304        if ((isset($param['ps'])) && (is_resource($param['ps']))) {
305            $this->_ps =& $param['ps'];
306            if ((isset($param['add_page'])) && ($param['add_page'] === false)) {
307                $addPage = false;
308            }
309        } else {
310            $this->_ps = ps_new();
311
312            if (isset($param['filename'])) {
313                ps_open_file($this->_ps, $param['filename']);
314            } else {
315                ps_open_file($this->_ps);
316            }
317
318            ps_set_parameter($this->_ps, 'warning', 'true');
319
320            ps_set_info($this->_ps, 'Creator', (isset($param['creator']) ? $param['creator'] : 'PEAR::Image_Canvas'));
321            ps_set_info($this->_ps, 'Author', (isset($param['author']) ? $param['author'] : 'Jesper Veggerby'));
322            ps_set_info($this->_ps, 'Title', (isset($param['title']) ? $param['title'] : 'Image_Canvas'));
323        }
324
325        if ($addPage) {
326            ps_begin_page($this->_ps, $this->_pageWidth, $this->_pageHeight);
327        }
328        $this->_reset();
329
330        $this->_pslib = $this->_version();
331    }
332
333    /**
334     * Get the x-point from the relative to absolute coordinates
335     *
336     * @param float $x The relative x-coordinate (in percentage of total width)
337     * @return float The x-coordinate as applied to the canvas
338     * @access private
339     */
340    function _getX($x)
341    {
342        return $this->_left + $x;
343    }
344
345    /**
346     * Get the y-point from the relative to absolute coordinates
347     *
348     * @param float $y The relative y-coordinate (in percentage of total width)
349     * @return float The y-coordinate as applied to the canvas
350     * @access private
351     */
352    function _getY($y)
353    {
354        return $this->_pageHeight - ($this->_top + $y);
355    }
356
357    /**
358     * Get the color index for the RGB color
359     *
360     * @param int $color The color
361     * @return int The GD image index of the color
362     * @access private
363     */
364    function _color($color = false)
365    {
366        if (($color === false) || ($color === 'opague') || ($color === 'transparent')) {
367            return false;
368        } else {
369            $color = Image_Canvas_Color::color2RGB($color);
370            $color[0] = $color[0]/255;
371            $color[1] = $color[1]/255;
372            $color[2] = $color[2]/255;
373            return $color;
374        }
375    }
376
377    /**
378     * Get the PostScript linestyle
379     *
380     * @param mixed $lineStyle The line style to return, false if the one
381     *   explicitly set
382     * @return bool True if set (so that a line should be drawn)
383     * @access private
384     */
385    function _setLineStyle($lineStyle = false)
386    {
387        if ($lineStyle === false) {
388            $lineStyle = $this->_lineStyle;
389        }
390
391        if (($lineStyle == 'transparent') || ($lineStyle === false)) {
392            return false;
393        }
394
395        if (is_array($lineStyle)) {
396            // TODO Implement linestyles in pslib (using ps_setcolor(.., 'pattern'...); ?
397            reset($lineStyle);
398            $lineStyle = current($lineStyle);
399        }
400
401        $color = $this->_color($lineStyle);
402
403        ps_setlinewidth($this->_ps, $this->_thickness);
404        ps_setcolor($this->_ps, 'stroke', 'rgb', $color[0], $color[1], $color[2], 0);
405        return true;
406    }
407
408    /**
409     * Set the PostScript fill style
410     *
411     * @param mixed $fillStyle The fillstyle to return, false if the one
412     *   explicitly set
413     * @return bool True if set (so that a line should be drawn)
414     * @access private
415     */
416    function _setFillStyle($fillStyle = false)
417    {
418        if ($fillStyle === false) {
419            $fillStyle = $this->_fillStyle;
420        }
421
422        if (($fillStyle == 'transparent') || ($fillStyle === false)) {
423            return false;
424        }
425
426        $color = $this->_color($fillStyle);
427
428        ps_setcolor($this->_ps, 'fill', 'rgb', $color[0], $color[1], $color[2], 0);
429        return true;
430    }
431
432    /**
433     * Set the PostScript font
434     *
435     * @access private
436     */
437    function _setFont()
438    {
439        $this->_psFont = false;
440        if (isset($this->_font['name'])) {
441            ps_set_parameter($this->_ps, 'FontOutline', $this->_font['name'] . '=' . $this->_font['file']);
442            $this->_psFont = ps_findfont($this->_ps, $this->_font['name'], $this->_font['encoding'], $this->_font['embed']);
443
444            if ($this->_psFont) {
445                ps_setfont($this->_ps, $this->_psFont, $this->_font['size']);
446                $this->_setFillStyle($this->_font['color']);
447            }
448        } else {
449            $this->_setFillStyle('black');
450        }
451    }
452
453    /**
454     * Sets an image that should be used for filling.
455     *
456     * Image filling is not supported with PostScript, filling 'transparent'
457     *
458     * @param string $filename The filename of the image to fill with
459     */
460    function setFillImage($filename)
461    {
462        $this->_fillStyle = 'transparent';
463    }
464
465    /**
466     * Sets a gradient fill
467     *
468     * Gradient filling is not supported with PostScript, end color used as solid fill.
469     *
470     * @param array $gradient Gradient fill options
471     */
472    function setGradientFill($gradient)
473    {
474        $this->_fillStyle = $gradient['end'];
475    }
476
477    /**
478     * Sets the font options.
479     *
480     * The $font array may have the following entries:
481     *
482     * 'ttf' = the .ttf file (either the basename, filename or full path)
483     * If 'ttf' is specified, then the following can be specified
484     *
485     * 'size' = size in pixels
486     *
487     * 'angle' = the angle with which to write the text
488     *
489     * @param array $font The font options.
490     */
491    function setFont($fontOptions)
492    {
493        parent::setFont($fontOptions);
494
495        if (!isset($this->_font['size'])) {
496            $this->_font['size'] = 12;
497        }
498
499        if (!isset($this->_font['encoding'])) {
500            $this->_font['encoding'] = null;
501        }
502
503        if ($this->_font['name'] == 'Helvetica') {
504            $this->_font['embed'] = 0;
505        }
506
507        if (!isset($this->_font['color'])) {
508            $this->_font['color'] = 'black';
509        }
510    }
511
512    /**
513     * Resets the canvas.
514     *
515     * Includes fillstyle, linestyle, thickness and polygon
516     *
517     * @access private
518     */
519    function _reset()
520    {
521//        ps_initgraphics($this->_ps);
522        parent::_reset();
523    }
524
525    /**
526     * Draw a line
527     *
528     * Parameter array:
529     * 'x0': int X start point
530     * 'y0': int Y start point
531     * 'x1': int X end point
532     * 'y1': int Y end point
533     * 'color': mixed [optional] The line color
534     * @param array $params Parameter array
535     */
536    function line($params)
537    {
538        $color = (isset($params['color']) ? $params['color'] : false);
539        if ($this->_setLineStyle($color)) {
540            ps_moveto($this->_ps, $this->_getX($params['x0']), $this->_getY($params['y0']));
541            ps_lineto($this->_ps, $this->_getX($params['x1']), $this->_getY($params['y1']));
542            ps_stroke($this->_ps);
543        }
544        parent::line($params);
545    }
546
547    /**
548     * Parameter array:
549     * 'connect': bool [optional] Specifies whether the start point should be
550     *   connected to the endpoint (closed polygon) or not (connected line)
551     * 'fill': mixed [optional] The fill color
552     * 'line': mixed [optional] The line color
553     * @param array $params Parameter array
554     */
555    function polygon($params = array())
556    {
557        $connectEnds = (isset($params['connect']) ? $params['connect'] : false);
558        $fillColor = (isset($params['fill']) ? $params['fill'] : false);
559        $lineColor = (isset($params['line']) ? $params['line'] : false);
560
561        $line = $this->_setLineStyle($lineColor);
562        $fill = false;
563        if ($connectEnds) {
564            $fill = $this->_setFillStyle($fillColor);
565        }
566
567        $first = true;
568        foreach ($this->_polygon as $point) {
569            if ($first === true) {
570                ps_moveto($this->_ps, $point['X'], $point['Y']);
571                $first = $point;
572            } else {
573                if (isset($last['P1X'])) {
574                    ps_curveto($this->_ps,
575                        $last['P1X'],
576                        $last['P1Y'],
577                        $last['P2X'],
578                        $last['P2Y'],
579                        $point['X'],
580                        $point['Y']
581                    );
582                } else {
583                    ps_lineto($this->_ps,
584                        $point['X'],
585                        $point['Y']
586                    );
587                }
588            }
589            $last = $point;
590        }
591
592        if ($connectEnds) {
593            if (isset($last['P1X'])) {
594                ps_curveto($this->_ps,
595                    $last['P1X'],
596                    $last['P1Y'],
597                    $last['P2X'],
598                    $last['P2Y'],
599                    $first['X'],
600                    $first['Y']
601                );
602            } else {
603                ps_lineto($this->_ps,
604                    $first['X'],
605                    $first['Y']
606                );
607            }
608        }
609
610        if (($line) && ($fill)) {
611            ps_fill_stroke($this->_ps);
612        } elseif ($line) {
613            ps_stroke($this->_ps);
614        } elseif ($fill) {
615            ps_fill($this->_ps);
616        }
617        parent::polygon($params);
618    }
619
620    /**
621     * Draw a rectangle
622     *
623     * Parameter array:
624     * 'x0': int X start point
625     * 'y0': int Y start point
626     * 'x1': int X end point
627     * 'y1': int Y end point
628     * 'fill': mixed [optional] The fill color
629     * 'line': mixed [optional] The line color
630     * @param array $params Parameter array
631     */
632    function rectangle($params)
633    {
634        $x0 = $this->_getX($params['x0']);
635        $y0 = $this->_getY($params['y0']);
636        $x1 = $this->_getX($params['x1']);
637        $y1 = $this->_getY($params['y1']);
638        $fillColor = (isset($params['fill']) ? $params['fill'] : false);
639        $lineColor = (isset($params['line']) ? $params['line'] : false);
640
641        $line = $this->_setLineStyle($lineColor);
642        $fill = $this->_setFillStyle($fillColor);
643        if (($line) || ($fill)) {
644            ps_rect($this->_ps, min($x0, $x1), min($y0, $y1), abs($x1 - $x0), abs($y1 - $y0));
645            if (($line) && ($fill)) {
646                ps_fill_stroke($this->_ps);
647            } elseif ($line) {
648                ps_stroke($this->_ps);
649            } elseif ($fill) {
650                ps_fill($this->_ps);
651            }
652        }
653        parent::rectangle($params);
654    }
655
656    /**
657     * Draw an ellipse
658     *
659     * Parameter array:
660     * 'x': int X center point
661     * 'y': int Y center point
662     * 'rx': int X radius
663     * 'ry': int Y radius
664     * 'fill': mixed [optional] The fill color
665     * 'line': mixed [optional] The line color
666     * @param array $params Parameter array
667     */
668    function ellipse($params)
669    {
670        $x = $params['x'];
671        $y = $params['y'];
672        $rx = $params['rx'];
673        $ry = $params['ry'];
674        $fillColor = (isset($params['fill']) ? $params['fill'] : false);
675        $lineColor = (isset($params['line']) ? $params['line'] : false);
676
677        $line = $this->_setLineStyle($lineColor);
678        $fill = $this->_setFillStyle($fillColor);
679        if (($line) || ($fill)) {
680            if ($rx == $ry) {
681                ps_circle($this->_ps, $this->_getX($x), $this->_getY($y), $rx);
682            } else {
683                ps_moveto($this->_ps, $this->_getX($x - $rx), $this->_getY($y));
684                ps_curveto($this->_ps,
685                    $this->_getX($x - $rx), $this->_getY($y),
686                    $this->_getX($x - $rx), $this->_getY($y - $ry),
687                    $this->_getX($x), $this->_getY($y - $ry)
688                );
689                ps_curveto($this->_ps,
690                    $this->_getX($x), $this->_getY($y - $ry),
691                    $this->_getX($x + $rx), $this->_getY($y - $ry),
692                    $this->_getX($x + $rx), $this->_getY($y)
693                );
694                ps_curveto($this->_ps,
695                    $this->_getX($x + $rx), $this->_getY($y),
696                    $this->_getX($x + $rx), $this->_getY($y + $ry),
697                    $this->_getX($x), $this->_getY($y + $ry)
698                );
699                ps_curveto($this->_ps,
700                    $this->_getX($x), $this->_getY($y + $ry),
701                    $this->_getX($x - $rx), $this->_getY($y + $ry),
702                    $this->_getX($x - $rx), $this->_getY($y)
703                );
704            }
705
706            if (($line) && ($fill)) {
707                ps_fill_stroke($this->_ps);
708            } elseif ($line) {
709                ps_stroke($this->_ps);
710            } elseif ($fill) {
711                ps_fill($this->_ps);
712            }
713        }
714        parent::ellipse($params);
715    }
716
717    /**
718     * Draw a pie slice
719     *
720     * Parameter array:
721     * 'x': int X center point
722     * 'y': int Y center point
723     * 'rx': int X radius
724     * 'ry': int Y radius
725     * 'v1': int The starting angle (in degrees)
726     * 'v2': int The end angle (in degrees)
727     * 'srx': int [optional] Starting X-radius of the pie slice (i.e. for a doughnut)
728     * 'sry': int [optional] Starting Y-radius of the pie slice (i.e. for a doughnut)
729     * 'fill': mixed [optional] The fill color
730     * 'line': mixed [optional] The line color
731     * @param array $params Parameter array
732     */
733    function pieslice($params)
734    {
735        $x = $this->_getX($params['x']);
736        $y = $this->_getY($params['y']);
737        $rx = $this->_getX($params['rx']);
738        $ry = $this->_getY($params['ry']);
739        $v1 = $this->_getX($params['v1']);
740        $v2 = $this->_getY($params['v2']);
741        $srx = $this->_getX($params['srx']);
742        $sry = $this->_getY($params['sry']);
743        $fillColor = (isset($params['fill']) ? $params['fill'] : false);
744        $lineColor = (isset($params['line']) ? $params['line'] : false);
745
746        // TODO Implement pslib::pieSlice()
747        parent::pieslice($params);
748    }
749
750    /**
751     * Get the width of a text,
752     *
753     * @param string $text The text to get the width of
754     * @return int The width of the text
755     */
756    function textWidth($text)
757    {
758        if ($this->_psFont === false) {
759             return $this->_font['size'] * 0.7 * strlen($text);
760         } else {
761            return ps_stringwidth($this->_ps, $text, $this->_psFont, $this->_font['size']);
762        }
763    }
764
765    /**
766     * Get the height of a text,
767     *
768     * @param string $text The text to get the height of
769     * @return int The height of the text
770     */
771    function textHeight($text)
772    {
773        if (isset($this->_font['size'])) {
774            return $this->_font['size'];
775        } else {
776            return 12;
777        }
778    }
779
780    /**
781     * Writes text
782     *
783     * Parameter array:
784     * 'x': int X-point of text
785     * 'y': int Y-point of text
786     * 'text': string The text to add
787     * 'alignment': array [optional] Alignment
788     * 'color': mixed [optional] The color of the text
789     */
790    function addText($params)
791    {
792        $x = $this->_getX($params['x']);
793        $y = $this->_getY($params['y']);
794        $text = $params['text'];
795        $color = (isset($params['color']) ? $params['color'] : false);
796        $alignment = (isset($params['alignment']) ? $params['alignment'] : false);
797
798        $this->_setFont();
799
800        $textWidth = $this->textWidth($text);
801        $textHeight = $this->textHeight($text);
802
803        if (!is_array($alignment)) {
804            $alignment = array('vertical' => 'top', 'horizontal' => 'left');
805        }
806
807        if (!isset($alignment['vertical'])) {
808            $alignment['vertical'] = 'top';
809        }
810
811        if (!isset($alignment['horizontal'])) {
812            $alignment['horizontal'] = 'left';
813        }
814
815        if ($alignment['horizontal'] == 'right') {
816            $x = $x - $textWidth;
817        } elseif ($alignment['horizontal'] == 'center') {
818            $x = $x - ($textWidth / 2);
819        }
820
821        $y -= $textHeight;
822
823        if ($alignment['vertical'] == 'bottom') {
824            $y = $y + $textHeight;
825        } elseif ($alignment['vertical'] == 'center') {
826            $y = $y + ($textHeight / 2);
827        }
828
829        if (($color === false) && (isset($this->_font['color']))) {
830            $color = $this->_font['color'];
831        }
832
833        ps_show_xy($this->_ps, $text, $x, $y);
834
835        parent::addText($params);
836    }
837
838    /**
839     * Overlay image
840     *
841     * Parameter array:
842     * 'x': int X-point of overlayed image
843     * 'y': int Y-point of overlayed image
844     * 'filename': string The filename of the image to overlay
845     * 'width': int [optional] The width of the overlayed image (resizing if possible)
846     * 'height': int [optional] The height of the overlayed image (resizing if possible)
847     * 'alignment': array [optional] Alignment
848     */
849    function image($params)
850    {
851        $x = $this->_getX($params['x']);
852        $y = $this->_getY($params['y']);
853        $filename = $params['filename'];
854        $width = (isset($params['width']) ? $params['width'] : false);
855        $height = (isset($params['height']) ? $params['height'] : false);
856        $alignment = (isset($params['alignment']) ? $params['alignment'] : false);
857
858        if (substr($filename, -4) == '.png') {
859            $type = 'png';
860        } elseif (substr($filename, -4) == '.jpg') {
861            $type = 'jpeg';
862        }
863
864        $image = ps_open_image_file($this->_ps, $type, realpath($filename), '');
865        $width_ = ps_get_value($this->_ps, 'imagewidth', $image);
866        $height_ = ps_get_value($this->_ps, 'imageheight', $image);
867
868        $outputWidth = ($width !== false ? $width : $width_);
869        $outputHeight = ($height !== false ? $height : $height_);
870
871        if (!is_array($alignment)) {
872            $alignment = array('vertical' => 'top', 'horizontal' => 'left');
873        }
874
875        if (!isset($alignment['vertical'])) {
876            $alignment['vertical'] = 'top';
877        }
878
879        if (!isset($alignment['horizontal'])) {
880            $alignment['horizontal'] = 'left';
881        }
882
883        if ($alignment['horizontal'] == 'right') {
884            $x -= $outputWidth;
885        } elseif ($alignment['horizontal'] == 'center') {
886            $x -= $outputWidth / 2;
887        }
888
889        if ($alignment['vertical'] == 'top') {
890            $y += $outputHeight;
891        } elseif ($alignment['vertical'] == 'center') {
892            $y += $outputHeight / 2;
893        }
894
895        if (($width === false) && ($height === false)) {
896            $scale = 1;
897        } else {
898            $scale = max(($height/$height_), ($width/$width_));
899        }
900
901        ps_place_image($this->_ps, $image, $x, $y, $scale);
902        ps_close_image($this->_ps, $image);
903
904        parent::image($params);
905    }
906
907    /**
908     * Output the result of the canvas
909     *
910     * @param array $param Parameter array
911     * @abstract
912     */
913    function show($param = false)
914    {
915        parent::show($param);
916        ps_end_page($this->_ps);
917        ps_close($this->_ps);
918
919        $buf = ps_get_buffer($this->_ps);
920        $len = strlen($buf);
921
922        header('Content-type: application/postscript');
923        header('Content-Length: ' . $len);
924        header('Content-Disposition: inline; filename=image_graph.ps');
925        print $buf;
926
927        ps_delete($this->_ps);
928    }
929
930    /**
931     * Output the result of the canvas
932     *
933     * @param array $param Parameter array
934     * @abstract
935     */
936    function save($param = false)
937    {
938        parent::save($param);
939        ps_end_page($this->_ps);
940        ps_close($this->_ps);
941
942        if($param['filename'] == "") {
943					$buf = ps_get_buffer($this->_ps);
944					$len = strlen($buf);
945
946					$fp = @fopen($param['filename'], 'wb');
947					if ($fp) {
948							fwrite($fp, $buf, strlen($buf));
949							fclose($fp);
950					}
951				}
952
953        ps_delete($this->_ps);
954    }
955
956    /**
957     * Get a canvas specific HTML tag.
958     *
959     * This method implicitly saves the canvas to the filename in the
960     * filesystem path specified and parses it as URL specified by URL path
961     *
962     * Parameter array:
963     * 'filename': string
964     * 'filepath': string Path to the file on the file system. Remember the final slash
965     * 'urlpath': string Path to the file available through an URL. Remember the final slash
966     * 'title': string The url title
967     */
968    function toHtml($params)
969    {
970        parent::toHtml($params);
971        return '<a href="' . $params['urlpath'] . $params['filename'] . '">' . $params['title'] . '</a>';
972    }
973
974    /**
975     * Check which major version of pslib is installed
976     *
977     * @return int The mahor version number of pslib
978     * @access private
979     */
980    function _version()
981    {
982        $result = false;
983        $version = '';
984        if (function_exists('ps_get_majorversion')) {
985            $version = ps_get_majorversion();
986        } else if (function_exists('ps_get_value')) {
987            $version = ps_get_value($this->_ps, 'major', 0);
988        } else {
989            ob_start();
990            phpinfo(8);
991            $php_info = ob_get_contents();
992            ob_end_clean();
993
994            if (preg_match("/<td[^>]*>pslib Version *<\/td><td[^>]*>([^<]*)<\/td>/",
995                $php_info, $result))
996            {
997                $version = $result[1];
998            }
999        }
1000
1001        if (preg_match('/([0-9]{1,2})\.[0-9]{1,2}(\.[0-9]{1,2})?/', trim($version), $result)) {
1002            return $result[1];
1003        } else {
1004            return $version;
1005        }
1006    }
1007
1008}
1009
1010?>
1011