Example #1
0
 /**
  * Returns an array of hyphenation patterns.
  * @param $file (string) TEX file containing hypenation patterns. TEX pattrns can be downloaded from http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
  * @return array of hyphenation patterns
  * @author Nicola Asuni
  * @since 4.9.012 (2010-04-12)
  * @public static
  */
 public static function getHyphenPatternsFromTEX($file)
 {
     // TEX patterns are available at:
     // http://www.ctan.org/tex-archive/language/hyph-utf8/tex/generic/hyph-utf8/patterns/
     $data = file_get_contents($file);
     $patterns = array();
     // remove comments
     $data = preg_replace('/\\%[^\\n]*/', '', $data);
     // extract the patterns part
     preg_match('/\\\\patterns\\{([^\\}]*)\\}/i', $data, $matches);
     $data = trim(substr($matches[0], 10, -1));
     // extract each pattern
     $patterns_array = preg_split('/[\\s]+/', $data);
     // create new language array of patterns
     $patterns = array();
     foreach ($patterns_array as $val) {
         if (!TcpdfStatic::empty_string($val)) {
             $val = trim($val);
             $val = str_replace('\'', '\\\'', $val);
             $key = preg_replace('/[0-9]+/', '', $val);
             $patterns[$key] = $val;
         }
     }
     return $patterns;
 }
Example #2
0
    public function printPDF($type, $categorie_id = 1, $products = array(), $declinaisons = array(), $unitmeasure = 'mm', $orientation = 'P', $font_size = 10, $color = '000000', $margins = array('top' => 0, 'left' => 0, 'right' => 0, 'bottom' => 0), $barheight = 80, $barwidth = 50, $blocheight = 150, $blocwidth = 100, $blockborder = 0, $dimension = 'A4', $stretch = 0, $setborder = 1, $showname = 1, $showref = 1, $showprice = 1, $nbproduct = 1000, $manufacturer_id = 0)
    {
        $usereduction = Configuration::get('JC-EANUPC-usereduction');
        $font_size_barcode = $font_size;
        $unitmeasure == 'mm' ? $font_size *= 2.84 : ($font_size *= 1);
        $color = $this->hex2RGB(trim($color));
        $max_length = 1000;
        if (Configuration::get('JC-EANUPC-truncateproductname')) {
            $max_length = Configuration::get('JC-EANUPC-truncateproductname');
        }
        $allproduct = array();
        if (!isset($products) || empty($products) || !$products || $products['0'] == '0') {
            $allproduct = Product::getProducts((int) $this->context->language->id, 0, '3000', 'id_product', 'ASC', $categorie_id);
        } else {
            if (empty($declinaisons)) {
                $i = 0;
                foreach ($products as $product) {
                    $p = new Product($product, false, $this->context->language->id);
                    $allproduct[$i]['id_product'] = $p->id;
                    $allproduct[$i]['name'] = $p->name;
                    $allproduct[$i]['price'] = Product::getPriceStatic($p->id, true, null, 6, null, false, $usereduction);
                    $allproduct[$i]['reference'] = $p->reference;
                    $allproduct[$i]['ean13'] = $p->ean13;
                    $allproduct[$i]['upc'] = $p->upc;
                    $allproduct[$i]['id_manufacturer'] = $p->id_manufacturer;
                    $allproduct[$i]['quantity'] = Product::getQuantity($p->id);
                    $i++;
                }
            } else {
                $i = 0;
                $comb_array = array();
                foreach ($products as $product) {
                    $p = new Product($product, $this->context->language->id);
                    if ($p->id) {
                        /* Build attributes combinations */
                        $combinations = $p->getAttributeCombinations($this->context->language->id);
                        $groups = array();
                        if (is_array($combinations)) {
                            foreach ($combinations as $k => $combination) {
                                if (in_array($combination['id_product_attribute'], $declinaisons)) {
                                    // $price_to_convert = Tools::convertPrice($p->getPrice(true), $this->context->currency);
                                    // $price = Tools::displayPrice($price_to_convert, $this->context->currency);
                                    $comb_array[$combination['id_product_attribute']]['id_product_attribute'] = $combination['id_product_attribute'];
                                    $comb_array[$combination['id_product_attribute']]['attributes'][] = $combination['group_name'] . '-' . $combination['attribute_name'];
                                    $comb_array[$combination['id_product_attribute']]['wholesale_price'] = $combination['wholesale_price'];
                                    $comb_array[$combination['id_product_attribute']]['price'] = Product::getPriceStatic($p->id, true, $combination['id_product_attribute'], 6, null, false, $usereduction);
                                    $comb_array[$combination['id_product_attribute']]['weight'] = $combination['weight'] . Configuration::get('PS_WEIGHT_UNIT');
                                    $comb_array[$combination['id_product_attribute']]['unit_impact'] = $combination['unit_price_impact'];
                                    $comb_array[$combination['id_product_attribute']]['reference'] = $combination['reference'];
                                    $comb_array[$combination['id_product_attribute']]['ean13'] = $combination['ean13'];
                                    $comb_array[$combination['id_product_attribute']]['quantity'] = $combination['quantity'];
                                    $comb_array[$combination['id_product_attribute']]['upc'] = $combination['upc'];
                                    $comb_array[$combination['id_product_attribute']]['product_name'] = $p->name[$this->context->language->id];
                                }
                            }
                        }
                    }
                }
                $i = 0;
                foreach ($comb_array as $product) {
                    // $p = new Product($product, false, $this->context->language->id);
                    // $allproduct[$i]['id_product'] = $product['id_product'];
                    $allproduct[$i]['name'] = $product['product_name'] . ' | ' . implode(',', $product['attributes']);
                    $allproduct[$i]['price'] = $product['price'];
                    $allproduct[$i]['reference'] = $product['reference'];
                    $allproduct[$i]['ean13'] = $product['ean13'];
                    $allproduct[$i]['upc'] = $product['upc'];
                    $allproduct[$i]['quantity'] = $product['quantity'];
                    // $allproduct[$i]['id_manufacturer'] = $p->id_manufacturer;
                    $i++;
                }
            }
        }
        $array = array(1 => 'one', 2 => 'two', 3 => 'three');
        if ($manufacturer_id != 0) {
            foreach ($allproduct as $key => $product) {
                if ($product['id_manufacturer'] != $manufacturer_id) {
                    unset($allproduct[$key]);
                }
            }
        }
        if ($dimension == 'C76') {
            $custom_layout = array($blocwidth + $margins['left'] + $margins['right'], $blocheight + $barheight);
            $pdf = new TCPDF($orientation, $unitmeasure, $custom_layout);
        } else {
            $pdf = new TCPDF($orientation, $unitmeasure, $dimension);
        }
        $pdf->SetCreator('bqbarcodegeneration');
        $pdf->setPrintHeader(false);
        $pdf->setPrintFooter(false);
        $pdf->SetFont('helvetica', '', $font_size);
        // set default font subsetting mode
        $pdf->setFontSubsetting(true);
        // set font
        $pdf->SetFont('freeserif', '', $font_size);
        $pdf->SetMargins($margins['left'], $margins['top'], $margins['right'], false);
        $pdf->SetHeaderMargin(0);
        $pdf->SetFooterMargin(0);
        // set auto page breaks
        $pdf->SetAutoPageBreak(true, $margins['bottom']);
        $page_width = TcpdfStatic::getPageSizeFromFormat($dimension);
        $nblignes = 1;
        if ($dimension == 'C76') {
            $nbcol = 1;
        } else {
            $nbcol = (int) $page_width[0] / ($blocwidth / 25.4 * 72);
            $nblignes = (int) $page_width[1] / ($blocheight / 25.4 * 72);
        }
        if ((int) $nbcol == 0) {
            $nbcol = 1;
        }
        // limit division by zero
        $c = 0;
        if ($type == 'upc') {
            $type2 = 'UPCA';
        } else {
            $type2 = $type;
        }
        $htmloutput = '<table border="0" ><tr>';
        foreach ($allproduct as $product) {
            $quantity = 0;
            if ($nbproduct == 0) {
                $quantity = $product['quantity'];
            } else {
                $quantity = $nbproduct;
            }
            if ($quantity == 0) {
                $quantity++;
            }
            for ($i = 0; $i < $quantity; $i++) {
                $barcode = $product[$type];
                $pdf_params = $pdf->serializeTCPDFtagParameters(array(Tools::substr($barcode, 0, -1), $type2, '', '', $barwidth, $barheight, 0.4, array('position' => 'C', 'border' => $setborder == 2 ? true : false, 'padding' => 0, 'fitwidth' => false, 'fgcolor' => $color, 'bgcolor' => array(255, 255, 255), 'text' => true, 'font' => 'Helvetica', 'font_size' => $font_size_barcode, 'stretchtext' => $stretch, 'label' => ''), 'N'));
                if ($c % $nbcol == 0 && $c > 0 && $dimension != 'C76') {
                    $htmloutput .= '</tr><tr>';
                }
                // $currency = Currency::getCurrent();
                // convert to pixel unit 1mm = 2.84
                if (empty($barcode)) {
                    $this->html .= '<div class=" error">' . $this->l('There is one or more missed code in selected type, plz regenerate missed code and try again') . '</div>';
                    return $this->html;
                }
                $autre = '';
                if ($showname) {
                    $autre .= ' <tr><td><div style="text-align:left;">' . Tools::truncate($product['name'], $max_length) . '</div></td></tr>';
                }
                // if ($showprice)
                // $autre .= ' <tr><td><p style="text-align:left">'.$this->l('Price').': '.Tools::displayPrice(Product::getPriceStatic($product['id_product'])).'</p></td></tr>';
                if ($showprice) {
                    $autre .= ' <tr><td><p style="text-align:left">' . $this->l('Price') . ': ' . Tools::displayPrice($product['price']) . '</p></td></tr>';
                }
                if ($showref) {
                    $autre .= ' <tr><td><p style="text-align:left">' . $this->l('Reference') . ': ' . $product['reference'] . '</p></td></tr>';
                }
                $htmloutput .= '<td style="' . ($blockborder == 1 ? 'border:1px dashed #555; border-right:0px solid #fff; ' : '') . ' font-size:' . $font_size . '%;margin:0;padding:0">
			&nbsp;&nbsp;<table>
			' . $autre . '
			</table>';
                $htmloutput .= '<table border="0" ><tr><td colspan=2>
			<tcpdf method="write1DBarcode" params="' . $pdf_params . '"/></td></tr>
			<tr><td style="font-size:150%"><p style="text-align:center">' . Tools::substr($barcode, 0, 1) . ' ' . wordwrap(Tools::substr($barcode, 1, 12), 4, '  ', true) . '</p></td></tr></table>';
                $htmloutput .= '</td>';
                $c++;
                if ($dimension == 'C76') {
                    $htmloutput .= '</tr></table>';
                    $pdf->AddPage();
                    $pdf->writeHTML($htmloutput, true, 0, true, 0);
                    $htmloutput = '<table border="0" ><tr>';
                }
            }
        }
        $htmloutput .= '</tr></table> ';
        if ($dimension != 'C76') {
            // $pdf->SetAutoPageBreak(true, 0);
            $pdf->AddPage();
            $pdf->writeHTML($htmloutput, true, 0, true, 0);
        }
        $filename = dirname(__FILE__) . '/pdf/' . $type . '.pdf';
        //Output the document
        ob_clean();
        $pdf->Output($filename, 'F');
        header('Content-type: application/pdf; charset=utf-8');
        header('Content-Disposition: inline; filename="$filename"');
        header('Content-Length: ' . filesize($filename));
        readfile($filename);
    }
Example #3
0
 /**
  * Extract info from a PNG file without using the GD library.
  * @param $file (string) image file to parse
  * @return array structure containing the image data
  * @public static
  */
 public static function _parsepng($file)
 {
     $f = fopen($file, 'rb');
     if ($f === false) {
         // Can't open image file
         return false;
     }
     //Check signature
     if (fread($f, 8) != chr(137) . 'PNG' . chr(13) . chr(10) . chr(26) . chr(10)) {
         // Not a PNG file
         return false;
     }
     //Read header chunk
     fread($f, 4);
     if (fread($f, 4) != 'IHDR') {
         //Incorrect PNG file
         return false;
     }
     $w = TcpdfStatic::_freadint($f);
     $h = TcpdfStatic::_freadint($f);
     $bpc = ord(fread($f, 1));
     $ct = ord(fread($f, 1));
     if ($ct == 0) {
         $colspace = 'DeviceGray';
     } elseif ($ct == 2) {
         $colspace = 'DeviceRGB';
     } elseif ($ct == 3) {
         $colspace = 'Indexed';
     } else {
         // alpha channel
         fclose($f);
         return 'pngalpha';
     }
     if (ord(fread($f, 1)) != 0) {
         // Unknown compression method
         fclose($f);
         return false;
     }
     if (ord(fread($f, 1)) != 0) {
         // Unknown filter method
         fclose($f);
         return false;
     }
     if (ord(fread($f, 1)) != 0) {
         // Interlacing not supported
         fclose($f);
         return false;
     }
     fread($f, 4);
     $channels = $ct == 2 ? 3 : 1;
     $parms = '/DecodeParms << /Predictor 15 /Colors ' . $channels . ' /BitsPerComponent ' . $bpc . ' /Columns ' . $w . ' >>';
     //Scan chunks looking for palette, transparency and image data
     $pal = '';
     $trns = '';
     $data = '';
     $icc = false;
     do {
         $n = TcpdfStatic::_freadint($f);
         $type = fread($f, 4);
         if ($type == 'PLTE') {
             // read palette
             $pal = TcpdfStatic::rfread($f, $n);
             fread($f, 4);
         } elseif ($type == 'tRNS') {
             // read transparency info
             $t = TcpdfStatic::rfread($f, $n);
             if ($ct == 0) {
                 $trns = array(ord($t[1]));
             } elseif ($ct == 2) {
                 $trns = array(ord($t[1]), ord($t[3]), ord($t[5]));
             } else {
                 $pos = strpos($t, chr(0));
                 if ($pos !== false) {
                     $trns = array($pos);
                 }
             }
             fread($f, 4);
         } elseif ($type == 'IDAT') {
             // read image data block
             $data .= TcpdfStatic::rfread($f, $n);
             fread($f, 4);
         } elseif ($type == 'iCCP') {
             // skip profile name
             $len = 0;
             while (ord(fread($f, 1)) > 0 and $len < 80) {
                 ++$len;
             }
             // skip null separator
             fread($f, 1);
             // get compression method
             if (ord(fread($f, 1)) != 0) {
                 // Unknown filter method
                 fclose($f);
                 return false;
             }
             // read ICC Color Profile
             $icc = TcpdfStatic::rfread($f, $n - $len - 2);
             // decompress profile
             $icc = gzuncompress($icc);
             fread($f, 4);
         } elseif ($type == 'IEND') {
             break;
         } else {
             TcpdfStatic::rfread($f, $n + 4);
         }
     } while ($n);
     if ($colspace == 'Indexed' and empty($pal)) {
         // Missing palette
         fclose($f);
         return false;
     }
     fclose($f);
     return array('w' => $w, 'h' => $h, 'ch' => $channels, 'icc' => $icc, 'cs' => $colspace, 'bpc' => $bpc, 'f' => 'FlateDecode', 'parms' => $parms, 'pal' => $pal, 'trns' => $trns, 'data' => $data);
 }
Example #4
0
 /**
  * Reverse the RLT substrings using the Bidirectional Algorithm (http://unicode.org/reports/tr9/).
  * @param $ta (array) array of characters composing the string.
  * @param $str (string) string to process
  * @param $forcertl (bool) if 'R' forces RTL, if 'L' forces LTR
  * @param $isunicode (boolean) True if the document is in Unicode mode, false otherwise.
  * @param $currentfont (array) Reference to current font array.
  * @return array of unicode chars
  * @author Nicola Asuni
  * @since 2.4.000 (2008-03-06)
  * @public static
  */
 public static function utf8Bidi($ta, $str = '', $forcertl = false, $isunicode = true, &$currentfont)
 {
     // paragraph embedding level
     $pel = 0;
     // max level
     $maxlevel = 0;
     if (TcpdfStatic::empty_string($str)) {
         // create string from array
         $str = self::UTF8ArrSubString($ta, '', '', $isunicode);
     }
     // check if string contains arabic text
     if (preg_match(TcpdfFontData::$uni_RE_PATTERN_ARABIC, $str)) {
         $arabic = true;
     } else {
         $arabic = false;
     }
     // check if string contains RTL text
     if (!($forcertl or $arabic or preg_match(TcpdfFontData::$uni_RE_PATTERN_RTL, $str))) {
         return $ta;
     }
     // get number of chars
     $numchars = count($ta);
     if ($forcertl == 'R') {
         $pel = 1;
     } elseif ($forcertl == 'L') {
         $pel = 0;
     } else {
         // P2. In each paragraph, find the first character of type L, AL, or R.
         // 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.
         for ($i = 0; $i < $numchars; ++$i) {
             $type = TcpdfFontData::$uni_type[$ta[$i]];
             if ($type == 'L') {
                 $pel = 0;
                 break;
             } elseif ($type == 'AL' or $type == 'R') {
                 $pel = 1;
                 break;
             }
         }
     }
     // Current Embedding Level
     $cel = $pel;
     // directional override status
     $dos = 'N';
     $remember = array();
     // start-of-level-run
     $sor = $pel % 2 ? 'R' : 'L';
     $eor = $sor;
     // Array of characters data
     $chardata = array();
     // 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.
     // In the resolution of levels in rules I1 and I2, the maximum embedding level of 62 can be reached.
     for ($i = 0; $i < $numchars; ++$i) {
         if ($ta[$i] == TcpdfFontData::$uni_RLE) {
             // X2. With each RLE, compute the least greater odd embedding level.
             //	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.
             //	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
             $next_level = $cel + $cel % 2 + 1;
             if ($next_level < 62) {
                 $remember[] = array('num' => TcpdfFontData::$uni_RLE, 'cel' => $cel, 'dos' => $dos);
                 $cel = $next_level;
                 $dos = 'N';
                 $sor = $eor;
                 $eor = $cel % 2 ? 'R' : 'L';
             }
         } elseif ($ta[$i] == TcpdfFontData::$uni_LRE) {
             // X3. With each LRE, compute the least greater even embedding level.
             //	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.
             //	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
             $next_level = $cel + 2 - $cel % 2;
             if ($next_level < 62) {
                 $remember[] = array('num' => TcpdfFontData::$uni_LRE, 'cel' => $cel, 'dos' => $dos);
                 $cel = $next_level;
                 $dos = 'N';
                 $sor = $eor;
                 $eor = $cel % 2 ? 'R' : 'L';
             }
         } elseif ($ta[$i] == TcpdfFontData::$uni_RLO) {
             // X4. With each RLO, compute the least greater odd embedding level.
             //	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.
             //	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
             $next_level = $cel + $cel % 2 + 1;
             if ($next_level < 62) {
                 $remember[] = array('num' => TcpdfFontData::$uni_RLO, 'cel' => $cel, 'dos' => $dos);
                 $cel = $next_level;
                 $dos = 'R';
                 $sor = $eor;
                 $eor = $cel % 2 ? 'R' : 'L';
             }
         } elseif ($ta[$i] == TcpdfFontData::$uni_LRO) {
             // X5. With each LRO, compute the least greater even embedding level.
             //	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.
             //	b. If the new level would not be valid, then this code is invalid. Do not change the current level or override status.
             $next_level = $cel + 2 - $cel % 2;
             if ($next_level < 62) {
                 $remember[] = array('num' => TcpdfFontData::$uni_LRO, 'cel' => $cel, 'dos' => $dos);
                 $cel = $next_level;
                 $dos = 'L';
                 $sor = $eor;
                 $eor = $cel % 2 ? 'R' : 'L';
             }
         } elseif ($ta[$i] == TcpdfFontData::$uni_PDF) {
             // 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.
             if (count($remember)) {
                 $last = count($remember) - 1;
                 if ($remember[$last]['num'] == TcpdfFontData::$uni_RLE or $remember[$last]['num'] == TcpdfFontData::$uni_LRE or $remember[$last]['num'] == TcpdfFontData::$uni_RLO or $remember[$last]['num'] == TcpdfFontData::$uni_LRO) {
                     $match = array_pop($remember);
                     $cel = $match['cel'];
                     $dos = $match['dos'];
                     $sor = $eor;
                     $eor = ($cel > $match['cel'] ? $cel : $match['cel']) % 2 ? 'R' : 'L';
                 }
             }
         } elseif ($ta[$i] != TcpdfFontData::$uni_RLE and $ta[$i] != TcpdfFontData::$uni_LRE and $ta[$i] != TcpdfFontData::$uni_RLO and $ta[$i] != TcpdfFontData::$uni_LRO and $ta[$i] != TcpdfFontData::$uni_PDF) {
             // X6. For all types besides RLE, LRE, RLO, LRO, and PDF:
             //	a. Set the level of the current character to the current embedding level.
             //	b. Whenever the directional override status is not neutral, reset the current character type to the directional override status.
             if ($dos != 'N') {
                 $chardir = $dos;
             } else {
                 if (isset(TcpdfFontData::$uni_type[$ta[$i]])) {
                     $chardir = TcpdfFontData::$uni_type[$ta[$i]];
                 } else {
                     $chardir = 'L';
                 }
             }
             // stores string characters and other information
             $chardata[] = array('char' => $ta[$i], 'level' => $cel, 'type' => $chardir, 'sor' => $sor, 'eor' => $eor);
         }
     }
     // end for each char
     // X8. All explicit directional embeddings and overrides are completely terminated at the end of each paragraph. Paragraph separators are not included in the embedding.
     // X9. Remove all RLE, LRE, RLO, LRO, PDF, and BN codes.
     // 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.
     // 3.3.3 Resolving Weak Types
     // 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.
     // Nonspacing marks are now resolved based on the previous characters.
     $numchars = count($chardata);
     // 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.
     $prevlevel = -1;
     // track level changes
     $levcount = 0;
     // counts consecutive chars at the same level
     for ($i = 0; $i < $numchars; ++$i) {
         if ($chardata[$i]['type'] == 'NSM') {
             if ($levcount) {
                 $chardata[$i]['type'] = $chardata[$i]['sor'];
             } elseif ($i > 0) {
                 $chardata[$i]['type'] = $chardata[$i - 1]['type'];
             }
         }
         if ($chardata[$i]['level'] != $prevlevel) {
             $levcount = 0;
         } else {
             ++$levcount;
         }
         $prevlevel = $chardata[$i]['level'];
     }
     // 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.
     $prevlevel = -1;
     $levcount = 0;
     for ($i = 0; $i < $numchars; ++$i) {
         if ($chardata[$i]['char'] == 'EN') {
             for ($j = $levcount; $j >= 0; $j--) {
                 if ($chardata[$j]['type'] == 'AL') {
                     $chardata[$i]['type'] = 'AN';
                 } elseif ($chardata[$j]['type'] == 'L' or $chardata[$j]['type'] == 'R') {
                     break;
                 }
             }
         }
         if ($chardata[$i]['level'] != $prevlevel) {
             $levcount = 0;
         } else {
             ++$levcount;
         }
         $prevlevel = $chardata[$i]['level'];
     }
     // W3. Change all ALs to R.
     for ($i = 0; $i < $numchars; ++$i) {
         if ($chardata[$i]['type'] == 'AL') {
             $chardata[$i]['type'] = 'R';
         }
     }
     // 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.
     $prevlevel = -1;
     $levcount = 0;
     for ($i = 0; $i < $numchars; ++$i) {
         if ($levcount > 0 and $i + 1 < $numchars and $chardata[$i + 1]['level'] == $prevlevel) {
             if ($chardata[$i]['type'] == 'ES' and $chardata[$i - 1]['type'] == 'EN' and $chardata[$i + 1]['type'] == 'EN') {
                 $chardata[$i]['type'] = 'EN';
             } elseif ($chardata[$i]['type'] == 'CS' and $chardata[$i - 1]['type'] == 'EN' and $chardata[$i + 1]['type'] == 'EN') {
                 $chardata[$i]['type'] = 'EN';
             } elseif ($chardata[$i]['type'] == 'CS' and $chardata[$i - 1]['type'] == 'AN' and $chardata[$i + 1]['type'] == 'AN') {
                 $chardata[$i]['type'] = 'AN';
             }
         }
         if ($chardata[$i]['level'] != $prevlevel) {
             $levcount = 0;
         } else {
             ++$levcount;
         }
         $prevlevel = $chardata[$i]['level'];
     }
     // W5. A sequence of European terminators adjacent to European numbers changes to all European numbers.
     $prevlevel = -1;
     $levcount = 0;
     for ($i = 0; $i < $numchars; ++$i) {
         if ($chardata[$i]['type'] == 'ET') {
             if ($levcount > 0 and $chardata[$i - 1]['type'] == 'EN') {
                 $chardata[$i]['type'] = 'EN';
             } else {
                 $j = $i + 1;
                 while ($j < $numchars and $chardata[$j]['level'] == $prevlevel) {
                     if ($chardata[$j]['type'] == 'EN') {
                         $chardata[$i]['type'] = 'EN';
                         break;
                     } elseif ($chardata[$j]['type'] != 'ET') {
                         break;
                     }
                     ++$j;
                 }
             }
         }
         if ($chardata[$i]['level'] != $prevlevel) {
             $levcount = 0;
         } else {
             ++$levcount;
         }
         $prevlevel = $chardata[$i]['level'];
     }
     // W6. Otherwise, separators and terminators change to Other Neutral.
     $prevlevel = -1;
     $levcount = 0;
     for ($i = 0; $i < $numchars; ++$i) {
         if ($chardata[$i]['type'] == 'ET' or $chardata[$i]['type'] == 'ES' or $chardata[$i]['type'] == 'CS') {
             $chardata[$i]['type'] = 'ON';
         }
         if ($chardata[$i]['level'] != $prevlevel) {
             $levcount = 0;
         } else {
             ++$levcount;
         }
         $prevlevel = $chardata[$i]['level'];
     }
     //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.
     $prevlevel = -1;
     $levcount = 0;
     for ($i = 0; $i < $numchars; ++$i) {
         if ($chardata[$i]['char'] == 'EN') {
             for ($j = $levcount; $j >= 0; $j--) {
                 if ($chardata[$j]['type'] == 'L') {
                     $chardata[$i]['type'] = 'L';
                 } elseif ($chardata[$j]['type'] == 'R') {
                     break;
                 }
             }
         }
         if ($chardata[$i]['level'] != $prevlevel) {
             $levcount = 0;
         } else {
             ++$levcount;
         }
         $prevlevel = $chardata[$i]['level'];
     }
     // 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.
     $prevlevel = -1;
     $levcount = 0;
     for ($i = 0; $i < $numchars; ++$i) {
         if ($levcount > 0 and $i + 1 < $numchars and $chardata[$i + 1]['level'] == $prevlevel) {
             if ($chardata[$i]['type'] == 'N' and $chardata[$i - 1]['type'] == 'L' and $chardata[$i + 1]['type'] == 'L') {
                 $chardata[$i]['type'] = 'L';
             } elseif ($chardata[$i]['type'] == 'N' and ($chardata[$i - 1]['type'] == 'R' or $chardata[$i - 1]['type'] == 'EN' or $chardata[$i - 1]['type'] == 'AN') and ($chardata[$i + 1]['type'] == 'R' or $chardata[$i + 1]['type'] == 'EN' or $chardata[$i + 1]['type'] == 'AN')) {
                 $chardata[$i]['type'] = 'R';
             } elseif ($chardata[$i]['type'] == 'N') {
                 // N2. Any remaining neutrals take the embedding direction
                 $chardata[$i]['type'] = $chardata[$i]['sor'];
             }
         } elseif ($levcount == 0 and $i + 1 < $numchars and $chardata[$i + 1]['level'] == $prevlevel) {
             // first char
             if ($chardata[$i]['type'] == 'N' and $chardata[$i]['sor'] == 'L' and $chardata[$i + 1]['type'] == 'L') {
                 $chardata[$i]['type'] = 'L';
             } elseif ($chardata[$i]['type'] == 'N' and ($chardata[$i]['sor'] == 'R' or $chardata[$i]['sor'] == 'EN' or $chardata[$i]['sor'] == 'AN') and ($chardata[$i + 1]['type'] == 'R' or $chardata[$i + 1]['type'] == 'EN' or $chardata[$i + 1]['type'] == 'AN')) {
                 $chardata[$i]['type'] = 'R';
             } elseif ($chardata[$i]['type'] == 'N') {
                 // N2. Any remaining neutrals take the embedding direction
                 $chardata[$i]['type'] = $chardata[$i]['sor'];
             }
         } elseif ($levcount > 0 and ($i + 1 == $numchars or $i + 1 < $numchars and $chardata[$i + 1]['level'] != $prevlevel)) {
             //last char
             if ($chardata[$i]['type'] == 'N' and $chardata[$i - 1]['type'] == 'L' and $chardata[$i]['eor'] == 'L') {
                 $chardata[$i]['type'] = 'L';
             } elseif ($chardata[$i]['type'] == 'N' and ($chardata[$i - 1]['type'] == 'R' or $chardata[$i - 1]['type'] == 'EN' or $chardata[$i - 1]['type'] == 'AN') and ($chardata[$i]['eor'] == 'R' or $chardata[$i]['eor'] == 'EN' or $chardata[$i]['eor'] == 'AN')) {
                 $chardata[$i]['type'] = 'R';
             } elseif ($chardata[$i]['type'] == 'N') {
                 // N2. Any remaining neutrals take the embedding direction
                 $chardata[$i]['type'] = $chardata[$i]['sor'];
             }
         } elseif ($chardata[$i]['type'] == 'N') {
             // N2. Any remaining neutrals take the embedding direction
             $chardata[$i]['type'] = $chardata[$i]['sor'];
         }
         if ($chardata[$i]['level'] != $prevlevel) {
             $levcount = 0;
         } else {
             ++$levcount;
         }
         $prevlevel = $chardata[$i]['level'];
     }
     // 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.
     // I2. For all characters with an odd (right-to-left) embedding direction, those of type L, EN or AN go up one level.
     for ($i = 0; $i < $numchars; ++$i) {
         $odd = $chardata[$i]['level'] % 2;
         if ($odd) {
             if ($chardata[$i]['type'] == 'L' or $chardata[$i]['type'] == 'AN' or $chardata[$i]['type'] == 'EN') {
                 $chardata[$i]['level'] += 1;
             }
         } else {
             if ($chardata[$i]['type'] == 'R') {
                 $chardata[$i]['level'] += 1;
             } elseif ($chardata[$i]['type'] == 'AN' or $chardata[$i]['type'] == 'EN') {
                 $chardata[$i]['level'] += 2;
             }
         }
         $maxlevel = max($chardata[$i]['level'], $maxlevel);
     }
     // L1. On each line, reset the embedding level of the following characters to the paragraph embedding level:
     //	1. Segment separators,
     //	2. Paragraph separators,
     //	3. Any sequence of whitespace characters preceding a segment separator or paragraph separator, and
     //	4. Any sequence of white space characters at the end of the line.
     for ($i = 0; $i < $numchars; ++$i) {
         if ($chardata[$i]['type'] == 'B' or $chardata[$i]['type'] == 'S') {
             $chardata[$i]['level'] = $pel;
         } elseif ($chardata[$i]['type'] == 'WS') {
             $j = $i + 1;
             while ($j < $numchars) {
                 if ($chardata[$j]['type'] == 'B' or $chardata[$j]['type'] == 'S' or $j == $numchars - 1 and $chardata[$j]['type'] == 'WS') {
                     $chardata[$i]['level'] = $pel;
                     break;
                 } elseif ($chardata[$j]['type'] != 'WS') {
                     break;
                 }
                 ++$j;
             }
         }
     }
     // Arabic Shaping
     // 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.
     if ($arabic) {
         $endedletter = array(1569, 1570, 1571, 1572, 1573, 1575, 1577, 1583, 1584, 1585, 1586, 1608, 1688);
         $alfletter = array(1570, 1571, 1573, 1575);
         $chardata2 = $chardata;
         $laaletter = false;
         $charAL = array();
         $x = 0;
         for ($i = 0; $i < $numchars; ++$i) {
             if (TcpdfFontData::$uni_type[$chardata[$i]['char']] == 'AL' or $chardata[$i]['char'] == 32 or $chardata[$i]['char'] == 8204) {
                 $charAL[$x] = $chardata[$i];
                 $charAL[$x]['i'] = $i;
                 $chardata[$i]['x'] = $x;
                 ++$x;
             }
         }
         $numAL = $x;
         for ($i = 0; $i < $numchars; ++$i) {
             $thischar = $chardata[$i];
             if ($i > 0) {
                 $prevchar = $chardata[$i - 1];
             } else {
                 $prevchar = false;
             }
             if ($i + 1 < $numchars) {
                 $nextchar = $chardata[$i + 1];
             } else {
                 $nextchar = false;
             }
             if (TcpdfFontData::$uni_type[$thischar['char']] == 'AL') {
                 $x = $thischar['x'];
                 if ($x > 0) {
                     $prevchar = $charAL[$x - 1];
                 } else {
                     $prevchar = false;
                 }
                 if ($x + 1 < $numAL) {
                     $nextchar = $charAL[$x + 1];
                 } else {
                     $nextchar = false;
                 }
                 // if laa letter
                 if ($prevchar !== false and $prevchar['char'] == 1604 and in_array($thischar['char'], $alfletter)) {
                     $arabicarr = TcpdfFontData::$uni_laa_array;
                     $laaletter = true;
                     if ($x > 1) {
                         $prevchar = $charAL[$x - 2];
                     } else {
                         $prevchar = false;
                     }
                 } else {
                     $arabicarr = TcpdfFontData::$uni_arabicsubst;
                     $laaletter = false;
                 }
                 if ($prevchar !== false and $nextchar !== false and (TcpdfFontData::$uni_type[$prevchar['char']] == 'AL' or TcpdfFontData::$uni_type[$prevchar['char']] == 'NSM') and (TcpdfFontData::$uni_type[$nextchar['char']] == 'AL' or TcpdfFontData::$uni_type[$nextchar['char']] == 'NSM') and $prevchar['type'] == $thischar['type'] and $nextchar['type'] == $thischar['type'] and $nextchar['char'] != 1567) {
                     if (in_array($prevchar['char'], $endedletter)) {
                         if (isset($arabicarr[$thischar['char']][2])) {
                             // initial
                             $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
                         }
                     } else {
                         if (isset($arabicarr[$thischar['char']][3])) {
                             // medial
                             $chardata2[$i]['char'] = $arabicarr[$thischar['char']][3];
                         }
                     }
                 } elseif ($nextchar !== false and (TcpdfFontData::$uni_type[$nextchar['char']] == 'AL' or TcpdfFontData::$uni_type[$nextchar['char']] == 'NSM') and $nextchar['type'] == $thischar['type'] and $nextchar['char'] != 1567) {
                     if (isset($arabicarr[$chardata[$i]['char']][2])) {
                         // initial
                         $chardata2[$i]['char'] = $arabicarr[$thischar['char']][2];
                     }
                 } elseif ($prevchar !== false and (TcpdfFontData::$uni_type[$prevchar['char']] == 'AL' or TcpdfFontData::$uni_type[$prevchar['char']] == 'NSM') and $prevchar['type'] == $thischar['type'] or $nextchar !== false and $nextchar['char'] == 1567) {
                     // final
                     if ($i > 1 and $thischar['char'] == 1607 and $chardata[$i - 1]['char'] == 1604 and $chardata[$i - 2]['char'] == 1604) {
                         //Allah Word
                         // mark characters to delete with false
                         $chardata2[$i - 2]['char'] = false;
                         $chardata2[$i - 1]['char'] = false;
                         $chardata2[$i]['char'] = 65010;
                     } else {
                         if ($prevchar !== false and in_array($prevchar['char'], $endedletter)) {
                             if (isset($arabicarr[$thischar['char']][0])) {
                                 // isolated
                                 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
                             }
                         } else {
                             if (isset($arabicarr[$thischar['char']][1])) {
                                 // final
                                 $chardata2[$i]['char'] = $arabicarr[$thischar['char']][1];
                             }
                         }
                     }
                 } elseif (isset($arabicarr[$thischar['char']][0])) {
                     // isolated
                     $chardata2[$i]['char'] = $arabicarr[$thischar['char']][0];
                 }
                 // if laa letter
                 if ($laaletter) {
                     // mark characters to delete with false
                     $chardata2[$charAL[$x - 1]['i']]['char'] = false;
                 }
             }
             // end if AL (Arabic Letter)
         }
         // end for each char
         /*
          * Combining characters that can occur with Arabic Shadda (0651 HEX, 1617 DEC) are replaced.
          * Putting the combining mark and shadda in the same glyph allows us to avoid the two marks overlapping each other in an illegible manner.
          */
         for ($i = 0; $i < $numchars - 1; ++$i) {
             if ($chardata2[$i]['char'] == 1617 and isset(TcpdfFontData::$uni_diacritics[$chardata2[$i + 1]['char']])) {
                 // check if the subtitution font is defined on current font
                 if (isset($currentfont['cw'][TcpdfFontData::$uni_diacritics[$chardata2[$i + 1]['char']]])) {
                     $chardata2[$i]['char'] = false;
                     $chardata2[$i + 1]['char'] = TcpdfFontData::$uni_diacritics[$chardata2[$i + 1]['char']];
                 }
             }
         }
         // remove marked characters
         foreach ($chardata2 as $key => $value) {
             if ($value['char'] === false) {
                 unset($chardata2[$key]);
             }
         }
         $chardata = array_values($chardata2);
         $numchars = count($chardata);
         unset($chardata2);
         unset($arabicarr);
         unset($laaletter);
         unset($charAL);
     }
     // 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.
     for ($j = $maxlevel; $j > 0; $j--) {
         $ordarray = array();
         $revarr = array();
         $onlevel = false;
         for ($i = 0; $i < $numchars; ++$i) {
             if ($chardata[$i]['level'] >= $j) {
                 $onlevel = true;
                 if (isset(TcpdfFontData::$uni_mirror[$chardata[$i]['char']])) {
                     // 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.
                     $chardata[$i]['char'] = TcpdfFontData::$uni_mirror[$chardata[$i]['char']];
                 }
                 $revarr[] = $chardata[$i];
             } else {
                 if ($onlevel) {
                     $revarr = array_reverse($revarr);
                     $ordarray = array_merge($ordarray, $revarr);
                     $revarr = array();
                     $onlevel = false;
                 }
                 $ordarray[] = $chardata[$i];
             }
         }
         if ($onlevel) {
             $revarr = array_reverse($revarr);
             $ordarray = array_merge($ordarray, $revarr);
         }
         $chardata = $ordarray;
     }
     $ordarray = array();
     foreach ($chardata as $cd) {
         $ordarray[] = $cd['char'];
         // store char values for subsetting
         $currentfont['subsetchars'][$cd['char']] = true;
     }
     return $ordarray;
 }