--- svn/zend/Zend/Pdf/Page.php	2009-06-26 18:03:48.000000000 +0200
+++ lib/Zend/Pdf/Page.php	2009-07-02 15:08:05.000000000 +0200
@@ -117,6 +117,39 @@
      */
     const LINE_DASHING_SOLID = 0;
 
+  /*  Cursor Directions */
+
+    /**
+     * Move cursor from left to right.
+     */
+    const CURSOR_DIRECTION_LEFT  = 'left';
+
+    /**
+     * Move cursor from right to left.
+     */
+    const CURSOR_DIRECTION_RIGHT = 'right';
+    
+  /* Text Block Alignment */
+    
+    /**
+     * Left align text block.
+     */
+    const ALIGN_LEFT    = 'left';
+
+    /**
+     * Right align text block.
+     */
+    const ALIGN_RIGHT   = 'right';
+
+    /**
+     * Center text block.
+     */
+    const ALIGN_CENTER  = 'center';
+
+    /**
+     * Justify text in block.
+     */
+    const ALIGN_JUSTIFY = 'justify';
 
 
     /**
@@ -188,6 +221,32 @@
     protected $_fontSize;
 
     /**
+     * x position when the cursor was set. Used after line wrapping.
+     */
+    protected $_cursor_original_x = 0;
+    
+    /**
+     * Current x position of cursor.
+     */
+    protected $_cursor_x = 0;
+
+    /**
+     * Current y position of cursor.
+     */
+    protected $_cursor_y = 0;
+    
+    /**
+     * Set cursor direction.
+     */
+    protected $_cursor_direction = self::CURSOR_DIRECTION_LEFT;
+    
+    /**
+     * Block width for text cursor.
+     */
+    protected $_cursor_width = 0;
+
+
+    /**
      * Object constructor.
      * Constructor signatures:
      *
@@ -334,6 +393,8 @@
         $this->_pageDictionary->MediaBox->items[] = new Zend_Pdf_Element_Numeric($pageWidth);
         $this->_pageDictionary->MediaBox->items[] = new Zend_Pdf_Element_Numeric($pageHeight);
         $this->_pageDictionary->Contents     = new Zend_Pdf_Element_Array();
+
+        $this->setTextCursor(0, $this->getHeight());
     }
 
 
@@ -1392,33 +1453,73 @@
     }
 
     /**
-     * Draw a line of text at the specified position.
-     *
+     * Draw a line of text at the specified position or current cursor position.
+     * 
+     * @see Zend_Pdf_Page::drawPage()
      * @param string $text
-     * @param float $x
-     * @param float $y
+     * @param float|null $x (optional) if null cursor position is taken and advanced afterwards
+     * @param float|null $y (optional) if null cursor position is taken and advanced if text wrapped
      * @param string $charEncoding (optional) Character encoding of source text.
      *   Defaults to current locale.
+     * @param bool $ignore_width (optional) draw text, but don't wrap
      * @throws Zend_Pdf_Exception
      * @return Zend_Pdf_Page
      */
-    public function drawText($text, $x, $y, $charEncoding = '')
+     public function drawText($text, $x = null, $y = null, $charEncoding = '', $ignore_width = false)
     {
         if ($this->_font === null) {
             require_once 'Zend/Pdf/Exception.php';
             throw new Zend_Pdf_Exception('Font has not been set');
         }
 
-        $this->_addProcSet('Text');
+        if ($x === null) {
+            $x = $this->_cursor_x;
+            if ($charEncoding) {
+                $text_width = $this->widthForString($text, $charEncoding);
+            } else {
+                $text_width = $this->widthForString($text);
+            }
+        }
+        if ($y === null) {
+            $y = $this->_cursor_y;
+        }
 
-        $textObj = new Zend_Pdf_Element_String($this->_font->encodeString($text, $charEncoding));
-        $xObj    = new Zend_Pdf_Element_Numeric($x);
-        $yObj    = new Zend_Pdf_Element_Numeric($y);
-
-        $this->_contents .= "BT\n"
-                         .  $xObj->toString() . ' ' . $yObj->toString() . " Td\n"
-                         .  $textObj->toString() . " Tj\n"
-                         .  "ET\n";
+        if ($ignore_width || !$this->_cursor_width) {
+            $this->_addProcSet('Text');
+    
+            $textObj = new Zend_Pdf_Element_String($this->_font->encodeString($text, $charEncoding));
+            $xObj    = new Zend_Pdf_Element_Numeric($x);
+            $yObj    = new Zend_Pdf_Element_Numeric($y);
+    
+            $this->_contents .= "BT\n"
+                             .  $xObj->toString() . ' ' . $yObj->toString() . " Td\n"
+                             .  $textObj->toString() . " Tj\n"
+                             .  "ET\n";
+
+            if (isset($text_width)) {
+                switch ($this->_cursor_direction) {
+                    case self::CURSOR_DIRECTION_RIGHT:
+                        $this->textCursorMove(-$text_width);
+                        break;
+                    case self::CURSOR_DIRECTION_LEFT:
+                    default:
+                        $this->textCursorMove($text_width); 
+                        break;              
+                }
+            }
+        } else {    
+            $lines = $this->_wrapText($text, $this->_cursor_width, $this->_cursor_x - $this->_cursor_original_x);
+            $last = count($lines) - 1;
+            foreach ($lines as $k => $line) {
+                $this->drawText(implode(' ', $line['words']), null, null, $charEncoding, true);
+                if ($k != $last) {
+                    $this->textCursorNewline();
+                }
+            }
+            if ($text[strlen($text) - 1] == ' ') {
+                $this->drawText(' ', null, null, $charEncoding, true);
+            }
+        }
 
         return $this;
     }
@@ -1596,5 +1697,218 @@
 
         return $this;
     }
+
+    /**
+     * Calculate the width for the given string.
+     *
+     * @param string $string
+     * @param string $charset charset of the string
+     * @return int width of string
+     */
+    public function widthForString($string, $charset = 'ISO-8859-1') {
+        $drawingString = iconv($charset, 'UTF-16BE//IGNORE', $string);
+        $characters = array();
+        for ($i = 0; $i < strlen($drawingString); $i++) {
+            $characters[] = (ord($drawingString[$i++]) << 8) | ord($drawingString[$i]);
+        }
+
+        $glyphs = $this->_font->glyphNumbersForCharacters($characters);
+        $widths = $this->_font->widthsForGlyphs($glyphs);
+
+        $stringWidth = (array_sum($widths) / $this->_font->getUnitsPerEm()) * $this->_fontSize;      
+        return $stringWidth;
+    }
+
+    /**
+     * Get height of one or more line(s) in with current font and font size.
+     *
+     * @param int $lines number of lines
+     * @param int $extraSpacing spaceing between lines
+     * @return int line height
+     */
+    public function getLineHeight($lines = 1, $extraSpacing = 1) {
+        return $lines * $this->_fontSize * $this->_font->getLineHeight() / $this->_font->getUnitsPerEm() + $extraSpacing;
+    }
+
+    /**
+     * Set position of text cursor.
+     *
+     * @param int $x x position
+     * @param int $y y position
+     * @return Zend_Pdf_Page fluid interface
+     */
+    public function setTextCursor($x = null, $y = null) {
+        if ($x !== null) {
+            $this->_cursor_original_x = $x;
+            $this->_cursor_x = $x;
+        }
+        if ($y !== null) {
+            $this->_cursor_y = $y;
+        }
+        
+        return $this;
+    }
+    
+    /**
+     * Move text cursor in relation to current position
+     *
+     * @param float $x_offset x offset
+     * @param float $y_offset y offset
+     * @return Zend_Pdf_Page fluid interface
+     */ 
+    public function textCursorMove($x_offset = null, $y_offset = null) { 
+        if ($x_offset !== null) {
+            $this->_cursor_x += $x_offset;
+        }
+        if ($y_offset !== null) {
+            $this->_cursor_y += $y_offset;
+        }
+        
+        return $this;
+    }
+    
+    /**
+     * Set width of block used for cursor. If the width is reached a newline is started.
+     * Wrapping happens at whitespace.
+     *
+     * @param float|null $width block width. null to deactivate
+     * @return Zend_Pdf_Page fluid interface
+     */ 
+    public function setTextCursorWidth($width) {
+        $this->_cursor_width = $width;
+        return $this;
+    }
+    
+    /**
+     * Start a newline. The x position is reset and line height is added to the y position
+     * 
+     * @return Zend_Pdf_Page fluid interface
+     */ 
+    public function textCursorNewline() { 
+        $this->setTextCursor($this->_cursor_original_x);
+        $this->textCursorMove(null, -$this->getLineHeight());
+        return $this;
+    }
+
+    /**
+     * Set if cursor moves from left to right or from right to left
+     * 
+     * @param string $direction self::CURSOR_DIRECTION_LEFT or self::CURSOR_DIRECTION_RIGHT
+     * @return Zend_Pdf_Page fluid interface
+     */ 
+    public function setTextCursorDirection($direction) { 
+        $this->_cursor_direction = $direction;
+        return $this;
+    }
+
+    /**
+     * Helper method to wrap text to lines. The wrapping is done at whitespace if the text gets longer
+     * as $width.
+     * 
+     * @param string $text the text to wrap
+     * @param int $width
+     * @param int $initial_line_offset x offset for start position in first line
+     * @return array array with lines as array('words' => array(...), 'word_lengths' => array(...), 'total_length' => <int>)
+     */
+    protected function _wrapText($text, $width, $initial_line_offset = 0) {
+        $lines = array();
+        $line_init = array(
+            'words'        => array(),
+            'word_lengths' => array(),
+            'total_length' => 0
+        );
+        $line = $line_init;
+        $line['total_length'] = $initial_line_offset;
+        
+        $text = preg_split('%[\n\r ]+%', $text, -1, PREG_SPLIT_NO_EMPTY);
+        $space_length = $this->widthForString(' ');
+        foreach ($text as $word) {
+            $word_length = $this->widthForString($word);
+            if ($word_length > $width) {
+                if ($line['words']) {
+                    $lines[] = $line;
+                }
+                $lines[] = array(
+                    'words'        => array($word),
+                    'word_lengths' => array($word_length),
+                    'total_length' => array($word_length)
+                );
+                $line = $line_init;
+                continue;
+            }
+            if ($line['total_length'] + $word_length > $width) {
+                $line['total_length'] -= $space_length;
+                $lines[] = $line;
+                $line = $line_init;
+            }
+            $line['words'][]        = $word;
+            $line['word_lengths'][] = $word_length;
+            $line['total_length']  += $word_length + $space_length;
+        }
+        if ($line) {
+            $line['total_length'] -= $space_length;
+            $lines[] = $line;
+        }
+        
+        return $lines;
+    }
+    
+    /**
+     * Draw a text in a block with a fixed width and an optional fixed height. The text can be left or right
+     * aligned, centered of justified. If height is given, but the text would be longer an exception is thrown.
+     *
+     * This method may also be called as drawTextBlock($text, $width). The block is than drawn to the current
+     * cursor position.
+     *
+     * @param string $text
+     * @param int $x x position
+     * @param int $y y position
+     * @param int $width block width
+     * @param int|null $height optional height to check for
+     * @param string $align one of: self::ALIGN_LEFT, self::ALIGN_RIGHT, self::ALIGN_CENTER, self::ALIGN_JUSTIFY
+     * @throws Zend_Pdf_Exception
+     */
+    public function drawTextBlock($text, $x, $y = null, $width = null, $height = null, $align = self::ALIGN_LEFT) {
+        if ($width === null) {
+            $widht = $x;
+            $this->textCursorNewline();
+            $x = $this->_cursor_x;
+            $y = $this->_cursor_y;
+        }
+        
+        $lines = $this->_wrapText($text, $width);   
+        
+        if ($height !== null && $this->getLineHeight(count($lines)) > $height) {
+            throw new Zend_Pdf_Exception('height overflow');
+        }
+        $line_height = $this->getLineHeight();
+        foreach ($lines as $k => $line) {
+            switch($align) {
+                case self::ALIGN_JUSTIFY:
+                    if (count($line['words']) < 2 || $k == count($lines) - 1) {
+                        $this->drawText(implode(' ', $line['words']), $x, $y);
+                        break;                      
+                    }
+                    $space_width = ($width - array_sum($line['word_lengths'])) / (count($line['words']) - 1);
+                    $pos = $x;
+                    foreach ($line['words'] as $k => $word) {
+                        $this->drawText($word, $pos, $y);
+                        $pos += $line['word_lengths'][$k] + $space_width;
+                    }
+                    break;
+                case self::ALIGN_CENTER:
+                    $this->drawText(implode(' ', $line['words']), $x + ($width - $line['total_length']) / 2, $y);
+                    break;
+                case self::ALIGN_RIGHT:
+                    $this->drawText(implode(' ', $line['words']), $x + $width - $line['total_length'], $y);
+                    break;
+                case self::ALIGN_LEFT:
+                default:
+                    $this->drawText(implode(' ', $line['words']), $x, $y);
+                    break;
+            }
+            $y -= $line_height;
+        }
+    }
 }
 
