See Release Notes
Long Term Support Release
Differences Between: [Versions 310 and 401] [Versions 311 and 401] [Versions 39 and 401] [Versions 400 and 401]
1 <?php 2 //============================================================+ 3 // File name : tcpdf_fonts.php 4 // Version : 1.1.0 5 // Begin : 2008-01-01 6 // Last Update : 2014-12-10 7 // Author : Nicola Asuni - Tecnick.com LTD - www.tecnick.com - info@tecnick.com 8 // License : GNU-LGPL v3 (http://www.gnu.org/copyleft/lesser.html) 9 // ------------------------------------------------------------------- 10 // Copyright (C) 2008-2014 Nicola Asuni - Tecnick.com LTD 11 // 12 // This file is part of TCPDF software library. 13 // 14 // TCPDF is free software: you can redistribute it and/or modify it 15 // under the terms of the GNU Lesser General Public License as 16 // published by the Free Software Foundation, either version 3 of the 17 // License, or (at your option) any later version. 18 // 19 // TCPDF is distributed in the hope that it will be useful, but 20 // WITHOUT ANY WARRANTY; without even the implied warranty of 21 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. 22 // See the GNU Lesser General Public License for more details. 23 // 24 // You should have received a copy of the GNU Lesser General Public License 25 // along with TCPDF. If not, see <http://www.gnu.org/licenses/>. 26 // 27 // See LICENSE.TXT file for more information. 28 // ------------------------------------------------------------------- 29 // 30 // Description :Font methods for TCPDF library. 31 // 32 //============================================================+ 33 34 /** 35 * @file 36 * Unicode data and font methods for TCPDF library. 37 * @author Nicola Asuni 38 * @package com.tecnick.tcpdf 39 */ 40 41 /** 42 * @class TCPDF_FONTS 43 * Font methods for TCPDF library. 44 * @package com.tecnick.tcpdf 45 * @version 1.1.0 46 * @author Nicola Asuni - info@tecnick.com 47 */ 48 class TCPDF_FONTS { 49 50 /** 51 * Static cache used for speed up uniord performances 52 * @protected 53 */ 54 protected static $cache_uniord = array(); 55 56 /** 57 * Convert and add the selected TrueType or Type1 font to the fonts folder (that must be writeable). 58 * @param string $fontfile Font file (full path). 59 * @param string $fonttype Font type. Leave empty for autodetect mode. Valid values are: TrueTypeUnicode, TrueType, Type1, CID0JP = CID-0 Japanese, CID0KR = CID-0 Korean, CID0CS = CID-0 Chinese Simplified, CID0CT = CID-0 Chinese Traditional. 60 * @param string $enc Name of the encoding table to use. Leave empty for default mode. Omit this parameter for TrueType Unicode and symbolic fonts like Symbol or ZapfDingBats. 61 * @param int $flags Unsigned 32-bit integer containing flags specifying various characteristics of the font (PDF32000:2008 - 9.8.2 Font Descriptor Flags): +1 for fixed font; +4 for symbol or +32 for non-symbol; +64 for italic. Fixed and Italic mode are generally autodetected so you have to set it to 32 = non-symbolic font (default) or 4 = symbolic font. 62 * @param string $outpath Output path for generated font files (must be writeable by the web server). Leave empty for default font folder. 63 * @param int $platid Platform ID for CMAP table to extract (when building a Unicode font for Windows this value should be 3, for Macintosh should be 1). 64 * @param int $encid Encoding ID for CMAP table to extract (when building a Unicode font for Windows this value should be 1, for Macintosh should be 0). When Platform ID is 3, legal values for Encoding ID are: 0=Symbol, 1=Unicode, 2=ShiftJIS, 3=PRC, 4=Big5, 5=Wansung, 6=Johab, 7=Reserved, 8=Reserved, 9=Reserved, 10=UCS-4. 65 * @param boolean $addcbbox If true includes the character bounding box information on the php font file. 66 * @param boolean $link If true link to system font instead of copying the font data (not transportable) - Note: do not work with Type1 fonts. 67 * @return string|false TCPDF font name or boolean false in case of error. 68 * @author Nicola Asuni 69 * @since 5.9.123 (2010-09-30) 70 * @public static 71 */ 72 public static function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false, $link=false) { 73 if (!TCPDF_STATIC::file_exists($fontfile)) { 74 // Could not find file 75 return false; 76 } 77 // font metrics 78 $fmetric = array(); 79 // build new font name for TCPDF compatibility 80 $font_path_parts = pathinfo($fontfile); 81 if (!isset($font_path_parts['filename'])) { 82 $font_path_parts['filename'] = substr($font_path_parts['basename'], 0, -(strlen($font_path_parts['extension']) + 1)); 83 } 84 $font_name = strtolower($font_path_parts['filename']); 85 $font_name = preg_replace('/[^a-z0-9_]/', '', $font_name); 86 $search = array('bold', 'oblique', 'italic', 'regular'); 87 $replace = array('b', 'i', 'i', ''); 88 $font_name = str_replace($search, $replace, $font_name); 89 if (empty($font_name)) { 90 // set generic name 91 $font_name = 'tcpdffont'; 92 } 93 // set output path 94 if (empty($outpath)) { 95 $outpath = self::_getfontpath(); 96 } 97 // check if this font already exist 98 if (@TCPDF_STATIC::file_exists($outpath.$font_name.'.php')) { 99 // this font already exist (delete it from fonts folder to rebuild it) 100 return $font_name; 101 } 102 $fmetric['file'] = $font_name; 103 $fmetric['ctg'] = $font_name.'.ctg.z'; 104 // get font data 105 $font = file_get_contents($fontfile); 106 $fmetric['originalsize'] = strlen($font); 107 // autodetect font type 108 if (empty($fonttype)) { 109 if (TCPDF_STATIC::_getULONG($font, 0) == 0x10000) { 110 // True Type (Unicode or not) 111 $fonttype = 'TrueTypeUnicode'; 112 } elseif (substr($font, 0, 4) == 'OTTO') { 113 // Open Type (Unicode or not) 114 //Unsupported font format: OpenType with CFF data 115 return false; 116 } else { 117 // Type 1 118 $fonttype = 'Type1'; 119 } 120 } 121 // set font type 122 switch ($fonttype) { 123 case 'CID0CT': 124 case 'CID0CS': 125 case 'CID0KR': 126 case 'CID0JP': { 127 $fmetric['type'] = 'cidfont0'; 128 break; 129 } 130 case 'Type1': { 131 $fmetric['type'] = 'Type1'; 132 if (empty($enc) AND (($flags & 4) == 0)) { 133 $enc = 'cp1252'; 134 } 135 break; 136 } 137 case 'TrueType': { 138 $fmetric['type'] = 'TrueType'; 139 break; 140 } 141 case 'TrueTypeUnicode': 142 default: { 143 $fmetric['type'] = 'TrueTypeUnicode'; 144 break; 145 } 146 } 147 // set encoding maps (if any) 148 $fmetric['enc'] = preg_replace('/[^A-Za-z0-9_\-]/', '', $enc); 149 $fmetric['diff'] = ''; 150 if (($fmetric['type'] == 'TrueType') OR ($fmetric['type'] == 'Type1')) { 151 if (!empty($enc) AND ($enc != 'cp1252') AND isset(TCPDF_FONT_DATA::$encmap[$enc])) { 152 // build differences from reference encoding 153 $enc_ref = TCPDF_FONT_DATA::$encmap['cp1252']; 154 $enc_target = TCPDF_FONT_DATA::$encmap[$enc]; 155 $last = 0; 156 for ($i = 32; $i <= 255; ++$i) { 157 if ($enc_target[$i] != $enc_ref[$i]) { 158 if ($i != ($last + 1)) { 159 $fmetric['diff'] .= $i.' '; 160 } 161 $last = $i; 162 $fmetric['diff'] .= '/'.$enc_target[$i].' '; 163 } 164 } 165 } 166 } 167 // parse the font by type 168 if ($fmetric['type'] == 'Type1') { 169 // ---------- TYPE 1 ---------- 170 // read first segment 171 $a = unpack('Cmarker/Ctype/Vsize', substr($font, 0, 6)); 172 if ($a['marker'] != 128) { 173 // Font file is not a valid binary Type1 174 return false; 175 } 176 $fmetric['size1'] = $a['size']; 177 $data = substr($font, 6, $fmetric['size1']); 178 // read second segment 179 $a = unpack('Cmarker/Ctype/Vsize', substr($font, (6 + $fmetric['size1']), 6)); 180 if ($a['marker'] != 128) { 181 // Font file is not a valid binary Type1 182 return false; 183 } 184 $fmetric['size2'] = $a['size']; 185 $encrypted = substr($font, (12 + $fmetric['size1']), $fmetric['size2']); 186 $data .= $encrypted; 187 // store compressed font 188 $fmetric['file'] .= '.z'; 189 $fp = TCPDF_STATIC::fopenLocal($outpath.$fmetric['file'], 'wb'); 190 fwrite($fp, gzcompress($data)); 191 fclose($fp); 192 // get font info 193 $fmetric['Flags'] = $flags; 194 preg_match ('#/FullName[\s]*\(([^\)]*)#', $font, $matches); 195 $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $matches[1]); 196 preg_match('#/FontBBox[\s]*{([^}]*)#', $font, $matches); 197 $fmetric['bbox'] = trim($matches[1]); 198 $bv = explode(' ', $fmetric['bbox']); 199 $fmetric['Ascent'] = intval($bv[3]); 200 $fmetric['Descent'] = intval($bv[1]); 201 preg_match('#/ItalicAngle[\s]*([0-9\+\-]*)#', $font, $matches); 202 $fmetric['italicAngle'] = intval($matches[1]); 203 if ($fmetric['italicAngle'] != 0) { 204 $fmetric['Flags'] |= 64; 205 } 206 preg_match('#/UnderlinePosition[\s]*([0-9\+\-]*)#', $font, $matches); 207 $fmetric['underlinePosition'] = intval($matches[1]); 208 preg_match('#/UnderlineThickness[\s]*([0-9\+\-]*)#', $font, $matches); 209 $fmetric['underlineThickness'] = intval($matches[1]); 210 preg_match('#/isFixedPitch[\s]*([^\s]*)#', $font, $matches); 211 if ($matches[1] == 'true') { 212 $fmetric['Flags'] |= 1; 213 } 214 // get internal map 215 $imap = array(); 216 if (preg_match_all('#dup[\s]([0-9]+)[\s]*/([^\s]*)[\s]put#sU', $font, $fmap, PREG_SET_ORDER) > 0) { 217 foreach ($fmap as $v) { 218 $imap[$v[2]] = $v[1]; 219 } 220 } 221 // decrypt eexec encrypted part 222 $r = 55665; // eexec encryption constant 223 $c1 = 52845; 224 $c2 = 22719; 225 $elen = strlen($encrypted); 226 $eplain = ''; 227 for ($i = 0; $i < $elen; ++$i) { 228 $chr = ord($encrypted[$i]); 229 $eplain .= chr($chr ^ ($r >> 8)); 230 $r = ((($chr + $r) * $c1 + $c2) % 65536); 231 } 232 if (preg_match('#/ForceBold[\s]*([^\s]*)#', $eplain, $matches) > 0) { 233 if ($matches[1] == 'true') { 234 $fmetric['Flags'] |= 0x40000; 235 } 236 } 237 if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) { 238 $fmetric['StemV'] = intval($matches[1]); 239 } else { 240 $fmetric['StemV'] = 70; 241 } 242 if (preg_match('#/StdHW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) { 243 $fmetric['StemH'] = intval($matches[1]); 244 } else { 245 $fmetric['StemH'] = 30; 246 } 247 if (preg_match('#/BlueValues[\s]*\[([^\]]*)#', $eplain, $matches) > 0) { 248 $bv = explode(' ', $matches[1]); 249 if (count($bv) >= 6) { 250 $v1 = intval($bv[2]); 251 $v2 = intval($bv[4]); 252 if ($v1 <= $v2) { 253 $fmetric['XHeight'] = $v1; 254 $fmetric['CapHeight'] = $v2; 255 } else { 256 $fmetric['XHeight'] = $v2; 257 $fmetric['CapHeight'] = $v1; 258 } 259 } else { 260 $fmetric['XHeight'] = 450; 261 $fmetric['CapHeight'] = 700; 262 } 263 } else { 264 $fmetric['XHeight'] = 450; 265 $fmetric['CapHeight'] = 700; 266 } 267 // get the number of random bytes at the beginning of charstrings 268 if (preg_match('#/lenIV[\s]*([0-9]*)#', $eplain, $matches) > 0) { 269 $lenIV = intval($matches[1]); 270 } else { 271 $lenIV = 4; 272 } 273 $fmetric['Leading'] = 0; 274 // get charstring data 275 $eplain = substr($eplain, (strpos($eplain, '/CharStrings') + 1)); 276 preg_match_all('#/([A-Za-z0-9\.]*)[\s][0-9]+[\s]RD[\s](.*)[\s]ND#sU', $eplain, $matches, PREG_SET_ORDER); 277 if (!empty($enc) AND isset(TCPDF_FONT_DATA::$encmap[$enc])) { 278 $enc_map = TCPDF_FONT_DATA::$encmap[$enc]; 279 } else { 280 $enc_map = false; 281 } 282 $fmetric['cw'] = ''; 283 $fmetric['MaxWidth'] = 0; 284 $cwidths = array(); 285 foreach ($matches as $k => $v) { 286 $cid = 0; 287 if (isset($imap[$v[1]])) { 288 $cid = $imap[$v[1]]; 289 } elseif ($enc_map !== false) { 290 $cid = array_search($v[1], $enc_map); 291 if ($cid === false) { 292 $cid = 0; 293 } elseif ($cid > 1000) { 294 $cid -= 1000; 295 } 296 } 297 // decrypt charstring encrypted part 298 $r = 4330; // charstring encryption constant 299 $c1 = 52845; 300 $c2 = 22719; 301 $cd = $v[2]; 302 $clen = strlen($cd); 303 $ccom = array(); 304 for ($i = 0; $i < $clen; ++$i) { 305 $chr = ord($cd[$i]); 306 $ccom[] = ($chr ^ ($r >> 8)); 307 $r = ((($chr + $r) * $c1 + $c2) % 65536); 308 } 309 // decode numbers 310 $cdec = array(); 311 $ck = 0; 312 $i = $lenIV; 313 while ($i < $clen) { 314 if ($ccom[$i] < 32) { 315 $cdec[$ck] = $ccom[$i]; 316 if (($ck > 0) AND ($cdec[$ck] == 13)) { 317 // hsbw command: update width 318 $cwidths[$cid] = $cdec[($ck - 1)]; 319 } 320 ++$i; 321 } elseif (($ccom[$i] >= 32) AND ($ccom[$i] <= 246)) { 322 $cdec[$ck] = ($ccom[$i] - 139); 323 ++$i; 324 } elseif (($ccom[$i] >= 247) AND ($ccom[$i] <= 250)) { 325 $cdec[$ck] = ((($ccom[$i] - 247) * 256) + $ccom[($i + 1)] + 108); 326 $i += 2; 327 } elseif (($ccom[$i] >= 251) AND ($ccom[$i] <= 254)) { 328 $cdec[$ck] = ((-($ccom[$i] - 251) * 256) - $ccom[($i + 1)] - 108); 329 $i += 2; 330 } elseif ($ccom[$i] == 255) { 331 $sval = chr($ccom[($i + 1)]).chr($ccom[($i + 2)]).chr($ccom[($i + 3)]).chr($ccom[($i + 4)]); 332 $vsval = unpack('li', $sval); 333 $cdec[$ck] = $vsval['i']; 334 $i += 5; 335 } 336 ++$ck; 337 } 338 } // end for each matches 339 $fmetric['MissingWidth'] = $cwidths[0]; 340 $fmetric['MaxWidth'] = $fmetric['MissingWidth']; 341 $fmetric['AvgWidth'] = 0; 342 // set chars widths 343 for ($cid = 0; $cid <= 255; ++$cid) { 344 if (isset($cwidths[$cid])) { 345 if ($cwidths[$cid] > $fmetric['MaxWidth']) { 346 $fmetric['MaxWidth'] = $cwidths[$cid]; 347 } 348 $fmetric['AvgWidth'] += $cwidths[$cid]; 349 $fmetric['cw'] .= ','.$cid.'=>'.$cwidths[$cid]; 350 } else { 351 $fmetric['cw'] .= ','.$cid.'=>'.$fmetric['MissingWidth']; 352 } 353 } 354 $fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths)); 355 } else { 356 // ---------- TRUE TYPE ---------- 357 $offset = 0; // offset position of the font data 358 if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) { 359 // sfnt version must be 0x00010000 for TrueType version 1.0. 360 return false; 361 } 362 if ($fmetric['type'] != 'cidfont0') { 363 if ($link) { 364 // creates a symbolic link to the existing font 365 symlink($fontfile, $outpath.$fmetric['file']); 366 } else { 367 // store compressed font 368 $fmetric['file'] .= '.z'; 369 $fp = TCPDF_STATIC::fopenLocal($outpath.$fmetric['file'], 'wb'); 370 fwrite($fp, gzcompress($font)); 371 fclose($fp); 372 } 373 } 374 $offset += 4; 375 // get number of tables 376 $numTables = TCPDF_STATIC::_getUSHORT($font, $offset); 377 $offset += 2; 378 // skip searchRange, entrySelector and rangeShift 379 $offset += 6; 380 // tables array 381 $table = array(); 382 // ---------- get tables ---------- 383 for ($i = 0; $i < $numTables; ++$i) { 384 // get table info 385 $tag = substr($font, $offset, 4); 386 $offset += 4; 387 $table[$tag] = array(); 388 $table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset); 389 $offset += 4; 390 $table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset); 391 $offset += 4; 392 $table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset); 393 $offset += 4; 394 } 395 // check magicNumber 396 $offset = $table['head']['offset'] + 12; 397 if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) { 398 // magicNumber must be 0x5F0F3CF5 399 return false; 400 } 401 $offset += 4; 402 $offset += 2; // skip flags 403 // get FUnits 404 $fmetric['unitsPerEm'] = TCPDF_STATIC::_getUSHORT($font, $offset); 405 $offset += 2; 406 // units ratio constant 407 $urk = (1000 / $fmetric['unitsPerEm']); 408 $offset += 16; // skip created, modified 409 $xMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 410 $offset += 2; 411 $yMin = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 412 $offset += 2; 413 $xMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 414 $offset += 2; 415 $yMax = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 416 $offset += 2; 417 $fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.''; 418 $macStyle = TCPDF_STATIC::_getUSHORT($font, $offset); 419 $offset += 2; 420 // PDF font flags 421 $fmetric['Flags'] = $flags; 422 if (($macStyle & 2) == 2) { 423 // italic flag 424 $fmetric['Flags'] |= 64; 425 } 426 // get offset mode (indexToLocFormat : 0 = short, 1 = long) 427 $offset = $table['head']['offset'] + 50; 428 $short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0); 429 $offset += 2; 430 // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table 431 $indexToLoc = array(); 432 $offset = $table['loca']['offset']; 433 if ($short_offset) { 434 // short version 435 $tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1 436 for ($i = 0; $i < $tot_num_glyphs; ++$i) { 437 $indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2; 438 if (isset($indexToLoc[($i - 1)]) && ($indexToLoc[$i] == $indexToLoc[($i - 1)])) { 439 // the last glyph didn't have an outline 440 unset($indexToLoc[($i - 1)]); 441 } 442 $offset += 2; 443 } 444 } else { 445 // long version 446 $tot_num_glyphs = floor($table['loca']['length'] / 4); // numGlyphs + 1 447 for ($i = 0; $i < $tot_num_glyphs; ++$i) { 448 $indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset); 449 if (isset($indexToLoc[($i - 1)]) && ($indexToLoc[$i] == $indexToLoc[($i - 1)])) { 450 // the last glyph didn't have an outline 451 unset($indexToLoc[($i - 1)]); 452 } 453 $offset += 4; 454 } 455 } 456 // get glyphs indexes of chars from cmap table 457 $offset = $table['cmap']['offset'] + 2; 458 $numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset); 459 $offset += 2; 460 $encodingTables = array(); 461 for ($i = 0; $i < $numEncodingTables; ++$i) { 462 $encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset); 463 $offset += 2; 464 $encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset); 465 $offset += 2; 466 $encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset); 467 $offset += 4; 468 } 469 // ---------- get os/2 metrics ---------- 470 $offset = $table['OS/2']['offset']; 471 $offset += 2; // skip version 472 // xAvgCharWidth 473 $fmetric['AvgWidth'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 474 $offset += 2; 475 // usWeightClass 476 $usWeightClass = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk); 477 // estimate StemV and StemH (400 = usWeightClass for Normal - Regular font) 478 $fmetric['StemV'] = round((70 * $usWeightClass) / 400); 479 $fmetric['StemH'] = round((30 * $usWeightClass) / 400); 480 $offset += 2; 481 $offset += 2; // usWidthClass 482 $fsType = TCPDF_STATIC::_getSHORT($font, $offset); 483 $offset += 2; 484 if ($fsType == 2) { 485 // This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner. 486 return false; 487 } 488 // ---------- get font name ---------- 489 $fmetric['name'] = ''; 490 $offset = $table['name']['offset']; 491 $offset += 2; // skip Format selector (=0). 492 // Number of NameRecords that follow n. 493 $numNameRecords = TCPDF_STATIC::_getUSHORT($font, $offset); 494 $offset += 2; 495 // Offset to start of string storage (from start of table). 496 $stringStorageOffset = TCPDF_STATIC::_getUSHORT($font, $offset); 497 $offset += 2; 498 for ($i = 0; $i < $numNameRecords; ++$i) { 499 $offset += 6; // skip Platform ID, Platform-specific encoding ID, Language ID. 500 // Name ID. 501 $nameID = TCPDF_STATIC::_getUSHORT($font, $offset); 502 $offset += 2; 503 if ($nameID == 6) { 504 // String length (in bytes). 505 $stringLength = TCPDF_STATIC::_getUSHORT($font, $offset); 506 $offset += 2; 507 // String offset from start of storage area (in bytes). 508 $stringOffset = TCPDF_STATIC::_getUSHORT($font, $offset); 509 $offset += 2; 510 $offset = ($table['name']['offset'] + $stringStorageOffset + $stringOffset); 511 $fmetric['name'] = substr($font, $offset, $stringLength); 512 $fmetric['name'] = preg_replace('/[^a-zA-Z0-9_\-]/', '', $fmetric['name']); 513 break; 514 } else { 515 $offset += 4; // skip String length, String offset 516 } 517 } 518 if (empty($fmetric['name'])) { 519 $fmetric['name'] = $font_name; 520 } 521 // ---------- get post data ---------- 522 $offset = $table['post']['offset']; 523 $offset += 4; // skip Format Type 524 $fmetric['italicAngle'] = TCPDF_STATIC::_getFIXED($font, $offset); 525 $offset += 4; 526 $fmetric['underlinePosition'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 527 $offset += 2; 528 $fmetric['underlineThickness'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 529 $offset += 2; 530 $isFixedPitch = (TCPDF_STATIC::_getULONG($font, $offset) == 0) ? false : true; 531 $offset += 2; 532 if ($isFixedPitch) { 533 $fmetric['Flags'] |= 1; 534 } 535 // ---------- get hhea data ---------- 536 $offset = $table['hhea']['offset']; 537 $offset += 4; // skip Table version number 538 // Ascender 539 $fmetric['Ascent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 540 $offset += 2; 541 // Descender 542 $fmetric['Descent'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 543 $offset += 2; 544 // LineGap 545 $fmetric['Leading'] = round(TCPDF_STATIC::_getFWORD($font, $offset) * $urk); 546 $offset += 2; 547 // advanceWidthMax 548 $fmetric['MaxWidth'] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk); 549 $offset += 2; 550 $offset += 22; // skip some values 551 // get the number of hMetric entries in hmtx table 552 $numberOfHMetrics = TCPDF_STATIC::_getUSHORT($font, $offset); 553 // ---------- get maxp data ---------- 554 $offset = $table['maxp']['offset']; 555 $offset += 4; // skip Table version number 556 // get the the number of glyphs in the font. 557 $numGlyphs = TCPDF_STATIC::_getUSHORT($font, $offset); 558 // ---------- get CIDToGIDMap ---------- 559 $ctg = array(); 560 $c = 0; 561 foreach ($encodingTables as $enctable) { 562 // get only specified Platform ID and Encoding ID 563 if (($enctable['platformID'] == $platid) AND ($enctable['encodingID'] == $encid)) { 564 $offset = $table['cmap']['offset'] + $enctable['offset']; 565 $format = TCPDF_STATIC::_getUSHORT($font, $offset); 566 $offset += 2; 567 switch ($format) { 568 case 0: { // Format 0: Byte encoding table 569 $offset += 4; // skip length and version/language 570 for ($c = 0; $c < 256; ++$c) { 571 $g = TCPDF_STATIC::_getBYTE($font, $offset); 572 $ctg[$c] = $g; 573 ++$offset; 574 } 575 break; 576 } 577 case 2: { // Format 2: High-byte mapping through table 578 $offset += 4; // skip length and version/language 579 $numSubHeaders = 0; 580 for ($i = 0; $i < 256; ++$i) { 581 // Array that maps high bytes to subHeaders: value is subHeader index * 8. 582 $subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8); 583 $offset += 2; 584 if ($numSubHeaders < $subHeaderKeys[$i]) { 585 $numSubHeaders = $subHeaderKeys[$i]; 586 } 587 } 588 // the number of subHeaders is equal to the max of subHeaderKeys + 1 589 ++$numSubHeaders; 590 // read subHeader structures 591 $subHeaders = array(); 592 $numGlyphIndexArray = 0; 593 for ($k = 0; $k < $numSubHeaders; ++$k) { 594 $subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset); 595 $offset += 2; 596 $subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset); 597 $offset += 2; 598 $subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset); 599 $offset += 2; 600 $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset); 601 $offset += 2; 602 $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8)); 603 $subHeaders[$k]['idRangeOffset'] /= 2; 604 $numGlyphIndexArray += $subHeaders[$k]['entryCount']; 605 } 606 for ($k = 0; $k < $numGlyphIndexArray; ++$k) { 607 $glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 608 $offset += 2; 609 } 610 for ($i = 0; $i < 256; ++$i) { 611 $k = $subHeaderKeys[$i]; 612 if ($k == 0) { 613 // one byte code 614 $c = $i; 615 $g = $glyphIndexArray[0]; 616 $ctg[$c] = $g; 617 } else { 618 // two bytes code 619 $start_byte = $subHeaders[$k]['firstCode']; 620 $end_byte = $start_byte + $subHeaders[$k]['entryCount']; 621 for ($j = $start_byte; $j < $end_byte; ++$j) { 622 // combine high and low bytes 623 $c = (($i << 8) + $j); 624 $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']); 625 $g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536; 626 if ($g < 0) { 627 $g = 0; 628 } 629 $ctg[$c] = $g; 630 } 631 } 632 } 633 break; 634 } 635 case 4: { // Format 4: Segment mapping to delta values 636 $length = TCPDF_STATIC::_getUSHORT($font, $offset); 637 $offset += 2; 638 $offset += 2; // skip version/language 639 $segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2); 640 $offset += 2; 641 $offset += 6; // skip searchRange, entrySelector, rangeShift 642 $endCount = array(); // array of end character codes for each segment 643 for ($k = 0; $k < $segCount; ++$k) { 644 $endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 645 $offset += 2; 646 } 647 $offset += 2; // skip reservedPad 648 $startCount = array(); // array of start character codes for each segment 649 for ($k = 0; $k < $segCount; ++$k) { 650 $startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 651 $offset += 2; 652 } 653 $idDelta = array(); // delta for all character codes in segment 654 for ($k = 0; $k < $segCount; ++$k) { 655 $idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 656 $offset += 2; 657 } 658 $idRangeOffset = array(); // Offsets into glyphIdArray or 0 659 for ($k = 0; $k < $segCount; ++$k) { 660 $idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 661 $offset += 2; 662 } 663 $gidlen = (floor($length / 2) - 8 - (4 * $segCount)); 664 $glyphIdArray = array(); // glyph index array 665 for ($k = 0; $k < $gidlen; ++$k) { 666 $glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 667 $offset += 2; 668 } 669 for ($k = 0; $k < $segCount - 1; ++$k) { 670 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) { 671 if ($idRangeOffset[$k] == 0) { 672 $g = ($idDelta[$k] + $c) % 65536; 673 } else { 674 $gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k)); 675 $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536; 676 } 677 if ($g < 0) { 678 $g = 0; 679 } 680 $ctg[$c] = $g; 681 } 682 } 683 break; 684 } 685 case 6: { // Format 6: Trimmed table mapping 686 $offset += 4; // skip length and version/language 687 $firstCode = TCPDF_STATIC::_getUSHORT($font, $offset); 688 $offset += 2; 689 $entryCount = TCPDF_STATIC::_getUSHORT($font, $offset); 690 $offset += 2; 691 for ($k = 0; $k < $entryCount; ++$k) { 692 $c = ($k + $firstCode); 693 $g = TCPDF_STATIC::_getUSHORT($font, $offset); 694 $offset += 2; 695 $ctg[$c] = $g; 696 } 697 break; 698 } 699 case 8: { // Format 8: Mixed 16-bit and 32-bit coverage 700 $offset += 10; // skip reserved, length and version/language 701 for ($k = 0; $k < 8192; ++$k) { 702 $is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset); 703 ++$offset; 704 } 705 $nGroups = TCPDF_STATIC::_getULONG($font, $offset); 706 $offset += 4; 707 for ($i = 0; $i < $nGroups; ++$i) { 708 $startCharCode = TCPDF_STATIC::_getULONG($font, $offset); 709 $offset += 4; 710 $endCharCode = TCPDF_STATIC::_getULONG($font, $offset); 711 $offset += 4; 712 $startGlyphID = TCPDF_STATIC::_getULONG($font, $offset); 713 $offset += 4; 714 for ($k = $startCharCode; $k <= $endCharCode; ++$k) { 715 $is32idx = floor($c / 8); 716 if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) { 717 $c = $k; 718 } else { 719 // 32 bit format 720 // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4) 721 //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232 722 //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888 723 $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888; 724 } 725 $ctg[$c] = 0; 726 ++$startGlyphID; 727 } 728 } 729 break; 730 } 731 case 10: { // Format 10: Trimmed array 732 $offset += 10; // skip reserved, length and version/language 733 $startCharCode = TCPDF_STATIC::_getULONG($font, $offset); 734 $offset += 4; 735 $numChars = TCPDF_STATIC::_getULONG($font, $offset); 736 $offset += 4; 737 for ($k = 0; $k < $numChars; ++$k) { 738 $c = ($k + $startCharCode); 739 $g = TCPDF_STATIC::_getUSHORT($font, $offset); 740 $ctg[$c] = $g; 741 $offset += 2; 742 } 743 break; 744 } 745 case 12: { // Format 12: Segmented coverage 746 $offset += 10; // skip length and version/language 747 $nGroups = TCPDF_STATIC::_getULONG($font, $offset); 748 $offset += 4; 749 for ($k = 0; $k < $nGroups; ++$k) { 750 $startCharCode = TCPDF_STATIC::_getULONG($font, $offset); 751 $offset += 4; 752 $endCharCode = TCPDF_STATIC::_getULONG($font, $offset); 753 $offset += 4; 754 $startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset); 755 $offset += 4; 756 for ($c = $startCharCode; $c <= $endCharCode; ++$c) { 757 $ctg[$c] = $startGlyphCode; 758 ++$startGlyphCode; 759 } 760 } 761 break; 762 } 763 case 13: { // Format 13: Many-to-one range mappings 764 // to be implemented ... 765 break; 766 } 767 case 14: { // Format 14: Unicode Variation Sequences 768 // to be implemented ... 769 break; 770 } 771 } 772 } 773 } 774 if (!isset($ctg[0])) { 775 $ctg[0] = 0; 776 } 777 // get xHeight (height of x) 778 $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[120]] + 4); 779 $yMin = TCPDF_STATIC::_getFWORD($font, $offset); 780 $offset += 4; 781 $yMax = TCPDF_STATIC::_getFWORD($font, $offset); 782 $offset += 2; 783 $fmetric['XHeight'] = round(($yMax - $yMin) * $urk); 784 // get CapHeight (height of H) 785 $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[72]] + 4); 786 $yMin = TCPDF_STATIC::_getFWORD($font, $offset); 787 $offset += 4; 788 $yMax = TCPDF_STATIC::_getFWORD($font, $offset); 789 $offset += 2; 790 $fmetric['CapHeight'] = round(($yMax - $yMin) * $urk); 791 // ceate widths array 792 $cw = array(); 793 $offset = $table['hmtx']['offset']; 794 for ($i = 0 ; $i < $numberOfHMetrics; ++$i) { 795 $cw[$i] = round(TCPDF_STATIC::_getUFWORD($font, $offset) * $urk); 796 $offset += 4; // skip lsb 797 } 798 if ($numberOfHMetrics < $numGlyphs) { 799 // fill missing widths with the last value 800 $cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]); 801 } 802 $fmetric['MissingWidth'] = $cw[0]; 803 $fmetric['cw'] = ''; 804 $fmetric['cbbox'] = ''; 805 for ($cid = 0; $cid <= 65535; ++$cid) { 806 if (isset($ctg[$cid])) { 807 if (isset($cw[$ctg[$cid]])) { 808 $fmetric['cw'] .= ','.$cid.'=>'.$cw[$ctg[$cid]]; 809 } 810 if ($addcbbox AND isset($indexToLoc[$ctg[$cid]])) { 811 $offset = ($table['glyf']['offset'] + $indexToLoc[$ctg[$cid]]); 812 $xMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 2) * $urk); 813 $yMin = round(TCPDF_STATIC::_getFWORD($font, $offset + 4) * $urk); 814 $xMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 6) * $urk); 815 $yMax = round(TCPDF_STATIC::_getFWORD($font, $offset + 8) * $urk); 816 $fmetric['cbbox'] .= ','.$cid.'=>array('.$xMin.','.$yMin.','.$xMax.','.$yMax.')'; 817 } 818 } 819 } 820 } // end of true type 821 if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) { 822 $fmetric['type'] = 'TrueType'; 823 } 824 // ---------- create php font file ---------- 825 $pfile = '<'.'?'.'php'."\n"; 826 $pfile .= '// TCPDF FONT FILE DESCRIPTION'."\n"; 827 $pfile .= '$type=\''.$fmetric['type'].'\';'."\n"; 828 $pfile .= '$name=\''.$fmetric['name'].'\';'."\n"; 829 $pfile .= '$up='.$fmetric['underlinePosition'].';'."\n"; 830 $pfile .= '$ut='.$fmetric['underlineThickness'].';'."\n"; 831 if ($fmetric['MissingWidth'] > 0) { 832 $pfile .= '$dw='.$fmetric['MissingWidth'].';'."\n"; 833 } else { 834 $pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n"; 835 } 836 $pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n"; 837 if ($fmetric['type'] == 'Type1') { 838 // Type 1 839 $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n"; 840 $pfile .= '$file=\''.$fmetric['file'].'\';'."\n"; 841 $pfile .= '$size1='.$fmetric['size1'].';'."\n"; 842 $pfile .= '$size2='.$fmetric['size2'].';'."\n"; 843 } else { 844 $pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n"; 845 if ($fmetric['type'] == 'cidfont0') { 846 // CID-0 847 switch ($fonttype) { 848 case 'CID0JP': { 849 $pfile .= '// Japanese'."\n"; 850 $pfile .= '$enc=\'UniJIS-UTF16-H\';'."\n"; 851 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Japan1\',\'Supplement\'=>5);'."\n"; 852 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n"; 853 break; 854 } 855 case 'CID0KR': { 856 $pfile .= '// Korean'."\n"; 857 $pfile .= '$enc=\'UniKS-UTF16-H\';'."\n"; 858 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'Korea1\',\'Supplement\'=>0);'."\n"; 859 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ak12.php\');'."\n"; 860 break; 861 } 862 case 'CID0CS': { 863 $pfile .= '// Chinese Simplified'."\n"; 864 $pfile .= '$enc=\'UniGB-UTF16-H\';'."\n"; 865 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'GB1\',\'Supplement\'=>2);'."\n"; 866 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_ag15.php\');'."\n"; 867 break; 868 } 869 case 'CID0CT': 870 default: { 871 $pfile .= '// Chinese Traditional'."\n"; 872 $pfile .= '$enc=\'UniCNS-UTF16-H\';'."\n"; 873 $pfile .= '$cidinfo=array(\'Registry\'=>\'Adobe\', \'Ordering\'=>\'CNS1\',\'Supplement\'=>0);'."\n"; 874 $pfile .= 'include(dirname(__FILE__).\'/uni2cid_aj16.php\');'."\n"; 875 break; 876 } 877 } 878 } else { 879 // TrueType 880 $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n"; 881 $pfile .= '$file=\''.$fmetric['file'].'\';'."\n"; 882 $pfile .= '$ctg=\''.$fmetric['ctg'].'\';'."\n"; 883 // create CIDToGIDMap 884 $cidtogidmap = str_pad('', 131072, "\x00"); // (256 * 256 * 2) = 131072 885 foreach ($ctg as $cid => $gid) { 886 $cidtogidmap = self::updateCIDtoGIDmap($cidtogidmap, $cid, $ctg[$cid]); 887 } 888 // store compressed CIDToGIDMap 889 $fp = TCPDF_STATIC::fopenLocal($outpath.$fmetric['ctg'], 'wb'); 890 fwrite($fp, gzcompress($cidtogidmap)); 891 fclose($fp); 892 } 893 } 894 $pfile .= '$desc=array('; 895 $pfile .= '\'Flags\'=>'.$fmetric['Flags'].','; 896 $pfile .= '\'FontBBox\'=>\'['.$fmetric['bbox'].']\','; 897 $pfile .= '\'ItalicAngle\'=>'.$fmetric['italicAngle'].','; 898 $pfile .= '\'Ascent\'=>'.$fmetric['Ascent'].','; 899 $pfile .= '\'Descent\'=>'.$fmetric['Descent'].','; 900 $pfile .= '\'Leading\'=>'.$fmetric['Leading'].','; 901 $pfile .= '\'CapHeight\'=>'.$fmetric['CapHeight'].','; 902 $pfile .= '\'XHeight\'=>'.$fmetric['XHeight'].','; 903 $pfile .= '\'StemV\'=>'.$fmetric['StemV'].','; 904 $pfile .= '\'StemH\'=>'.$fmetric['StemH'].','; 905 $pfile .= '\'AvgWidth\'=>'.$fmetric['AvgWidth'].','; 906 $pfile .= '\'MaxWidth\'=>'.$fmetric['MaxWidth'].','; 907 $pfile .= '\'MissingWidth\'=>'.$fmetric['MissingWidth'].''; 908 $pfile .= ');'."\n"; 909 if (!empty($fmetric['cbbox'])) { 910 $pfile .= '$cbbox=array('.substr($fmetric['cbbox'], 1).');'."\n"; 911 } 912 $pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n"; 913 $pfile .= '// --- EOF ---'."\n"; 914 // store file 915 $fp = TCPDF_STATIC::fopenLocal($outpath.$font_name.'.php', 'w'); 916 fwrite($fp, $pfile); 917 fclose($fp); 918 // return TCPDF font name 919 return $font_name; 920 } 921 922 /** 923 * Returs the checksum of a TTF table. 924 * @param string $table table to check 925 * @param int $length length of table in bytes 926 * @return int checksum 927 * @author Nicola Asuni 928 * @since 5.2.000 (2010-06-02) 929 * @public static 930 */ 931 public static function _getTTFtableChecksum($table, $length) { 932 $sum = 0; 933 $tlen = ($length / 4); 934 $offset = 0; 935 for ($i = 0; $i < $tlen; ++$i) { 936 $v = unpack('Ni', substr($table, $offset, 4)); 937 $sum += $v['i']; 938 $offset += 4; 939 } 940 $sum = unpack('Ni', pack('N', $sum)); 941 return $sum['i']; 942 } 943 944 /** 945 * Returns a subset of the TrueType font data without the unused glyphs. 946 * @param string $font TrueType font data. 947 * @param array $subsetchars Array of used characters (the glyphs to keep). 948 * @return string A subset of TrueType font data without the unused glyphs. 949 * @author Nicola Asuni 950 * @since 5.2.000 (2010-06-02) 951 * @public static 952 */ 953 public static function _getTrueTypeFontSubset($font, $subsetchars) { 954 ksort($subsetchars); 955 $offset = 0; // offset position of the font data 956 if (TCPDF_STATIC::_getULONG($font, $offset) != 0x10000) { 957 // sfnt version must be 0x00010000 for TrueType version 1.0. 958 return $font; 959 } 960 $c = 0; 961 $offset += 4; 962 // get number of tables 963 $numTables = TCPDF_STATIC::_getUSHORT($font, $offset); 964 $offset += 2; 965 // skip searchRange, entrySelector and rangeShift 966 $offset += 6; 967 // tables array 968 $table = array(); 969 // for each table 970 for ($i = 0; $i < $numTables; ++$i) { 971 // get table info 972 $tag = substr($font, $offset, 4); 973 $offset += 4; 974 $table[$tag] = array(); 975 $table[$tag]['checkSum'] = TCPDF_STATIC::_getULONG($font, $offset); 976 $offset += 4; 977 $table[$tag]['offset'] = TCPDF_STATIC::_getULONG($font, $offset); 978 $offset += 4; 979 $table[$tag]['length'] = TCPDF_STATIC::_getULONG($font, $offset); 980 $offset += 4; 981 } 982 // check magicNumber 983 $offset = $table['head']['offset'] + 12; 984 if (TCPDF_STATIC::_getULONG($font, $offset) != 0x5F0F3CF5) { 985 // magicNumber must be 0x5F0F3CF5 986 return $font; 987 } 988 $offset += 4; 989 // get offset mode (indexToLocFormat : 0 = short, 1 = long) 990 $offset = $table['head']['offset'] + 50; 991 $short_offset = (TCPDF_STATIC::_getSHORT($font, $offset) == 0); 992 $offset += 2; 993 // get the offsets to the locations of the glyphs in the font, relative to the beginning of the glyphData table 994 $indexToLoc = array(); 995 $offset = $table['loca']['offset']; 996 if ($short_offset) { 997 // short version 998 $tot_num_glyphs = floor($table['loca']['length'] / 2); // numGlyphs + 1 999 for ($i = 0; $i < $tot_num_glyphs; ++$i) { 1000 $indexToLoc[$i] = TCPDF_STATIC::_getUSHORT($font, $offset) * 2; 1001 $offset += 2; 1002 } 1003 } else { 1004 // long version 1005 $tot_num_glyphs = ($table['loca']['length'] / 4); // numGlyphs + 1 1006 for ($i = 0; $i < $tot_num_glyphs; ++$i) { 1007 $indexToLoc[$i] = TCPDF_STATIC::_getULONG($font, $offset); 1008 $offset += 4; 1009 } 1010 } 1011 // get glyphs indexes of chars from cmap table 1012 $subsetglyphs = array(); // glyph IDs on key 1013 $subsetglyphs[0] = true; // character codes that do not correspond to any glyph in the font should be mapped to glyph index 0 1014 $offset = $table['cmap']['offset'] + 2; 1015 $numEncodingTables = TCPDF_STATIC::_getUSHORT($font, $offset); 1016 $offset += 2; 1017 $encodingTables = array(); 1018 for ($i = 0; $i < $numEncodingTables; ++$i) { 1019 $encodingTables[$i]['platformID'] = TCPDF_STATIC::_getUSHORT($font, $offset); 1020 $offset += 2; 1021 $encodingTables[$i]['encodingID'] = TCPDF_STATIC::_getUSHORT($font, $offset); 1022 $offset += 2; 1023 $encodingTables[$i]['offset'] = TCPDF_STATIC::_getULONG($font, $offset); 1024 $offset += 4; 1025 } 1026 foreach ($encodingTables as $enctable) { 1027 // get all platforms and encodings 1028 $offset = $table['cmap']['offset'] + $enctable['offset']; 1029 $format = TCPDF_STATIC::_getUSHORT($font, $offset); 1030 $offset += 2; 1031 switch ($format) { 1032 case 0: { // Format 0: Byte encoding table 1033 $offset += 4; // skip length and version/language 1034 for ($c = 0; $c < 256; ++$c) { 1035 if (isset($subsetchars[$c])) { 1036 $g = TCPDF_STATIC::_getBYTE($font, $offset); 1037 $subsetglyphs[$g] = true; 1038 } 1039 ++$offset; 1040 } 1041 break; 1042 } 1043 case 2: { // Format 2: High-byte mapping through table 1044 $offset += 4; // skip length and version/language 1045 $numSubHeaders = 0; 1046 for ($i = 0; $i < 256; ++$i) { 1047 // Array that maps high bytes to subHeaders: value is subHeader index * 8. 1048 $subHeaderKeys[$i] = (TCPDF_STATIC::_getUSHORT($font, $offset) / 8); 1049 $offset += 2; 1050 if ($numSubHeaders < $subHeaderKeys[$i]) { 1051 $numSubHeaders = $subHeaderKeys[$i]; 1052 } 1053 } 1054 // the number of subHeaders is equal to the max of subHeaderKeys + 1 1055 ++$numSubHeaders; 1056 // read subHeader structures 1057 $subHeaders = array(); 1058 $numGlyphIndexArray = 0; 1059 for ($k = 0; $k < $numSubHeaders; ++$k) { 1060 $subHeaders[$k]['firstCode'] = TCPDF_STATIC::_getUSHORT($font, $offset); 1061 $offset += 2; 1062 $subHeaders[$k]['entryCount'] = TCPDF_STATIC::_getUSHORT($font, $offset); 1063 $offset += 2; 1064 $subHeaders[$k]['idDelta'] = TCPDF_STATIC::_getUSHORT($font, $offset); 1065 $offset += 2; 1066 $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC::_getUSHORT($font, $offset); 1067 $offset += 2; 1068 $subHeaders[$k]['idRangeOffset'] -= (2 + (($numSubHeaders - $k - 1) * 8)); 1069 $subHeaders[$k]['idRangeOffset'] /= 2; 1070 $numGlyphIndexArray += $subHeaders[$k]['entryCount']; 1071 } 1072 for ($k = 0; $k < $numGlyphIndexArray; ++$k) { 1073 $glyphIndexArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 1074 $offset += 2; 1075 } 1076 for ($i = 0; $i < 256; ++$i) { 1077 $k = $subHeaderKeys[$i]; 1078 if ($k == 0) { 1079 // one byte code 1080 $c = $i; 1081 if (isset($subsetchars[$c])) { 1082 $g = $glyphIndexArray[0]; 1083 $subsetglyphs[$g] = true; 1084 } 1085 } else { 1086 // two bytes code 1087 $start_byte = $subHeaders[$k]['firstCode']; 1088 $end_byte = $start_byte + $subHeaders[$k]['entryCount']; 1089 for ($j = $start_byte; $j < $end_byte; ++$j) { 1090 // combine high and low bytes 1091 $c = (($i << 8) + $j); 1092 if (isset($subsetchars[$c])) { 1093 $idRangeOffset = ($subHeaders[$k]['idRangeOffset'] + $j - $subHeaders[$k]['firstCode']); 1094 $g = ($glyphIndexArray[$idRangeOffset] + $subHeaders[$k]['idDelta']) % 65536; 1095 if ($g < 0) { 1096 $g = 0; 1097 } 1098 $subsetglyphs[$g] = true; 1099 } 1100 } 1101 } 1102 } 1103 break; 1104 } 1105 case 4: { // Format 4: Segment mapping to delta values 1106 $length = TCPDF_STATIC::_getUSHORT($font, $offset); 1107 $offset += 2; 1108 $offset += 2; // skip version/language 1109 $segCount = floor(TCPDF_STATIC::_getUSHORT($font, $offset) / 2); 1110 $offset += 2; 1111 $offset += 6; // skip searchRange, entrySelector, rangeShift 1112 $endCount = array(); // array of end character codes for each segment 1113 for ($k = 0; $k < $segCount; ++$k) { 1114 $endCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 1115 $offset += 2; 1116 } 1117 $offset += 2; // skip reservedPad 1118 $startCount = array(); // array of start character codes for each segment 1119 for ($k = 0; $k < $segCount; ++$k) { 1120 $startCount[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 1121 $offset += 2; 1122 } 1123 $idDelta = array(); // delta for all character codes in segment 1124 for ($k = 0; $k < $segCount; ++$k) { 1125 $idDelta[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 1126 $offset += 2; 1127 } 1128 $idRangeOffset = array(); // Offsets into glyphIdArray or 0 1129 for ($k = 0; $k < $segCount; ++$k) { 1130 $idRangeOffset[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 1131 $offset += 2; 1132 } 1133 $gidlen = (floor($length / 2) - 8 - (4 * $segCount)); 1134 $glyphIdArray = array(); // glyph index array 1135 for ($k = 0; $k < $gidlen; ++$k) { 1136 $glyphIdArray[$k] = TCPDF_STATIC::_getUSHORT($font, $offset); 1137 $offset += 2; 1138 } 1139 for ($k = 0; $k < $segCount; ++$k) { 1140 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++$c) { 1141 if (isset($subsetchars[$c])) { 1142 if ($idRangeOffset[$k] == 0) { 1143 $g = ($idDelta[$k] + $c) % 65536; 1144 } else { 1145 $gid = (floor($idRangeOffset[$k] / 2) + ($c - $startCount[$k]) - ($segCount - $k)); 1146 $g = ($glyphIdArray[$gid] + $idDelta[$k]) % 65536; 1147 } 1148 if ($g < 0) { 1149 $g = 0; 1150 } 1151 $subsetglyphs[$g] = true; 1152 } 1153 } 1154 } 1155 break; 1156 } 1157 case 6: { // Format 6: Trimmed table mapping 1158 $offset += 4; // skip length and version/language 1159 $firstCode = TCPDF_STATIC::_getUSHORT($font, $offset); 1160 $offset += 2; 1161 $entryCount = TCPDF_STATIC::_getUSHORT($font, $offset); 1162 $offset += 2; 1163 for ($k = 0; $k < $entryCount; ++$k) { 1164 $c = ($k + $firstCode); 1165 if (isset($subsetchars[$c])) { 1166 $g = TCPDF_STATIC::_getUSHORT($font, $offset); 1167 $subsetglyphs[$g] = true; 1168 } 1169 $offset += 2; 1170 } 1171 break; 1172 } 1173 case 8: { // Format 8: Mixed 16-bit and 32-bit coverage 1174 $offset += 10; // skip reserved, length and version/language 1175 for ($k = 0; $k < 8192; ++$k) { 1176 $is32[$k] = TCPDF_STATIC::_getBYTE($font, $offset); 1177 ++$offset; 1178 } 1179 $nGroups = TCPDF_STATIC::_getULONG($font, $offset); 1180 $offset += 4; 1181 for ($i = 0; $i < $nGroups; ++$i) { 1182 $startCharCode = TCPDF_STATIC::_getULONG($font, $offset); 1183 $offset += 4; 1184 $endCharCode = TCPDF_STATIC::_getULONG($font, $offset); 1185 $offset += 4; 1186 $startGlyphID = TCPDF_STATIC::_getULONG($font, $offset); 1187 $offset += 4; 1188 for ($k = $startCharCode; $k <= $endCharCode; ++$k) { 1189 $is32idx = floor($c / 8); 1190 if ((isset($is32[$is32idx])) AND (($is32[$is32idx] & (1 << (7 - ($c % 8)))) == 0)) { 1191 $c = $k; 1192 } else { 1193 // 32 bit format 1194 // convert to decimal (http://www.unicode.org/faq//utf_bom.html#utf16-4) 1195 //LEAD_OFFSET = (0xD800 - (0x10000 >> 10)) = 55232 1196 //SURROGATE_OFFSET = (0x10000 - (0xD800 << 10) - 0xDC00) = -56613888 1197 $c = ((55232 + ($k >> 10)) << 10) + (0xDC00 + ($k & 0x3FF)) -56613888; 1198 } 1199 if (isset($subsetchars[$c])) { 1200 $subsetglyphs[$startGlyphID] = true; 1201 } 1202 ++$startGlyphID; 1203 } 1204 } 1205 break; 1206 } 1207 case 10: { // Format 10: Trimmed array 1208 $offset += 10; // skip reserved, length and version/language 1209 $startCharCode = TCPDF_STATIC::_getULONG($font, $offset); 1210 $offset += 4; 1211 $numChars = TCPDF_STATIC::_getULONG($font, $offset); 1212 $offset += 4; 1213 for ($k = 0; $k < $numChars; ++$k) { 1214 $c = ($k + $startCharCode); 1215 if (isset($subsetchars[$c])) { 1216 $g = TCPDF_STATIC::_getUSHORT($font, $offset); 1217 $subsetglyphs[$g] = true; 1218 } 1219 $offset += 2; 1220 } 1221 break; 1222 } 1223 case 12: { // Format 12: Segmented coverage 1224 $offset += 10; // skip length and version/language 1225 $nGroups = TCPDF_STATIC::_getULONG($font, $offset); 1226 $offset += 4; 1227 for ($k = 0; $k < $nGroups; ++$k) { 1228 $startCharCode = TCPDF_STATIC::_getULONG($font, $offset); 1229 $offset += 4; 1230 $endCharCode = TCPDF_STATIC::_getULONG($font, $offset); 1231 $offset += 4; 1232 $startGlyphCode = TCPDF_STATIC::_getULONG($font, $offset); 1233 $offset += 4; 1234 for ($c = $startCharCode; $c <= $endCharCode; ++$c) { 1235 if (isset($subsetchars[$c])) { 1236 $subsetglyphs[$startGlyphCode] = true; 1237 } 1238 ++$startGlyphCode; 1239 } 1240 } 1241 break; 1242 } 1243 case 13: { // Format 13: Many-to-one range mappings 1244 // to be implemented ... 1245 break; 1246 } 1247 case 14: { // Format 14: Unicode Variation Sequences 1248 // to be implemented ... 1249 break; 1250 } 1251 } 1252 } 1253 // include all parts of composite glyphs 1254 $new_sga = $subsetglyphs; 1255 while (!empty($new_sga)) { 1256 $sga = $new_sga; 1257 $new_sga = array(); 1258 foreach ($sga as $key => $val) { 1259 if (isset($indexToLoc[$key])) { 1260 $offset = ($table['glyf']['offset'] + $indexToLoc[$key]); 1261 $numberOfContours = TCPDF_STATIC::_getSHORT($font, $offset); 1262 $offset += 2; 1263 if ($numberOfContours < 0) { // composite glyph 1264 $offset += 8; // skip xMin, yMin, xMax, yMax 1265 do { 1266 $flags = TCPDF_STATIC::_getUSHORT($font, $offset); 1267 $offset += 2; 1268 $glyphIndex = TCPDF_STATIC::_getUSHORT($font, $offset); 1269 $offset += 2; 1270 if (!isset($subsetglyphs[$glyphIndex])) { 1271 // add missing glyphs 1272 $new_sga[$glyphIndex] = true; 1273 } 1274 // skip some bytes by case 1275 if ($flags & 1) { 1276 $offset += 4; 1277 } else { 1278 $offset += 2; 1279 } 1280 if ($flags & 8) { 1281 $offset += 2; 1282 } elseif ($flags & 64) { 1283 $offset += 4; 1284 } elseif ($flags & 128) { 1285 $offset += 8; 1286 } 1287 } while ($flags & 32); 1288 } 1289 } 1290 } 1291 $subsetglyphs += $new_sga; 1292 } 1293 // sort glyphs by key (and remove duplicates) 1294 ksort($subsetglyphs); 1295 // build new glyf and loca tables 1296 $glyf = ''; 1297 $loca = ''; 1298 $offset = 0; 1299 $glyf_offset = $table['glyf']['offset']; 1300 for ($i = 0; $i < $tot_num_glyphs; ++$i) { 1301 if (isset($subsetglyphs[$i])) { 1302 $length = ($indexToLoc[($i + 1)] - $indexToLoc[$i]); 1303 $glyf .= substr($font, ($glyf_offset + $indexToLoc[$i]), $length); 1304 } else { 1305 $length = 0; 1306 } 1307 if ($short_offset) { 1308 $loca .= pack('n', floor($offset / 2)); 1309 } else { 1310 $loca .= pack('N', $offset); 1311 } 1312 $offset += $length; 1313 } 1314 // array of table names to preserve (loca and glyf tables will be added later) 1315 // the cmap table is not needed and shall not be present, since the mapping from character codes to glyph descriptions is provided separately 1316 $table_names = array ('head', 'hhea', 'hmtx', 'maxp', 'cvt ', 'fpgm', 'prep'); // minimum required table names 1317 // get the tables to preserve 1318 $offset = 12; 1319 foreach ($table as $tag => $val) { 1320 if (in_array($tag, $table_names)) { 1321 $table[$tag]['data'] = substr($font, $table[$tag]['offset'], $table[$tag]['length']); 1322 if ($tag == 'head') { 1323 // set the checkSumAdjustment to 0 1324 $table[$tag]['data'] = substr($table[$tag]['data'], 0, 8)."\x0\x0\x0\x0".substr($table[$tag]['data'], 12); 1325 } 1326 $pad = 4 - ($table[$tag]['length'] % 4); 1327 if ($pad != 4) { 1328 // the length of a table must be a multiple of four bytes 1329 $table[$tag]['length'] += $pad; 1330 $table[$tag]['data'] .= str_repeat("\x0", $pad); 1331 } 1332 $table[$tag]['offset'] = $offset; 1333 $offset += $table[$tag]['length']; 1334 // check sum is not changed (so keep the following line commented) 1335 //$table[$tag]['checkSum'] = self::_getTTFtableChecksum($table[$tag]['data'], $table[$tag]['length']); 1336 } else { 1337 unset($table[$tag]); 1338 } 1339 } 1340 // add loca 1341 $table['loca']['data'] = $loca; 1342 $table['loca']['length'] = strlen($loca); 1343 $pad = 4 - ($table['loca']['length'] % 4); 1344 if ($pad != 4) { 1345 // the length of a table must be a multiple of four bytes 1346 $table['loca']['length'] += $pad; 1347 $table['loca']['data'] .= str_repeat("\x0", $pad); 1348 } 1349 $table['loca']['offset'] = $offset; 1350 $table['loca']['checkSum'] = self::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']); 1351 $offset += $table['loca']['length']; 1352 // add glyf 1353 $table['glyf']['data'] = $glyf; 1354 $table['glyf']['length'] = strlen($glyf); 1355 $pad = 4 - ($table['glyf']['length'] % 4); 1356 if ($pad != 4) { 1357 // the length of a table must be a multiple of four bytes 1358 $table['glyf']['length'] += $pad; 1359 $table['glyf']['data'] .= str_repeat("\x0", $pad); 1360 } 1361 $table['glyf']['offset'] = $offset; 1362 $table['glyf']['checkSum'] = self::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']); 1363 // rebuild font 1364 $font = ''; 1365 $font .= pack('N', 0x10000); // sfnt version 1366 $numTables = count($table); 1367 $font .= pack('n', $numTables); // numTables 1368 $entrySelector = floor(log($numTables, 2)); 1369 $searchRange = pow(2, $entrySelector) * 16; 1370 $rangeShift = ($numTables * 16) - $searchRange; 1371 $font .= pack('n', $searchRange); // searchRange 1372 $font .= pack('n', $entrySelector); // entrySelector 1373 $font .= pack('n', $rangeShift); // rangeShift 1374 $offset = ($numTables * 16); 1375 foreach ($table as $tag => $data) { 1376 $font .= $tag; // tag 1377 $font .= pack('N', $data['checkSum']); // checkSum 1378 $font .= pack('N', ($data['offset'] + $offset)); // offset 1379 $font .= pack('N', $data['length']); // length 1380 } 1381 foreach ($table as $data) { 1382 $font .= $data['data']; 1383 } 1384 // set checkSumAdjustment on head table 1385 $checkSumAdjustment = 0xB1B0AFBA - self::_getTTFtableChecksum($font, strlen($font)); 1386 $font = substr($font, 0, $table['head']['offset'] + 8).pack('N', $checkSumAdjustment).substr($font, $table['head']['offset'] + 12); 1387 return $font; 1388 } 1389 1390 /** 1391 * Outputs font widths 1392 * @param array $font font data 1393 * @param int $cidoffset offset for CID values 1394 * @return string PDF command string for font widths 1395 * @author Nicola Asuni 1396 * @since 4.4.000 (2008-12-07) 1397 * @public static 1398 */ 1399 public static function _putfontwidths($font, $cidoffset=0) { 1400 ksort($font['cw']); 1401 $rangeid = 0; 1402 $range = array(); 1403 $prevcid = -2; 1404 $prevwidth = -1; 1405 $interval = false; 1406 // for each character 1407 foreach ($font['cw'] as $cid => $width) { 1408 $cid -= $cidoffset; 1409 if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) { 1410 // ignore the unused characters (font subsetting) 1411 continue; 1412 } 1413 if ($width != $font['dw']) { 1414 if ($cid == ($prevcid + 1)) { 1415 // consecutive CID 1416 if ($width == $prevwidth) { 1417 if ($width == $range[$rangeid][0]) { 1418 $range[$rangeid][] = $width; 1419 } else { 1420 array_pop($range[$rangeid]); 1421 // new range 1422 $rangeid = $prevcid; 1423 $range[$rangeid] = array(); 1424 $range[$rangeid][] = $prevwidth; 1425 $range[$rangeid][] = $width; 1426 } 1427 $interval = true; 1428 $range[$rangeid]['interval'] = true; 1429 } else { 1430 if ($interval) { 1431 // new range 1432 $rangeid = $cid; 1433 $range[$rangeid] = array(); 1434 $range[$rangeid][] = $width; 1435 } else { 1436 $range[$rangeid][] = $width; 1437 } 1438 $interval = false; 1439 } 1440 } else { 1441 // new range 1442 $rangeid = $cid; 1443 $range[$rangeid] = array(); 1444 $range[$rangeid][] = $width; 1445 $interval = false; 1446 } 1447 $prevcid = $cid; 1448 $prevwidth = $width; 1449 } 1450 } 1451 // optimize ranges 1452 $prevk = -1; 1453 $nextk = -1; 1454 $prevint = false; 1455 foreach ($range as $k => $ws) { 1456 $cws = count($ws); 1457 if (($k == $nextk) AND (!$prevint) AND ((!isset($ws['interval'])) OR ($cws < 4))) { 1458 if (isset($range[$k]['interval'])) { 1459 unset($range[$k]['interval']); 1460 } 1461 $range[$prevk] = array_merge($range[$prevk], $range[$k]); 1462 unset($range[$k]); 1463 } else { 1464 $prevk = $k; 1465 } 1466 $nextk = $k + $cws; 1467 if (isset($ws['interval'])) { 1468 if ($cws > 3) { 1469 $prevint = true; 1470 } else { 1471 $prevint = false; 1472 } 1473 if (isset($range[$k]['interval'])) { 1474 unset($range[$k]['interval']); 1475 } 1476 --$nextk; 1477 } else { 1478 $prevint = false; 1479 } 1480 } 1481 // output data 1482 $w = ''; 1483 foreach ($range as $k => $ws) { 1484 if (count(array_count_values($ws)) == 1) { 1485 // interval mode is more compact 1486 $w .= ' '.$k.' '.($k + count($ws) - 1).' '.$ws[0]; 1487 } else { 1488 // range mode 1489 $w .= ' '.$k.' [ '.implode(' ', $ws).' ]'; 1490 } 1491 } 1492 return '/W ['.$w.' ]'; 1493 } 1494 1495 1496 1497 1498 /** 1499 * Update the CIDToGIDMap string with a new value. 1500 * @param string $map CIDToGIDMap. 1501 * @param int $cid CID value. 1502 * @param int $gid GID value. 1503 * @return string CIDToGIDMap. 1504 * @author Nicola Asuni 1505 * @since 5.9.123 (2011-09-29) 1506 * @public static 1507 */ 1508 public static function updateCIDtoGIDmap($map, $cid, $gid) { 1509 if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) { 1510 if ($gid > 0xFFFF) { 1511 $gid -= 0x10000; 1512 } 1513 $map[($cid * 2)] = chr($gid >> 8); 1514 $map[(($cid * 2) + 1)] = chr($gid & 0xFF); 1515 } 1516 return $map; 1517 } 1518 1519 /** 1520 * Return fonts path 1521 * @return string 1522 * @public static 1523 */ 1524 public static function _getfontpath() { 1525 if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__).'/../fonts'))) { 1526 if (substr($fdir, -1) != '/') { 1527 $fdir .= '/'; 1528 } 1529 define('K_PATH_FONTS', $fdir); 1530 } 1531 return defined('K_PATH_FONTS') ? K_PATH_FONTS : ''; 1532 } 1533 1534 1535 1536 /** 1537 * Return font full path 1538 * @param string $file Font file name. 1539 * @param string $fontdir Font directory (set to false fto search on default directories) 1540 * @return string Font full path or empty string 1541 * @author Nicola Asuni 1542 * @since 6.0.025 1543 * @public static 1544 */ 1545 public static function getFontFullPath($file, $fontdir=false) { 1546 $fontfile = ''; 1547 // search files on various directories 1548 if (($fontdir !== false) AND @TCPDF_STATIC::file_exists($fontdir.$file)) { 1549 $fontfile = $fontdir.$file; 1550 } elseif (@TCPDF_STATIC::file_exists(self::_getfontpath().$file)) { 1551 $fontfile = self::_getfontpath().$file; 1552 } elseif (@TCPDF_STATIC::file_exists($file)) { 1553 $fontfile = $file; 1554 } 1555 return $fontfile; 1556 } 1557 1558 1559 1560 1561 /** 1562 * Get a reference font size. 1563 * @param string $size String containing font size value. 1564 * @param float $refsize Reference font size in points. 1565 * @return float value in points 1566 * @public static 1567 */ 1568 public static function getFontRefSize($size, $refsize=12) { 1569 switch ($size) { 1570 case 'xx-small': { 1571 $size = ($refsize - 4); 1572 break; 1573 } 1574 case 'x-small': { 1575 $size = ($refsize - 3); 1576 break; 1577 } 1578 case 'small': { 1579 $size = ($refsize - 2); 1580 break; 1581 } 1582 case 'medium': { 1583 $size = $refsize; 1584 break; 1585 } 1586 case 'large': { 1587 $size = ($refsize + 2); 1588 break; 1589 } 1590 case 'x-large': { 1591 $size = ($refsize + 4); 1592 break; 1593 } 1594 case 'xx-large': { 1595 $size = ($refsize + 6); 1596 break; 1597 } 1598 case 'smaller': { 1599 $size = ($refsize - 3); 1600 break; 1601 } 1602 case 'larger': { 1603 $size = ($refsize + 3); 1604 break; 1605 } 1606 } 1607 return $size; 1608 } 1609 1610 1611 1612 1613 1614 1615 1616 1617 1618 1619 1620 1621 1622 1623 1624 1625 1626 1627 1628 1629 1630 1631 1632 1633 1634 1635 1636 1637 1638 1639 1640 1641 1642 1643 1644 1645 1646 1647 1648 1649 // ==================================================================================================================== 1650 // REIMPLEMENTED 1651 // ==================================================================================================================== 1652 1653 1654 1655 1656 1657 1658 1659 1660 /** 1661 * Returns the unicode caracter specified by the value 1662 * @param int $c UTF-8 value 1663 * @param boolean $unicode True if we are in unicode mode, false otherwise. 1664 * @return string Returns the specified character. 1665 * @since 2.3.000 (2008-03-05) 1666 * @public static 1667 */ 1668 public static function unichr($c, $unicode=true) { 1669 $c = intval($c); 1670 if (!$unicode) { 1671 return chr($c); 1672 } elseif ($c <= 0x7F) { 1673 // one byte 1674 return chr($c); 1675 } elseif ($c <= 0x7FF) { 1676 // two bytes 1677 return chr(0xC0 | $c >> 6).chr(0x80 | $c & 0x3F); 1678 } elseif ($c <= 0xFFFF) { 1679 // three bytes 1680 return chr(0xE0 | $c >> 12).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); 1681 } elseif ($c <= 0x10FFFF) { 1682 // four bytes 1683 return chr(0xF0 | $c >> 18).chr(0x80 | $c >> 12 & 0x3F).chr(0x80 | $c >> 6 & 0x3F).chr(0x80 | $c & 0x3F); 1684 } else { 1685 return ''; 1686 } 1687 } 1688 1689 /** 1690 * Returns the unicode caracter specified by UTF-8 value 1691 * @param int $c UTF-8 value 1692 * @return string Returns the specified character. 1693 * @public static 1694 */ 1695 public static function unichrUnicode($c) { 1696 return self::unichr($c, true); 1697 } 1698 1699 /** 1700 * Returns the unicode caracter specified by ASCII value 1701 * @param int $c UTF-8 value 1702 * @return string Returns the specified character. 1703 * @public static 1704 */ 1705 public static function unichrASCII($c) { 1706 return self::unichr($c, false); 1707 } 1708 1709 /** 1710 * Converts array of UTF-8 characters to UTF16-BE string.<br> 1711 * Based on: http://www.faqs.org/rfcs/rfc2781.html 1712 * <pre> 1713 * Encoding UTF-16: 1714 * 1715 * Encoding of a single character from an ISO 10646 character value to 1716 * UTF-16 proceeds as follows. Let U be the character number, no greater 1717 * than 0x10FFFF. 1718 * 1719 * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and 1720 * terminate. 1721 * 1722 * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF, 1723 * U' must be less than or equal to 0xFFFFF. That is, U' can be 1724 * represented in 20 bits. 1725 * 1726 * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and 1727 * 0xDC00, respectively. These integers each have 10 bits free to 1728 * encode the character value, for a total of 20 bits. 1729 * 1730 * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order 1731 * bits of W1 and the 10 low-order bits of U' to the 10 low-order 1732 * bits of W2. Terminate. 1733 * 1734 * Graphically, steps 2 through 4 look like: 1735 * U' = yyyyyyyyyyxxxxxxxxxx 1736 * W1 = 110110yyyyyyyyyy 1737 * W2 = 110111xxxxxxxxxx 1738 * </pre> 1739 * @param array $unicode array containing UTF-8 unicode values 1740 * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF) 1741 * @return string 1742 * @protected 1743 * @author Nicola Asuni 1744 * @since 2.1.000 (2008-01-08) 1745 * @public static 1746 */ 1747 public static function arrUTF8ToUTF16BE($unicode, $setbom=false) { 1748 $outstr = ''; // string to be returned 1749 if ($setbom) { 1750 $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM) 1751 } 1752 foreach ($unicode as $char) { 1753 if ($char == 0x200b) { 1754 // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B) 1755 } elseif ($char == 0xFFFD) { 1756 $outstr .= "\xFF\xFD"; // replacement character 1757 } elseif ($char < 0x10000) { 1758 $outstr .= chr($char >> 0x08); 1759 $outstr .= chr($char & 0xFF); 1760 } else { 1761 $char -= 0x10000; 1762 $w1 = 0xD800 | ($char >> 0x0a); 1763 $w2 = 0xDC00 | ($char & 0x3FF); 1764 $outstr .= chr($w1 >> 0x08); 1765 $outstr .= chr($w1 & 0xFF); 1766 $outstr .= chr($w2 >> 0x08); 1767 $outstr .= chr($w2 & 0xFF); 1768 } 1769 } 1770 return $outstr; 1771 } 1772 1773 /** 1774 * Convert an array of UTF8 values to array of unicode characters 1775 * @param array $ta The input array of UTF8 values. 1776 * @param boolean $isunicode True for Unicode mode, false otherwise. 1777 * @return array Return array of unicode characters 1778 * @since 4.5.037 (2009-04-07) 1779 * @public static 1780 */ 1781 public static function UTF8ArrayToUniArray($ta, $isunicode=true) { 1782 if ($isunicode) { 1783 return array_map(array('TCPDF_FONTS', 'unichrUnicode'), $ta); 1784 } 1785 return array_map(array('TCPDF_FONTS', 'unichrASCII'), $ta); 1786 } 1787 1788 /** 1789 * Extract a slice of the $strarr array and return it as string. 1790 * @param string[] $strarr The input array of characters. 1791 * @param int $start the starting element of $strarr. 1792 * @param int $end first element that will not be returned. 1793 * @param boolean $unicode True if we are in unicode mode, false otherwise. 1794 * @return string Return part of a string 1795 * @public static 1796 */ 1797 public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) { 1798 if (strlen($start) == 0) { 1799 $start = 0; 1800 } 1801 if (strlen($end) == 0) { 1802 $end = count($strarr); 1803 } 1804 $string = ''; 1805 for ($i = $start; $i < $end; ++$i) { 1806 $string .= self::unichr($strarr[$i], $unicode); 1807 } 1808 return $string; 1809 } 1810 1811 /** 1812 * Extract a slice of the $uniarr array and return it as string. 1813 * @param string[] $uniarr The input array of characters. 1814 * @param int $start the starting element of $strarr. 1815 * @param int $end first element that will not be returned. 1816 * @return string Return part of a string 1817 * @since 4.5.037 (2009-04-07) 1818 * @public static 1819 */ 1820 public static function UniArrSubString($uniarr, $start='', $end='') { 1821 if (strlen($start) == 0) { 1822 $start = 0; 1823 } 1824 if (strlen($end) == 0) { 1825 $end = count($uniarr); 1826 } 1827 $string = ''; 1828 for ($i=$start; $i < $end; ++$i) { 1829 $string .= $uniarr[$i]; 1830 } 1831 return $string; 1832 } 1833 1834 /** 1835 * Converts UTF-8 characters array to array of Latin1 characters array<br> 1836 * @param array $unicode array containing UTF-8 unicode values 1837 * @return array 1838 * @author Nicola Asuni 1839 * @since 4.8.023 (2010-01-15) 1840 * @public static 1841 */ 1842 public static function UTF8ArrToLatin1Arr($unicode) { 1843 $outarr = array(); // array to be returned 1844 foreach ($unicode as $char) { 1845 if ($char < 256) { 1846 $outarr[] = $char; 1847 } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) { 1848 // map from UTF-8 1849 $outarr[] = TCPDF_FONT_DATA::$uni_utf8tolatin[$char]; 1850 } elseif ($char == 0xFFFD) { 1851 // skip 1852 } else { 1853 $outarr[] = 63; // '?' character 1854 } 1855 } 1856 return $outarr; 1857 } 1858 1859 /** 1860 * Converts UTF-8 characters array to Latin1 string<br> 1861 * @param array $unicode array containing UTF-8 unicode values 1862 * @return string 1863 * @author Nicola Asuni 1864 * @since 4.8.023 (2010-01-15) 1865 * @public static 1866 */ 1867 public static function UTF8ArrToLatin1($unicode) { 1868 $outstr = ''; // string to be returned 1869 foreach ($unicode as $char) { 1870 if ($char < 256) { 1871 $outstr .= chr($char); 1872 } elseif (array_key_exists($char, TCPDF_FONT_DATA::$uni_utf8tolatin)) { 1873 // map from UTF-8 1874 $outstr .= chr(TCPDF_FONT_DATA::$uni_utf8tolatin[$char]); 1875 } elseif ($char == 0xFFFD) { 1876 // skip 1877 } else { 1878 $outstr .= '?'; 1879 } 1880 } 1881 return $outstr; 1882 } 1883 1884 /** 1885 * Converts UTF-8 character to integer value.<br> 1886 * Uses the getUniord() method if the value is not cached. 1887 * @param string $uch character string to process. 1888 * @return int Unicode value 1889 * @public static 1890 */ 1891 public static function uniord($uch) { 1892 if (!isset(self::$cache_uniord[$uch])) { 1893 self::$cache_uniord[$uch] = self::getUniord($uch); 1894 } 1895 return self::$cache_uniord[$uch]; 1896 } 1897 1898 /** 1899 * Converts UTF-8 character to integer value.<br> 1900 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br> 1901 * Based on: http://www.faqs.org/rfcs/rfc3629.html 1902 * <pre> 1903 * Char. number range | UTF-8 octet sequence 1904 * (hexadecimal) | (binary) 1905 * --------------------+----------------------------------------------- 1906 * 0000 0000-0000 007F | 0xxxxxxx 1907 * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx 1908 * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx 1909 * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx 1910 * --------------------------------------------------------------------- 1911 * 1912 * ABFN notation: 1913 * --------------------------------------------------------------------- 1914 * UTF8-octets = *( UTF8-char ) 1915 * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4 1916 * UTF8-1 = %x00-7F 1917 * UTF8-2 = %xC2-DF UTF8-tail 1918 * 1919 * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) / 1920 * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail ) 1921 * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) / 1922 * %xF4 %x80-8F 2( UTF8-tail ) 1923 * UTF8-tail = %x80-BF 1924 * --------------------------------------------------------------------- 1925 * </pre> 1926 * @param string $uch character string to process. 1927 * @return int Unicode value 1928 * @author Nicola Asuni 1929 * @public static 1930 */ 1931 public static function getUniord($uch) { 1932 if (function_exists('mb_convert_encoding')) { 1933 list(, $char) = @unpack('N', mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8')); 1934 if ($char >= 0) { 1935 return $char; 1936 } 1937 } 1938 $bytes = array(); // array containing single character byte sequences 1939 $countbytes = 0; 1940 $numbytes = 1; // number of octetc needed to represent the UTF-8 character 1941 $length = strlen($uch); 1942 for ($i = 0; $i < $length; ++$i) { 1943 $char = ord($uch[$i]); // get one string character at time 1944 if ($countbytes == 0) { // get starting octect 1945 if ($char <= 0x7F) { 1946 return $char; // use the character "as is" because is ASCII 1947 } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN) 1948 $bytes[] = ($char - 0xC0) << 0x06; 1949 ++$countbytes; 1950 $numbytes = 2; 1951 } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN) 1952 $bytes[] = ($char - 0xE0) << 0x0C; 1953 ++$countbytes; 1954 $numbytes = 3; 1955 } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN) 1956 $bytes[] = ($char - 0xF0) << 0x12; 1957 ++$countbytes; 1958 $numbytes = 4; 1959 } else { 1960 // use replacement character for other invalid sequences 1961 return 0xFFFD; 1962 } 1963 } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN 1964 $bytes[] = $char - 0x80; 1965 ++$countbytes; 1966 if ($countbytes == $numbytes) { 1967 // compose UTF-8 bytes to a single unicode value 1968 $char = $bytes[0]; 1969 for ($j = 1; $j < $numbytes; ++$j) { 1970 $char += ($bytes[$j] << (($numbytes - $j - 1) * 0x06)); 1971 } 1972 if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) { 1973 // The definition of UTF-8 prohibits encoding character numbers between 1974 // U+D800 and U+DFFF, which are reserved for use with the UTF-16 1975 // encoding form (as surrogate pairs) and do not directly represent 1976 // characters. 1977 return 0xFFFD; // use replacement character 1978 } else { 1979 return $char; 1980 } 1981 } 1982 } else { 1983 // use replacement character for other invalid sequences 1984 return 0xFFFD; 1985 } 1986 } 1987 return 0xFFFD; 1988 } 1989 1990 /** 1991 * Converts UTF-8 strings to codepoints array.<br> 1992 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br> 1993 * @param string $str string to process. 1994 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise. 1995 * @param array $currentfont Reference to current font array. 1996 * @return array containing codepoints (UTF-8 characters values) 1997 * @author Nicola Asuni 1998 * @public static 1999 */ 2000 public static function UTF8StringToArray($str, $isunicode, &$currentfont) { 2001 $str = is_null($str) ? '' : $str; 2002 if ($isunicode) { 2003 // requires PCRE unicode support turned on 2004 $chars = TCPDF_STATIC::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY); 2005 $carr = array_map(array('TCPDF_FONTS', 'uniord'), $chars); 2006 } else { 2007 $chars = str_split($str); 2008 $carr = array_map('ord', $chars); 2009 } 2010 if (is_array($currentfont['subsetchars']) && is_array($carr)) { 2011 $currentfont['subsetchars'] += array_fill_keys($carr, true); 2012 } else { 2013 $currentfont['subsetchars'] = array_merge($currentfont['subsetchars'], $carr); 2014 } 2015 return $carr; 2016 } 2017 2018 /** 2019 * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br> 2020 * @param string $str string to process. 2021 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise. 2022 * @param array $currentfont Reference to current font array. 2023 * @return string 2024 * @since 3.2.000 (2008-06-23) 2025 * @public static 2026 */ 2027 public static function UTF8ToLatin1($str, $isunicode, &$currentfont) { 2028 $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values 2029 return self::UTF8ArrToLatin1($unicode); 2030 } 2031 2032 /** 2033 * Converts UTF-8 strings to UTF16-BE.<br> 2034 * @param string $str string to process. 2035 * @param boolean $setbom if true set the Byte Order Mark (BOM = 0xFEFF) 2036 * @param boolean $isunicode True when the documetn is in Unicode mode, false otherwise. 2037 * @param array $currentfont Reference to current font array. 2038 * @return string 2039 * @author Nicola Asuni 2040 * @since 1.53.0.TC005 (2005-01-05) 2041 * @public static 2042 */ 2043 public static function UTF8ToUTF16BE($str, $setbom, $isunicode, &$currentfont) { 2044 if (!$isunicode) { 2045 return $str; // string is not in unicode 2046 } 2047 $unicode = self::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values 2048 return self::arrUTF8ToUTF16BE($unicode, $setbom); 2049 } 2050 2051 /** 2052 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). 2053 * @param string $str string to manipulate. 2054 * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF) 2055 * @param bool $forcertl if true forces RTL text direction 2056 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise. 2057 * @param array $currentfont Reference to current font array. 2058 * @return string 2059 * @author Nicola Asuni 2060 * @since 2.1.000 (2008-01-08) 2061 * @public static 2062 */ 2063 public static function utf8StrRev($str, $setbom, $forcertl, $isunicode, &$currentfont) { 2064 return self::utf8StrArrRev(self::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont); 2065 } 2066 2067 /** 2068 * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). 2069 * @param array $arr array of unicode values. 2070 * @param string $str string to manipulate (or empty value). 2071 * @param bool $setbom if true set the Byte Order Mark (BOM = 0xFEFF) 2072 * @param bool $forcertl if true forces RTL text direction 2073 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise. 2074 * @param array $currentfont Reference to current font array. 2075 * @return string 2076 * @author Nicola Asuni 2077 * @since 4.9.000 (2010-03-27) 2078 * @public static 2079 */ 2080 public static function utf8StrArrRev($arr, $str, $setbom, $forcertl, $isunicode, &$currentfont) { 2081 return self::arrUTF8ToUTF16BE(self::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom); 2082 } 2083 2084 /** 2085 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/). 2086 * @param array $ta array of characters composing the string. 2087 * @param string $str string to process 2088 * @param bool $forcertl if 'R' forces RTL, if 'L' forces LTR 2089 * @param boolean $isunicode True if the document is in Unicode mode, false otherwise. 2090 * @param array $currentfont Reference to current font array. 2091 * @return array of unicode chars 2092 * @author Nicola Asuni 2093 * @since 2.4.000 (2008-03-06) 2094 * @public static 2095 */ 2096 public static function utf8Bidi($ta, $str, $forcertl, $isunicode, &$currentfont) { 2097 // paragraph embedding level 2098 $pel = 0; 2099 // max level 2100 $maxlevel = 0; 2101 if (TCPDF_STATIC::empty_string($str)) { 2102 // create string from array 2103 $str = self::UTF8ArrSubString($ta, '', '', $isunicode); 2104 } 2105 // check if string contains arabic text 2106 if (preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_ARABIC, $str)) { 2107 $arabic = true; 2108 } else { 2109 $arabic = false; 2110 } 2111 // check if string contains RTL text 2112 if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA::$uni_RE_PATTERN_RTL, $str))) { 2113 return $ta; 2114 } 2115 2116 // get number of chars 2117 $numchars = count($ta); 2118 2119 if ($forcertl == 'R') { 2120 $pel = 1; 2121 } elseif ($forcertl == 'L') { 2122 $pel = 0; 2123 } else { 2124 // P2. In each paragraph, find the first character of type L, AL, or R. 2125 // P3. If a character is found in P2 and it is of type AL or R, then set the paragraph embedding level to one; otherwise, set it to zero. 2126 for ($i=0; $i < $numchars; ++$i) { 2127 $type = TCPDF_FONT_DATA::$uni_type[$ta[$i]]; 2128 if ($type == 'L') { 2129 $pel = 0; 2130 break; 2131 } elseif (($type == 'AL') OR ($type == 'R')) { 2132 $pel = 1; 2133 break; 2134 } 2135 } 2136 } 2137 2138 // Current Embedding Level 2139 $cel = $pel; 2140 // directional override status 2141 $dos = 'N'; 2142 $remember = array(); 2143 // start-of-level-run 2144 $sor = $pel % 2 ? 'R' : 'L'; 2145 $eor = $sor; 2146 2147 // Array of characters data 2148 $chardata = Array(); 2149 2150 // X1. Begin by setting the current embedding level to the paragraph embedding level. Set the directional override status to neutral. Process each character iteratively, applying rules X2 through X9. Only embedding levels from 0 to 61 are valid in this phase. 2151 // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached. 2152 for ($i=0; $i < $numchars; ++$i) { 2153 if ($ta[$i] == TCPDF_FONT_DATA::$uni_RLE) { 2154 // X2. With each RLE, compute the least greater odd embedding level. 2155 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral. 2156 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status. 2157 $next_level = $cel + ($cel % 2) + 1; 2158 if ($next_level < 62) { 2159 $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLE, 'cel' => $cel, 'dos' => $dos); 2160 $cel = $next_level; 2161 $dos = 'N'; 2162 $sor = $eor; 2163 $eor = $cel % 2 ? 'R' : 'L'; 2164 } 2165 } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRE) { 2166 // X3. With each LRE, compute the least greater even embedding level. 2167 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to neutral. 2168 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status. 2169 $next_level = $cel + 2 - ($cel % 2); 2170 if ( $next_level < 62 ) { 2171 $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRE, 'cel' => $cel, 'dos' => $dos); 2172 $cel = $next_level; 2173 $dos = 'N'; 2174 $sor = $eor; 2175 $eor = $cel % 2 ? 'R' : 'L'; 2176 } 2177 } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_RLO) { 2178 // X4. With each RLO, compute the least greater odd embedding level. 2179 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to right-to-left. 2180 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status. 2181 $next_level = $cel + ($cel % 2) + 1; 2182 if ($next_level < 62) { 2183 $remember[] = array('num' => TCPDF_FONT_DATA::$uni_RLO, 'cel' => $cel, 'dos' => $dos); 2184 $cel = $next_level; 2185 $dos = 'R'; 2186 $sor = $eor; 2187 $eor = $cel % 2 ? 'R' : 'L'; 2188 } 2189 } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_LRO) { 2190 // X5. With each LRO, compute the least greater even embedding level. 2191 // a. If this new level would be valid, then this embedding code is valid. Remember (push) the current embedding level and override status. Reset the current level to this new level, and reset the override status to left-to-right. 2192 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status. 2193 $next_level = $cel + 2 - ($cel % 2); 2194 if ( $next_level < 62 ) { 2195 $remember[] = array('num' => TCPDF_FONT_DATA::$uni_LRO, 'cel' => $cel, 'dos' => $dos); 2196 $cel = $next_level; 2197 $dos = 'L'; 2198 $sor = $eor; 2199 $eor = $cel % 2 ? 'R' : 'L'; 2200 } 2201 } elseif ($ta[$i] == TCPDF_FONT_DATA::$uni_PDF) { 2202 // X7. With each PDF, determine the matching embedding or override code. If there was a valid matching code, restore (pop) the last remembered (pushed) embedding level and directional override. 2203 if (count($remember)) { 2204 $last = count($remember ) - 1; 2205 if (($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLE) OR 2206 ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRE) OR 2207 ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_RLO) OR 2208 ($remember[$last]['num'] == TCPDF_FONT_DATA::$uni_LRO)) { 2209 $match = array_pop($remember); 2210 $cel = $match['cel']; 2211 $dos = $match['dos']; 2212 $sor = $eor; 2213 $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L'; 2214 } 2215 } 2216 } elseif (($ta[$i] != TCPDF_FONT_DATA::$uni_RLE) AND 2217 ($ta[$i] != TCPDF_FONT_DATA::$uni_LRE) AND 2218 ($ta[$i] != TCPDF_FONT_DATA::$uni_RLO) AND 2219 ($ta[$i] != TCPDF_FONT_DATA::$uni_LRO) AND 2220 ($ta[$i] != TCPDF_FONT_DATA::$uni_PDF)) { 2221 // X6. For all types besides RLE, LRE, RLO, LRO, and PDF: 2222 // a. Set the level of the current character to the current embedding level. 2223 // b. Whenever the directional override status is not neutral, reset the current character type to the directional override status. 2224 if ($dos != 'N') { 2225 $chardir = $dos; 2226 } else { 2227 if (isset(TCPDF_FONT_DATA::$uni_type[$ta[$i]])) { 2228 $chardir = TCPDF_FONT_DATA::$uni_type[$ta[$i]]; 2229 } else { 2230 $chardir = 'L'; 2231 } 2232 } 2233 // stores string characters and other information 2234 $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor); 2235 } 2236 } // end for each char 2237 2238 // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding. 2239 // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes. 2240 // X10. The remaining rules are applied to each run of characters at the same level. For each run, determine the start-of-level-run (sor) and end-of-level-run (eor) type, either L or R. This depends on the higher of the two levels on either side of the boundary (at the start or end of the paragraph, the level of the 'other' run is the base embedding level). If the higher level is odd, the type is R; otherwise, it is L. 2241 2242 // 3.3.3 Resolving Weak Types 2243 // Weak types are now resolved one level run at a time. At level run boundaries where the type of the character on the other side of the boundary is required, the type assigned to sor or eor is used. 2244 // Nonspacing marks are now resolved based on the previous characters. 2245 $numchars = count($chardata); 2246 2247 // W1. Examine each nonspacing mark (NSM) in the level run, and change the type of the NSM to the type of the previous character. If the NSM is at the start of the level run, it will get the type of sor. 2248 $prevlevel = -1; // track level changes 2249 $levcount = 0; // counts consecutive chars at the same level 2250 for ($i=0; $i < $numchars; ++$i) { 2251 if ($chardata[$i]['type'] == 'NSM') { 2252 if ($levcount) { 2253 $chardata[$i]['type'] = $chardata[$i]['sor']; 2254 } elseif ($i > 0) { 2255 $chardata[$i]['type'] = $chardata[($i-1)]['type']; 2256 } 2257 } 2258 if ($chardata[$i]['level'] != $prevlevel) { 2259 $levcount = 0; 2260 } else { 2261 ++$levcount; 2262 } 2263 $prevlevel = $chardata[$i]['level']; 2264 } 2265 2266 // W2. Search backward from each instance of a European number until the first strong type (R, L, AL, or sor) is found. If an AL is found, change the type of the European number to Arabic number. 2267 $prevlevel = -1; 2268 $levcount = 0; 2269 for ($i=0; $i < $numchars; ++$i) { 2270 if ($chardata[$i]['char'] == 'EN') { 2271 for ($j=$levcount; $j >= 0; $j--) { 2272 if ($chardata[$j]['type'] == 'AL') { 2273 $chardata[$i]['type'] = 'AN'; 2274 } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) { 2275 break; 2276 } 2277 } 2278 } 2279 if ($chardata[$i]['level'] != $prevlevel) { 2280 $levcount = 0; 2281 } else { 2282 ++$levcount; 2283 } 2284 $prevlevel = $chardata[$i]['level']; 2285 } 2286 2287 // W3. Change all ALs to R. 2288 for ($i=0; $i < $numchars; ++$i) { 2289 if ($chardata[$i]['type'] == 'AL') { 2290 $chardata[$i]['type'] = 'R'; 2291 } 2292 } 2293 2294 // W4. A single European separator between two European numbers changes to a European number. A single common separator between two numbers of the same type changes to that type. 2295 $prevlevel = -1; 2296 $levcount = 0; 2297 for ($i=0; $i < $numchars; ++$i) { 2298 if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) { 2299 if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) { 2300 $chardata[$i]['type'] = 'EN'; 2301 } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+1)]['type'] == 'EN')) { 2302 $chardata[$i]['type'] = 'EN'; 2303 } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+1)]['type'] == 'AN')) { 2304 $chardata[$i]['type'] = 'AN'; 2305 } 2306 } 2307 if ($chardata[$i]['level'] != $prevlevel) { 2308 $levcount = 0; 2309 } else { 2310 ++$levcount; 2311 } 2312 $prevlevel = $chardata[$i]['level']; 2313 } 2314 2315 // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers. 2316 $prevlevel = -1; 2317 $levcount = 0; 2318 for ($i=0; $i < $numchars; ++$i) { 2319 if ($chardata[$i]['type'] == 'ET') { 2320 if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) { 2321 $chardata[$i]['type'] = 'EN'; 2322 } else { 2323 $j = $i+1; 2324 while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) { 2325 if ($chardata[$j]['type'] == 'EN') { 2326 $chardata[$i]['type'] = 'EN'; 2327 break; 2328 } elseif ($chardata[$j]['type'] != 'ET') { 2329 break; 2330 } 2331 ++$j; 2332 } 2333 } 2334 } 2335 if ($chardata[$i]['level'] != $prevlevel) { 2336 $levcount = 0; 2337 } else { 2338 ++$levcount; 2339 } 2340 $prevlevel = $chardata[$i]['level']; 2341 } 2342 2343 // W6. Otherwise, separators and terminators change to Other Neutral. 2344 $prevlevel = -1; 2345 $levcount = 0; 2346 for ($i=0; $i < $numchars; ++$i) { 2347 if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) { 2348 $chardata[$i]['type'] = 'ON'; 2349 } 2350 if ($chardata[$i]['level'] != $prevlevel) { 2351 $levcount = 0; 2352 } else { 2353 ++$levcount; 2354 } 2355 $prevlevel = $chardata[$i]['level']; 2356 } 2357 2358 //W7. Search backward from each instance of a European number until the first strong type (R, L, or sor) is found. If an L is found, then change the type of the European number to L. 2359 $prevlevel = -1; 2360 $levcount = 0; 2361 for ($i=0; $i < $numchars; ++$i) { 2362 if ($chardata[$i]['char'] == 'EN') { 2363 for ($j=$levcount; $j >= 0; $j--) { 2364 if ($chardata[$j]['type'] == 'L') { 2365 $chardata[$i]['type'] = 'L'; 2366 } elseif ($chardata[$j]['type'] == 'R') { 2367 break; 2368 } 2369 } 2370 } 2371 if ($chardata[$i]['level'] != $prevlevel) { 2372 $levcount = 0; 2373 } else { 2374 ++$levcount; 2375 } 2376 $prevlevel = $chardata[$i]['level']; 2377 } 2378 2379 // N1. A sequence of neutrals takes the direction of the surrounding strong text if the text on both sides has the same direction. European and Arabic numbers act as if they were R in terms of their influence on neutrals. Start-of-level-run (sor) and end-of-level-run (eor) are used at level run boundaries. 2380 $prevlevel = -1; 2381 $levcount = 0; 2382 for ($i=0; $i < $numchars; ++$i) { 2383 if (($levcount > 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) { 2384 if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) { 2385 $chardata[$i]['type'] = 'L'; 2386 } elseif (($chardata[$i]['type'] == 'N') AND 2387 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND 2388 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) { 2389 $chardata[$i]['type'] = 'R'; 2390 } elseif ($chardata[$i]['type'] == 'N') { 2391 // N2. Any remaining neutrals take the embedding direction 2392 $chardata[$i]['type'] = $chardata[$i]['sor']; 2393 } 2394 } elseif (($levcount == 0) AND (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] == $prevlevel)) { 2395 // first char 2396 if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+1)]['type'] == 'L')) { 2397 $chardata[$i]['type'] = 'L'; 2398 } elseif (($chardata[$i]['type'] == 'N') AND 2399 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND 2400 (($chardata[($i+1)]['type'] == 'R') OR ($chardata[($i+1)]['type'] == 'EN') OR ($chardata[($i+1)]['type'] == 'AN'))) { 2401 $chardata[$i]['type'] = 'R'; 2402 } elseif ($chardata[$i]['type'] == 'N') { 2403 // N2. Any remaining neutrals take the embedding direction 2404 $chardata[$i]['type'] = $chardata[$i]['sor']; 2405 } 2406 } elseif (($levcount > 0) AND ((($i+1) == $numchars) OR (($i+1) < $numchars) AND ($chardata[($i+1)]['level'] != $prevlevel))) { 2407 //last char 2408 if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) { 2409 $chardata[$i]['type'] = 'L'; 2410 } elseif (($chardata[$i]['type'] == 'N') AND 2411 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND 2412 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) { 2413 $chardata[$i]['type'] = 'R'; 2414 } elseif ($chardata[$i]['type'] == 'N') { 2415 // N2. Any remaining neutrals take the embedding direction 2416 $chardata[$i]['type'] = $chardata[$i]['sor']; 2417 } 2418 } elseif ($chardata[$i]['type'] == 'N') { 2419 // N2. Any remaining neutrals take the embedding direction 2420 $chardata[$i]['type'] = $chardata[$i]['sor']; 2421 } 2422 if ($chardata[$i]['level'] != $prevlevel) { 2423 $levcount = 0; 2424 } else { 2425 ++$levcount; 2426 } 2427 $prevlevel = $chardata[$i]['level']; 2428 } 2429 2430 // I1. For all characters with an even (left-to-right) embedding direction, those of type R go up one level and those of type AN or EN go up two levels. 2431 // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level. 2432 for ($i=0; $i < $numchars; ++$i) { 2433 $odd = $chardata[$i]['level'] % 2; 2434 if ($odd) { 2435 if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) { 2436 $chardata[$i]['level'] += 1; 2437 } 2438 } else { 2439 if ($chardata[$i]['type'] == 'R') { 2440 $chardata[$i]['level'] += 1; 2441 } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) { 2442 $chardata[$i]['level'] += 2; 2443 } 2444 } 2445 $maxlevel = max($chardata[$i]['level'],$maxlevel); 2446 } 2447 2448 // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level: 2449 // 1. Segment separators, 2450 // 2. Paragraph separators, 2451 // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and 2452 // 4. Any sequence of white space characters at the end of the line. 2453 for ($i=0; $i < $numchars; ++$i) { 2454 if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) { 2455 $chardata[$i]['level'] = $pel; 2456 } elseif ($chardata[$i]['type'] == 'WS') { 2457 $j = $i+1; 2458 while ($j < $numchars) { 2459 if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR 2460 (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) { 2461 $chardata[$i]['level'] = $pel; 2462 break; 2463 } elseif ($chardata[$j]['type'] != 'WS') { 2464 break; 2465 } 2466 ++$j; 2467 } 2468 } 2469 } 2470 2471 // Arabic Shaping 2472 // Cursively connected scripts, such as Arabic or Syriac, require the selection of positional character shapes that depend on adjacent characters. Shaping is logically applied after the Bidirectional Algorithm is used and is limited to characters within the same directional run. 2473 if ($arabic) { 2474 $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688); 2475 $alfletter = array(1570,1571,1573,1575); 2476 $chardata2 = $chardata; 2477 $laaletter = false; 2478 $charAL = array(); 2479 $x = 0; 2480 for ($i=0; $i < $numchars; ++$i) { 2481 if ((TCPDF_FONT_DATA::$uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) { 2482 $charAL[$x] = $chardata[$i]; 2483 $charAL[$x]['i'] = $i; 2484 $chardata[$i]['x'] = $x; 2485 ++$x; 2486 } 2487 } 2488 $numAL = $x; 2489 for ($i=0; $i < $numchars; ++$i) { 2490 $thischar = $chardata[$i]; 2491 if ($i > 0) { 2492 $prevchar = $chardata[($i-1)]; 2493 } else { 2494 $prevchar = false; 2495 } 2496 if (($i+1) < $numchars) { 2497 $nextchar = $chardata[($i+1)]; 2498 } else { 2499 $nextchar = false; 2500 } 2501 if (TCPDF_FONT_DATA::$uni_type[$thischar['char']] == 'AL') { 2502 $x = $thischar['x']; 2503 if ($x > 0) { 2504 $prevchar = $charAL[($x-1)]; 2505 } else { 2506 $prevchar = false; 2507 } 2508 if (($x+1) < $numAL) { 2509 $nextchar = $charAL[($x+1)]; 2510 } else { 2511 $nextchar = false; 2512 } 2513 // if laa letter 2514 if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) { 2515 $arabicarr = TCPDF_FONT_DATA::$uni_laa_array; 2516 $laaletter = true; 2517 if ($x > 1) { 2518 $prevchar = $charAL[($x-2)]; 2519 } else { 2520 $prevchar = false; 2521 } 2522 } else { 2523 $arabicarr = TCPDF_FONT_DATA::$uni_arabicsubst; 2524 $laaletter = false; 2525 } 2526 if (($prevchar !== false) AND ($nextchar !== false) AND 2527 ((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND 2528 ((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND 2529 ($prevchar['type'] == $thischar['type']) AND 2530 ($nextchar['type'] == $thischar['type']) AND 2531 ($nextchar['char'] != 1567)) { 2532 if (in_array($prevchar['char'], $endedletter)) { 2533 if (isset($arabicarr[$thischar['char']][2])) { 2534 // initial 2535 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2]; 2536 } 2537 } else { 2538 if (isset($arabicarr[$thischar['char']][3])) { 2539 // medial 2540 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3]; 2541 } 2542 } 2543 } elseif (($nextchar !== false) AND 2544 ((TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$nextchar['char']] == 'NSM')) AND 2545 ($nextchar['type'] == $thischar['type']) AND 2546 ($nextchar['char'] != 1567)) { 2547 if (isset($arabicarr[$chardata[$i]['char']][2])) { 2548 // initial 2549 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2]; 2550 } 2551 } elseif ((($prevchar !== false) AND 2552 ((TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA::$uni_type[$prevchar['char']] == 'NSM')) AND 2553 ($prevchar['type'] == $thischar['type'])) OR 2554 (($nextchar !== false) AND ($nextchar['char'] == 1567))) { 2555 // final 2556 if (($i > 1) AND ($thischar['char'] == 1607) AND 2557 ($chardata[$i-1]['char'] == 1604) AND 2558 ($chardata[$i-2]['char'] == 1604)) { 2559 //Allah Word 2560 // mark characters to delete with false 2561 $chardata2[$i-2]['char'] = false; 2562 $chardata2[$i-1]['char'] = false; 2563 $chardata2[$i]['char'] = 65010; 2564 } else { 2565 if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) { 2566 if (isset($arabicarr[$thischar['char']][0])) { 2567 // isolated 2568 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0]; 2569 } 2570 } else { 2571 if (isset($arabicarr[$thischar['char']][1])) { 2572 // final 2573 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1]; 2574 } 2575 } 2576 } 2577 } elseif (isset($arabicarr[$thischar['char']][0])) { 2578 // isolated 2579 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0]; 2580 } 2581 // if laa letter 2582 if ($laaletter) { 2583 // mark characters to delete with false 2584 $chardata2[($charAL[($x-1)]['i'])]['char'] = false; 2585 } 2586 } // end if AL (Arabic Letter) 2587 } // end for each char 2588 /* 2589 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced. 2590 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner. 2591 */ 2592 for ($i = 0; $i < ($numchars-1); ++$i) { 2593 if (($chardata2[$i]['char'] == 1617) AND (isset(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])]))) { 2594 // check if the subtitution font is defined on current font 2595 if (isset($currentfont['cw'][(TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])])])) { 2596 $chardata2[$i]['char'] = false; 2597 $chardata2[$i+1]['char'] = TCPDF_FONT_DATA::$uni_diacritics[($chardata2[$i+1]['char'])]; 2598 } 2599 } 2600 } 2601 // remove marked characters 2602 foreach ($chardata2 as $key => $value) { 2603 if ($value['char'] === false) { 2604 unset($chardata2[$key]); 2605 } 2606 } 2607 $chardata = array_values($chardata2); 2608 $numchars = count($chardata); 2609 unset($chardata2); 2610 unset($arabicarr); 2611 unset($laaletter); 2612 unset($charAL); 2613 } 2614 2615 // L2. From the highest level found in the text to the lowest odd level on each line, including intermediate levels not actually present in the text, reverse any contiguous sequence of characters that are at that level or higher. 2616 for ($j=$maxlevel; $j > 0; $j--) { 2617 $ordarray = Array(); 2618 $revarr = Array(); 2619 $onlevel = false; 2620 for ($i=0; $i < $numchars; ++$i) { 2621 if ($chardata[$i]['level'] >= $j) { 2622 $onlevel = true; 2623 if (isset(TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']])) { 2624 // L4. A character is depicted by a mirrored glyph if and only if (a) the resolved directionality of that character is R, and (b) the Bidi_Mirrored property value of that character is true. 2625 $chardata[$i]['char'] = TCPDF_FONT_DATA::$uni_mirror[$chardata[$i]['char']]; 2626 } 2627 $revarr[] = $chardata[$i]; 2628 } else { 2629 if ($onlevel) { 2630 $revarr = array_reverse($revarr); 2631 $ordarray = array_merge($ordarray, $revarr); 2632 $revarr = Array(); 2633 $onlevel = false; 2634 } 2635 $ordarray[] = $chardata[$i]; 2636 } 2637 } 2638 if ($onlevel) { 2639 $revarr = array_reverse($revarr); 2640 $ordarray = array_merge($ordarray, $revarr); 2641 } 2642 $chardata = $ordarray; 2643 } 2644 $ordarray = array(); 2645 foreach ($chardata as $cd) { 2646 $ordarray[] = $cd['char']; 2647 // store char values for subsetting 2648 $currentfont['subsetchars'][$cd['char']] = true; 2649 } 2650 return $ordarray; 2651 } 2652 2653 } // END OF TCPDF_FONTS CLASS 2654 2655 //============================================================+ 2656 // END OF FILE 2657 //============================================================+
title
Description
Body
title
Description
Body
title
Description
Body
title
Body