include/tcpdf_fonts.php Quellcode

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




Korrekturen, Hinweise und Ergänzungen

Bitte scheuen Sie sich nicht und melden Sie, was auf dieser Seite sachlich falsch oder irreführend ist, was ergänzt werden sollte, was fehlt usw. Dazu bitte oben aus dem Menü Seite den Eintrag Support Forum wählen. Es ist eine kostenlose Anmeldung erforderlich, um Anmerkungen zu posten. Unpassende Postings, Spam usw. werden kommentarlos entfernt.