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