*  
{@link http://developer.apple.com/textfonts/TTRefMan/RM06/Chap6cmap.html}
 *  {@link http://www.microsoft.com/OpenType/OTSpec/cmap.htm}
 *  {@link http://partners.adobe.com/public/developer/opentype/index_cmap.html}
 * 
 *
 * @todo Write code for Zend_Pdf_FontCmap_HighByteMapping class.
 * @todo Write code for Zend_Pdf_FontCmap_MixedCoverage class.
 * @todo Write code for Zend_Pdf_FontCmap_TrimmedArray class.
 * @todo Write code for Zend_Pdf_FontCmap_SegmentedCoverage class.
 *
 * @package    Zend_Pdf
 * @subpackage Fonts
 * @copyright  Copyright (c) 2005-2007 Zend Technologies USA Inc. (http://www.zend.com)
 * @license    http://framework.zend.com/license/new-bsd     New BSD License
 */
abstract class Zend_Pdf_Cmap
{
  /**** Class Constants ****/
  /* Cmap Table Types */
    /**
     * Byte Encoding character map table type.
     */
    const TYPE_BYTE_ENCODING = 0x00;
    /**
     * High Byte Mapping character map table type.
     */
    const TYPE_HIGH_BYTE_MAPPING = 0x02;
    /**
     * Segment Value to Delta Mapping character map table type.
     */
    const TYPE_SEGMENT_TO_DELTA = 0x04;
    /**
     * Trimmed Table character map table type.
     */
    const TYPE_TRIMMED_TABLE = 0x06;
    /**
     * Mixed Coverage character map table type.
     */
    const TYPE_MIXED_COVERAGE = 0x08;
    /**
     * Trimmed Array character map table type.
     */
    const TYPE_TRIMMED_ARRAY = 0x0a;
    /**
     * Segmented Coverage character map table type.
     */
    const TYPE_SEGMENTED_COVERAGE = 0x0c;
    /**
     * Static Byte Encoding character map table type. Variant of
     * {@link TYPE_BYTEENCODING}.
     */
    const TYPE_BYTE_ENCODING_STATIC = 0xf1;
    /**
     * Unknown character map table type.
     */
    const TYPE_UNKNOWN = 0xff;
  /* Special Glyph Names */
    /**
     * Glyph representing missing characters.
     */
    const MISSING_CHARACTER_GLYPH = 0x00;
  /**** Public Interface ****/
  /* Factory Methods */
    /**
     * Instantiates the appropriate concrete subclass based on the type of cmap
     * table and returns the instance.
     *
     * The cmap type must be one of the following values:
     * 
     *  - {@link Zend_Pdf_Cmap::TYPE_BYTE_ENCODING}
     *  
 - {@link Zend_Pdf_Cmap::TYPE_BYTE_ENCODING_STATIC}
     *  
 - {@link Zend_Pdf_Cmap::TYPE_HIGH_BYTE_MAPPING}
     *  
 - {@link Zend_Pdf_Cmap::TYPE_SEGMENT_TO_DELTA}
     *  
 - {@link Zend_Pdf_Cmap::TYPE_TRIMMED_TABLE}
     *  
 - {@link Zend_Pdf_Cmap::TYPE_MIXED_COVERAGE}
     *  
 - {@link Zend_Pdf_Cmap::TYPE_TRIMMED_ARRAY}
     *  
 - {@link Zend_Pdf_Cmap::TYPE_SEGMENTED_COVERAGE}
     * 
 
     *
     * Throws an exception if the table type is invalid or the cmap table data
     * cannot be validated.
     *
     * @param integer $cmapType Type of cmap.
     * @param mixed $cmapData Cmap table data. Usually a string or array.
     * @return Zend_Pdf_Cmap
     * @throws Zend_Pdf_Exception
     */
    public static function cmapWithTypeData($cmapType, $cmapData)
    {
        switch ($cmapType) {
            case Zend_Pdf_Cmap::TYPE_BYTE_ENCODING:
                return new Zend_Pdf_Cmap_ByteEncoding($cmapData);
            case Zend_Pdf_Cmap::TYPE_BYTE_ENCODING_STATIC:
                return new Zend_Pdf_Cmap_ByteEncoding_Static($cmapData);
            case Zend_Pdf_Cmap::TYPE_HIGH_BYTE_MAPPING:
                throw new Zend_Pdf_Exception('High byte mapping cmap currently unsupported',
                                             Zend_Pdf_Exception::CMAP_TYPE_UNSUPPORTED);
            case Zend_Pdf_Cmap::TYPE_SEGMENT_TO_DELTA:
                return new Zend_Pdf_Cmap_SegmentToDelta($cmapData);
            case Zend_Pdf_Cmap::TYPE_TRIMMED_TABLE:
                return new Zend_Pdf_Cmap_TrimmedTable($cmapData);
            case Zend_Pdf_Cmap::TYPE_MIXED_COVERAGE:
                throw new Zend_Pdf_Exception('Mixed coverage cmap currently unsupported',
                                             Zend_Pdf_Exception::CMAP_TYPE_UNSUPPORTED);
            case Zend_Pdf_Cmap::TYPE_TRIMMED_ARRAY:
                throw new Zend_Pdf_Exception('Trimmed array cmap currently unsupported',
                                             Zend_Pdf_Exception::CMAP_TYPE_UNSUPPORTED);
            case Zend_Pdf_Cmap::TYPE_SEGMENTED_COVERAGE:
                throw new Zend_Pdf_Exception('Segmented coverage cmap currently unsupported',
                                             Zend_Pdf_Exception::CMAP_TYPE_UNSUPPORTED);
            default:
                throw new Zend_Pdf_Exception("Unknown cmap type: $cmapType",
                                             Zend_Pdf_Exception::CMAP_UNKNOWN_TYPE);
        }
    }
  /* Abstract Methods */
    /**
     * Object constructor
     *
     * Parses the raw binary table data. Throws an exception if the table is
     * malformed.
     *
     * @param string $cmapData Raw binary cmap table data.
     * @throws Zend_Pdf_Exception
     */
    abstract public function __construct($cmapData);
    /**
     * Returns an array of glyph numbers corresponding to the Unicode characters.
     *
     * If a particular character doesn't exist in this font, the special 'missing
     * character glyph' will be substituted.
     *
     * See also {@link glyphNumberForCharacter()}.
     *
     * @param array $characterCodes Array of Unicode character codes (code points).
     * @return array Array of glyph numbers.
     */
    abstract public function glyphNumbersForCharacters($characterCodes);
    /**
     * Returns the glyph number corresponding to the Unicode character.
     *
     * If a particular character doesn't exist in this font, the special 'missing
     * character glyph' will be substituted.
     *
     * See also {@link glyphNumbersForCharacters()} which is optimized for bulk
     * operations.
     *
     * @param integer $characterCode Unicode character code (code point).
     * @return integer Glyph number.
     */
    abstract public function glyphNumberForCharacter($characterCode);
    /**
     * Returns an array containing the Unicode characters that have entries in
     * this character map.
     *
     * @return array Unicode character codes.
     */
    abstract public function getCoveredCharacters();
  /**** Internal Methods ****/
  /* Internal Utility Methods */
    /**
     * Extracts a signed 2-byte integer from a string.
     *
     * Integers are always big-endian. Throws an exception if the index is out
     * of range.
     *
     * @param string &$data
     * @param integer $index Position in string of integer.
     * @return integer
     * @throws Zend_Pdf_Exception
     */
    protected function _extractInt2(&$data, $index)
    {
        if (($index < 0) | (($index + 1) > strlen($data))) {
            throw new Zend_Pdf_Exception("Index out of range: $index",
                                         Zend_Pdf_Exception::INDEX_OUT_OF_RANGE);
        }
        $number = ord($data[$index]);
        if (($number & 0x80) == 0x80) {    // negative
            $number = ~((((~ $number) & 0xff) << 8) | ((~ ord($data[++$index])) & 0xff));
        } else {
            $number = ($number << 8) | ord($data[++$index]);
        }
        return $number;
    }
    /**
     * Extracts an unsigned 2-byte integer from a string.
     *
     * Integers are always big-endian. Throws an exception if the index is out
     * of range.
     *
     * @param string &$data
     * @param integer $index Position in string of integer.
     * @return integer
     * @throws Zend_Pdf_Exception
     */
    protected function _extractUInt2(&$data, $index)
    {
        if (($index < 0) | (($index + 1) > strlen($data))) {
            throw new Zend_Pdf_Exception("Index out of range: $index",
                                         Zend_Pdf_Exception::INDEX_OUT_OF_RANGE);
        }
        $number = (ord($data[$index]) << 8) | ord($data[++$index]);
        return $number;
    }
    /**
     * Extracts an unsigned 4-byte integer from a string.
     *
     * Integers are always big-endian. Throws an exception if the index is out
     * of range.
     *
     * NOTE: If you ask for a 4-byte unsigned integer on a 32-bit machine, the
     * resulting value WILL BE SIGNED because PHP uses signed integers internally
     * for everything. To guarantee portability, be sure to use bitwise or
     * similar operators on large integers!
     *
     * @param string &$data
     * @param integer $index Position in string of integer.
     * @return integer
     * @throws Zend_Pdf_Exception
     */
    protected function _extractUInt4(&$data, $index)
    {
        if (($index < 0) | (($index + 3) > strlen($data))) {
            throw new Zend_Pdf_Exception("Index out of range: $index",
                                         Zend_Pdf_Exception::INDEX_OUT_OF_RANGE);
        }
        $number = (ord($data[$index]) << 24) | (ord($data[++$index]) << 16) |
                  (ord($data[++$index]) << 8) | ord($data[++$index]);
        return $number;
    }
}