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