public function Visualize($table_data, $font_size) { $ww_table = $this->wordWrapTable($table_data, $font_size, true); $rows = count($ww_table); $cols = array_keys($ww_table[0]); $col_size = array(); if ($this->encoding->useMB()) { $plus = mb_convert_encoding("+", $this->encoding->getEncodingType()); $newline = mb_convert_encoding("\n", $this->encoding->getEncodingType()); $dash = mb_convert_encoding("-", $this->encoding->getEncodingType()); $space = mb_convert_encoding(" ", $this->encoding->getEncodingType()); $vert = mb_convert_encoding('|', $this->encoding->getEncodingType()); $out = mb_convert_encoding("", $this->encoding->getEncodingType()); } else { $plus = "+"; $newline = "\n"; $dash = "-"; $space = " "; $vert = '|'; $out = ""; } $lengths = array(); for ($r = 0; $r < $rows; $r++) { $lengths[$r] = array(); } foreach ($cols as $col) { $max = 0; for ($r = 0; $r < $rows; $r++) { $z = count($ww_table[$r][$col]); for ($i = 0; $i < $z; $i++) { $line = $ww_table[$r][$col][$i]; if ($this->encoding->useMB()) { $w = mb_strlen($line, $this->encoding->getEncodingType()); } else { $w = strlen($line); } $lengths[$r][$col][$i] = $w; if ($w > $max) { $max = $w; } } } $col_size[$col] = $max; } //now do the display for ($r = 0; $r < $rows; $r++) { foreach ($cols as $col) { $out .= $plus; for ($j = 0; $j < $col_size[$col]; $j++) { $out .= $dash; } } $out .= $plus . $newline; $z = count($ww_table[$r][$cols[0]]); for ($i = 0; $i < $z; $i++) { foreach ($cols as $col) { $out .= $vert . $ww_table[$r][$col][$i]; for ($j = $lengths[$r][$col][$i]; $j < $col_size[$col]; $j++) { $out .= $space; } } $out .= $vert . $newline; } } foreach ($cols as $col) { $out .= $plus; for ($j = 0; $j < $col_size[$col]; $j++) { $out .= $dash; } } $out .= $plus . $newline; return $out; }
/** * Get the parts of a word which breaks along hyphenation points or any non-letter. * @param string $word the word we wish to break up * @param bool $supress true (default)to suppress hyphenation points at the beginning/end of a word. * @returns an the associative array has * a string 'Subword' which tells what the subword is, the int 'Offset' tells where the subword started, * the int 'Length' the length of the subword, and the boolean 'IsLetter' which tells us if the * subword is a composed of letters (by the Unicode convention) or not. */ public function getWordParts($word, $supress = true) { $word_parts = array(); $mb_encoding = $this->enc->getEncodingType(); $use_mb = $this->enc->useMB(); mb_regex_encoding($mb_encoding); if ($use_mb) { $word_len = mb_strlen($word, $mb_encoding); } else { $word_len = strlen($word); } if ($mb_encoding != 'UTF-8') { if ($mb_encoding) { $utf8_word = mb_convert_encoding($word, 'UTF-8', $mb_encoding); } else { $utf8_word = mb_convert_encoding($word, 'UTF-8'); } } else { $utf8_word = $word; } $i = 0; $prev_i = 0; $c = ''; do { $sub_word_len = 0; $is_letter = true; while ($i < $word_len && $is_letter) { $c = mb_substr($utf8_word, 0, 1, 'UTF-8'); //get the first character $utf8_word = mb_substr($utf8_word, 1); //delete the first character if (!preg_match('/^\\p{L}/', $c)) { //the character is not a letter $is_letter = false; } $sub_word_len++; $i++; } if (!$is_letter) { //the last character we read was a non-letter $sub_word_len--; } if ($use_mb) { $subword = mb_substr($word, $prev_i, $sub_word_len, $mb_encoding); } else { $subword = substr($word, $prev_i, $sub_word_len); } if ($sub_word_len > 0) { //get the hyphenation points for the word. $hp = $this->HyphenateWord($subword, $mb_encoding, $supress); $num_hp = count($hp); if ($use_mb) { for ($k = 0; $k < $num_hp - 1; $k++) { $word_parts[] = array('Offset' => $prev_i + $hp[$k], 'Length' => $hp[$k + 1] - $hp[$k], 'Subword' => mb_substr($subword, $hp[$k], $hp[$k + 1] - $hp[$k], $mb_encoding), 'IsLetter' => True); } $word_parts[] = array('Offset' => $prev_i + $hp[$num_hp - 1], 'Length' => $sub_word_len - $hp[$num_hp - 1], 'Subword' => mb_substr($subword, $hp[$num_hp - 1], $sub_word_len - $hp[$num_hp - 1], $mb_encoding), 'IsLetter' => True); } else { for ($k = 0; $k < $num_hp - 1; $k++) { $word_parts[] = array('Offset' => $prev_i + $hp[$k], 'Length' => $hp[$k + 1] - $hp[$k], 'Subword' => substr($subword, $hp[$k], $hp[$k + 1] - $hp[$k]), 'IsLetter' => True); } $word_parts[] = array('Offset' => $i + $hp[$num_hp - 1], 'Length' => $sub_word_len - $hp[$num_hp - 1], 'Subword' => substr($subword, $hp[$num_hp - 1]), 'IsLetter' => True); } } if (!$is_letter) { $word_parts[] = array('Offset' => $i, 'Length' => 1, 'Subword' => $c, 'IsLetter' => False); } $prev_i = $i; } while ($i < $word_len); return $word_parts; }
/** * Get the I2CE_Encoding according to one of the standard adobe encodings */ protected function getAdobeStandardEncoding($encoding) { if (isset($this->adobe_standard_encondings[$encoding])) { return; } $enc = new I2CE_Encoding($encoding); if (!isset($this->glyph_list)) { $this->loadGlyphList(); } $a = $this->load_file('PDF_CORE', $encoding . '.list'); foreach ($a as $l) { $l = trim($l); $e = explode(" ", rtrim($l)); $cc = (int) $e[0]; $gn = $e[1]; $cps = $this->glyph_list[$gn]; if (count($cps) == 1) { //a adobe glyphname may be associated to several unicode codepoints (e.g. lamedholam) //if this is the case, we ignore it. $enc->setGlyphname($cps[0], $gn); $enc->setCharacterCode($cps[0], $cc); } } $this->adobe_standard_encodings[$encoding] = $enc; return $enc; }
/** * Load the font metrics from an afm file * (Some of this code was stolen from makefont.php) * @param I2CE_Encoding $encoding -- the character encoding used in the file * @param string $afmfile * Caution: Units for these files are 1/1000 of a point where a point is 1/72 of an inch */ protected function loadFontMetricFromAFM($encoding, $afmfile) { $prev_dir = $this->getDirection(); $sub = mb_substitute_character(); mb_substitute_character("none"); //Read a font metric file $found_afmfile = I2CE::getFileSearch()->search('AFM_PATH', $afmfile); if (!$found_afmfile) { die('Error: AFM file not found: ' . $afmfile); } else { $afmfile = $found_afmfile; } $a = file($found_afmfile); if (empty($a)) { die('File empty' . $afmfile); } $this->setGlobal(); //global information is the default $this->setLinegap(0); //no line gap information is present in a AFM file $mode = 0; /** * Modes are 0: header or wirting direction * 10: Character metrics * 20: Kerning * 21: Kerning Tracking * 21: Kerning Pairs * 30: Composites */ foreach ($a as $l) { $e = explode(' ', rtrim($l)); $code = $e[0]; switch ($mode) { case 10: //character metrics $vals = array(); unset($cc); unset($gn); switch ($code) { case 'EndCharacterMetrics': case 'EndCharMetrics': $mode = 0; continue 2; //break out of the switch($mode) //break out of the switch($mode) case 'C': $cc = (int) $e[1]; break; case 'CH': $cc = hexdec($e[1]); break; } if (!isset($cc)) { break; } $i = 3; while ($i < count($e)) { $subcode = $e[$i]; switch ($subcode) { case 'WX': case 'W0X': case 'W1X': case 'WY': case 'W0Y': case 'W1Y': //widths and heights $vals[$subcode] = (double) $e[$i + 1]; $i = $i + 2; break; case 'W': case 'W0': case 'W1': case 'VV': //widths and heights $vals[$subcode] = array((double) $e[$i + 1], (double) $e[$i + 2]); $i = $i + 3; break; case 'N': //postscript glyph name $gn = $e[$i + 1]; $i = $i + 2; break; case 'B': //bounding box $vals[$subcode] = array((double) $e[$i + 1], (double) $e[$i + 2], (double) $e[$i + 3], (double) $e[$i + 4]); $i = $i + 5; break; case 'L': //Ligature sequence -- may have more than one if (!isset($vals['L'])) { $vals['L'] = array(); } $vals['L'][] = array($e[$i + 1], $e[$i + 2]); $i = $i + 3; $i++; break; default: $i++; break; } } unset($uc); if ($cc < 0) { //not a valid character code if ($gn !== null) { //try to get the unicode code point of the glyphname based on our encoding $uc = $encoding->UnicodeFromGlyphname($gn); if ($uc === null) { //we failed //try to get a unicode code point from the glyphname if (preg_match('/^uni([0-9A-F]{4})$/', $gn, $ucs)) { //we have a unicode codepoint $uc = $ucs[1]; } } } } else { //we have a valid character code $uc = $encoding->UnicodeFromCharactercode($cc); if ($gn !== null) { $this->gn2cc[$gn] = $cc; } } if ($uc !== null && $uc <= 0xffff && $uc >= 0 && $uc !== 0xfffd) { //replacement character $uc = I2CE_UTF8::cp_to_code($uc); //convert to UTF8 $cc = mb_convert_encoding($uc, $this->getEncoding()->getEncodingType(), 'UTF-8'); if ($this->getEncoding()->useMB()) { if (mb_strlen($cc, $this->getEncoding()->getEncodingType()) === 0) { $cc = -1; } } else { if (strlen($cc) === 0) { $cc = -1; } } } else { $cc = -1; } foreach (array('WX', 'W0X') as $key) { if (array_key_exists($key, $vals) && $vals[$key] !== null) { $this->setDirection('H'); if ($cc !== -1) { $this->setCharacterWidth($cc, $vals[$key]); } if ($gn !== null) { $this->setCharacterWidth($gn, $vals[$key]); } } } if (array_key_exists('W1X', $vals) && $vals['W1X'] !== null) { $this->setDirection('V'); if ($cc !== -1) { $this->setCharacterWidth($cc, $vals['W1X']); } if (null !== $gn) { $this->setCharacterWidth($gn, $vals['W1X']); } } foreach (array('WY', 'W0Y') as $key) { if (array_key_exists($key, $vals) && null !== $vals[$key]) { $this->setDirection('H'); if ($cc !== -1) { $this->setCharacterHeight($cc, $vals[$key]); } if (null !== $gn) { $this->setCharacterHeight($gn, $vals[$key]); } } } if (array_key_exists('W1Y', $vals) && null !== $vals['W1Y']) { $this->setDirection('V'); if ($cc !== -1) { $this->setCharacterHeight($cc, $vals['W1Y']); } if (null !== $gn) { $this->setCharacterHeight($gn, $vals['W1Y']); } } foreach (array('W', 'W0') as $key) { $this->setDirection('H'); if (array_key_exists($key, $vals) && null !== $vals[$key]) { if ($cc !== -1) { $this->setCharacterWidth($cc, $vals[$key]); $this->setCharacterHeight($cc, $vals[$key]); } if (null !== $gn) { $this->setCharacterWidth($gn, $vals[$key]); $this->setCharacterHeight($gn, $vals[$key]); } } } if (array_key_exists('W1', $vals) && null !== $vals['W1']) { $this->setDirection('V'); if ($cc !== -1) { $this->setCharacterWidth($cc, $vals[$key]); $this->setCharacterHeight($cc, $vals[$key]); } if (null !== $gn) { $this->setCharacterWidth($gn, $vals[$key]); $this->setCharacterHeight($gn, $vals[$key]); } } foreach (array('VVector' => 'VV', 'BoundingBox' => 'B', 'Ligature' => 'L') as $name => $key) { $this->setGlobal(); if (array_key_exists($key, $vals) && null !== $vals[$key]) { if ($cc !== -1) { $this->setCharacterInfo($cc, $name, $vals[$key]); } if (null !== $gn) { $this->setCharacterInfo($gn, $name, $vals[$key]); } } } break; case 20: //kerning switch ($code) { case 'EndKernData': $mode = 0; break; case 'StartTrackKern': $mode = 21; break; case 'StartKernPairs': case 'StartKernPairs0': $this->setDirection('H'); $mode = 22; break; case 'StartKernPairs1': $this->setDirection('V'); $mode = 22; break; } break; case 21: //kerning tracking switch ($code) { case 'EndTrackKern': $mode = 20; break; case '': break; } break; case 22: //kerning pairs switch ($code) { case 'EndKernPairs': $mode = 21; break; case 'KP': //kerning pairs are given by glyph name $this->setDirection('H'); $this->setKerningByPair($e[1], $e[2], (double) $e[3]); $this->setDirection('V'); $this->setKerningByPair($e[1], $e[2], (double) $e[4]); //get the corresponding character codes and insert into the table $this->setGlobal(); $cc1 = $this->getEncoding()->getCodeFromGlyphname($e[1]); $cc2 = $this->getEncoding()->getCodeFromGlyphname($e[2]); if ($cc1 !== -1 && $cc2 !== -1) { $this->setDirection('H'); $this->setKerningByPair($cc1, $cc2, (double) $e[3]); $this->setDirection('V'); $this->setKerningByPair($cc1, $cc2, (double) $e[4]); } break; case 'KPH': //not sure what is best to do here. $ch1 = ltrim(rtrim($e[1], '>'), '<'); $ch2 = ltrim(rtrim($e[1], '>'), '<'); $this->setDirection('H'); $this->setKerningByPair($ch1, $ch2, (double) $e[3]); $this->setDirection('V'); $this->setKerningByPair($ch1, $ch2, (double) $e[4]); break; case 'KPX': $this->setDirection('H'); $this->setKerningByPair($e[1], $e[2], (double) $e[3]); if (!array_key_exists($e[1], $this->gn2cc) || !array_key_exists($e[2], $this->gn2cc)) { break; } $cc1 = $this->gn2cc[$e[1]]; $cc2 = $this->gn2cc[$e[2]]; if ($cc1 !== -1 && $cc2 !== -1) { $this->setKerningByPair($cc1, $cc2, (double) $e[3]); } break; case 'KPY': $this->setDirection('V'); $this->setKerningByPair($e[1], $e[2], (double) $e[3]); if (!array_key_exists($e[1], $this->gn2cc) || !array_key_exists($e[2], $this->gn2cc)) { break; } $cc1 = $this->gn2cc[$e[1]]; $cc2 = $this->gn2cc[$e[2]]; if ($cc1 !== -1 && $cc2 !== -1) { $this->setKerningByPair($cc1, $cc2, (double) $e[3]); } break; } break; case 30: //composites switch ($code) { case 'EndComposites': $mode = 0; break; } break; default: //header/global information or writing direction switch ($code) { case 'BeginCharacterMetrics': case 'StartCharMetrics': $mode = 10; break; case 'BeginKernData': case 'StartKernData': $mode = 20; break; case 'BeginComposites': case 'StartComposites': $mode = 03; break; case 'StartDirection': //does not have to exist in which case we are in direction 0 if ($e[1] == '1') { $this->setDirection('V'); } else { $this->setDirection('H'); } break; case 'EndDirection': $this->setGlobal(); break; case 'CharWidth': $dir = $this->getDirection(); //this is not global information if ($dir === -1) { // however the keyword StartDirection is optional $this->setDirection('H'); } $this->setFixedWidth(true); $this->setFixedWidthSize((double) $e[1]); $this->setFixedHeightSize((double) $e[2]); $this->setDirection($dir); break; case 'UnderlinePosition': case 'UnderlineThickness': case 'ItalicAngle': $dir = $this->getDirection(); //this is not global information if ($dir === -1) { // however the keyword StartDirection is optional $this->setDirection('H'); } $this->setFontCharacteristic($code, $e[1]); $this->setDirection($dir); break; case 'IsFixedPitch': $isfixed = strpos(strtolower($e[1]), 'true') === 0; $dir = $this->getDirection(); //this is not global information if ($dir === -1) { // however the keyword StartDirection is optional $this->setDirection('H'); } $this->setFixedWidth($isfixed); $this->setDirection($dir); break; case 'isFixedV': case 'isBaseFont': $this->setGlobal(); $this->setFontCharacteristic($code, strpos(strtolower($e[1]), 'true')); break; case 'MappingScheme': case 'EscCar': case 'Characters': $this->setGlobal(); $this->setFontCharacteristic($code, (int) $e[1]); break; case 'Notice': case 'Comment': $this->setGlobal(); $val = $this->getFontCharacteristic($code); if ($val === null) { $val = ""; } $this->setFontCharacteristic($code, $val . substr($l, strlen($code) + 1)); break; case 'CapHeight': case 'XHeight': $this->setGlobal(); $this->setFontCharacteristic($code, (double) $e[1]); break; case 'Ascender': $this->setGlobal(); $this->setAscender((double) $e[1]); break; case 'Descender': $this->setGlobal(); $this->setDescender((double) $e[1]); break; case 'VVector': $this->setGlobal(); $this->setFontCharacteristic($code, array((double) $e[1], (double) $e[2])); case 'FontBBox': $this->setGlobal(); $this->setBoundingBox(array((double) $e[1], (double) $e[2], (double) $e[3], (double) $e[4])); break; default: //the rest are strings for global font information $this->setGlobal(); $this->setFontCharacteristic($code, $e[1]); break; } break; } } //normalize a few values. $this->setGlobal(); $asc = $this->getAscender(); if ($asc == 0) { $d = mb_convert_encoding('d', $this->getEncoding()->getEncodingType(), 'ASCII'); $ht = $this->getCharacterHeight($d); if ($ht != 0) { $this->setAscender($ht); } else { $this->setDirection('H'); $ht = $this->getCharacterHeight($d); if ($ht != 0) { $this->setAscender($ht); } else { $this->setGlobal(); $bbox = $this->getBoundingBox(); $this->setAscender($bbox[3]); } } } $this->setGlobal(); $dsc = $this->getAscender(); if ($dsc == 0) { $p = mb_convert_encoding('p', $this->getEncoding()->getEncodingType(), 'ASCII'); $bbox = $this->getCharacterInfo($p, 'BoundingBox'); if (!$bbox == null) { $this->setDescender($bbox[1]); } else { $this->setDirection('H'); $bbox = $this->getCharacterInfo($p, 'BoundingBox'); if (!$bbox == null) { $this->setDescender($bbox[1]); } else { $this->setGlobal(); $bbox = $this->getBoundingBox(); $this->setDescender($bbox[1]); } } } $this->setDirection($prev_dir); mb_substitute_character($sub); }