You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

442 lines
21 KiB

11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
11 years ago
  1. <?php
  2. /**
  3. * Zend Framework
  4. *
  5. * LICENSE
  6. *
  7. * This source file is subject to the new BSD license that is bundled
  8. * with this package in the file LICENSE.txt.
  9. * It is also available through the world-wide-web at this URL:
  10. * http://framework.zend.com/license/new-bsd
  11. * If you did not receive a copy of the license and are unable to
  12. * obtain it through the world-wide-web, please send an email
  13. * to license@zend.com so we can send you a copy immediately.
  14. *
  15. * @category Zend
  16. * @package Zend_Pdf
  17. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  18. * @license http://framework.zend.com/license/new-bsd New BSD License
  19. * @version $Id$
  20. */
  21. /** Internally used classes */
  22. // require_once 'Zend/Pdf/Element/Array.php';
  23. // require_once 'Zend/Pdf/Element/Name.php';
  24. // require_once 'Zend/Pdf/Element/Numeric.php';
  25. /** Zend_Pdf_Resource_Image */
  26. // require_once 'Zend/Pdf/Resource/Image.php';
  27. /**
  28. * TIFF image
  29. *
  30. * @package Zend_Pdf
  31. * @copyright Copyright (c) 2005-2014 Zend Technologies USA Inc. (http://www.zend.com)
  32. * @license http://framework.zend.com/license/new-bsd New BSD License
  33. */
  34. class Zend_Pdf_Resource_Image_Tiff extends Zend_Pdf_Resource_Image
  35. {
  36. const TIFF_FIELD_TYPE_BYTE=1;
  37. const TIFF_FIELD_TYPE_ASCII=2;
  38. const TIFF_FIELD_TYPE_SHORT=3;
  39. const TIFF_FIELD_TYPE_LONG=4;
  40. const TIFF_FIELD_TYPE_RATIONAL=5;
  41. const TIFF_TAG_IMAGE_WIDTH=256;
  42. const TIFF_TAG_IMAGE_LENGTH=257; //Height
  43. const TIFF_TAG_BITS_PER_SAMPLE=258;
  44. const TIFF_TAG_COMPRESSION=259;
  45. const TIFF_TAG_PHOTOMETRIC_INTERPRETATION=262;
  46. const TIFF_TAG_STRIP_OFFSETS=273;
  47. const TIFF_TAG_SAMPLES_PER_PIXEL=277;
  48. const TIFF_TAG_STRIP_BYTE_COUNTS=279;
  49. const TIFF_COMPRESSION_UNCOMPRESSED = 1;
  50. const TIFF_COMPRESSION_CCITT1D = 2;
  51. const TIFF_COMPRESSION_GROUP_3_FAX = 3;
  52. const TIFF_COMPRESSION_GROUP_4_FAX = 4;
  53. const TIFF_COMPRESSION_LZW = 5;
  54. const TIFF_COMPRESSION_JPEG = 6;
  55. const TIFF_COMPRESSION_FLATE = 8;
  56. const TIFF_COMPRESSION_FLATE_OBSOLETE_CODE = 32946;
  57. const TIFF_COMPRESSION_PACKBITS = 32773;
  58. const TIFF_PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO=0;
  59. const TIFF_PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO=1;
  60. const TIFF_PHOTOMETRIC_INTERPRETATION_RGB=2;
  61. const TIFF_PHOTOMETRIC_INTERPRETATION_RGB_INDEXED=3;
  62. const TIFF_PHOTOMETRIC_INTERPRETATION_CMYK=5;
  63. const TIFF_PHOTOMETRIC_INTERPRETATION_YCBCR=6;
  64. const TIFF_PHOTOMETRIC_INTERPRETATION_CIELAB=8;
  65. protected $_width;
  66. protected $_height;
  67. protected $_imageProperties;
  68. protected $_endianType;
  69. protected $_fileSize;
  70. protected $_bitsPerSample;
  71. protected $_compression;
  72. protected $_filter;
  73. protected $_colorCode;
  74. protected $_whiteIsZero;
  75. protected $_blackIsZero;
  76. protected $_colorSpace;
  77. protected $_imageDataOffset;
  78. protected $_imageDataLength;
  79. const TIFF_ENDIAN_BIG=0;
  80. const TIFF_ENDIAN_LITTLE=1;
  81. const UNPACK_TYPE_BYTE=0;
  82. const UNPACK_TYPE_SHORT=1;
  83. const UNPACK_TYPE_LONG=2;
  84. const UNPACK_TYPE_RATIONAL=3;
  85. /**
  86. * Byte unpacking function
  87. *
  88. * Makes it possible to unpack bytes in one statement for enhanced logic readability.
  89. *
  90. * @param int $type
  91. * @param string $bytes
  92. * @throws Zend_Pdf_Exception
  93. */
  94. protected function unpackBytes($type, $bytes) {
  95. if(!isset($this->_endianType)) {
  96. // require_once 'Zend/Pdf/Exception.php';
  97. throw new Zend_Pdf_Exception("The unpackBytes function can only be used after the endianness of the file is known");
  98. }
  99. switch($type) {
  100. case Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_BYTE:
  101. $format = 'C';
  102. $unpacked = unpack($format, $bytes);
  103. return $unpacked[1];
  104. break;
  105. case Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT:
  106. $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'v':'n';
  107. $unpacked = unpack($format, $bytes);
  108. return $unpacked[1];
  109. break;
  110. case Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG:
  111. $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'V':'N';
  112. $unpacked = unpack($format, $bytes);
  113. return $unpacked[1];
  114. break;
  115. case Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_RATIONAL:
  116. $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'V2':'N2';
  117. $unpacked = unpack($format, $bytes);
  118. return ($unpacked[1]/$unpacked[2]);
  119. break;
  120. }
  121. }
  122. /**
  123. * Object constructor
  124. *
  125. * @param string $imageFileName
  126. * @throws Zend_Pdf_Exception
  127. */
  128. public function __construct($imageFileName)
  129. {
  130. if (($imageFile = @fopen($imageFileName, 'rb')) === false ) {
  131. // require_once 'Zend/Pdf/Exception.php';
  132. throw new Zend_Pdf_Exception( "Can not open '$imageFileName' file for reading." );
  133. }
  134. $byteOrderIndicator = fread($imageFile, 2);
  135. if($byteOrderIndicator == 'II') {
  136. $this->_endianType = Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE;
  137. } else if($byteOrderIndicator == 'MM') {
  138. $this->_endianType = Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_BIG;
  139. } else {
  140. // require_once 'Zend/Pdf/Exception.php';
  141. throw new Zend_Pdf_Exception( "Not a tiff file or Tiff corrupt. No byte order indication found" );
  142. }
  143. $version = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2));
  144. if($version != 42) {
  145. // require_once 'Zend/Pdf/Exception.php';
  146. throw new Zend_Pdf_Exception( "Not a tiff file or Tiff corrupt. Incorrect version number." );
  147. }
  148. $ifdOffset = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, fread($imageFile, 4));
  149. $fileStats = fstat($imageFile);
  150. $this->_fileSize = $fileStats['size'];
  151. /*
  152. * Tiff files are stored as a series of Image File Directories (IFD) each direcctory
  153. * has a specific number of entries each 12 bytes in length. At the end of the directories
  154. * is four bytes pointing to the offset of the next IFD.
  155. */
  156. while($ifdOffset > 0) {
  157. if(fseek($imageFile, $ifdOffset, SEEK_SET) == -1 || $ifdOffset+2 >= $this->_fileSize) {
  158. // require_once 'Zend/Pdf/Exception.php';
  159. 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);
  160. }
  161. $numDirEntries = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2));
  162. /*
  163. * Since we now know how many entries are in this (IFD) we can extract the data.
  164. * The format of a TIFF directory entry is:
  165. *
  166. * 2 bytes (short) tag code; See TIFF_TAG constants at the top for supported values. (There are many more in the spec)
  167. * 2 bytes (short) field type
  168. * 4 bytes (long) number of values, or value count.
  169. * 4 bytes (mixed) data if the data will fit into 4 bytes or an offset if the data is too large.
  170. */
  171. for($dirEntryIdx = 1; $dirEntryIdx <= $numDirEntries; $dirEntryIdx++) {
  172. $tag = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2));
  173. $fieldType = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2));
  174. $valueCount = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, fread($imageFile, 4));
  175. switch($fieldType) {
  176. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_BYTE:
  177. $fieldLength = $valueCount;
  178. break;
  179. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_ASCII:
  180. $fieldLength = $valueCount;
  181. break;
  182. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_SHORT:
  183. $fieldLength = $valueCount * 2;
  184. break;
  185. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_LONG:
  186. $fieldLength = $valueCount * 4;
  187. break;
  188. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_RATIONAL:
  189. $fieldLength = $valueCount * 8;
  190. break;
  191. default:
  192. $fieldLength = $valueCount;
  193. }
  194. $offsetBytes = fread($imageFile, 4);
  195. if($fieldLength <= 4) {
  196. switch($fieldType) {
  197. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_BYTE:
  198. $value = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_BYTE, $offsetBytes);
  199. break;
  200. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_ASCII:
  201. //Fall through to next case
  202. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_LONG:
  203. $value = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, $offsetBytes);
  204. break;
  205. case Zend_Pdf_Resource_Image_Tiff::TIFF_FIELD_TYPE_SHORT:
  206. //Fall through to next case
  207. default:
  208. $value = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, $offsetBytes);
  209. }
  210. } else {
  211. $refOffset = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, $offsetBytes);
  212. }
  213. /*
  214. * Linear tag processing is probably not the best way to do this. I've processed the tags according to the
  215. * Tiff 6 specification and make some assumptions about when tags will be < 4 bytes and fit into $value and when
  216. * they will be > 4 bytes and require seek/extraction of the offset. Same goes for extracting arrays of data, like
  217. * the data offsets and length. This should be fixed in the future.
  218. */
  219. switch($tag) {
  220. case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_IMAGE_WIDTH:
  221. $this->_width = $value;
  222. break;
  223. case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_IMAGE_LENGTH:
  224. $this->_height = $value;
  225. break;
  226. case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_BITS_PER_SAMPLE:
  227. if($valueCount>1) {
  228. $fp = ftell($imageFile);
  229. fseek($imageFile, $refOffset, SEEK_SET);
  230. $this->_bitsPerSample = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_SHORT, fread($imageFile, 2));
  231. fseek($imageFile, $fp, SEEK_SET);
  232. } else {
  233. $this->_bitsPerSample = $value;
  234. }
  235. break;
  236. case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_COMPRESSION:
  237. $this->_compression = $value;
  238. switch($value) {
  239. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_UNCOMPRESSED:
  240. $this->_filter = 'None';
  241. break;
  242. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_CCITT1D:
  243. //Fall through to next case
  244. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_GROUP_3_FAX:
  245. //Fall through to next case
  246. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_GROUP_4_FAX:
  247. $this->_filter = 'CCITTFaxDecode';
  248. // require_once 'Zend/Pdf/Exception.php';
  249. throw new Zend_Pdf_Exception("CCITTFaxDecode Compression Mode Not Currently Supported");
  250. break;
  251. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_LZW:
  252. $this->_filter = 'LZWDecode';
  253. // require_once 'Zend/Pdf/Exception.php';
  254. throw new Zend_Pdf_Exception("LZWDecode Compression Mode Not Currently Supported");
  255. break;
  256. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_JPEG:
  257. $this->_filter = 'DCTDecode'; //Should work, doesnt...
  258. // require_once 'Zend/Pdf/Exception.php';
  259. throw new Zend_Pdf_Exception("JPEG Compression Mode Not Currently Supported");
  260. break;
  261. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_FLATE:
  262. //fall through to next case
  263. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_FLATE_OBSOLETE_CODE:
  264. $this->_filter = 'FlateDecode';
  265. // require_once 'Zend/Pdf/Exception.php';
  266. throw new Zend_Pdf_Exception("ZIP/Flate Compression Mode Not Currently Supported");
  267. break;
  268. case Zend_Pdf_Resource_Image_Tiff::TIFF_COMPRESSION_PACKBITS:
  269. $this->_filter = 'RunLengthDecode';
  270. break;
  271. }
  272. break;
  273. case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_PHOTOMETRIC_INTERPRETATION:
  274. $this->_colorCode = $value;
  275. $this->_whiteIsZero = false;
  276. $this->_blackIsZero = false;
  277. switch($value) {
  278. case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_WHITE_IS_ZERO:
  279. $this->_whiteIsZero = true;
  280. $this->_colorSpace = 'DeviceGray';
  281. break;
  282. case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_BLACK_IS_ZERO:
  283. $this->_blackIsZero = true;
  284. $this->_colorSpace = 'DeviceGray';
  285. break;
  286. case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_YCBCR:
  287. //fall through to next case
  288. case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_RGB:
  289. $this->_colorSpace = 'DeviceRGB';
  290. break;
  291. case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_RGB_INDEXED:
  292. $this->_colorSpace = 'Indexed';
  293. break;
  294. case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_CMYK:
  295. $this->_colorSpace = 'DeviceCMYK';
  296. break;
  297. case Zend_Pdf_Resource_Image_Tiff::TIFF_PHOTOMETRIC_INTERPRETATION_CIELAB:
  298. $this->_colorSpace = 'Lab';
  299. break;
  300. default:
  301. // require_once 'Zend/Pdf/Exception.php';
  302. throw new Zend_Pdf_Exception('TIFF: Unknown or Unsupported Color Type: '. $value);
  303. }
  304. break;
  305. case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_STRIP_OFFSETS:
  306. if($valueCount>1) {
  307. $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'V*':'N*';
  308. $fp = ftell($imageFile);
  309. fseek($imageFile, $refOffset, SEEK_SET);
  310. $stripOffsetsBytes = fread($imageFile, $fieldLength);
  311. $this->_imageDataOffset = unpack($format, $stripOffsetsBytes);
  312. fseek($imageFile, $fp, SEEK_SET);
  313. } else {
  314. $this->_imageDataOffset = $value;
  315. }
  316. break;
  317. case Zend_Pdf_Resource_Image_Tiff::TIFF_TAG_STRIP_BYTE_COUNTS:
  318. if($valueCount>1) {
  319. $format = ($this->_endianType == Zend_Pdf_Resource_Image_Tiff::TIFF_ENDIAN_LITTLE)?'V*':'N*';
  320. $fp = ftell($imageFile);
  321. fseek($imageFile, $refOffset, SEEK_SET);
  322. $stripByteCountsBytes = fread($imageFile, $fieldLength);
  323. $this->_imageDataLength = unpack($format, $stripByteCountsBytes);
  324. fseek($imageFile, $fp, SEEK_SET);
  325. } else {
  326. $this->_imageDataLength = $value;
  327. }
  328. break;
  329. default:
  330. //For debugging. It should be harmless to ignore unknown tags, though there is some good info in them.
  331. //echo "Unknown tag detected: ". $tag . " value: ". $value;
  332. }
  333. }
  334. $ifdOffset = $this->unpackBytes(Zend_Pdf_Resource_Image_Tiff::UNPACK_TYPE_LONG, fread($imageFile, 4));
  335. }
  336. if(!isset($this->_imageDataOffset) || !isset($this->_imageDataLength)) {
  337. // require_once 'Zend/Pdf/Exception.php';
  338. throw new Zend_Pdf_Exception("TIFF: The image processed did not contain image data as expected.");
  339. }
  340. $imageDataBytes = '';
  341. if(is_array($this->_imageDataOffset)) {
  342. if(!is_array($this->_imageDataLength)) {
  343. // require_once 'Zend/Pdf/Exception.php';
  344. throw new Zend_Pdf_Exception("TIFF: The image contained multiple data offsets but not multiple data lengths. Tiff may be corrupt.");
  345. }
  346. foreach($this->_imageDataOffset as $idx => $offset) {
  347. fseek($imageFile, $this->_imageDataOffset[$idx], SEEK_SET);
  348. $imageDataBytes .= fread($imageFile, $this->_imageDataLength[$idx]);
  349. }
  350. } else {
  351. fseek($imageFile, $this->_imageDataOffset, SEEK_SET);
  352. $imageDataBytes = fread($imageFile, $this->_imageDataLength);
  353. }
  354. if($imageDataBytes === '') {
  355. // require_once 'Zend/Pdf/Exception.php';
  356. throw new Zend_Pdf_Exception("TIFF: No data. Image Corruption");
  357. }
  358. fclose($imageFile);
  359. parent::__construct();
  360. $imageDictionary = $this->_resource->dictionary;
  361. if(!isset($this->_width) || !isset($this->_width)) {
  362. // require_once 'Zend/Pdf/Exception.php';
  363. throw new Zend_Pdf_Exception("Problem reading tiff file. Tiff is probably corrupt.");
  364. }
  365. $this->_imageProperties = array();
  366. $this->_imageProperties['bitDepth'] = $this->_bitsPerSample;
  367. $this->_imageProperties['fileSize'] = $this->_fileSize;
  368. $this->_imageProperties['TIFFendianType'] = $this->_endianType;
  369. $this->_imageProperties['TIFFcompressionType'] = $this->_compression;
  370. $this->_imageProperties['TIFFwhiteIsZero'] = $this->_whiteIsZero;
  371. $this->_imageProperties['TIFFblackIsZero'] = $this->_blackIsZero;
  372. $this->_imageProperties['TIFFcolorCode'] = $this->_colorCode;
  373. $this->_imageProperties['TIFFimageDataOffset'] = $this->_imageDataOffset;
  374. $this->_imageProperties['TIFFimageDataLength'] = $this->_imageDataLength;
  375. $this->_imageProperties['PDFfilter'] = $this->_filter;
  376. $this->_imageProperties['PDFcolorSpace'] = $this->_colorSpace;
  377. $imageDictionary->Width = new Zend_Pdf_Element_Numeric($this->_width);
  378. if($this->_whiteIsZero === true) {
  379. $imageDictionary->Decode = new Zend_Pdf_Element_Array(array(new Zend_Pdf_Element_Numeric(1), new Zend_Pdf_Element_Numeric(0)));
  380. }
  381. $imageDictionary->Height = new Zend_Pdf_Element_Numeric($this->_height);
  382. $imageDictionary->ColorSpace = new Zend_Pdf_Element_Name($this->_colorSpace);
  383. $imageDictionary->BitsPerComponent = new Zend_Pdf_Element_Numeric($this->_bitsPerSample);
  384. if(isset($this->_filter) && $this->_filter != 'None') {
  385. $imageDictionary->Filter = new Zend_Pdf_Element_Name($this->_filter);
  386. }
  387. $this->_resource->value = $imageDataBytes;
  388. $this->_resource->skipFilters();
  389. }
  390. /**
  391. * Image width (defined in Zend_Pdf_Resource_Image_Interface)
  392. */
  393. public function getPixelWidth() {
  394. return $this->_width;
  395. }
  396. /**
  397. * Image height (defined in Zend_Pdf_Resource_Image_Interface)
  398. */
  399. public function getPixelHeight() {
  400. return $this->_height;
  401. }
  402. /**
  403. * Image properties (defined in Zend_Pdf_Resource_Image_Interface)
  404. */
  405. public function getProperties() {
  406. return $this->_imageProperties;
  407. }
  408. }