/** * Adds unicode fonts.<br> * Based on PDF Reference 1.3 (section 5) * @access protected * @author Nicola Asuni * @since 1.52.0.TC005 (2005-01-05) */ protected function _puttruetypeembedded($font) { // Type0 Font // A composite font composed of other fonts, organized hierarchically $this->_newobj(); $this->_out('<</Type /Font'); $this->_out('/Subtype /Type0'); $this->_out('/BaseFont /' . $font['name'] . ''); $this->_out('/Encoding /Identity-H'); //The horizontal identity mapping for 2-byte CIDs; may be used with CIDFonts using any Registry, Ordering, and Supplement values. $this->_out('/DescendantFonts [' . ($this->n + 1) . ' 0 R]'); $this->_out('/ToUnicode ' . ($this->n + 2) . ' 0 R'); $this->_out('>>'); $this->_out('endobj'); // CIDFontType2 // A CIDFont whose glyph descriptions are based on TrueType font technology $this->_newobj(); $this->_out('<</Type /Font'); $this->_out('/Subtype /CIDFontType2'); $this->_out('/BaseFont /' . $font['name'] . ''); $this->_out('/CIDSystemInfo ' . ($this->n + 2) . ' 0 R'); $this->_out('/FontDescriptor ' . ($this->n + 3) . ' 0 R'); if (isset($font['desc']['MissingWidth'])) { $this->_out('/DW ' . $font['desc']['MissingWidth'] . ''); // The default width for glyphs in the CIDFont MissingWidth } $w = ""; $fm = $font['font_metric']; $fm->setDirection('H'); $char_widths = $font['font_metric']->getCharacterWidths(); foreach ($char_widths as $char => $width) { $cid = I2CE_UTF8::to_codepoints($char); if (empty($cid)) { continue; } $w .= '' . $cid[0] . ' [' . $width . '] '; // define a specific width for each individual CID } $this->_out('/W [' . $w . ']'); // A description of the widths for the glyphs in the CIDFont $this->_out('/CIDToGIDMap ' . ($this->n + 4) . ' 0 R'); $this->_out('>>'); $this->_out('endobj'); // ToUnicode // is a stream object that contains the definition of the CMap // (PDF Reference 1.3 chap. 5.9) $this->_newobj(); $this->_out('<</Length 383>>'); $this->_out('stream'); $this->_out('/CIDInit /ProcSet findresource begin'); $this->_out('12 dict begin'); $this->_out('begincmap'); $this->_out('/CIDSystemInfo'); $this->_out('<</Registry (Adobe)'); $this->_out('/Ordering (UCS)'); $this->_out('/Supplement 0'); $this->_out('>> def'); $this->_out('/CMapName /Adobe-Identity-UCS def'); $this->_out('/CMapType 2 def'); $this->_out('1 begincodespacerange'); $this->_out('<0000> <FFFF>'); $this->_out('endcodespacerange'); $this->_out('1 beginbfrange'); $this->_out('<0000> <FFFF> <0000>'); $this->_out('endbfrange'); $this->_out('endcmap'); $this->_out('CMapName currentdict /CMap defineresource pop'); $this->_out('end'); $this->_out('end'); $this->_out('endstream'); $this->_out('endobj'); // CIDSystemInfo dictionary // A dictionary containing entries that define the character collection of the CIDFont. $this->_newobj(); $this->_out('<</Registry (Adobe)'); // A string identifying an issuer of character collections $this->_out('/Ordering (UCS)'); // A string that uniquely names a character collection issued by a specific registry $this->_out('/Supplement 0'); // The supplement number of the character collection. $this->_out('>>'); $this->_out('endobj'); // Font descriptor -- object $n+4 // A font descriptor describing the CIDFont default metrics other than its glyph widths $this->_newobj(); $this->_out('<</Type /FontDescriptor'); $this->_out('/FontName /' . $font['name']); foreach ($font['desc'] as $key => $value) { $this->_out('/' . $key . ' ' . $value); } //the stream containing the TrueType font program $this->_out('/FontFile2 ' . $this->FontFiles[$font['file']]['n'] . ' 0 R'); $this->_out('>>'); $this->_out('endobj'); // Embed CIDToGIDMap -- object $n+5 // A specification of the mapping from CIDs to glyph indices $this->_newobj(); $size = strlen($font['cidtogid']); $this->_out('<</Length ' . $size . ''); if ($font['compress']) { $this->_out('/Filter /FlateDecode'); } $this->_out('>>'); $this->_putstream($font['cidtogid']); $this->_out('endobj'); }
public function getEncodedCharactersFromGlyphID($gid) { $cps = $this->GIDtoCPs[$gid]; $out = mb_convert_encoding('', $this->getEncoding()->getEncodingType(), 'ASCII'); if (is_array($cps)) { foreach ($cps as $cp) { if ($cp == -1) { $cp = 0xfffd; } $out .= mb_convert_encoding(I2CE_UTF8::cp_to_code($cp), $this->getEncoding()->getEncodingType(), 'UTF-8'); } } return $out; }
/** * 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); }