source: trunk/client/inc/hpdf5/tecnickcom/tcpdf/include/tcpdf_fonts.php@ 404

Last change on this file since 404 was 347, checked in by roby, 4 years ago

Aggiornamento per compatibilità con php7.4

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