2 //============================================================+
3 // File name : tcpdf_fonts.php
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
12 // This file is part of TCPDF software library.
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.
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.
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/>.
27 // See LICENSE.TXT file for more information.
28 // -------------------------------------------------------------------
30 // Description :Font methods for TCPDF library.
32 //============================================================+
36 * Unicode data and font methods for TCPDF library.
37 * @author Nicola Asuni
38 * @package com.tecnick.tcpdf
43 * Font methods for TCPDF library.
44 * @package com.tecnick.tcpdf
46 * @author Nicola Asuni - info@tecnick.com
51 * Static cache used for speed up uniord performances
54 protected static $cache_uniord = array();
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)
72 public static function addTTFfont($fontfile, $fonttype='', $enc='', $flags=32, $outpath='', $platid=3, $encid=1, $addcbbox=false, $link=false) {
73 if (!file_exists($fontfile)) {
74 // Could not find file
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));
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)) {
91 $font_name = 'tcpdffont';
94 if (empty($outpath)) {
95 $outpath = self
::_getfontpath();
97 // check if this font already exist
98 if (@file_exists
($outpath.$font_name.'.php')) {
99 // this font already exist (delete it from fonts folder to rebuild it)
102 $fmetric['file'] = $font_name;
103 $fmetric['ctg'] = $font_name.'.ctg.z';
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
127 $fmetric['type'] = 'cidfont0';
131 $fmetric['type'] = 'Type1';
132 if (empty($enc) AND (($flags & 4) == 0)) {
138 $fmetric['type'] = 'TrueType';
141 case 'TrueTypeUnicode':
143 $fmetric['type'] = 'TrueTypeUnicode';
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];
156 for ($i = 32; $i <= 255; ++
$i) {
157 if ($enc_target != $enc_ref[$i]) {
158 if ($i != ($last +
1)) {
159 $fmetric['diff'] .= $i.' ';
162 $fmetric['diff'] .= '/'.$enc_target[$i].' ';
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
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
184 $fmetric['size2'] = $a['size'];
185 $encrypted = substr($font, (12 +
$fmetric['size1']), $fmetric['size2']);
187 // store compressed font
188 $fmetric['file'] .= '.z';
189 $fp = TCPDF_STATIC
::fopenLocal($outpath.$fmetric['file'], 'wb');
190 fwrite($fp, gzcompress($data));
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;
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;
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];
221 // decrypt eexec encrypted part
222 $r = 55665; // eexec encryption constant
225 $elen = strlen($encrypted);
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);
232 if (preg_match('#/ForceBold[\s]*([^\s]*)#', $eplain, $matches) > 0) {
233 if ($matches[1] == 'true') {
234 $fmetric['Flags'] |
= 0x40000;
237 if (preg_match('#/StdVW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
238 $fmetric['StemV'] = intval($matches[1]);
240 $fmetric['StemV'] = 70;
242 if (preg_match('#/StdHW[\s]*\[([^\]]*)#', $eplain, $matches) > 0) {
243 $fmetric['StemH'] = intval($matches[1]);
245 $fmetric['StemH'] = 30;
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]);
253 $fmetric['XHeight'] = $v1;
254 $fmetric['CapHeight'] = $v2;
256 $fmetric['XHeight'] = $v2;
257 $fmetric['CapHeight'] = $v1;
260 $fmetric['XHeight'] = 450;
261 $fmetric['CapHeight'] = 700;
264 $fmetric['XHeight'] = 450;
265 $fmetric['CapHeight'] = 700;
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]);
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];
283 $fmetric['MaxWidth'] = 0;
285 foreach ($matches as $k => $v) {
287 if (isset($imap[$v[1]])) {
289 } elseif ($enc_map !== false) {
290 $cid = array_search($v[1], $enc_map);
291 if ($cid === false) {
293 } elseif ($cid > 1000) {
297 // decrypt charstring encrypted part
298 $r = 4330; // charstring encryption constant
304 for ($i = 0; $i < $clen; ++
$i) {
306 $ccom[] = ($chr ^
($r >> 8));
307 $r = ((($chr +
$r) * $c1 +
$c2) %
65536);
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)];
321 } elseif (($ccom[$i] >= 32) AND ($ccom[$i] <= 246)) {
322 $cdec[$ck] = ($ccom[$i] - 139);
324 } elseif (($ccom[$i] >= 247) AND ($ccom[$i] <= 250)) {
325 $cdec[$ck] = ((($ccom[$i] - 247) * 256) +
$ccom[($i +
1)] +
108);
327 } elseif (($ccom[$i] >= 251) AND ($ccom[$i] <= 254)) {
328 $cdec[$ck] = ((-($ccom[$i] - 251) * 256) - $ccom[($i +
1)] - 108);
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'];
338 } // end for each matches
339 $fmetric['MissingWidth'] = $cwidths[0];
340 $fmetric['MaxWidth'] = $fmetric['MissingWidth'];
341 $fmetric['AvgWidth'] = 0;
343 for ($cid = 0; $cid <= 255; ++
$cid) {
344 if (isset($cwidths[$cid])) {
345 if ($cwidths[$cid] > $fmetric['MaxWidth']) {
346 $fmetric['MaxWidth'] = $cwidths[$cid];
348 $fmetric['AvgWidth'] +
= $cwidths[$cid];
349 $fmetric['cw'] .= ','.$cid.'=>'.$cwidths[$cid];
351 $fmetric['cw'] .= ','.$cid.'=>'.$fmetric['MissingWidth'];
354 $fmetric['AvgWidth'] = round($fmetric['AvgWidth'] / count($cwidths));
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.
362 if ($fmetric['type'] != 'cidfont0') {
364 // creates a symbolic link to the existing font
365 symlink($fontfile, $outpath.$fmetric['file']);
367 // store compressed font
368 $fmetric['file'] .= '.z';
369 $fp = TCPDF_STATIC
::fopenLocal($outpath.$fmetric['file'], 'wb');
370 fwrite($fp, gzcompress($font));
375 // get number of tables
376 $numTables = TCPDF_STATIC
::_getUSHORT($font, $offset);
378 // skip searchRange, entrySelector and rangeShift
382 // ---------- get tables ----------
383 for ($i = 0; $i < $numTables; ++
$i) {
385 $tag = substr($font, $offset, 4);
387 $table[$tag] = array();
388 $table[$tag]['checkSum'] = TCPDF_STATIC
::_getULONG($font, $offset);
390 $table[$tag]['offset'] = TCPDF_STATIC
::_getULONG($font, $offset);
392 $table[$tag]['length'] = TCPDF_STATIC
::_getULONG($font, $offset);
396 $offset = $table['head']['offset'] +
12;
397 if (TCPDF_STATIC
::_getULONG($font, $offset) != 0x5F0F3CF5) {
398 // magicNumber must be 0x5F0F3CF5
402 $offset +
= 2; // skip flags
404 $fmetric['unitsPerEm'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
406 // units ratio constant
407 $urk = (1000 / $fmetric['unitsPerEm']);
408 $offset +
= 16; // skip created, modified
409 $xMin = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
411 $yMin = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
413 $xMax = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
415 $yMax = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
417 $fmetric['bbox'] = ''.$xMin.' '.$yMin.' '.$xMax.' '.$yMax.'';
418 $macStyle = TCPDF_STATIC
::_getUSHORT($font, $offset);
421 $fmetric['Flags'] = $flags;
422 if (($macStyle & 2) == 2) {
424 $fmetric['Flags'] |
= 64;
426 // get offset mode (indexToLocFormat : 0 = short, 1 = long)
427 $offset = $table['head']['offset'] +
50;
428 $short_offset = (TCPDF_STATIC
::_getSHORT($font, $offset) == 0);
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'];
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)]);
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)]);
456 // get glyphs indexes of chars from cmap table
457 $offset = $table['cmap']['offset'] +
2;
458 $numEncodingTables = TCPDF_STATIC
::_getUSHORT($font, $offset);
460 $encodingTables = array();
461 for ($i = 0; $i < $numEncodingTables; ++
$i) {
462 $encodingTables[$i]['platformID'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
464 $encodingTables[$i]['encodingID'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
466 $encodingTables[$i]['offset'] = TCPDF_STATIC
::_getULONG($font, $offset);
469 // ---------- get os/2 metrics ----------
470 $offset = $table['OS/2']['offset'];
471 $offset +
= 2; // skip version
473 $fmetric['AvgWidth'] = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
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);
481 $offset +
= 2; // usWidthClass
482 $fsType = TCPDF_STATIC
::_getSHORT($font, $offset);
485 // This Font cannot be modified, embedded or exchanged in any manner without first obtaining permission of the legal owner.
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);
495 // Offset to start of string storage (from start of table).
496 $stringStorageOffset = TCPDF_STATIC
::_getUSHORT($font, $offset);
498 for ($i = 0; $i < $numNameRecords; ++
$i) {
499 $offset +
= 6; // skip Platform ID, Platform-specific encoding ID, Language ID.
501 $nameID = TCPDF_STATIC
::_getUSHORT($font, $offset);
504 // String length (in bytes).
505 $stringLength = TCPDF_STATIC
::_getUSHORT($font, $offset);
507 // String offset from start of storage area (in bytes).
508 $stringOffset = TCPDF_STATIC
::_getUSHORT($font, $offset);
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']);
515 $offset +
= 4; // skip String length, String offset
518 if (empty($fmetric['name'])) {
519 $fmetric['name'] = $font_name;
521 // ---------- get post data ----------
522 $offset = $table['post']['offset'];
523 $offset +
= 4; // skip Format Type
524 $fmetric['italicAngle'] = TCPDF_STATIC
::_getFIXED($font, $offset);
526 $fmetric['underlinePosition'] = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
528 $fmetric['underlineThickness'] = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
530 $isFixedPitch = (TCPDF_STATIC
::_getULONG($font, $offset) == 0) ?
false : true;
533 $fmetric['Flags'] |
= 1;
535 // ---------- get hhea data ----------
536 $offset = $table['hhea']['offset'];
537 $offset +
= 4; // skip Table version number
539 $fmetric['Ascent'] = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
542 $fmetric['Descent'] = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
545 $fmetric['Leading'] = round(TCPDF_STATIC
::_getFWORD($font, $offset) * $urk);
548 $fmetric['MaxWidth'] = round(TCPDF_STATIC
::_getUFWORD($font, $offset) * $urk);
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 ----------
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);
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);
576 case 2: { // Format 2: High-byte mapping through table
577 $offset +
= 4; // skip length and version/language
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);
583 if ($numSubHeaders < $subHeaderKeys[$i]) {
584 $numSubHeaders = $subHeaderKeys[$i];
587 // the number of subHeaders is equal to the max of subHeaderKeys + 1
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);
595 $subHeaders[$k]['entryCount'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
597 $subHeaders[$k]['idDelta'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
599 $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
601 $subHeaders[$k]['idRangeOffset'] -= (2 +
(($numSubHeaders - $k - 1) * 8));
602 $subHeaders[$k]['idRangeOffset'] /= 2;
603 $numGlyphIndexArray +
= $subHeaders[$k]['entryCount'];
605 for ($k = 0; $k < $numGlyphIndexArray; ++
$k) {
606 $glyphIndexArray[$k] = TCPDF_STATIC
::_getUSHORT($font, $offset);
609 for ($i = 0; $i < 256; ++
$i) {
610 $k = $subHeaderKeys[$i];
614 $g = $glyphIndexArray[0];
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;
634 case 4: { // Format 4: Segment mapping to delta values
635 $length = TCPDF_STATIC
::_getUSHORT($font, $offset);
637 $offset +
= 2; // skip version/language
638 $segCount = floor(TCPDF_STATIC
::_getUSHORT($font, $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);
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);
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);
657 $idRangeOffset = array(); // Offsets into glyphIdArray or 0
658 for ($k = 0; $k < $segCount; ++
$k) {
659 $idRangeOffset[$k] = TCPDF_STATIC
::_getUSHORT($font, $offset);
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);
668 for ($k = 0; $k < $segCount; ++
$k) {
669 for ($c = $startCount[$k]; $c <= $endCount[$k]; ++
$c) {
670 if ($idRangeOffset[$k] == 0) {
671 $g = ($idDelta[$k] +
$c) %
65536;
673 $gid = (floor($idRangeOffset[$k] / 2) +
($c - $startCount[$k]) - ($segCount - $k));
674 $g = ($glyphIdArray[$gid] +
$idDelta[$k]) %
65536;
684 case 6: { // Format 6: Trimmed table mapping
685 $offset +
= 4; // skip length and version/language
686 $firstCode = TCPDF_STATIC
::_getUSHORT($font, $offset);
688 $entryCount = TCPDF_STATIC
::_getUSHORT($font, $offset);
690 for ($k = 0; $k < $entryCount; ++
$k) {
691 $c = ($k +
$firstCode);
692 $g = TCPDF_STATIC
::_getUSHORT($font, $offset);
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);
704 $nGroups = TCPDF_STATIC
::_getULONG($font, $offset);
706 for ($i = 0; $i < $nGroups; ++
$i) {
707 $startCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
709 $endCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
711 $startGlyphID = TCPDF_STATIC
::_getULONG($font, $offset);
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)) {
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;
730 case 10: { // Format 10: Trimmed array
731 $offset +
= 10; // skip reserved, length and version/language
732 $startCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
734 $numChars = TCPDF_STATIC
::_getULONG($font, $offset);
736 for ($k = 0; $k < $numChars; ++
$k) {
737 $c = ($k +
$startCharCode);
738 $g = TCPDF_STATIC
::_getUSHORT($font, $offset);
744 case 12: { // Format 12: Segmented coverage
745 $offset +
= 10; // skip length and version/language
746 $nGroups = TCPDF_STATIC
::_getULONG($font, $offset);
748 for ($k = 0; $k < $nGroups; ++
$k) {
749 $startCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
751 $endCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
753 $startGlyphCode = TCPDF_STATIC
::_getULONG($font, $offset);
755 for ($c = $startCharCode; $c <= $endCharCode; ++
$c) {
756 $ctg[$c] = $startGlyphCode;
762 case 13: { // Format 13: Many-to-one range mappings
763 // to be implemented ...
766 case 14: { // Format 14: Unicode Variation Sequences
767 // to be implemented ...
773 if (!isset($ctg[0])) {
776 // get xHeight (height of x)
777 $offset = ($table['glyf']['offset'] +
$indexToLoc[$ctg[120]] +
4);
778 $yMin = TCPDF_STATIC
::_getFWORD($font, $offset);
780 $yMax = TCPDF_STATIC
::_getFWORD($font, $offset);
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);
787 $yMax = TCPDF_STATIC
::_getFWORD($font, $offset);
789 $fmetric['CapHeight'] = round(($yMax - $yMin) * $urk);
790 // ceate widths 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
797 if ($numberOfHMetrics < $numGlyphs) {
798 // fill missing widths with the last value
799 $cw = array_pad($cw, $numGlyphs, $cw[($numberOfHMetrics - 1)]);
801 $fmetric['MissingWidth'] = $cw[0];
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]];
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.')';
819 } // end of true type
820 if (($fmetric['type'] == 'TrueTypeUnicode') AND (count($ctg) == 256)) {
821 $fmetric['type'] = 'TrueType';
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";
833 $pfile .= '$dw='.$fmetric['AvgWidth'].';'."\n";
835 $pfile .= '$diff=\''.$fmetric['diff'].'\';'."\n";
836 if ($fmetric['type'] == 'Type1') {
838 $pfile .= '$enc=\''.$fmetric['enc'].'\';'."\n";
839 $pfile .= '$file=\''.$fmetric['file'].'\';'."\n";
840 $pfile .= '$size1='.$fmetric['size1'].';'."\n";
841 $pfile .= '$size2='.$fmetric['size2'].';'."\n";
843 $pfile .= '$originalsize='.$fmetric['originalsize'].';'."\n";
844 if ($fmetric['type'] == 'cidfont0') {
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";
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";
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";
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";
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]);
887 // store compressed CIDToGIDMap
888 $fp = TCPDF_STATIC
::fopenLocal($outpath.$fmetric['ctg'], 'wb');
889 fwrite($fp, gzcompress($cidtogidmap));
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'].'';
908 if (!empty($fmetric['cbbox'])) {
909 $pfile .= '$cbbox=array('.substr($fmetric['cbbox'], 1).');'."\n";
911 $pfile .= '$cw=array('.substr($fmetric['cw'], 1).');'."\n";
912 $pfile .= '// --- EOF ---'."\n";
914 $fp = TCPDF_STATIC
::fopenLocal($outpath.$font_name.'.php', 'w');
917 // return TCPDF font name
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)
930 public static function _getTTFtableChecksum($table, $length) {
932 $tlen = ($length / 4);
934 for ($i = 0; $i < $tlen; ++
$i) {
935 $v = unpack('Ni', substr($table, $offset, 4));
939 $sum = unpack('Ni', pack('N', $sum));
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)
952 public static function _getTrueTypeFontSubset($font, $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.
960 // get number of tables
961 $numTables = TCPDF_STATIC
::_getUSHORT($font, $offset);
963 // skip searchRange, entrySelector and rangeShift
968 for ($i = 0; $i < $numTables; ++
$i) {
970 $tag = substr($font, $offset, 4);
972 $table[$tag] = array();
973 $table[$tag]['checkSum'] = TCPDF_STATIC
::_getULONG($font, $offset);
975 $table[$tag]['offset'] = TCPDF_STATIC
::_getULONG($font, $offset);
977 $table[$tag]['length'] = TCPDF_STATIC
::_getULONG($font, $offset);
981 $offset = $table['head']['offset'] +
12;
982 if (TCPDF_STATIC
::_getULONG($font, $offset) != 0x5F0F3CF5) {
983 // magicNumber must be 0x5F0F3CF5
987 // get offset mode (indexToLocFormat : 0 = short, 1 = long)
988 $offset = $table['head']['offset'] +
50;
989 $short_offset = (TCPDF_STATIC
::_getSHORT($font, $offset) == 0);
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'];
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;
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);
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);
1015 $encodingTables = array();
1016 for ($i = 0; $i < $numEncodingTables; ++
$i) {
1017 $encodingTables[$i]['platformID'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
1019 $encodingTables[$i]['encodingID'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
1021 $encodingTables[$i]['offset'] = TCPDF_STATIC
::_getULONG($font, $offset);
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);
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;
1041 case 2: { // Format 2: High-byte mapping through table
1042 $offset +
= 4; // skip length and version/language
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);
1048 if ($numSubHeaders < $subHeaderKeys[$i]) {
1049 $numSubHeaders = $subHeaderKeys[$i];
1052 // the number of subHeaders is equal to the max of subHeaderKeys + 1
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);
1060 $subHeaders[$k]['entryCount'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
1062 $subHeaders[$k]['idDelta'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
1064 $subHeaders[$k]['idRangeOffset'] = TCPDF_STATIC
::_getUSHORT($font, $offset);
1066 $subHeaders[$k]['idRangeOffset'] -= (2 +
(($numSubHeaders - $k - 1) * 8));
1067 $subHeaders[$k]['idRangeOffset'] /= 2;
1068 $numGlyphIndexArray +
= $subHeaders[$k]['entryCount'];
1070 for ($k = 0; $k < $numGlyphIndexArray; ++
$k) {
1071 $glyphIndexArray[$k] = TCPDF_STATIC
::_getUSHORT($font, $offset);
1074 for ($i = 0; $i < 256; ++
$i) {
1075 $k = $subHeaderKeys[$i];
1079 if (isset($subsetchars[$c])) {
1080 $g = $glyphIndexArray[0];
1081 $subsetglyphs[$g] = true;
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;
1096 $subsetglyphs[$g] = true;
1103 case 4: { // Format 4: Segment mapping to delta values
1104 $length = TCPDF_STATIC
::_getUSHORT($font, $offset);
1106 $offset +
= 2; // skip version/language
1107 $segCount = floor(TCPDF_STATIC
::_getUSHORT($font, $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);
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);
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);
1126 $idRangeOffset = array(); // Offsets into glyphIdArray or 0
1127 for ($k = 0; $k < $segCount; ++
$k) {
1128 $idRangeOffset[$k] = TCPDF_STATIC
::_getUSHORT($font, $offset);
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);
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;
1143 $gid = (floor($idRangeOffset[$k] / 2) +
($c - $startCount[$k]) - ($segCount - $k));
1144 $g = ($glyphIdArray[$gid] +
$idDelta[$k]) %
65536;
1149 $subsetglyphs[$g] = true;
1155 case 6: { // Format 6: Trimmed table mapping
1156 $offset +
= 4; // skip length and version/language
1157 $firstCode = TCPDF_STATIC
::_getUSHORT($font, $offset);
1159 $entryCount = TCPDF_STATIC
::_getUSHORT($font, $offset);
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;
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);
1177 $nGroups = TCPDF_STATIC
::_getULONG($font, $offset);
1179 for ($i = 0; $i < $nGroups; ++
$i) {
1180 $startCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
1182 $endCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
1184 $startGlyphID = TCPDF_STATIC
::_getULONG($font, $offset);
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)) {
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;
1197 if (isset($subsetchars[$c])) {
1198 $subsetglyphs[$startGlyphID] = true;
1205 case 10: { // Format 10: Trimmed array
1206 $offset +
= 10; // skip reserved, length and version/language
1207 $startCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
1209 $numChars = TCPDF_STATIC
::_getULONG($font, $offset);
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;
1221 case 12: { // Format 12: Segmented coverage
1222 $offset +
= 10; // skip length and version/language
1223 $nGroups = TCPDF_STATIC
::_getULONG($font, $offset);
1225 for ($k = 0; $k < $nGroups; ++
$k) {
1226 $startCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
1228 $endCharCode = TCPDF_STATIC
::_getULONG($font, $offset);
1230 $startGlyphCode = TCPDF_STATIC
::_getULONG($font, $offset);
1232 for ($c = $startCharCode; $c <= $endCharCode; ++
$c) {
1233 if (isset($subsetchars[$c])) {
1234 $subsetglyphs[$startGlyphCode] = true;
1241 case 13: { // Format 13: Many-to-one range mappings
1242 // to be implemented ...
1245 case 14: { // Format 14: Unicode Variation Sequences
1246 // to be implemented ...
1251 // include all parts of composite glyphs
1252 $new_sga = $subsetglyphs;
1253 while (!empty($new_sga)) {
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);
1261 if ($numberOfContours < 0) { // composite glyph
1262 $offset +
= 8; // skip xMin, yMin, xMax, yMax
1264 $flags = TCPDF_STATIC
::_getUSHORT($font, $offset);
1266 $glyphIndex = TCPDF_STATIC
::_getUSHORT($font, $offset);
1268 if (!isset($subsetglyphs[$glyphIndex])) {
1269 // add missing glyphs
1270 $new_sga[$glyphIndex] = true;
1272 // skip some bytes by case
1280 } elseif ($flags & 64) {
1282 } elseif ($flags & 128) {
1285 } while ($flags & 32);
1289 $subsetglyphs +
= $new_sga;
1291 // sort glyphs by key (and remove duplicates)
1292 ksort($subsetglyphs);
1293 // build new glyf and loca tables
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);
1305 if ($short_offset) {
1306 $loca .= pack('n', floor($offset / 2));
1308 $loca .= pack('N', $offset);
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
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);
1324 $pad = 4 - ($table[$tag]['length'] %
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);
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']);
1335 unset($table[$tag]);
1339 $table['loca']['data'] = $loca;
1340 $table['loca']['length'] = strlen($loca);
1341 $pad = 4 - ($table['loca']['length'] %
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);
1347 $table['loca']['offset'] = $offset;
1348 $table['loca']['checkSum'] = self
::_getTTFtableChecksum($table['loca']['data'], $table['loca']['length']);
1349 $offset +
= $table['loca']['length'];
1351 $table['glyf']['data'] = $glyf;
1352 $table['glyf']['length'] = strlen($glyf);
1353 $pad = 4 - ($table['glyf']['length'] %
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);
1359 $table['glyf']['offset'] = $offset;
1360 $table['glyf']['checkSum'] = self
::_getTTFtableChecksum($table['glyf']['data'], $table['glyf']['length']);
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
1379 foreach ($table as $data) {
1380 $font .= $data['data'];
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);
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)
1397 public static function _putfontwidths($font, $cidoffset=0) {
1404 // for each character
1405 foreach ($font['cw'] as $cid => $width) {
1407 if ($font['subset'] AND (!isset($font['subsetchars'][$cid]))) {
1408 // ignore the unused characters (font subsetting)
1411 if ($width != $font['dw']) {
1412 if ($cid == ($prevcid +
1)) {
1414 if ($width == $prevwidth) {
1415 if ($width == $range[$rangeid][0]) {
1416 $range[$rangeid][] = $width;
1418 array_pop($range[$rangeid]);
1420 $rangeid = $prevcid;
1421 $range[$rangeid] = array();
1422 $range[$rangeid][] = $prevwidth;
1423 $range[$rangeid][] = $width;
1426 $range[$rangeid]['interval'] = true;
1431 $range[$rangeid] = array();
1432 $range[$rangeid][] = $width;
1434 $range[$rangeid][] = $width;
1441 $range[$rangeid] = array();
1442 $range[$rangeid][] = $width;
1446 $prevwidth = $width;
1453 foreach ($range as $k => $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']);
1459 $range[$prevk] = array_merge($range[$prevk], $range[$k]);
1465 if (isset($ws['interval'])) {
1471 if (isset($range[$k]['interval'])) {
1472 unset($range[$k]['interval']);
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];
1487 $w .= ' '.$k.' [ '.implode(' ', $ws).' ]';
1490 return '/W ['.$w.' ]';
1494 * Returns the unicode caracter specified by the value
1495 * @param $c (int) UTF-8 value
1496 * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
1497 * @return Returns the specified character.
1498 * @since 2.3.000 (2008-03-05)
1501 public static function unichr($c, $unicode=true) {
1504 } elseif ($c <= 0x7F) {
1507 } elseif ($c <= 0x7FF) {
1509 return chr(0xC0 |
$c >> 6).chr(0x80 |
$c & 0x3F);
1510 } elseif ($c <= 0xFFFF) {
1512 return chr(0xE0 |
$c >> 12).chr(0x80 |
$c >> 6 & 0x3F).chr(0x80 |
$c & 0x3F);
1513 } elseif ($c <= 0x10FFFF) {
1515 return chr(0xF0 |
$c >> 18).chr(0x80 |
$c >> 12 & 0x3F).chr(0x80 |
$c >> 6 & 0x3F).chr(0x80 |
$c & 0x3F);
1522 * Returns the unicode caracter specified by UTF-8 value
1523 * @param $c (int) UTF-8 value
1524 * @return Returns the specified character.
1527 public static function unichrUnicode($c) {
1528 return self
::unichr($c, true);
1532 * Returns the unicode caracter specified by ASCII value
1533 * @param $c (int) UTF-8 value
1534 * @return Returns the specified character.
1537 public static function unichrASCII($c) {
1538 return self
::unichr($c, false);
1542 * Converts array of UTF-8 characters to UTF16-BE string.<br>
1543 * Based on: http://www.faqs.org/rfcs/rfc2781.html
1547 * Encoding of a single character from an ISO 10646 character value to
1548 * UTF-16 proceeds as follows. Let U be the character number, no greater
1551 * 1) If U < 0x10000, encode U as a 16-bit unsigned integer and
1554 * 2) Let U' = U - 0x10000. Because U is less than or equal to 0x10FFFF,
1555 * U' must be less than or equal to 0xFFFFF. That is, U' can be
1556 * represented in 20 bits.
1558 * 3) Initialize two 16-bit unsigned integers, W1 and W2, to 0xD800 and
1559 * 0xDC00, respectively. These integers each have 10 bits free to
1560 * encode the character value, for a total of 20 bits.
1562 * 4) Assign the 10 high-order bits of the 20-bit U' to the 10 low-order
1563 * bits of W1 and the 10 low-order bits of U' to the 10 low-order
1564 * bits of W2. Terminate.
1566 * Graphically, steps 2 through 4 look like:
1567 * U' = yyyyyyyyyyxxxxxxxxxx
1568 * W1 = 110110yyyyyyyyyy
1569 * W2 = 110111xxxxxxxxxx
1571 * @param $unicode (array) array containing UTF-8 unicode values
1572 * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
1575 * @author Nicola Asuni
1576 * @since 2.1.000 (2008-01-08)
1579 public static function arrUTF8ToUTF16BE($unicode, $setbom=false) {
1580 $outstr = ''; // string to be returned
1582 $outstr .= "\xFE\xFF"; // Byte Order Mark (BOM)
1584 foreach ($unicode as $char) {
1585 if ($char == 0x200b) {
1586 // skip Unicode Character 'ZERO WIDTH SPACE' (DEC:8203, U+200B)
1587 } elseif ($char == 0xFFFD) {
1588 $outstr .= "\xFF\xFD"; // replacement character
1589 } elseif ($char < 0x10000) {
1590 $outstr .= chr($char >> 0x08);
1591 $outstr .= chr($char & 0xFF);
1594 $w1 = 0xD800 |
($char >> 0x0a);
1595 $w2 = 0xDC00 |
($char & 0x3FF);
1596 $outstr .= chr($w1 >> 0x08);
1597 $outstr .= chr($w1 & 0xFF);
1598 $outstr .= chr($w2 >> 0x08);
1599 $outstr .= chr($w2 & 0xFF);
1606 * Convert an array of UTF8 values to array of unicode characters
1607 * @param $ta (array) The input array of UTF8 values.
1608 * @param $isunicode (boolean) True for Unicode mode, false otherwise.
1609 * @return Return array of unicode characters
1610 * @since 4.5.037 (2009-04-07)
1613 public static function UTF8ArrayToUniArray($ta, $isunicode=true) {
1615 return array_map(array('TCPDF_FONTS', 'unichrUnicode'), $ta);
1617 return array_map(array('TCPDF_FONTS', 'unichrASCII'), $ta);
1621 * Extract a slice of the $strarr array and return it as string.
1622 * @param $strarr (string) The input array of characters.
1623 * @param $start (int) the starting element of $strarr.
1624 * @param $end (int) first element that will not be returned.
1625 * @param $unicode (boolean) True if we are in unicode mode, false otherwise.
1626 * @return Return part of a string
1629 public static function UTF8ArrSubString($strarr, $start='', $end='', $unicode=true) {
1630 if (strlen($start) == 0) {
1633 if (strlen($end) == 0) {
1634 $end = count($strarr);
1637 for ($i = $start; $i < $end; ++
$i) {
1638 $string .= self
::unichr($strarr[$i], $unicode);
1644 * Extract a slice of the $uniarr array and return it as string.
1645 * @param $uniarr (string) The input array of characters.
1646 * @param $start (int) the starting element of $strarr.
1647 * @param $end (int) first element that will not be returned.
1648 * @return Return part of a string
1649 * @since 4.5.037 (2009-04-07)
1652 public static function UniArrSubString($uniarr, $start='', $end='') {
1653 if (strlen($start) == 0) {
1656 if (strlen($end) == 0) {
1657 $end = count($uniarr);
1660 for ($i=$start; $i < $end; ++
$i) {
1661 $string .= $uniarr[$i];
1667 * Update the CIDToGIDMap string with a new value.
1668 * @param $map (string) CIDToGIDMap.
1669 * @param $cid (int) CID value.
1670 * @param $gid (int) GID value.
1671 * @return (string) CIDToGIDMap.
1672 * @author Nicola Asuni
1673 * @since 5.9.123 (2011-09-29)
1676 public static function updateCIDtoGIDmap($map, $cid, $gid) {
1677 if (($cid >= 0) AND ($cid <= 0xFFFF) AND ($gid >= 0)) {
1678 if ($gid > 0xFFFF) {
1681 $map[($cid * 2)] = chr($gid >> 8);
1682 $map[(($cid * 2) +
1)] = chr($gid & 0xFF);
1692 public static function _getfontpath() {
1693 if (!defined('K_PATH_FONTS') AND is_dir($fdir = realpath(dirname(__FILE__
).'/../fonts'))) {
1694 if (substr($fdir, -1) != '/') {
1697 define('K_PATH_FONTS', $fdir);
1699 return defined('K_PATH_FONTS') ? K_PATH_FONTS
: '';
1703 * Return font full path
1704 * @param $file (string) Font file name.
1705 * @param $fontdir (string) Font directory (set to false fto search on default directories)
1706 * @return string Font full path or empty string
1707 * @author Nicola Asuni
1711 public static function getFontFullPath($file, $fontdir=false) {
1713 // search files on various directories
1714 if (($fontdir !== false) AND @file_exists
($fontdir.$file)) {
1715 $fontfile = $fontdir.$file;
1716 } elseif (@file_exists
(self
::_getfontpath().$file)) {
1717 $fontfile = self
::_getfontpath().$file;
1718 } elseif (@file_exists
($file)) {
1725 * Converts UTF-8 characters array to array of Latin1 characters array<br>
1726 * @param $unicode (array) array containing UTF-8 unicode values
1728 * @author Nicola Asuni
1729 * @since 4.8.023 (2010-01-15)
1732 public static function UTF8ArrToLatin1Arr($unicode) {
1733 $outarr = array(); // array to be returned
1734 foreach ($unicode as $char) {
1737 } elseif (array_key_exists($char, TCPDF_FONT_DATA
::$uni_utf8tolatin)) {
1739 $outarr[] = TCPDF_FONT_DATA
::$uni_utf8tolatin[$char];
1740 } elseif ($char == 0xFFFD) {
1743 $outarr[] = 63; // '?' character
1750 * Converts UTF-8 characters array to array of Latin1 string<br>
1751 * @param $unicode (array) array containing UTF-8 unicode values
1753 * @author Nicola Asuni
1754 * @since 4.8.023 (2010-01-15)
1757 public static function UTF8ArrToLatin1($unicode) {
1758 $outstr = ''; // string to be returned
1759 foreach ($unicode as $char) {
1761 $outstr .= chr($char);
1762 } elseif (array_key_exists($char, TCPDF_FONT_DATA
::$uni_utf8tolatin)) {
1764 $outstr .= chr(TCPDF_FONT_DATA
::$uni_utf8tolatin[$char]);
1765 } elseif ($char == 0xFFFD) {
1775 * Converts UTF-8 character to integer value.<br>
1776 * Uses the getUniord() method if the value is not cached.
1777 * @param $uch (string) character string to process.
1778 * @return integer Unicode value
1781 public static function uniord($uch) {
1782 if (!isset(self
::$cache_uniord[$uch])) {
1783 self
::$cache_uniord[$uch] = self
::getUniord($uch);
1785 return self
::$cache_uniord[$uch];
1789 * Converts UTF-8 character to integer value.<br>
1790 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1791 * Based on: http://www.faqs.org/rfcs/rfc3629.html
1793 * Char. number range | UTF-8 octet sequence
1794 * (hexadecimal) | (binary)
1795 * --------------------+-----------------------------------------------
1796 * 0000 0000-0000 007F | 0xxxxxxx
1797 * 0000 0080-0000 07FF | 110xxxxx 10xxxxxx
1798 * 0000 0800-0000 FFFF | 1110xxxx 10xxxxxx 10xxxxxx
1799 * 0001 0000-0010 FFFF | 11110xxx 10xxxxxx 10xxxxxx 10xxxxxx
1800 * ---------------------------------------------------------------------
1803 * ---------------------------------------------------------------------
1804 * UTF8-octets = *( UTF8-char )
1805 * UTF8-char = UTF8-1 / UTF8-2 / UTF8-3 / UTF8-4
1807 * UTF8-2 = %xC2-DF UTF8-tail
1809 * UTF8-3 = %xE0 %xA0-BF UTF8-tail / %xE1-EC 2( UTF8-tail ) /
1810 * %xED %x80-9F UTF8-tail / %xEE-EF 2( UTF8-tail )
1811 * UTF8-4 = %xF0 %x90-BF 2( UTF8-tail ) / %xF1-F3 3( UTF8-tail ) /
1812 * %xF4 %x80-8F 2( UTF8-tail )
1813 * UTF8-tail = %x80-BF
1814 * ---------------------------------------------------------------------
1816 * @param $uch (string) character string to process.
1817 * @return integer Unicode value
1818 * @author Nicola Asuni
1821 public static function getUniord($uch) {
1822 if (function_exists('mb_convert_encoding')) {
1823 list(, $char) = @unpack
('N', mb_convert_encoding($uch, 'UCS-4BE', 'UTF-8'));
1828 $bytes = array(); // array containing single character byte sequences
1830 $numbytes = 1; // number of octetc needed to represent the UTF-8 character
1831 $length = strlen($uch);
1832 for ($i = 0; $i < $length; ++
$i) {
1833 $char = ord($uch[$i]); // get one string character at time
1834 if ($countbytes == 0) { // get starting octect
1835 if ($char <= 0x7F) {
1836 return $char; // use the character "as is" because is ASCII
1837 } elseif (($char >> 0x05) == 0x06) { // 2 bytes character (0x06 = 110 BIN)
1838 $bytes[] = ($char - 0xC0) << 0x06;
1841 } elseif (($char >> 0x04) == 0x0E) { // 3 bytes character (0x0E = 1110 BIN)
1842 $bytes[] = ($char - 0xE0) << 0x0C;
1845 } elseif (($char >> 0x03) == 0x1E) { // 4 bytes character (0x1E = 11110 BIN)
1846 $bytes[] = ($char - 0xF0) << 0x12;
1850 // use replacement character for other invalid sequences
1853 } elseif (($char >> 0x06) == 0x02) { // bytes 2, 3 and 4 must start with 0x02 = 10 BIN
1854 $bytes[] = $char - 0x80;
1856 if ($countbytes == $numbytes) {
1857 // compose UTF-8 bytes to a single unicode value
1859 for ($j = 1; $j < $numbytes; ++
$j) {
1860 $char +
= ($bytes[$j] << (($numbytes - $j - 1) * 0x06));
1862 if ((($char >= 0xD800) AND ($char <= 0xDFFF)) OR ($char >= 0x10FFFF)) {
1863 // The definition of UTF-8 prohibits encoding character numbers between
1864 // U+D800 and U+DFFF, which are reserved for use with the UTF-16
1865 // encoding form (as surrogate pairs) and do not directly represent
1867 return 0xFFFD; // use replacement character
1873 // use replacement character for other invalid sequences
1881 * Converts UTF-8 strings to codepoints array.<br>
1882 * Invalid byte sequences will be replaced with 0xFFFD (replacement character)<br>
1883 * @param $str (string) string to process.
1884 * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1885 * @param $currentfont (array) Reference to current font array.
1886 * @return array containing codepoints (UTF-8 characters values)
1887 * @author Nicola Asuni
1890 public static function UTF8StringToArray($str, $isunicode=true, &$currentfont) {
1892 // requires PCRE unicode support turned on
1893 $chars = TCPDF_STATIC
::pregSplit('//','u', $str, -1, PREG_SPLIT_NO_EMPTY
);
1894 $carr = array_map(array('TCPDF_FONTS', 'uniord'), $chars);
1896 $chars = str_split($str);
1897 $carr = array_map('ord', $chars);
1899 $currentfont['subsetchars'] +
= array_fill_keys($carr, true);
1904 * Converts UTF-8 strings to Latin1 when using the standard 14 core fonts.<br>
1905 * @param $str (string) string to process.
1906 * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1907 * @param $currentfont (array) Reference to current font array.
1909 * @since 3.2.000 (2008-06-23)
1912 public static function UTF8ToLatin1($str, $isunicode=true, &$currentfont) {
1913 $unicode = self
::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
1914 return self
::UTF8ArrToLatin1($unicode);
1918 * Converts UTF-8 strings to UTF16-BE.<br>
1919 * @param $str (string) string to process.
1920 * @param $setbom (boolean) if true set the Byte Order Mark (BOM = 0xFEFF)
1921 * @param $isunicode (boolean) True when the documetn is in Unicode mode, false otherwise.
1922 * @param $currentfont (array) Reference to current font array.
1924 * @author Nicola Asuni
1925 * @since 1.53.0.TC005 (2005-01-05)
1928 public static function UTF8ToUTF16BE($str, $setbom=false, $isunicode=true, &$currentfont) {
1930 return $str; // string is not in unicode
1932 $unicode = self
::UTF8StringToArray($str, $isunicode, $currentfont); // array containing UTF-8 unicode values
1933 return self
::arrUTF8ToUTF16BE($unicode, $setbom);
1937 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1938 * @param $str (string) string to manipulate.
1939 * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
1940 * @param $forcertl (bool) if true forces RTL text direction
1941 * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1942 * @param $currentfont (array) Reference to current font array.
1944 * @author Nicola Asuni
1945 * @since 2.1.000 (2008-01-08)
1948 public static function utf8StrRev($str, $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
1949 return self
::utf8StrArrRev(self
::UTF8StringToArray($str, $isunicode, $currentfont), $str, $setbom, $forcertl, $isunicode, $currentfont);
1953 * Reverse the RLT substrings array using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1954 * @param $arr (array) array of unicode values.
1955 * @param $str (string) string to manipulate (or empty value).
1956 * @param $setbom (bool) if true set the Byte Order Mark (BOM = 0xFEFF)
1957 * @param $forcertl (bool) if true forces RTL text direction
1958 * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1959 * @param $currentfont (array) Reference to current font array.
1961 * @author Nicola Asuni
1962 * @since 4.9.000 (2010-03-27)
1965 public static function utf8StrArrRev($arr, $str='', $setbom=false, $forcertl=false, $isunicode=true, &$currentfont) {
1966 return self
::arrUTF8ToUTF16BE(self
::utf8Bidi($arr, $str, $forcertl, $isunicode, $currentfont), $setbom);
1970 * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
1971 * @param $ta (array) array of characters composing the string.
1972 * @param $str (string) string to process
1973 * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
1974 * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
1975 * @param $currentfont (array) Reference to current font array.
1976 * @return array of unicode chars
1977 * @author Nicola Asuni
1978 * @since 2.4.000 (2008-03-06)
1981 public static function utf8Bidi($ta, $str='', $forcertl=false, $isunicode=true, &$currentfont) {
1982 // paragraph embedding level
1986 if (TCPDF_STATIC
::empty_string($str)) {
1987 // create string from array
1988 $str = self
::UTF8ArrSubString($ta, '', '', $isunicode);
1990 // check if string contains arabic text
1991 if (preg_match(TCPDF_FONT_DATA
::$uni_RE_PATTERN_ARABIC, $str)) {
1996 // check if string contains RTL text
1997 if (!($forcertl OR $arabic OR preg_match(TCPDF_FONT_DATA
::$uni_RE_PATTERN_RTL, $str))) {
2001 // get number of chars
2002 $numchars = count($ta);
2004 if ($forcertl == 'R') {
2006 } elseif ($forcertl == 'L') {
2009 // P2. In each paragraph, find the first character of type L, AL, or R.
2010 // 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.
2011 for ($i=0; $i < $numchars; ++
$i) {
2012 $type = TCPDF_FONT_DATA
::$uni_type[$ta[$i]];
2016 } elseif (($type == 'AL') OR ($type == 'R')) {
2023 // Current Embedding Level
2025 // directional override status
2027 $remember = array();
2028 // start-of-level-run
2029 $sor = $pel %
2 ?
'R' : 'L';
2032 // Array of characters data
2033 $chardata = Array();
2035 // 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.
2036 // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
2037 for ($i=0; $i < $numchars; ++
$i) {
2038 if ($ta[$i] == TCPDF_FONT_DATA
::$uni_RLE) {
2039 // X2. With each RLE, compute the least greater odd embedding level.
2040 // 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.
2041 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2042 $next_level = $cel +
($cel %
2) +
1;
2043 if ($next_level < 62) {
2044 $remember[] = array('num' => TCPDF_FONT_DATA
::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
2048 $eor = $cel %
2 ?
'R' : 'L';
2050 } elseif ($ta[$i] == TCPDF_FONT_DATA
::$uni_LRE) {
2051 // X3. With each LRE, compute the least greater even embedding level.
2052 // 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.
2053 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2054 $next_level = $cel +
2 - ($cel %
2);
2055 if ( $next_level < 62 ) {
2056 $remember[] = array('num' => TCPDF_FONT_DATA
::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
2060 $eor = $cel %
2 ?
'R' : 'L';
2062 } elseif ($ta[$i] == TCPDF_FONT_DATA
::$uni_RLO) {
2063 // X4. With each RLO, compute the least greater odd embedding level.
2064 // 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.
2065 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2066 $next_level = $cel +
($cel %
2) +
1;
2067 if ($next_level < 62) {
2068 $remember[] = array('num' => TCPDF_FONT_DATA
::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
2072 $eor = $cel %
2 ?
'R' : 'L';
2074 } elseif ($ta[$i] == TCPDF_FONT_DATA
::$uni_LRO) {
2075 // X5. With each LRO, compute the least greater even embedding level.
2076 // 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.
2077 // b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
2078 $next_level = $cel +
2 - ($cel %
2);
2079 if ( $next_level < 62 ) {
2080 $remember[] = array('num' => TCPDF_FONT_DATA
::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
2084 $eor = $cel %
2 ?
'R' : 'L';
2086 } elseif ($ta[$i] == TCPDF_FONT_DATA
::$uni_PDF) {
2087 // 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.
2088 if (count($remember)) {
2089 $last = count($remember ) - 1;
2090 if (($remember[$last]['num'] == TCPDF_FONT_DATA
::$uni_RLE) OR
2091 ($remember[$last]['num'] == TCPDF_FONT_DATA
::$uni_LRE) OR
2092 ($remember[$last]['num'] == TCPDF_FONT_DATA
::$uni_RLO) OR
2093 ($remember[$last]['num'] == TCPDF_FONT_DATA
::$uni_LRO)) {
2094 $match = array_pop($remember);
2095 $cel = $match['cel'];
2096 $dos = $match['dos'];
2098 $eor = ($cel > $match['cel'] ?
$cel : $match['cel']) %
2 ?
'R' : 'L';
2101 } elseif (($ta[$i] != TCPDF_FONT_DATA
::$uni_RLE) AND
2102 ($ta[$i] != TCPDF_FONT_DATA
::$uni_LRE) AND
2103 ($ta[$i] != TCPDF_FONT_DATA
::$uni_RLO) AND
2104 ($ta[$i] != TCPDF_FONT_DATA
::$uni_LRO) AND
2105 ($ta[$i] != TCPDF_FONT_DATA
::$uni_PDF)) {
2106 // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
2107 // a. Set the level of the current character to the current embedding level.
2108 // b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
2112 if (isset(TCPDF_FONT_DATA
::$uni_type[$ta[$i]])) {
2113 $chardir = TCPDF_FONT_DATA
::$uni_type[$ta[$i]];
2118 // stores string characters and other information
2119 $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
2121 } // end for each char
2123 // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
2124 // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
2125 // 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.
2127 // 3.3.3 Resolving Weak Types
2128 // 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.
2129 // Nonspacing marks are now resolved based on the previous characters.
2130 $numchars = count($chardata);
2132 // 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.
2133 $prevlevel = -1; // track level changes
2134 $levcount = 0; // counts consecutive chars at the same level
2135 for ($i=0; $i < $numchars; ++
$i) {
2136 if ($chardata[$i]['type'] == 'NSM') {
2138 $chardata[$i]['type'] = $chardata[$i]['sor'];
2140 $chardata[$i]['type'] = $chardata[($i-1)]['type'];
2143 if ($chardata[$i]['level'] != $prevlevel) {
2148 $prevlevel = $chardata[$i]['level'];
2151 // 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.
2154 for ($i=0; $i < $numchars; ++
$i) {
2155 if ($chardata[$i]['char'] == 'EN') {
2156 for ($j=$levcount; $j >= 0; $j--) {
2157 if ($chardata[$j]['type'] == 'AL') {
2158 $chardata[$i]['type'] = 'AN';
2159 } elseif (($chardata[$j]['type'] == 'L') OR ($chardata[$j]['type'] == 'R')) {
2164 if ($chardata[$i]['level'] != $prevlevel) {
2169 $prevlevel = $chardata[$i]['level'];
2172 // W3. Change all ALs to R.
2173 for ($i=0; $i < $numchars; ++
$i) {
2174 if ($chardata[$i]['type'] == 'AL') {
2175 $chardata[$i]['type'] = 'R';
2179 // 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.
2182 for ($i=0; $i < $numchars; ++
$i) {
2183 if (($levcount > 0) AND (($i+
1) < $numchars) AND ($chardata[($i+
1)]['level'] == $prevlevel)) {
2184 if (($chardata[$i]['type'] == 'ES') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+
1)]['type'] == 'EN')) {
2185 $chardata[$i]['type'] = 'EN';
2186 } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'EN') AND ($chardata[($i+
1)]['type'] == 'EN')) {
2187 $chardata[$i]['type'] = 'EN';
2188 } elseif (($chardata[$i]['type'] == 'CS') AND ($chardata[($i-1)]['type'] == 'AN') AND ($chardata[($i+
1)]['type'] == 'AN')) {
2189 $chardata[$i]['type'] = 'AN';
2192 if ($chardata[$i]['level'] != $prevlevel) {
2197 $prevlevel = $chardata[$i]['level'];
2200 // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
2203 for ($i=0; $i < $numchars; ++
$i) {
2204 if ($chardata[$i]['type'] == 'ET') {
2205 if (($levcount > 0) AND ($chardata[($i-1)]['type'] == 'EN')) {
2206 $chardata[$i]['type'] = 'EN';
2209 while (($j < $numchars) AND ($chardata[$j]['level'] == $prevlevel)) {
2210 if ($chardata[$j]['type'] == 'EN') {
2211 $chardata[$i]['type'] = 'EN';
2213 } elseif ($chardata[$j]['type'] != 'ET') {
2220 if ($chardata[$i]['level'] != $prevlevel) {
2225 $prevlevel = $chardata[$i]['level'];
2228 // W6. Otherwise, separators and terminators change to Other Neutral.
2231 for ($i=0; $i < $numchars; ++
$i) {
2232 if (($chardata[$i]['type'] == 'ET') OR ($chardata[$i]['type'] == 'ES') OR ($chardata[$i]['type'] == 'CS')) {
2233 $chardata[$i]['type'] = 'ON';
2235 if ($chardata[$i]['level'] != $prevlevel) {
2240 $prevlevel = $chardata[$i]['level'];
2243 //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.
2246 for ($i=0; $i < $numchars; ++
$i) {
2247 if ($chardata[$i]['char'] == 'EN') {
2248 for ($j=$levcount; $j >= 0; $j--) {
2249 if ($chardata[$j]['type'] == 'L') {
2250 $chardata[$i]['type'] = 'L';
2251 } elseif ($chardata[$j]['type'] == 'R') {
2256 if ($chardata[$i]['level'] != $prevlevel) {
2261 $prevlevel = $chardata[$i]['level'];
2264 // 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.
2267 for ($i=0; $i < $numchars; ++
$i) {
2268 if (($levcount > 0) AND (($i+
1) < $numchars) AND ($chardata[($i+
1)]['level'] == $prevlevel)) {
2269 if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[($i+
1)]['type'] == 'L')) {
2270 $chardata[$i]['type'] = 'L';
2271 } elseif (($chardata[$i]['type'] == 'N') AND
2272 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2273 (($chardata[($i+
1)]['type'] == 'R') OR ($chardata[($i+
1)]['type'] == 'EN') OR ($chardata[($i+
1)]['type'] == 'AN'))) {
2274 $chardata[$i]['type'] = 'R';
2275 } elseif ($chardata[$i]['type'] == 'N') {
2276 // N2. Any remaining neutrals take the embedding direction
2277 $chardata[$i]['type'] = $chardata[$i]['sor'];
2279 } elseif (($levcount == 0) AND (($i+
1) < $numchars) AND ($chardata[($i+
1)]['level'] == $prevlevel)) {
2281 if (($chardata[$i]['type'] == 'N') AND ($chardata[$i]['sor'] == 'L') AND ($chardata[($i+
1)]['type'] == 'L')) {
2282 $chardata[$i]['type'] = 'L';
2283 } elseif (($chardata[$i]['type'] == 'N') AND
2284 (($chardata[$i]['sor'] == 'R') OR ($chardata[$i]['sor'] == 'EN') OR ($chardata[$i]['sor'] == 'AN')) AND
2285 (($chardata[($i+
1)]['type'] == 'R') OR ($chardata[($i+
1)]['type'] == 'EN') OR ($chardata[($i+
1)]['type'] == 'AN'))) {
2286 $chardata[$i]['type'] = 'R';
2287 } elseif ($chardata[$i]['type'] == 'N') {
2288 // N2. Any remaining neutrals take the embedding direction
2289 $chardata[$i]['type'] = $chardata[$i]['sor'];
2291 } elseif (($levcount > 0) AND ((($i+
1) == $numchars) OR (($i+
1) < $numchars) AND ($chardata[($i+
1)]['level'] != $prevlevel))) {
2293 if (($chardata[$i]['type'] == 'N') AND ($chardata[($i-1)]['type'] == 'L') AND ($chardata[$i]['eor'] == 'L')) {
2294 $chardata[$i]['type'] = 'L';
2295 } elseif (($chardata[$i]['type'] == 'N') AND
2296 (($chardata[($i-1)]['type'] == 'R') OR ($chardata[($i-1)]['type'] == 'EN') OR ($chardata[($i-1)]['type'] == 'AN')) AND
2297 (($chardata[$i]['eor'] == 'R') OR ($chardata[$i]['eor'] == 'EN') OR ($chardata[$i]['eor'] == 'AN'))) {
2298 $chardata[$i]['type'] = 'R';
2299 } elseif ($chardata[$i]['type'] == 'N') {
2300 // N2. Any remaining neutrals take the embedding direction
2301 $chardata[$i]['type'] = $chardata[$i]['sor'];
2303 } elseif ($chardata[$i]['type'] == 'N') {
2304 // N2. Any remaining neutrals take the embedding direction
2305 $chardata[$i]['type'] = $chardata[$i]['sor'];
2307 if ($chardata[$i]['level'] != $prevlevel) {
2312 $prevlevel = $chardata[$i]['level'];
2315 // 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.
2316 // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
2317 for ($i=0; $i < $numchars; ++
$i) {
2318 $odd = $chardata[$i]['level'] %
2;
2320 if (($chardata[$i]['type'] == 'L') OR ($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2321 $chardata[$i]['level'] +
= 1;
2324 if ($chardata[$i]['type'] == 'R') {
2325 $chardata[$i]['level'] +
= 1;
2326 } elseif (($chardata[$i]['type'] == 'AN') OR ($chardata[$i]['type'] == 'EN')) {
2327 $chardata[$i]['level'] +
= 2;
2330 $maxlevel = max($chardata[$i]['level'],$maxlevel);
2333 // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
2334 // 1. Segment separators,
2335 // 2. Paragraph separators,
2336 // 3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
2337 // 4. Any sequence of white space characters at the end of the line.
2338 for ($i=0; $i < $numchars; ++
$i) {
2339 if (($chardata[$i]['type'] == 'B') OR ($chardata[$i]['type'] == 'S')) {
2340 $chardata[$i]['level'] = $pel;
2341 } elseif ($chardata[$i]['type'] == 'WS') {
2343 while ($j < $numchars) {
2344 if ((($chardata[$j]['type'] == 'B') OR ($chardata[$j]['type'] == 'S')) OR
2345 (($j == ($numchars-1)) AND ($chardata[$j]['type'] == 'WS'))) {
2346 $chardata[$i]['level'] = $pel;
2348 } elseif ($chardata[$j]['type'] != 'WS') {
2357 // 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.
2359 $endedletter = array(1569,1570,1571,1572,1573,1575,1577,1583,1584,1585,1586,1608,1688);
2360 $alfletter = array(1570,1571,1573,1575);
2361 $chardata2 = $chardata;
2365 for ($i=0; $i < $numchars; ++
$i) {
2366 if ((TCPDF_FONT_DATA
::$uni_type[$chardata[$i]['char']] == 'AL') OR ($chardata[$i]['char'] == 32) OR ($chardata[$i]['char'] == 8204)) {
2367 $charAL[$x] = $chardata[$i];
2368 $charAL[$x]['i'] = $i;
2369 $chardata[$i]['x'] = $x;
2374 for ($i=0; $i < $numchars; ++
$i) {
2375 $thischar = $chardata[$i];
2377 $prevchar = $chardata[($i-1)];
2381 if (($i+
1) < $numchars) {
2382 $nextchar = $chardata[($i+
1)];
2386 if (TCPDF_FONT_DATA
::$uni_type[$thischar['char']] == 'AL') {
2387 $x = $thischar['x'];
2389 $prevchar = $charAL[($x-1)];
2393 if (($x+
1) < $numAL) {
2394 $nextchar = $charAL[($x+
1)];
2399 if (($prevchar !== false) AND ($prevchar['char'] == 1604) AND (in_array($thischar['char'], $alfletter))) {
2400 $arabicarr = TCPDF_FONT_DATA
::$uni_laa_array;
2403 $prevchar = $charAL[($x-2)];
2408 $arabicarr = TCPDF_FONT_DATA
::$uni_arabicsubst;
2411 if (($prevchar !== false) AND ($nextchar !== false) AND
2412 ((TCPDF_FONT_DATA
::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA
::$uni_type[$prevchar['char']] == 'NSM')) AND
2413 ((TCPDF_FONT_DATA
::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA
::$uni_type[$nextchar['char']] == 'NSM')) AND
2414 ($prevchar['type'] == $thischar['type']) AND
2415 ($nextchar['type'] == $thischar['type']) AND
2416 ($nextchar['char'] != 1567)) {
2417 if (in_array($prevchar['char'], $endedletter)) {
2418 if (isset($arabicarr[$thischar['char']][2])) {
2420 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2423 if (isset($arabicarr[$thischar['char']][3])) {
2425 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
2428 } elseif (($nextchar !== false) AND
2429 ((TCPDF_FONT_DATA
::$uni_type[$nextchar['char']] == 'AL') OR (TCPDF_FONT_DATA
::$uni_type[$nextchar['char']] == 'NSM')) AND
2430 ($nextchar['type'] == $thischar['type']) AND
2431 ($nextchar['char'] != 1567)) {
2432 if (isset($arabicarr[$chardata[$i]['char']][2])) {
2434 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
2436 } elseif ((($prevchar !== false) AND
2437 ((TCPDF_FONT_DATA
::$uni_type[$prevchar['char']] == 'AL') OR (TCPDF_FONT_DATA
::$uni_type[$prevchar['char']] == 'NSM')) AND
2438 ($prevchar['type'] == $thischar['type'])) OR
2439 (($nextchar !== false) AND ($nextchar['char'] == 1567))) {
2441 if (($i > 1) AND ($thischar['char'] == 1607) AND
2442 ($chardata[$i-1]['char'] == 1604) AND
2443 ($chardata[$i-2]['char'] == 1604)) {
2445 // mark characters to delete with false
2446 $chardata2[$i-2]['char'] = false;
2447 $chardata2[$i-1]['char'] = false;
2448 $chardata2[$i]['char'] = 65010;
2450 if (($prevchar !== false) AND in_array($prevchar['char'], $endedletter)) {
2451 if (isset($arabicarr[$thischar['char']][0])) {
2453 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2456 if (isset($arabicarr[$thischar['char']][1])) {
2458 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
2462 } elseif (isset($arabicarr[$thischar['char']][0])) {
2464 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
2468 // mark characters to delete with false
2469 $chardata2[($charAL[($x-1)]['i'])]['char'] = false;
2471 } // end if AL (Arabic Letter)
2472 } // end for each char
2474 * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
2475 * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
2477 for ($i = 0; $i < ($numchars-1); ++
$i) {
2478 if (($chardata2[$i]['char'] == 1617) AND (isset(TCPDF_FONT_DATA
::$uni_diacritics[($chardata2[$i+
1]['char'])]))) {
2479 // check if the subtitution font is defined on current font
2480 if (isset($currentfont['cw'][(TCPDF_FONT_DATA
::$uni_diacritics[($chardata2[$i+
1]['char'])])])) {
2481 $chardata2[$i]['char'] = false;
2482 $chardata2[$i+
1]['char'] = TCPDF_FONT_DATA
::$uni_diacritics[($chardata2[$i+
1]['char'])];
2486 // remove marked characters
2487 foreach ($chardata2 as $key => $value) {
2488 if ($value['char'] === false) {
2489 unset($chardata2[$key]);
2492 $chardata = array_values($chardata2);
2493 $numchars = count($chardata);
2500 // 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.
2501 for ($j=$maxlevel; $j > 0; $j--) {
2502 $ordarray = Array();
2505 for ($i=0; $i < $numchars; ++
$i) {
2506 if ($chardata[$i]['level'] >= $j) {
2508 if (isset(TCPDF_FONT_DATA
::$uni_mirror[$chardata[$i]['char']])) {
2509 // 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.
2510 $chardata[$i]['char'] = TCPDF_FONT_DATA
::$uni_mirror[$chardata[$i]['char']];
2512 $revarr[] = $chardata[$i];
2515 $revarr = array_reverse($revarr);
2516 $ordarray = array_merge($ordarray, $revarr);
2520 $ordarray[] = $chardata[$i];
2524 $revarr = array_reverse($revarr);
2525 $ordarray = array_merge($ordarray, $revarr);
2527 $chardata = $ordarray;
2529 $ordarray = array();
2530 foreach ($chardata as $cd) {
2531 $ordarray[] = $cd['char'];
2532 // store char values for subsetting
2533 $currentfont['subsetchars'][$cd['char']] = true;
2539 * Get a reference font size.
2540 * @param $size (string) String containing font size value.
2541 * @param $refsize (float) Reference font size in points.
2542 * @return float value in points
2545 public static function getFontRefSize($size, $refsize=12) {
2548 $size = ($refsize - 4);
2552 $size = ($refsize - 3);
2556 $size = ($refsize - 2);
2564 $size = ($refsize +
2);
2568 $size = ($refsize +
4);
2572 $size = ($refsize +
6);
2576 $size = ($refsize - 3);
2580 $size = ($refsize +
3);
2587 } // END OF TCPDF_FONTS CLASS
2589 //============================================================+
2591 //============================================================+