_endianType)) { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("The unpackBytes function can only be used after the endianness of the file is known"); } switch($type) { case Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_BYTE: $format = 'C'; $unpacked = unpack($format, $bytes); return $unpacked[1]; break; case Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT: $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'v':'n'; $unpacked = unpack($format, $bytes); return $unpacked[1]; break; case Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG: $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'V':'N'; $unpacked = unpack($format, $bytes); return $unpacked[1]; break; case Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_RATIONAL: $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'V2':'N2'; $unpacked = unpack($format, $bytes); return ($unpacked[1]/$unpacked[2]); break; } } /** * Object constructor * * @param string $imageFileName * @throws Zend_Pdf_Exception */ public function __construct($imageFileName) { if (($imageFile = @fopen($imageFileName, 'rb')) === false ) { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception( "Can not open '$imageFileName' file for reading." ); } $byteOrderIndicator = fread($imageFile, 2); if($byteOrderIndicator == 'II') { $this->_endianType = Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE; } else if($byteOrderIndicator == 'MM') { $this->_endianType = Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_BIG; } else { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception( "Not a tiff file or Tiff corrupt. No byte order indication found" ); } $version = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2)); if($version != 42) { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception( "Not a tiff file or Tiff corrupt. Incorrect version number." ); } $ifdOffset = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, fread($imageFile, 4)); $fileStats = fstat($imageFile); $this->_fileSize = $fileStats['size']; /* * Tiff files are stored as a series of Image File Directories (IFD) each direcctory * has a specific number of entries each 12 bytes in length. At the end of the directories * is four bytes pointing to the offset of the next IFD. */ while($ifdOffset > 0) { if(fseek($imageFile, $ifdOffset, SEEK_SET) == -1 || $ifdOffset+2 >= $this->_fileSize) { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("Could not seek to the image file directory as indexed by the file. Likely cause is TIFF corruption. Offset: ". $ifdOffset); } $numDirEntries = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2)); /* * Since we now know how many entries are in this (IFD) we can extract the data. * The format of a TIFF directory entry is: * * 2 bytes (short) tag code; See TIFF_TAG constants at the top for supported values. (There are many more in the spec) * 2 bytes (short) field type * 4 bytes (long) number of values, or value count. * 4 bytes (mixed) data if the data will fit into 4 bytes or an offset if the data is too large. */ for($dirEntryIdx = 1; $dirEntryIdx <= $numDirEntries; $dirEntryIdx++) { $tag = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2)); $fieldType = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2)); $valueCount = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, fread($imageFile, 4)); switch($fieldType) { case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_BYTE: $fieldLength = $valueCount; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_ASCII: $fieldLength = $valueCount; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_SHORT: $fieldLength = $valueCount * 2; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_LONG: $fieldLength = $valueCount * 4; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_RATIONAL: $fieldLength = $valueCount * 8; break; default: $fieldLength = $valueCount; } $offsetBytes = fread($imageFile, 4); if($fieldLength <= 4) { switch($fieldType) { case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_BYTE: $value = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_BYTE, $offsetBytes); break; case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_ASCII: //Fall through to next case case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_LONG: $value = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, $offsetBytes); break; case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_SHORT: //Fall through to next case default: $value = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, $offsetBytes); } } else { $refOffset = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, $offsetBytes); } /* * Linear tag processing is probably not the best way to do this. I've processed the tags according to the * Tiff 6 specification and make some assumptions about when tags will be < 4 bytes and fit into $value and when * they will be > 4 bytes and require seek/extraction of the offset. Same goes for extracting arrays of data, like * the data offsets and length. This should be fixed in the future. */ switch($tag) { case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_IMAGE_WIDTH: $this->_width = $value; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_IMAGE_LENGTH: $this->_height = $value; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_BITS_PER_SAMPLE: if($valueCount>1) { $fp = ftell($imageFile); fseek($imageFile, $refOffset, SEEK_SET); $this->_bitsPerSample = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2)); fseek($imageFile, $fp, SEEK_SET); } else { $this->_bitsPerSample = $value; } break; case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_COMPRESSION: $this->_compression = $value; switch($value) { case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_UNCOMPRESSED: $this->_filter = 'None'; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_CCITT1D: //Fall through to next case case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_GROUP_3_FAX: //Fall through to next case case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_GROUP_4_FAX: $this->_filter = 'CCITTFaxDecode'; // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("CCITTFaxDecode Compression Mode Not Currently Supported"); break; case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_LZW: $this->_filter = 'LZWDecode'; // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("LZWDecode Compression Mode Not Currently Supported"); break; case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_JPEG: $this->_filter = 'DCTDecode'; //Should work, doesnt... // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("JPEG Compression Mode Not Currently Supported"); break; case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_FLATE: //fall through to next case case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_FLATE_OBSOLETE_CODE: $this->_filter = 'FlateDecode'; // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("ZIP/Flate Compression Mode Not Currently Supported"); break; case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_PACKBITS: $this->_filter = 'RunLengthDecode'; break; } break; case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_PHOTOMETRIC_INTERPRETATION: $this->_colorCode = $value; $this->_whiteIsZero = false; $this->_blackIsZero = false; switch($value) { case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO: $this->_whiteIsZero = true; $this->_colorSpace = 'DeviceGray'; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO: $this->_blackIsZero = true; $this->_colorSpace = 'DeviceGray'; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_YCBCR: //fall through to next case case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_RGB: $this->_colorSpace = 'DeviceRGB'; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_RGB_INDEXED: $this->_colorSpace = 'Indexed'; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_CMYK: $this->_colorSpace = 'DeviceCMYK'; break; case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_CIELAB: $this->_colorSpace = 'Lab'; break; default: // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception('TIFF: Unknown or Unsupported Color Type: '. $value); } break; case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_STRIP_OFFSETS: if($valueCount>1) { $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'V*':'N*'; $fp = ftell($imageFile); fseek($imageFile, $refOffset, SEEK_SET); $stripOffsetsBytes = fread($imageFile, $fieldLength); $this->_imageDataOffset = unpack($format, $stripOffsetsBytes); fseek($imageFile, $fp, SEEK_SET); } else { $this->_imageDataOffset = $value; } break; case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_STRIP_BYTE_COUNTS: if($valueCount>1) { $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'V*':'N*'; $fp = ftell($imageFile); fseek($imageFile, $refOffset, SEEK_SET); $stripByteCountsBytes = fread($imageFile, $fieldLength); $this->_imageDataLength = unpack($format, $stripByteCountsBytes); fseek($imageFile, $fp, SEEK_SET); } else { $this->_imageDataLength = $value; } break; default: //For debugging. It should be harmless to ignore unknown tags, though there is some good info in them. //echo "Unknown tag detected: ". $tag . " value: ". $value; } } $ifdOffset = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, fread($imageFile, 4)); } if(!isset($this->_imageDataOffset) || !isset($this->_imageDataLength)) { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("TIFF: The image processed did not contain image data as expected."); } $imageDataBytes = ''; if(is_array($this->_imageDataOffset)) { if(!is_array($this->_imageDataLength)) { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("TIFF: The image contained multiple data offsets but not multiple data lengths. Tiff may be corrupt."); } foreach($this->_imageDataOffset as $idx => $offset) { fseek($imageFile, $this->_imageDataOffset[$idx], SEEK_SET); $imageDataBytes .= fread($imageFile, $this->_imageDataLength[$idx]); } } else { fseek($imageFile, $this->_imageDataOffset, SEEK_SET); $imageDataBytes = fread($imageFile, $this->_imageDataLength); } if($imageDataBytes === '') { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("TIFF: No data. Image Corruption"); } fclose($imageFile); parent::__construct(); $imageDictionary = $this->_resource->dictionary; if(!isset($this->_width) || !isset($this->_width)) { // require_once 'Zend/Pdf/Exception.php'; throw new Zend_Pdf_Exception("Problem reading tiff file. Tiff is probably corrupt."); } $this->_imageProperties = array(); $this->_imageProperties['bitDepth'] = $this->_bitsPerSample; $this->_imageProperties['fileSize'] = $this->_fileSize; $this->_imageProperties['TIFFendianType'] = $this->_endianType; $this->_imageProperties['TIFFcompressionType'] = $this->_compression; $this->_imageProperties['TIFFwhiteIsZero'] = $this->_whiteIsZero; $this->_imageProperties['TIFFblackIsZero'] = $this->_blackIsZero; $this->_imageProperties['TIFFcolorCode'] = $this->_colorCode; $this->_imageProperties['TIFFimageDataOffset'] = $this->_imageDataOffset; $this->_imageProperties['TIFFimageDataLength'] = $this->_imageDataLength; $this->_imageProperties['PDFfilter'] = $this->_filter; $this->_imageProperties['PDFcolorSpace'] = $this->_colorSpace; $imageDictionary->Width = new Zend_Pdf_Element_Numeric($this->_width); if($this->_whiteIsZero === true) { $imageDictionary->Decode = new Zend_Pdf_Element_Array(array(new Zend_Pdf_Element_Numeric(1), new Zend_Pdf_Element_Numeric(0))); } $imageDictionary->Height = new Zend_Pdf_Element_Numeric($this->_height); $imageDictionary->ColorSpace = new Zend_Pdf_Element_Name($this->_colorSpace); $imageDictionary->BitsPerComponent = new Zend_Pdf_Element_Numeric($this->_bitsPerSample); if(isset($this->_filter) && $this->_filter != 'None') { $imageDictionary->Filter = new Zend_Pdf_Element_Name($this->_filter); } $this->_resource->value = $imageDataBytes; $this->_resource->skipFilters(); } /** * Image width (defined in Zend_Pdf_Resource_Image_Interface) */ public function getPixelWidth() { return $this->_width; } /** * Image height (defined in Zend_Pdf_Resource_Image_Interface) */ public function getPixelHeight() { return $this->_height; } /** * Image properties (defined in Zend_Pdf_Resource_Image_Interface) */ public function getProperties() { return $this->_imageProperties; } }