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