Example #1
0
 /**
  * Extract info from a JPEG file without using the GD library.
  * @param $file (string) image file to parse
  * @return array structure containing the image data
  * @public static
  */
 public static function _parsejpeg($file)
 {
     $a = getimagesize($file);
     if (empty($a)) {
         //Missing or incorrect image file
         return false;
     }
     if ($a[2] != 2) {
         // Not a JPEG file
         return false;
     }
     // bits per pixel
     $bpc = isset($a['bits']) ? intval($a['bits']) : 8;
     // number of image channels
     if (!isset($a['channels'])) {
         $channels = 3;
     } else {
         $channels = intval($a['channels']);
     }
     // default colour space
     switch ($channels) {
         case 1:
             $colspace = 'DeviceGray';
             break;
         case 3:
             $colspace = 'DeviceRGB';
             break;
         case 4:
             $colspace = 'DeviceCMYK';
             break;
         default:
             $channels = 3;
             $colspace = 'DeviceRGB';
             break;
     }
     // get file content
     $data = file_get_contents($file);
     // check for embedded ICC profile
     $icc = array();
     $offset = 0;
     while (($pos = strpos($data, "ICC_PROFILE", $offset)) !== false) {
         // get ICC sequence length
         $length = TCPDF_STATIC::_getUSHORT($data, $pos - 2) - 16;
         // marker sequence number
         $msn = max(1, ord($data[$pos + 12]));
         // number of markers (total of APP2 used)
         $nom = max(1, ord($data[$pos + 13]));
         // get sequence segment
         $icc[$msn - 1] = substr($data, $pos + 14, $length);
         // move forward to next sequence
         $offset = $pos + 14 + $length;
     }
     // order and compact ICC segments
     if (count($icc) > 0) {
         ksort($icc);
         $icc = implode('', $icc);
         if (ord($icc[36]) != 0x61 or ord($icc[37]) != 0x63 or ord($icc[38]) != 0x73 or ord($icc[39]) != 0x70) {
             // invalid ICC profile
             $icc = false;
         }
     } else {
         $icc = false;
     }
     return array('w' => $a[0], 'h' => $a[1], 'ch' => $channels, 'icc' => $icc, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'DCTDecode', 'data' => $data);
 }
	/**
	 * Returns a subset of the TrueType font data without the unused glyphs.
	 * @param $font (string) TrueType font data.
	 * @param $subsetchars (array) Array of used characters (the glyphs to keep).
	 * @return (string) A subset of TrueType font data without the unused glyphs.
	 * @author Nicola Asuni
	 * @since 5.2.000 (2010-06-02)
	 * @public static
	 */
	public static function _getTrueTypeFontSubset($font, $subsetchars) {
		ksort($subsetchars);
		$offset = 0; // offset position of the font data
		if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) {
			// sfnt version must be 0x00010000 for TrueType version 1.0.
			return $font;
		}
		$offset += 4;
		// get number of tables
		$numTables = TCPDF_STATIC::_getUSHORT($font, $offset);
		$offset += 2;
		// skip searchRange, entrySelector and rangeShift
		$offset += 6;
		// tables array
		$table = array();
		// for each table
		for ($i = 0; $i < $numTables; ++$i) {
			// get table info
			$tag = substr($font, $offset, 4);
			$offset += 4;
			$table[$tag] = array();
			$table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset);
			$offset += 4;
			$table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
			$offset += 4;
			$table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset);
			$offset += 4;
		}
		// check magicNumber
		$offset = $table['head']['offset'] + 12;
		if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) {
			// magicNumber must be 0x5F0F3CF5
			return $font;
		}
		$offset += 4;
		// get offset mode (indexToLocFormat : 0 = short, 1 = long)
		$offset = $table['head']['offset'] + 50;
		$short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0);
		$offset += 2;
		// get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table
		$indexToLoc = array();
		$offset = $table['loca']['offset'];
		if ($short_offset) {
			// short version
			$tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1
			for ($i = 0; $i < $tot_num_glyphs; ++$i) {
				$indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2;
				$offset += 2;
			}
		} else {
			// long version
			$tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1
			for ($i = 0; $i < $tot_num_glyphs; ++$i) {
				$indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset);
				$offset += 4;
			}
		}
		// get glyphs indexes of chars from cmap table
		$subsetglyphs = array(); // glyph IDs on key
		$subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0
		$offset = $table['cmap']['offset'] + 2;
		$numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset);
		$offset += 2;
		$encodingTables = array();
		for ($i = 0; $i < $numEncodingTables; ++$i) {
			$encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
			$offset += 2;
			$encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset);
			$offset += 2;
			$encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset);
			$offset += 4;
		}
		foreach ($encodingTables as $enctable) {
			// get all platforms and encodings
			$offset = $table['cmap']['offset'] + $enctable['offset'];
			$format = TCPDF_STATIC::_getUSHORT($font, $offset);
			$offset += 2;
			switch ($format) {
				case 0: { // Format 0: Byte encoding table
					$offset += 4; // skip length and version/language
					for ($c = 0; $c < 256; ++$c) {
						if (isset($subsetchars[$c])) {
							$g = TCPDF_STATIC::_getBYTE($font, $offset);
							$subsetglyphs[$g] = true;
						}
						++$offset;
					}
					break;
				}
				case 2: { // Format 2: High-byte mapping through table
					$offset += 4; // skip length and version/language
					$numSubHeaders = 0;
					for ($i = 0; $i < 256; ++$i) {
						// Array that maps high bytes to subHeaders: value is subHeader index * 8.
						$subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8);
						$offset += 2;
						if ($numSubHeaders < $subHeaderKeys[$i]) {
							$numSubHeaders = $subHeaderKeys[$i];
						}
					}
					// the number of subHeaders is equal to the max of subHeaderKeys + 1
					++$numSubHeaders;
					// read subHeader structures
					$subHeaders = array();
					$numGlyphIndexArray = 0;
					for ($k = 0; $k < $numSubHeaders; ++$k) {
						$subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
						$subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
						$subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
						$subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
						$subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8));
						$subHeaders[$k]['idRangeOffset'] /= 2;
						$numGlyphIndexArray += $subHeaders[$k]['entryCount'];
					}
					for ($k = 0; $k < $numGlyphIndexArray; ++$k) {
						$glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
					}
					for ($i = 0; $i < 256; ++$i) {
						$k = $subHeaderKeys[$i];
						if ($k == 0) {
							// one byte code
							$c = $i;
							if (isset($subsetchars[$c])) {
								$g = $glyphIndexArray[0];
								$subsetglyphs[$g] = true;
							}
						} else {
							// two bytes code
							$start_byte = $subHeaders[$k]['firstCode'];
							$end_byte = $start_byte + $subHeaders[$k]['entryCount'];
							for ($j = $start_byte; $j < $end_byte; ++$j) {
								// combine high and low bytes
								$c = (($i << 8) + $j);
								if (isset($subsetchars[$c])) {
									$idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']);
									$g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536;
									if ($g < 0) {
										$g = 0;
									}
									$subsetglyphs[$g] = true;
								}
							}
						}
					}
					break;
				}
				case 4: { // Format 4: Segment mapping to delta values
					$length = TCPDF_STATIC::_getUSHORT($font, $offset);
					$offset += 2;
					$offset += 2; // skip version/language
					$segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2);
					$offset += 2;
					$offset += 6; // skip searchRange, entrySelector, rangeShift
					$endCount = array(); // array of end character codes for each segment
					for ($k = 0; $k < $segCount; ++$k) {
						$endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
					}
					$offset += 2; // skip reservedPad
					$startCount = array(); // array of start character codes for each segment
					for ($k = 0; $k < $segCount; ++$k) {
						$startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
					}
					$idDelta = array(); // delta for all character codes in segment
					for ($k = 0; $k < $segCount; ++$k) {
						$idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
					}
					$idRangeOffset = array(); // Offsets into glyphIdArray or 0
					for ($k = 0; $k < $segCount; ++$k) {
						$idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
					}
					$gidlen = (floor($length / 2) - 8 - (4 * $segCount));
					$glyphIdArray = array(); // glyph index array
					for ($k = 0; $k < $gidlen; ++$k) {
						$glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset);
						$offset += 2;
					}
					for ($k = 0; $k < $segCount; ++$k) {
						for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) {
							if (isset($subsetchars[$c])) {
								if ($idRangeOffset[$k] == 0) {
									$g = ($idDelta[$k] + $c) % 65536;
								} else {
									$gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k));
									$g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536;
								}
								if ($g < 0) {
									$g = 0;
								}
								$subsetglyphs[$g] = true;
							}
						}
					}	
					break;
				}
				case 6: { // Format 6: Trimmed table mapping
					$offset += 4; // skip length and version/language
					$firstCode = TCPDF_STATIC::_getUSHORT($font, $offset);
					$offset += 2;
					$entryCount = TCPDF_STATIC::_getUSHORT($font, $offset);
					$offset += 2;
					for ($k = 0; $k < $entryCount; ++$k) {
						$c = ($k + $firstCode);
						if (isset($subsetchars[$c])) {
							$g = TCPDF_STATIC::_getUSHORT($font, $offset);
							$subsetglyphs[$g] = true;
						}
						$offset += 2;
					}
					break;
				}
				case 8: { // Format 8: Mixed 16-bit and 32-bit coverage
					$offset += 10; // skip reserved, length and version/language
					for ($k = 0; $k < 8192; ++$k) {
						$is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset);
						++$offset;
					}
					$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
					$offset += 4;
					for ($i = 0; $i < $nGroups; ++$i) {
						$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
						$offset += 4;
						$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
						$offset += 4;
						$startGlyphID = TCPDF_STATIC::_getULONG($font, $offset);
						$offset += 4;
						for ($k = $startCharCode; $k <= $endCharCode; ++$k) {
							$is32idx = floor($c / 8);
							if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) {
								$c = $k;
							} else {
								// 32 bit format
								// convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4)
								//LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232
								//SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888
								$c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888;
							}
							if (isset($subsetchars[$c])) {
								$subsetglyphs[$startGlyphID] = true;
							}
							++$startGlyphID;
						}
					}
					break;
				}
				case 10: { // Format 10: Trimmed array
					$offset += 10; // skip reserved, length and version/language
					$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
					$offset += 4;
					$numChars = TCPDF_STATIC::_getULONG($font, $offset);
					$offset += 4;
					for ($k = 0; $k < $numChars; ++$k) {
						$c = ($k + $startCharCode);
						if (isset($subsetchars[$c])) {
							$g = TCPDF_STATIC::_getUSHORT($font, $offset);
							$subsetglyphs[$g] = true;
						}
						$offset += 2;
					}
					break;
				}
				case 12: { // Format 12: Segmented coverage
					$offset += 10; // skip length and version/language
					$nGroups = TCPDF_STATIC::_getULONG($font, $offset);
					$offset += 4;
					for ($k = 0; $k < $nGroups; ++$k) {
						$startCharCode = TCPDF_STATIC::_getULONG($font, $offset);
						$offset += 4;
						$endCharCode = TCPDF_STATIC::_getULONG($font, $offset);
						$offset += 4;
						$startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset);
						$offset += 4;
						for ($c = $startCharCode; $c <= $endCharCode; ++$c) {
							if (isset($subsetchars[$c])) {
								$subsetglyphs[$startGlyphCode] = true;
							}
							++$startGlyphCode;
						}
					}
					break;
				}
				case 13: { // Format 13: Many-to-one range mappings
					// to be implemented ...
					break;
				}
				case 14: { // Format 14: Unicode Variation Sequences
					// to be implemented ...
					break;
				}
			}
		}
		// include all parts of composite glyphs
		$new_sga = $subsetglyphs;
		while (!empty($new_sga)) {
			$sga = $new_sga;
			$new_sga = array();
			foreach ($sga as $key => $val) {
				if (isset($indexToLoc[$key])) {
					$offset = ($table['glyf']['offset'] + $indexToLoc[$key]);
					$numberOfContours = TCPDF_STATIC::_getSHORT($font, $offset);
					$offset += 2;
					if ($numberOfContours < 0) { // composite glyph
						$offset += 8; // skip xMin, yMin, xMax, yMax
						do {
							$flags = TCPDF_STATIC::_getUSHORT($font, $offset);
							$offset += 2;
							$glyphIndex = TCPDF_STATIC::_getUSHORT($font, $offset);
							$offset += 2;
							if (!isset($subsetglyphs[$glyphIndex])) {
								// add missing glyphs
								$new_sga[$glyphIndex] = true;
							}
							// skip some bytes by case
							if ($flags & 1) {
								$offset += 4;
							} else {
								$offset += 2;
							}
							if ($flags & 8) {
								$offset += 2;
							} elseif ($flags & 64) {
								$offset += 4;
							} elseif ($flags & 128) {
								$offset += 8;
							}
						} while ($flags & 32);
					}
				}
			}
			$subsetglyphs += $new_sga;
		}
		// sort glyphs by key (and remove duplicates)
		ksort($subsetglyphs);
		// build new glyf and loca tables
		$glyf = '';
		$loca = '';
		$offset = 0;
		$glyf_offset = $table['glyf']['offset'];
		for ($i = 0; $i < $tot_num_glyphs; ++$i) {
			if (isset($subsetglyphs[$i])) {
				$length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]);
				$glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length);
			} else {
				$length = 0;
			}
			if ($short_offset) {
				$loca .= pack('n', floor($offset / 2));
			} else {
				$loca .= pack('N', $offset);
			}
			$offset += $length;
		}
		// array of table names to preserve (loca and glyf tables will be added later)
		// the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately
		$table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names
		// get the tables to preserve
		$offset = 12;
		foreach ($table as $tag => $val) {
			if (in_array($tag, $table_names)) {
				$table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']);
				if ($tag == 'head') {
					// set the checkSumAdjustment to 0
					$table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12);
				}
				$pad = 4 - ($table[$tag]['length'] % 4);
				if ($pad != 4) {
					// the length of a table must be a multiple of four bytes
					$table[$tag]['length'] += $pad;
					$table[$tag]['data'] .= str_repeat("\x0", $pad);
				}
				$table[$tag]['offset'] = $offset;
				$offset += $table[$tag]['length'];
				// check sum is not changed (so keep the following line commented)
				//$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']);
			} else {
				unset($table[$tag]);
			}
		}
		// add loca
		$table['loca']['data'] = $loca;
		$table['loca']['length'] = strlen($loca);
		$pad = 4 - ($table['loca']['length'] % 4);
		if ($pad != 4) {
			// the length of a table must be a multiple of four bytes
			$table['loca']['length'] += $pad;
			$table['loca']['data'] .= str_repeat("\x0", $pad);
		}
		$table['loca']['offset'] = $offset;
		$table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
		$offset += $table['loca']['length'];
		// add glyf
		$table['glyf']['data'] = $glyf;
		$table['glyf']['length'] = strlen($glyf);
		$pad = 4 - ($table['glyf']['length'] % 4);
		if ($pad != 4) {
			// the length of a table must be a multiple of four bytes
			$table['glyf']['length'] += $pad;
			$table['glyf']['data'] .= str_repeat("\x0", $pad);
		}
		$table['glyf']['offset'] = $offset;
		$table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
		// rebuild font
		$font = '';
		$font .= pack('N', 0x10000); // sfnt version
		$numTables = count($table);
		$font .= pack('n', $numTables); // numTables
		$entrySelector = floor(log($numTables, 2));
		$searchRange = pow(2, $entrySelector) * 16;
		$rangeShift = ($numTables * 16) - $searchRange;
		$font .= pack('n', $searchRange); // searchRange
		$font .= pack('n', $entrySelector); // entrySelector
		$font .= pack('n', $rangeShift); // rangeShift
		$offset = ($numTables * 16);
		foreach ($table as $tag => $data) {
			$font .= $tag; // tag
			$font .= pack('N', $data['checkSum']); // checkSum
			$font .= pack('N', ($data['offset'] + $offset)); // offset
			$font .= pack('N', $data['length']); // length
		}
		foreach ($table as $data) {
			$font .= $data['data'];
		}
		// set checkSumAdjustment on head table
		$checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font));
		$font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12);
		return $font;
	}