PluginProbe
E2Pdf – Export Pdf Tool for WordPress / 1.32.26
E2Pdf – Export Pdf Tool for WordPress v1.32.26
1.32.49 1.32.48 1.32.43 1.32.40 1.32.34 1.32.32 1.32.31 1.32.26 1.32.22 1.32.23 1.32.18 1.32.17 1.32.15 trunk 1.00.00 1.00.13 1.01.01 1.02.02 1.03.07 1.04.07 1.05.03 1.06.02 1.07.11 1.08.00 1.08.06 All 72 releases
e2pdf / classes / helper / e2pdf-rtl.php

e2pdf-rtl.php in E2Pdf – Export Pdf Tool for WordPress 1.32.26, at classes/helper/e2pdf-rtl.php

708 lines 23.6 KB
No matching file
Up and down to move Enter to open Esc to close
Raw Download Zip
1 <?php
2
3 /**
4 * File: /helper/e2pdf-rtl.php
5 *
6 * @package E2Pdf
7 * @license GPLv3 <https://www.gnu.org/licenses/gpl-3.0.html>
8 * @link https://e2pdf.com
9 *
10 * Portions of this class (functions: arabic(), getGlyphs(), preConvert(), a4MaxChars(),
11 * a4Lines(), utf8Glyphs(), decodeEntities(), decodeEntities2()) are derived from the
12 * I18N/Arabic library, which is licensed under the GNU Lesser General Public License (LGPL).
13 *
14 * Original Author: Khaled Al-Sham'aa <khaled@ar-php.org>
15 * Copyright: 2006-2016 Khaled Al-Sham'aa
16 * License: LGPL v3 <https://www.gnu.org/licenses/lgpl-3.0.html>
17 * Source: http://www.ar-php.org
18 */
19 if (!defined('ABSPATH')) {
20 die('Access denied.');
21 }
22
23 class Helper_E2pdf_Rtl {
24
25 private $_glyphs = null;
26 private $_hex = null;
27 private $_prevLink = null;
28 private $_nextLink = null;
29 private $_vowel = null;
30
31 public function rtl($value) {
32 if (preg_match('/\p{Bidi_Class=R}|\p{Bidi_Class=AL}/u', $value)) {
33 $reorderedText = $this->apply_bidi_algorithm($value);
34 return $this->process_characters($reorderedText);
35 } else {
36 return $value;
37 }
38 }
39
40 private function apply_bidi_algorithm($text) {
41 $runs = $this->analyze_runs($text);
42 return $this->reorder_runs($runs);
43 }
44
45 private function analyze_runs($text) {
46 $chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
47 $runs = [];
48 $currentRun = '';
49 $currentDir = null;
50 foreach ($chars as $char) {
51 $charDir = $this->get_char_direction($char);
52 if ($currentDir === null) {
53 $currentDir = $charDir;
54 $currentRun = $char;
55 } elseif ($currentDir === $charDir) {
56 $currentRun .= $char;
57 } else {
58 if ($currentRun !== '') {
59 $runs[] = ['text' => $currentRun, 'dir' => $currentDir];
60 }
61 $currentRun = $char;
62 $currentDir = $charDir;
63 }
64 }
65 if ($currentRun !== '') {
66 $runs[] = ['text' => $currentRun, 'dir' => $currentDir];
67 }
68 return $runs;
69 }
70
71 private function get_char_direction($char) {
72 if (preg_match('/\p{Bidi_Class=R}|\p{Bidi_Class=AL}/u', $char)) {
73 return 'RTL';
74 }
75 if (preg_match('/\p{N}/u', $char)) {
76 return 'NEUTRAL';
77 }
78
79 // spaces and punctuation
80 if (preg_match('/\s|[.!?،؟؛()[\]{}"\']/u', $char)) {
81 return 'NEUTRAL';
82 }
83
84 // everything else (Latin, etc.) is LTR
85 return 'LTR';
86 }
87
88 private function reorder_runs($runs) {
89 if (empty($runs)) {
90 return '';
91 }
92
93 // find the main paragraph direction (first strong character)
94 $mainDir = 'LTR'; // Default
95 foreach ($runs as $run) {
96 if ($run['dir'] === 'RTL') {
97 $mainDir = 'RTL';
98 break;
99 } elseif ($run['dir'] === 'LTR') {
100 break;
101 }
102 }
103
104 $result = '';
105 $rtlSequence = [];
106
107 foreach ($runs as $run) {
108 if ($run['dir'] === 'RTL') {
109 // collect RTL runs
110 $rtlSequence[] = $run;
111 } else {
112 // process any pending RTL sequence
113 if (!empty($rtlSequence)) {
114 // Reverse the RTL sequence and add to result
115 $rtlSequence = array_reverse($rtlSequence);
116 foreach ($rtlSequence as $rtlRun) {
117 $result .= $rtlRun['text'];
118 }
119 $rtlSequence = [];
120 }
121
122 // add LTR/NEUTRAL run as-is
123 $result .= $run['text'];
124 }
125 }
126
127 // process any remaining RTL sequence
128 if (!empty($rtlSequence)) {
129 $rtlSequence = array_reverse($rtlSequence);
130 foreach ($rtlSequence as $rtlRun) {
131 $result .= $rtlRun['text'];
132 }
133 }
134
135 // if main direction is RTL, reverse the entire result
136 if ($mainDir === 'RTL') {
137 $result = $this->reverse_by_words($result);
138 }
139
140 return $result;
141 }
142
143 private function reverse_by_words($text) {
144 // split by spaces while preserving them
145 $parts = preg_split('/(\s+)/u', $text, -1, PREG_SPLIT_DELIM_CAPTURE);
146 return implode('', array_reverse($parts));
147 }
148
149 private function process_characters($text) {
150 // split text to handle different scripts separately
151 $result = '';
152 $i = 0;
153 $length = mb_strlen($text, 'UTF-8');
154
155 while ($i < $length) {
156 $char = mb_substr($text, $i, 1, 'UTF-8');
157
158 if (preg_match('/\p{Arabic}/u', $char)) {
159 // collect arabic sequence
160 $arabicText = '';
161 while ($i < $length && preg_match('/\p{Arabic}/u', mb_substr($text, $i, 1, 'UTF-8'))) {
162 $arabicText .= mb_substr($text, $i, 1, 'UTF-8');
163 $i++;
164 }
165 $result .= $this->arabic($arabicText);
166 } elseif ($this->is_non_arabic_char(mb_substr($text, $i, 1, 'UTF-8'))) {
167 // collect non-arabic RTL sequence
168 $rtlText = '';
169 while ($i < $length && $this->is_non_arabic_char(mb_substr($text, $i, 1, 'UTF-8'))) {
170 $rtlText .= mb_substr($text, $i, 1, 'UTF-8');
171 $i++;
172 }
173 $result .= $this->non_arabic($rtlText);
174 } else {
175 // Regular character
176 $result .= $char;
177 $i++;
178 }
179 }
180 return $result;
181 }
182
183 function non_arabic($text) {
184 $chars = preg_split('//u', $text, -1, PREG_SPLIT_NO_EMPTY);
185 return implode('', array_reverse($chars));
186 }
187
188 function is_non_arabic_char($char) {
189 return preg_match('/\p{Bidi_Class=R}|\p{Bidi_Class=AL}/u', $char);
190 }
191
192 public function arabic($value, $max_chars = 99999, $hindo = true) {
193 $this->_prevLink = '،؟؛ـئبتثجحخسشصضطظعغفقكل�
194 نهي';
195 $this->_nextLink = 'ـآأؤإائبةتثجحخدذرز';
196 $this->_nextLink .= 'سشصضطظعغفقكل�
197 نهوىي';
198 $this->_vowel = 'ًٌٍَُِّْ';
199
200 $this->_glyphs = 'ًٌٍَُِّْٰ';
201 $this->_hex = '064B064B064B064B064C064C064C064C064D064D064D064D064E064E';
202 $this->_hex .= '064E064E064F064F064F064F06500650065006500651065106510651';
203 $this->_hex .= '06520652065206520670067006700670';
204
205 $this->_glyphs .= 'ءآأؤإئاب';
206 $this->_hex .= 'FE80FE80FE80FE80FE81FE82FE81FE82FE83FE84FE83FE84FE85FE86';
207 $this->_hex .= 'FE85FE86FE87FE88FE87FE88FE89FE8AFE8BFE8CFE8DFE8EFE8DFE8E';
208 $this->_hex .= 'FE8FFE90FE91FE92';
209
210 $this->_glyphs .= 'ةتثجحخدذ';
211 $this->_hex .= 'FE93FE94FE93FE94FE95FE96FE97FE98FE99FE9AFE9BFE9CFE9DFE9E';
212 $this->_hex .= 'FE9FFEA0FEA1FEA2FEA3FEA4FEA5FEA6FEA7FEA8FEA9FEAAFEA9FEAA';
213 $this->_hex .= 'FEABFEACFEABFEAC';
214
215 $this->_glyphs .= 'رزسشصضطظ';
216 $this->_hex .= 'FEADFEAEFEADFEAEFEAFFEB0FEAFFEB0FEB1FEB2FEB3FEB4FEB5FEB6';
217 $this->_hex .= 'FEB7FEB8FEB9FEBAFEBBFEBCFEBDFEBEFEBFFEC0FEC1FEC2FEC3FEC4';
218 $this->_hex .= 'FEC5FEC6FEC7FEC8';
219
220 $this->_glyphs .= 'عغفقكل�
221 ن';
222 $this->_hex .= 'FEC9FECAFECBFECCFECDFECEFECFFED0FED1FED2FED3FED4FED5FED6';
223 $this->_hex .= 'FED7FED8FED9FEDAFEDBFEDCFEDDFEDEFEDFFEE0FEE1FEE2FEE3FEE4';
224 $this->_hex .= 'FEE5FEE6FEE7FEE8';
225
226 $this->_glyphs .= 'هوىيـ،؟؛';
227 $this->_hex .= 'FEE9FEEAFEEBFEECFEEDFEEEFEEDFEEEFEEFFEF0FEEFFEF0FEF1FEF2';
228 $this->_hex .= 'FEF3FEF40640064006400640060C060C060C060C061F061F061F061F';
229 $this->_hex .= '061B061B061B061B';
230
231 // Support the extra 4 Persian letters (p), (ch), (zh) and (g)
232 // This needs value in getGlyphs function to be 52 instead of 48
233 // $this->_glyphs .= chr(129).chr(141).chr(142).chr(144);
234 // $this->_hex .= 'FB56FB57FB58FB59FB7AFB7BFB7CFB7DFB8AFB8BFB8AFB8BFB92';
235 // $this->_hex .= 'FB93FB94FB95';
236 //
237 // $this->_prevLink .= chr(129).chr(141).chr(142).chr(144);
238 // $this->_nextLink .= chr(129).chr(141).chr(142).chr(144);
239 //
240 // Example: $text = 'ن�
241 ونة قل�
242 : لاگچ ژافپ';
243 // Email Yossi Beck <yosbeck@gmail.com> ask him to save that example
244 // string using ANSI encoding in Notepad
245 $this->_glyphs .= '';
246 $this->_hex .= '';
247
248 $this->_glyphs .= 'لآلألإلا';
249 $this->_hex .= 'FEF5FEF6FEF5FEF6FEF7FEF8FEF7FEF8FEF9FEFAFEF9FEFAFEFBFEFC';
250 $this->_hex .= 'FEFBFEFC';
251
252 return $this->utf8Glyphs($value, $max_chars, $hindo);
253 }
254
255 /**
256 * Get glyphs
257 *
258 * @param string $char Char
259 * @param integer $type Type
260 *
261 * @return string
262 */
263 protected function getGlyphs($char, $type) {
264
265 $pos = mb_strpos($this->_glyphs, $char);
266
267 if ($pos > 49) {
268 $pos = ($pos - 49) / 2 + 49;
269 }
270
271 $pos = $pos * 16 + $type * 4;
272
273 return substr($this->_hex, $pos, 4);
274 }
275
276 /**
277 * Convert Arabic Windows-1256 charset string into glyph joining in UTF-8
278 * hexadecimals stream
279 *
280 * @param string $str Arabic string in Windows-1256 charset
281 *
282 * @return string Arabic glyph joining in UTF-8 hexadecimals stream
283 * @author Khaled Al-Sham'aa <khaled@ar-php.org>
284 */
285 protected function preConvert($str) {
286 $crntChar = null;
287 $prevChar = null;
288 $nextChar = null;
289 $output = '';
290
291 $_temp = mb_strlen($str);
292
293 for ($i = 0; $i < $_temp; $i++) {
294 $chars[] = mb_substr($str, $i, 1);
295 }
296
297 $max = count($chars);
298
299 for ($i = $max - 1; $i >= 0; $i--) {
300 $crntChar = $chars[$i];
301 $prevChar = ' ';
302
303 if ($i > 0) {
304 $prevChar = $chars[$i - 1];
305 }
306
307 if ($prevChar && mb_strpos($this->_vowel, $prevChar) !== false) {
308 $prevChar = $chars[$i - 2];
309 if ($prevChar && mb_strpos($this->_vowel, $prevChar) !== false) {
310 $prevChar = $chars[$i - 3];
311 }
312 }
313
314 $Reversed = false;
315 $flip_arr = ')]>}';
316 $ReversedChr = '([<{';
317
318 if ($crntChar && mb_strpos($flip_arr, $crntChar) !== false) {
319 $crntChar = $ReversedChr[mb_strpos($flip_arr, $crntChar)];
320 $Reversed = true;
321 } else {
322 $Reversed = false;
323 }
324
325 if ($crntChar && !$Reversed && (mb_strpos($ReversedChr, $crntChar) !== false)
326 ) {
327 $crntChar = $flip_arr[mb_strpos($ReversedChr, $crntChar)];
328 }
329
330 if (ord($crntChar) < 128) {
331 $output .= $crntChar;
332 $nextChar = $crntChar;
333 continue;
334 }
335
336 if ($crntChar == 'ل' && isset($chars[$i + 1]) && (mb_strpos('آأإا', $chars[$i + 1]) !== false)
337 ) {
338 continue;
339 }
340
341 if ($crntChar && mb_strpos($this->_vowel, $crntChar) !== false) {
342 if (isset($chars[$i + 1]) && (mb_strpos($this->_nextLink, $chars[$i + 1]) !== false) && (mb_strpos($this->_prevLink, $prevChar) !== false)
343 ) {
344 $output .= '&#x' . $this->getGlyphs($crntChar, 1) . ';';
345 } else {
346 $output .= '&#x' . $this->getGlyphs($crntChar, 0) . ';';
347 }
348 continue;
349 }
350
351 $form = 0;
352
353 if (($prevChar == 'لا' || $prevChar == 'لآ' || $prevChar == 'لأ' || $prevChar == 'لإ' || $prevChar == 'ل') && (mb_strpos('آأإا', $crntChar) !== false)
354 ) {
355 if (mb_strpos($this->_prevLink, $chars[$i - 2]) !== false) {
356 $form++;
357 }
358
359 if (mb_strpos($this->_vowel, $chars[$i - 1])) {
360 $output .= '&#x';
361 $output .= $this->getGlyphs($crntChar, $form) . ';';
362 } else {
363 $output .= '&#x';
364 $output .= $this->getGlyphs($prevChar . $crntChar, $form) . ';';
365 }
366 $nextChar = $prevChar;
367 continue;
368 }
369
370 if ($prevChar && mb_strpos($this->_prevLink, $prevChar) !== false) {
371 $form++;
372 }
373
374 if ($nextChar && mb_strpos($this->_nextLink, $nextChar) !== false) {
375 $form += 2;
376 }
377
378 $output .= '&#x' . $this->getGlyphs($crntChar, $form) . ';';
379 $nextChar = $crntChar;
380 }
381
382 // from Arabic Presentation Forms-B, Range: FE70-FEFF,
383 // file "UFE70.pdf" (in reversed order)
384 // into Arabic Presentation Forms-A, Range: FB50-FDFF, file "UFB50.pdf"
385 // Example: $output = str_replace('&#xFEA0;&#xFEDF;', '&#xFCC9;', $output);
386 // Lam Jeem
387
388 $output = $this->decodeEntities($output, $exclude = array('&'));
389 return $output;
390 }
391
392 /**
393 * Regression analysis calculate roughly the max number of character fit in
394 * one A4 page line for a given font size.
395 *
396 * @param integer $font Font size
397 *
398 * @return integer Maximum number of characters per line
399 * @author Khaled Al-Sham'aa <khaled@ar-php.org>
400 */
401 public function a4MaxChars($font) {
402 $x = 381.6 - 31.57 * $font + 1.182 * pow($font, 2) - 0.02052 *
403 pow($font, 3) + 0.0001342 * pow($font, 4);
404 return floor($x - 2);
405 }
406
407 /**
408 * Calculate the lines number of given Arabic text and font size that will
409 * fit in A4 page size
410 *
411 * @param string $str Arabic string you would like to split it into lines
412 * @param integer $font Font size
413 *
414 * @return integer Number of lines for a given Arabic string in A4 page size
415 * @author Khaled Al-Sham'aa <khaled@ar-php.org>
416 */
417 public function a4Lines($str, $font) {
418 $str = str_replace(array("\r\n", "\n", "\r"), "\n", $str);
419
420 $lines = 0;
421 $chars = 0;
422 $words = explode(' ', $str);
423 $w_count = count($words);
424 $max_chars = $this->a4MaxChars($font);
425
426 for ($i = 0; $i < $w_count; $i++) {
427 $w_len = mb_strlen($words[$i]) + 1;
428
429 if ($chars + $w_len < $max_chars) {
430 if (mb_strpos($words[$i], "\n") !== false) {
431 $words_nl = explode("\n", $words[$i]);
432
433 $nl_num = count($words_nl) - 1;
434 for ($j = 1; $j < $nl_num; $j++) {
435 $lines++;
436 }
437
438 $chars = mb_strlen($words_nl[$nl_num]) + 1;
439 } else {
440 $chars += $w_len;
441 }
442 } else {
443 $lines++;
444 $chars = $w_len;
445 }
446 }
447 $lines++;
448
449 return $lines;
450 }
451
452 /**
453 * Convert Arabic Windows-1256 charset string into glyph joining in UTF-8
454 * hexadecimals stream (take care of whole the document including English
455 * sections as well as numbers and arcs etc...)
456 *
457 * @param string $str Arabic string in Windows-1256 charset
458 * @param integer $max_chars Max number of chars you can fit in one line
459 * @param boolean $hindo If true use Hindo digits else use Arabic digits
460 *
461 * @return string Arabic glyph joining in UTF-8 hexadecimals stream (take
462 * care of whole document including English sections as well
463 * as numbers and arcs etc...)
464 * @author Khaled Al-Sham'aa <khaled@ar-php.org>
465 */
466 public function utf8Glyphs($str, $max_chars = 50, $hindo = true) {
467 $str = str_replace(array("\r\n", "\n", "\r"), " \n ", $str);
468 $str = str_replace("\t", ' ', $str);
469
470 $lines = array();
471 $words = explode(' ', $str);
472 $w_count = count($words);
473 $c_chars = 0;
474 $c_words = array();
475
476 $english = array();
477 $en_index = -1;
478
479 $en_words = array();
480 $en_stack = array();
481
482 for ($i = 0; $i < $w_count; $i++) {
483 $pattern = '/^(\n?)';
484 $pattern .= '[a-z\d\\/\@\#\$\%\^\&\*\(\)\_\~\"\'\[\]\{\}\;\,\|\-\.\:!]*';
485 $pattern .= '([\.\:\+\=\-\!،؟]?)$/i';
486
487 if (preg_match($pattern, $words[$i], $matches)) {
488 if ($matches[1]) {
489 $words[$i] = mb_substr($words[$i], 1) . $matches[1];
490 }
491 if ($matches[2]) {
492 $words[$i] = $matches[2] . mb_substr($words[$i], 0, -1);
493 }
494 $words[$i] = strrev($words[$i]);
495 array_push($english, $words[$i]);
496 if ($en_index == -1) {
497 $en_index = $i;
498 }
499 $en_words[] = true;
500 } elseif ($en_index != -1) {
501 $en_count = count($english);
502
503 for ($j = 0; $j < $en_count; $j++) {
504 $words[$en_index + $j] = $english[$en_count - 1 - $j];
505 }
506
507 $en_index = -1;
508 $english = array();
509
510 $en_words[] = false;
511 } else {
512 $en_words[] = false;
513 }
514 }
515
516 if ($en_index != -1) {
517 $en_count = count($english);
518
519 for ($j = 0; $j < $en_count; $j++) {
520 $words[$en_index + $j] = $english[$en_count - 1 - $j];
521 }
522 }
523
524 for ($i = 0; $i < $w_count; $i++) {
525 $w_len = mb_strlen($words[$i]) + 1;
526
527 if ($c_chars + $w_len < $max_chars) {
528 if (mb_strpos($words[$i], "\n") !== false) {
529 $words_nl = explode("\n", $words[$i]);
530
531 array_push($c_words, $words_nl[0]);
532 array_push($lines, implode(' ', $c_words));
533
534 $nl_num = count($words_nl) - 1;
535 for ($j = 1; $j < $nl_num; $j++) {
536 array_push($lines, $words_nl[$j]);
537 }
538
539 $c_words = array($words_nl[$nl_num]);
540 $c_chars = mb_strlen($words_nl[$nl_num]) + 1;
541 } else {
542 array_push($c_words, $words[$i]);
543 $c_chars += $w_len;
544 }
545 } else {
546 array_push($lines, implode(' ', $c_words));
547 $c_words = array($words[$i]);
548 $c_chars = $w_len;
549 }
550 }
551 array_push($lines, implode(' ', $c_words));
552
553 $maxLine = count($lines);
554 $output = '';
555
556 for ($j = $maxLine - 1; $j >= 0; $j--) {
557 $output .= $lines[$j] . "\n";
558 }
559
560 $output = rtrim($output);
561
562 $output = $this->preConvert($output);
563 if ($hindo) {
564 $nums = array(
565 '0', '1', '2', '3', '4',
566 '5', '6', '7', '8', '9',
567 );
568 $arNums = array(
569 '٠', '١', '٢', '٣', '٤',
570 '٥', '٦', '٧', '٨', '٩',
571 );
572
573 foreach ($nums as $k => $v) {
574 $p_nums[$k] = '/' . $v . '/ui';
575 }
576 $output = preg_replace($p_nums, $arNums, $output);
577
578 foreach ($arNums as $k => $v) {
579 $p_arNums[$k] = '/([a-z-\d]+)' . $v . '/ui';
580 }
581 foreach ($nums as $k => $v) {
582 $r_nums[$k] = '${1}' . $v;
583 }
584 $output = preg_replace($p_arNums, $r_nums, $output);
585
586 foreach ($arNums as $k => $v) {
587 $p_arNums[$k] = '/' . $v . '([a-z-\d]+)/ui';
588 }
589 foreach ($nums as $k => $v) {
590 $r_nums[$k] = $v . '${1}';
591 }
592 $output = preg_replace($p_arNums, $r_nums, $output);
593 }
594
595 return $output;
596 }
597
598 /**
599 * Decode all HTML entities (including numerical ones) to regular UTF-8 bytes.
600 * Double-escaped entities will only be decoded once
601 * ("&amp;lt;" becomes "&lt;", not "<").
602 *
603 * @param string $text The text to decode entities in.
604 * @param array $exclude An array of characters which should not be decoded.
605 * For example, array('<', '&', '"'). This affects
606 * both named and numerical entities.
607 *
608 * @return string
609 */
610 protected function decodeEntities($text, $exclude = array()) {
611 static $table;
612
613 // We store named entities in a table for quick processing.
614 if (!isset($table)) {
615 // Get all named HTML entities.
616 $table = array_flip(get_html_translation_table(HTML_ENTITIES));
617
618 // PHP gives us ISO-8859-1 data, we need UTF-8.
619 $table = array_map('utf8_encode', $table);
620
621 // Add apostrophe (XML)
622 $table['&apos;'] = "'";
623 }
624 $newtable = array_diff($table, $exclude);
625
626 // Use a regexp to select all entities in one pass, to avoid decoding
627 // double-escaped entities twice.
628 //return preg_replace('/&(#x?)?([A-Za-z0-9]+);/e',
629 // '$this->decodeEntities2("$1", "$2", "$0", $newtable,
630 // $exclude)', $text);
631
632 $pieces = explode('&', $text);
633 $text = array_shift($pieces);
634 foreach ($pieces as $piece) {
635 if ($piece[0] == '#') {
636 if ($piece[1] == 'x') {
637 $one = '#x';
638 } else {
639 $one = '#';
640 }
641 } else {
642 $one = '';
643 }
644 $end = mb_strpos($piece, ';');
645 $start = mb_strlen($one);
646
647 $two = mb_substr($piece, $start, $end - $start);
648 $zero = '&' . $one . $two . ';';
649 $text .= $this->decodeEntities2($one, $two, $zero, $newtable, $exclude) .
650 mb_substr($piece, $end + 1);
651 }
652 return $text;
653 }
654
655 /**
656 * Helper function for decodeEntities
657 *
658 * @param string $prefix Prefix
659 * @param string $codepoint Codepoint
660 * @param string $original Original
661 * @param array &$table Store named entities in a table
662 * @param array &$exclude An array of characters which should not be decoded
663 *
664 * @return string
665 */
666 protected function decodeEntities2(
667 $prefix, $codepoint, $original, &$table, &$exclude
668 ) {
669 // Named entity
670 if (!$prefix) {
671 if (isset($table[$original])) {
672 return $table[$original];
673 } else {
674 return $original;
675 }
676 }
677
678 // Hexadecimal numerical entity
679 if ($prefix == '#x') {
680 $codepoint = base_convert($codepoint, 16, 10);
681 }
682
683 // Encode codepoint as UTF-8 bytes
684 if ($codepoint < 0x80) {
685 $str = chr($codepoint);
686 } elseif ($codepoint < 0x800) {
687 $str = chr(0xC0 | ($codepoint >> 6)) .
688 chr(0x80 | ($codepoint & 0x3F));
689 } elseif ($codepoint < 0x10000) {
690 $str = chr(0xE0 | ($codepoint >> 12)) .
691 chr(0x80 | (($codepoint >> 6) & 0x3F)) .
692 chr(0x80 | ($codepoint & 0x3F));
693 } elseif ($codepoint < 0x200000) {
694 $str = chr(0xF0 | ($codepoint >> 18)) .
695 chr(0x80 | (($codepoint >> 12) & 0x3F)) .
696 chr(0x80 | (($codepoint >> 6) & 0x3F)) .
697 chr(0x80 | ($codepoint & 0x3F));
698 }
699
700 // Check for excluded characters
701 if (in_array($str, $exclude, false)) {
702 return $original;
703 } else {
704 return $str;
705 }
706 }
707 }
708