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