* @license http://www.gnu.org/copyleft/lesser.html GNU Lesser General Public License */ namespace FontLib\Table\Type; use FontLib\Table\Table; /** * `cmap` font table. * * @package php-font-lib */ class cmap extends Table { private static $header_format = array( "version" => self::uint16, "numberSubtables" => self::uint16, ); private static $subtable_header_format = array( "platformID" => self::uint16, "platformSpecificID" => self::uint16, "offset" => self::uint32, ); private static $subtable_v4_format = array( "length" => self::uint16, "language" => self::uint16, "segCountX2" => self::uint16, "searchRange" => self::uint16, "entrySelector" => self::uint16, "rangeShift" => self::uint16, ); protected function _parse() { $font = $this->getFont(); $cmap_offset = $font->pos(); $data = $font->unpack(self::$header_format); $subtables = array(); for ($i = 0; $i < $data["numberSubtables"]; $i++) { $subtables[] = $font->unpack(self::$subtable_header_format); } $data["subtables"] = $subtables; foreach ($data["subtables"] as $i => &$subtable) { $font->seek($cmap_offset + $subtable["offset"]); $subtable["format"] = $font->readUInt16(); // @todo Only CMAP version 4 if ($subtable["format"] != 4) { unset($data["subtables"][$i]); $data["numberSubtables"]--; continue; } $subtable += $font->unpack(self::$subtable_v4_format); $segCount = $subtable["segCountX2"] / 2; $subtable["segCount"] = $segCount; $endCode = $font->readUInt16Many($segCount); $font->readUInt16(); // reservedPad $startCode = $font->readUInt16Many($segCount); $idDelta = $font->readInt16Many($segCount); $ro_start = $font->pos(); $idRangeOffset = $font->readUInt16Many($segCount); $glyphIndexArray = array(); for ($i = 0; $i < $segCount; $i++) { $c1 = $startCode[$i]; $c2 = $endCode[$i]; $d = $idDelta[$i]; $ro = $idRangeOffset[$i]; if ($ro > 0) { $font->seek($subtable["offset"] + 2 * $i + $ro); } for ($c = $c1; $c <= $c2; $c++) { if ($ro == 0) { $gid = ($c + $d) & 0xFFFF; } else { $offset = ($c - $c1) * 2 + $ro; $offset = $ro_start + 2 * $i + $offset; $font->seek($offset); $gid = $font->readUInt16(); if ($gid != 0) { $gid = ($gid + $d) & 0xFFFF; } } if ($gid > 0) { $glyphIndexArray[$c] = $gid; } } } $subtable += array( "endCode" => $endCode, "startCode" => $startCode, "idDelta" => $idDelta, "idRangeOffset" => $idRangeOffset, "glyphIndexArray" => $glyphIndexArray, ); } $this->data = $data; } function _encode() { $font = $this->getFont(); $subset = $font->getSubset(); $glyphIndexArray = $font->getUnicodeCharMap(); $newGlyphIndexArray = array(); foreach ($glyphIndexArray as $code => $gid) { $new_gid = array_search($gid, $subset); if ($new_gid !== false) { $newGlyphIndexArray[$code] = $new_gid; } } ksort($newGlyphIndexArray); // Sort by char code $segments = array(); $i = -1; $prevCode = 0xFFFF; $prevGid = 0xFFFF; foreach ($newGlyphIndexArray as $code => $gid) { if ( $prevCode + 1 != $code || $prevGid + 1 != $gid ) { $i++; $segments[$i] = array(); } $segments[$i][] = array($code, $gid); $prevCode = $code; $prevGid = $gid; } $segments[][] = array(0xFFFF, 0xFFFF); $startCode = array(); $endCode = array(); $idDelta = array(); foreach ($segments as $codes) { $start = reset($codes); $end = end($codes); $startCode[] = $start[0]; $endCode[] = $end[0]; $idDelta[] = $start[1] - $start[0]; } $segCount = count($startCode); $idRangeOffset = array_fill(0, $segCount, 0); $searchRange = 1; $entrySelector = 0; while ($searchRange * 2 <= $segCount) { $searchRange *= 2; $entrySelector++; } $searchRange *= 2; $rangeShift = $segCount * 2 - $searchRange; $subtables = array( array( // header "platformID" => 3, // Unicode "platformSpecificID" => 1, "offset" => null, // subtable "format" => 4, "length" => null, "language" => 0, "segCount" => $segCount, "segCountX2" => $segCount * 2, "searchRange" => $searchRange, "entrySelector" => $entrySelector, "rangeShift" => $rangeShift, "startCode" => $startCode, "endCode" => $endCode, "idDelta" => $idDelta, "idRangeOffset" => $idRangeOffset, "glyphIndexArray" => $newGlyphIndexArray, ) ); $data = array( "version" => 0, "numberSubtables" => count($subtables), "subtables" => $subtables, ); $length = $font->pack(self::$header_format, $data); $subtable_headers_size = $data["numberSubtables"] * 8; // size of self::$subtable_header_format $subtable_headers_offset = $font->pos(); $length += $font->write(str_repeat("\0", $subtable_headers_size), $subtable_headers_size); // write subtables data foreach ($data["subtables"] as $i => $subtable) { $length_before = $length; $data["subtables"][$i]["offset"] = $length; $length += $font->writeUInt16($subtable["format"]); $before_subheader = $font->pos(); $length += $font->pack(self::$subtable_v4_format, $subtable); $segCount = $subtable["segCount"]; $length += $font->w(array(self::uint16, $segCount), $subtable["endCode"]); $length += $font->writeUInt16(0); // reservedPad $length += $font->w(array(self::uint16, $segCount), $subtable["startCode"]); $length += $font->w(array(self::int16, $segCount), $subtable["idDelta"]); $length += $font->w(array(self::uint16, $segCount), $subtable["idRangeOffset"]); $length += $font->w(array(self::uint16, $segCount), array_values($subtable["glyphIndexArray"])); $after_subtable = $font->pos(); $subtable["length"] = $length - $length_before; $font->seek($before_subheader); $length += $font->pack(self::$subtable_v4_format, $subtable); $font->seek($after_subtable); } // write subtables headers $font->seek($subtable_headers_offset); foreach ($data["subtables"] as $subtable) { $font->pack(self::$subtable_header_format, $subtable); } return $length; } }