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